Skip to content

Python: Fix bool parameter coercion so string "false" is not silently treated as True - #14163

Open
Sohum Trivedi (sohumt123) wants to merge 2 commits into
microsoft:mainfrom
sohumt123:fix-bool-string-coercion
Open

Sohum Trivedi (sohumt123) wants to merge 2 commits into
microsoft:mainfrom
sohumt123:fix-bool-string-coercion

Conversation

@sohumt123

Copy link
Copy Markdown

Motivation and Context

When a kernel function declares a bool parameter and the incoming KernelArguments value is a string — which is common when an LLM tool call quotes booleans as JSON strings, when arguments flow through the template engine (which passes strings), or when values originate from env/CLI/form input — KernelFunctionFromMethod._parse_parameter falls through to the generic param_type(value) fallback. Since bool("false") is True for any non-empty string, enabled="false" silently runs the function with enabled=True. No error, no warning — the flag is simply inverted.

Repro on current main:

@kernel_function
def set_flag(enabled: bool) -> str:
    return f"enabled={enabled}"

func = kernel.add_function(plugin_name="test", function_name="set_flag", function=set_flag)
result = await kernel.invoke(func, KernelArguments(enabled="false"))
# result: "enabled=True"  <-- flag inverted

This is also a cross-SDK inconsistency: the .NET SDK marshals the same case correctly ("false" -> false via TypeConverter/bool.Parse semantics).

Description

Adds an explicit branch in _parse_parameter for param_type is bool with a string value, before the generic fallback:

  • "true" / "1" -> True, "false" / "0" -> False (case-insensitive, whitespace-stripped)
  • any other string raises FunctionExecutionException(f"Cannot parse string '{value}' as bool.") instead of silently returning True
  • non-string values (real bool, int) and all other parameter types keep the existing behavior

Tests added in test_kernel_function_from_method.py:

  • parametrized _parse_parameter coverage for "true"/"True"/"1"/"false"/"False"/"FALSE"/"0"/" false " plus non-string True/False/1/0 passthrough
  • an invalid-string case ("yes") asserting the raised FunctionExecutionException
  • an end-to-end kernel.invoke test showing enabled="false" now reaches the function as False

All of these fail on main without the fix (the end-to-end test gets enabled=True) and pass with it. uv run --frozen pytest tests/unit/functions tests/unit/kernel passes (301 tests, including the 50 in the touched test file), and ruff check, ruff format --check, and mypy are clean on the touched files.

Contribution Checklist

…o True

When a kernel function declares a bool parameter and the incoming
argument is a string (e.g. an LLM tool call that quotes booleans, or
values coming through the template engine), _parse_parameter fell
through to the generic param_type(value) fallback. bool("false") is
True for any non-empty string, so enabled="false" silently ran the
function with enabled=True.

Parse bool-typed string arguments explicitly: "true"/"1" -> True,
"false"/"0" -> False (case-insensitive, whitespace-stripped), and
raise FunctionExecutionException for unparseable strings. Non-string
values keep the existing behavior. This matches the .NET SDK, which
parses "false" as false.
Copilot AI review requested due to automatic review settings July 17, 2026 17:25
@sohumt123
Sohum Trivedi (sohumt123) requested a review from a team as a code owner July 17, 2026 17:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes Python kernel function argument coercion so string booleans (e.g., "false") are parsed to the correct bool value instead of falling back to Python’s bool("false") == True behavior, aligning with expected tool-call/template argument flows and cross-SDK behavior.

Changes:

  • Add explicit string-to-bool parsing in KernelFunctionFromMethod._parse_parameter for "true"/"1" and "false"/"0" (case-insensitive, whitespace-trimmed), raising on invalid strings.
  • Add unit coverage for _parse_parameter(..., bool) with common string/non-string inputs and invalid string handling.
  • Add an end-to-end kernel.invoke test verifying "false" and "true" string arguments reach the function as False/True.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
python/semantic_kernel/functions/kernel_function_from_method.py Adds explicit string-to-bool parsing branch to avoid incorrect coercion from non-empty strings.
python/tests/unit/functions/test_kernel_function_from_method.py Adds unit + end-to-end tests to verify correct boolean coercion and invalid-string error behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


def test_parse_invalid_bool_string_raises_exception(get_custom_type_function_pydantic):
func = get_custom_type_function_pydantic
with pytest.raises(FunctionExecutionException, match=r"Cannot parse string 'yes' as bool."):
@sohumt123

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@rogerbarreto Roger Barreto (rogerbarreto) added the python Pull requests for the Python Semantic Kernel label Jul 22, 2026

@VANDRANKI PRABHU KIRAN VANDRANKI (VANDRANKI) left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Community review (does not clear the merge gate).

I read the full diff, applied it on upstream main (5b4a326) in a separate worktree and ran it from python/ with Python 3.13. I invoked real plugin functions through Kernel.invoke with string arguments, main versus PR.

The bug is real. On main, a function with enabled: bool called with KernelArguments(enabled="false") receives True, because _parse_parameter falls through to bool(value). I ran four signatures (bool, Optional[bool] = None, bool | None = None, Annotated[bool, "flag"] = False); all four resolve to type_object bool, so the new param_type is bool branch covers them. Results with the PR: "false", "False" and "0" give False, " true " gives True. On main every one of those gave True.

The same change makes more inputs raise. Per input, main gives -> PR gives:

  • "yes" -> True on main; PR raises FunctionExecutionException ("Parameter enabled is expected to be parsed to <class 'bool'> but is not.")
  • "no" -> True on main (wrong); PR raises
  • "on" -> True on main; PR raises
  • "" (empty string) -> False on main; PR raises

The empty string is the one I would look at. Before this PR an empty template variable passed to a bool parameter silently meant False; after it the whole function call fails. That could break an existing prompt that passes a possibly empty variable. Options: treat an empty or whitespace-only string as False, or accept yes/no/on/off as well. Raising on unknown text is defensible; I am only flagging that it is a behavior change and the description does not mention it.

Tests: tests/unit/functions, tests/unit/kernel and tests/unit/template_engine give 757 passed with the PR applied. I did not run the PR's new tests against main; the invocation matrix above is my evidence for main's behavior. From reading the code (not run), non-string values still go through the old bool(value) path and are unchanged.

The core fix looks correct; the empty-string case is the open question.

This branch had an error being deployed

1 failed deployment
github-app-auth — ac34c337 Deployed Sep 28, 2026 by eavanvalkenburg via add_label #29108
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Pull requests for the Python Semantic Kernel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants