Skip to content

fix(genai): don't mutate caller's tool dict in convert_to_genai_function_declarations#1889

Open
Andrew Chen (chuenchen309) wants to merge 1 commit into
langchain-ai:mainfrom
chuenchen309:fix/genai-tool-dict-mutation
Open

fix(genai): don't mutate caller's tool dict in convert_to_genai_function_declarations#1889
Andrew Chen (chuenchen309) wants to merge 1 commit into
langchain-ai:mainfrom
chuenchen309:fix/genai-tool-dict-mutation

Conversation

@chuenchen309

Copy link
Copy Markdown

Why

_format_to_genai_function_declaration binds function to the caller's tool dict (or its nested "function") and then normalizes parameters in place:

function = tool["function"]          # (or `cast("dict", tool)`)
function["parameters"] = function.get("parameters") or {}
if not function["parameters"].get("properties"):
    function["parameters"] = {}

So convert_to_genai_function_declarations([tool]) mutates the caller's object:

  • an OpenAI-format tool whose parameters lack a properties key has its parameters wiped to {} (losing required, additionalProperties, …);
  • a parameterless {"name", "description"} dict gets a parameters key added to it.

Since tool dicts are frequently shared/reused across providers and calls, this silently corrupts the caller's data.

What

Shallow-copy function before normalizing parameters. The lines only reassign the top-level parameters key, so a shallow copy fully prevents caller mutation while leaving the produced FunctionDeclaration byte-identical.

Tests

Added test_convert_to_genai_function_declarations_does_not_mutate_input (OpenAI-format-without-properties and parameterless cases). It fails on main (the input dict is mutated) and passes with the fix. Full test_function_utils.py: 33 passed; ruff and mypy clean.


🤖 AI disclosure: found by an automated code-review pass and fixed/tested with Claude Code (an AI coding agent). The regression is pinned by the new test.

…schema

`_format_to_genai_function_declaration` assigned `function` to the
caller's tool dict (or its nested `"function"`) and then normalized
`function["parameters"]` in place. So converting an OpenAI-format tool
whose `parameters` lacked a `properties` key wiped the caller's
`parameters` to `{}` (losing `required` / `additionalProperties`), and a
parameterless `{name, description}` dict had a `parameters` key added to
it.

Shallow-copy `function` before normalizing so the caller's tool dict is
left untouched. The produced FunctionDeclaration is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chuenchen309

Copy link
Copy Markdown
Author

CI note: the single red check is langchain-google-genai-us (llm-integration-tests), the live-network integration suite. This change is confined to _function_utils.py — it shallow-copies the tool dict before normalizing parameters, producing a byte-identical FunctionDeclaration (covered by test_function_utils.py, all 33 green, plus the 12 non-integration checks here). So the integration red looks unrelated to this diff. Happy to dig in if a specific integration test is implicated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant