Repository navigation
fix(trimmer): keep tool parameters named like schema keywords - #4036
Conversation
_trim_json_schema recursed into "properties" and dropped any parameter named description, title, $comment or examples, leaving those names in "required" and hiding the parameters from the model.
|
Picked up the Two notes from doing it, in case they're useful: the same class also covers Your read that the |
Summary
ToolOutputTrimmersilently deletes tool parameters whose names collide with JSON Schema prose keywords._trim_json_schemastripsdescription,title,$commentandexamplesto save tokens, but it recurses into every nested dict and applies that keyword filter uniformly — including toproperties, whose keys are user-defined parameter names, not schema keywords. A tool declaring a parameter nameddescription(e.g.create_ticket(title, description)) loses that parameter when itstool_search_outputis replayed from an older turn.The result is not just lossy, it is an invalid schema: the deleted names remain in
requiredwhileadditionalProperties: falsestill closes the object, so the model receives a schema whose required properties are undeclared. There is no error and no log line. Reproduced through the public filter API (CallModelData), no network or API key:The fix handles
propertiesbefore the keyword check, recursing into its values while keeping its keys verbatim. This only narrows what gets deleted — schema-level prose trimming is unchanged, and no public API, signature or field order is touched.Haystack fixed the same bug class in its own schema walker (
_remove_title_from_schemainhaystack/tools/from_function.py, commented "Keys of a 'properties' mapping are property names, not schema keywords.").Knowingly out of scope:
$defs/definitionsentries keyed by a lowercase keyword name are still dropped — I verified this happens ($defskeys['Address', 'description']→['Address']), which would also dangle a$refpointing at it. I left it out because that path is much harder to reach in practice —$defskeys come from Pydantic class names, which are conventionally capitalised — and folding it in would add a second permutation to the same abstraction beyond the reported defect. It is a one-line follow-up (if key in {"properties", "$defs", "definitions"}) if you'd rather have it covered here.Test plan
Added one case to
tests/extensions/test_tool_output_trimmer.py, going through the public trimmer call: parameters nameddescription,title,$commentandexamplesall survive, norequiredname is missing fromproperties, and a genuine schema-leveldescriptionis still stripped (so the trimming feature itself is still exercised).tests/extensions/test_tool_output_trimmer.py: 39 passed with the fix; 1 failed, 38 passed without it (the new test fails on thepropertiesassertion).tests/extensions/(whole directory): 1158 passed, 6 skipped.ruff check: all checks passed.ruff format --check: 842 files already formatted (no unrelated reformatting).mypyscoped to the two changed files:Success: no issues found.Verification-script note, per the template:
.agents/skills/code-change-verification/scripts/run.shcould not run here —makeis not installed on this machine (bothrun.shandrun.ps1abort at themakeavailability check), so I ran the underlying Makefile targets directly viauv run. Unrelated pre-existing failures I confirmed also fail on an unmodifiedmain(ddc39d0): 52 tracing-test failures (KeyError: ('trace_…', 'span_…')fromtests/testing_processor.py), one pre-existingpyrighterror insrc/agents/sandbox/util/tar_utils.py:161, and mypy errors confined tosandbox/,examples/and a Windows-onlySIGQUITreference.Issue number
None — self-reported; found while reading the trimmer.
Checks
.agents/skills/code-change-verification/scripts/run.sh— could not:makeunavailable locally (see Test plan); ran the equivalent Makefile targets directly insteadmain)/reviewbefore submitting this PR — n/aAI assistance was used to draft this change; it was reviewed, reproduced and verified by me.