fix(smolagents): restore nullable propagation and take the type from anyOf - #90
Open
Hrafz wants to merge 1 commit into
Open
fix(smolagents): restore nullable propagation and take the type from anyOf#90Hrafz wants to merge 1 commit into
nullable propagation and take the type from anyOf#90Hrafz wants to merge 1 commit into
Conversation
…anyOf SmolAgentsAdapter.adapt() copies inputSchema["properties"] into the smolagents tool without reading inputSchema["required"], so the schema the model sees marks every MCP parameter mandatory, including ones the server declared optional. ToolCallingAgent then refuses a call that omits a parameter the server said was optional. This is a regression. _generate_tool_inputs set inputs[k]["nullable"] in grll#11; 900880c (grll#23) replaced it with a direct properties copy and the nullable handling went with it. test_optional_sync kept passing because it asserts on call results, and direct calls still work -- only the advertised schema is wrong. Compute the required set from the resolved inputSchema and mark every property outside it nullable. Where a property has no top-level "type", take it from the first non-null anyOf branch instead of defaulting to "string", carrying that branch's enum. That also covers grll#68: a Literal[...] | None parameter currently arrives as a bare "string" with its enum buried in anyOf. Three tests assert on tool.inputs directly rather than on call results.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(smolagents): restore
nullablepropagation and take the type fromanyOfProblem
SmolAgentsAdapter.adapt()copiesinput_schema["properties"]into the smolagents tooland never reads
inputSchema["required"], so the schema the model sees lists every MCPparameter as mandatory — including the ones the server declared optional. It also
force-writes
"type": "string"onto any property with no top-leveltype, so ananyOf-typed parameter such asint | Noneis advertised as a string and aLiteral[...] | Noneloses itsenum.The
requiredhalf is a regression, not a gap._generate_tool_inputsused to setinputs[k]["nullable"] = "True", added in #11 ("Optional argument support for smolagents",merged 2025-03-05) after you wrote on #10:
Commit
900880c1(#23, add-google-genai-support, merged 2025-03-29) replaced that helperwith a direct
inputs=input_schema["properties"]copy, and thenullablehandling wentwith it.
test_optional_synckept passing because it asserts on call results, andforward(*args, **kwargs)still works — so nothing caught it.Against the repo's own optional-parameter fixture server:
Scope, measured
three(req="a")returns normally today andtest_optional_syncpasses on unmodifiedmain. What is wrong is the schema advertisedto the model and
ToolCallingAgent's argument validation.to_code_prompt()is byte-identical betweenmainand this branch for the requiredaspect, so
CodeAgent— which every example in the docs uses — is not affected. Theimpact is on tool-calling models.
defaultandanyOfdo survive per-property today; what is dropped is the top-levelrequiredarray. The resulting schema is self-contradictory rather thanoptionality-free: it carries
"default": nullon a property it simultaneously lists asrequired.
smolagents
@toolwith a defaulted argument also emits{"type": "string", "nullable": true}and also acceptsNonefor it.This also fixes #68
#68 reports
Literal[...] | Nonereaching smolagents with its type unresolved. On currentmainthe rawinputSchemafor such a parameter is:There is no top-level
type, so the current code stamps"string"and theenumis leftburied in
anyOfwhere smolagents does not look. Taking the type — and theenum— fromthe first non-null
anyOfbranch resolves that case. There is a test for exactly the shapein the issue.
Changes
inputSchemaand mark every property not init with
"nullable": True.type, take it from the first non-nullanyOfbranchinstead of defaulting to
"string", carrying that branch'senumwhen present.tool.inputsdirectly rather than on call results: one againstthe repo's own optional-parameter fixture server, one for
int | None, one forLiteral[...] | None. The first two fail on currentmain.src/mcpadapt/smolagents_adapter.py+21/−3,tests/test_smolagents_adapter.py+80.Known incompleteness
For
text: str | Nonewithrequired: ["text"]— required and nullable — this branchleaves
nullableunset where native smolagents marks itnullable: true.mainbehavesthe same way, so it is not a regression, but this is not full native parity and I would
rather say so than have you find it. That case looks like what #56 is aimed at, in
utils/modeling.py; this PR does not touch that file and the two should not conflict.Related open PRs, checked for overlap
#56 (
utils/modeling.py, required-but-nullable) and #86/#83 (no-input schemas, #78) touchadjacent behaviour but different code paths; #85 is
$refresolution. None of themrestores
nullablepropagation or resolvesanyOftypes insmolagents_adapter.py.