Skip to content

fix(discover): declare tool_call.arguments as a JSON string so constrained providers stop emptying it - #209

Merged
senamakel merged 3 commits into
mainfrom
tool-call-open-args
Sep 24, 2026
Merged

senamakel merged 3 commits into
mainfrom
tool-call-open-args

Conversation

@senamakel

Copy link
Copy Markdown
Member

Summary

tool_call.arguments is now declared as a JSON-encoded string instead of an open object.

A schema-constrained provider answered every tool_call with {}, dropping name as well as the arguments, so tools reached through tool_search could never actually be invoked. That broke OpenHuman's scheduled "email me the AAPL price" job.

The failure, as captured on the wire

OpenHuman's capture proxy recorded the managed backend's replies for openrouter/deepseek/deepseek-v4-flash-0731. The OpenRouter provider Sail Research served every one of them.

The GMAIL_SEND_EMAIL step came back as native tool_calls like this, three times out of three:

{"function": {"name": "tool_call", "arguments": "{}"}}

So the loop answered "tool_call needs a non-empty string name" until the model gave up.

Why a string, measured

I replayed the same captured request (tool_call step, 27 tools, same prompt) against the same route. The schema variant was the only change:

tool_call.arguments schema tool_call attempts name + args intact
{"type":"object"} (before) 3 0 / 3, all {}
{"type":"object","additionalProperties":true} 4 4 / 4
{"type":"string"} (this PR) 7 7 / 7

The open object works on this route, but it doesn't survive our own schema projections:

  • set_additional_properties_false (strict mode) overrides true to false.
  • The Conservative and Gemini cleaners strip the keyword.

On any of those routes the schema degrades back to the shape that fails. A string survives every projection; the new test asserts this for gemini, anthropic, openai, conservative, and both strict variants.

API Or Behavior Changes

  • The tool_call schema declares arguments: {"type": "string"}, and its description and the tool_search result text say to pass a JSON object string.
  • unwrap_tool_call is unchanged. It already decoded a JSON-string arguments and still accepts an object, so models that send an object keep working.
  • No public API changes.

Tests

  • New: tool_call_arguments_is_a_string_under_every_schema_projection

  • New: unwrap_tool_call_decodes_the_string_arguments_the_schema_asks_for. It covers the exact nested-quote and escaped-newline shape the provider returned, "{}", and refusing a non-object JSON string.

  • cargo fmt --check

  • cargo clippy --all-targets -- -D warnings

  • cargo clippy --all-targets --all-features -- -D warnings

  • cargo build --all-targets (via test/clippy)

  • cargo build --all-targets --all-features (via test/clippy)

  • cargo test --workspace: 0 failures

  • cargo test --all-features: 0 failures

End to end in OpenHuman: built on this branch and ran the real cron job on the same Sail Research route, captured through the proxy.

  • The call arrived as tool_call {name: "GMAIL_SEND_EMAIL", arguments: "{…body, is_html, recipient_email, subject, user_id}"}.
  • The email was delivered, and the job reported "Email sent".

Still open: the provider also intermittently empties the first call of a parallel batch (web_search_tool {}). That call is recovered by the existing argument validation and retry, and it is not addressed here.

Documentation

The doc comment on tool_call_schema records the provider behaviour and why a string rather than an open object. unwrap_tool_call's doc notes that both forms are accepted. The discover README describes the function set, which is unchanged.

The `arguments` field in the tool call schema was declared as a JSON object, but this caused providers with strict schema validation to reject valid tool calls because the open-ended object type was interpreted as disallowing any keys. Changing the type to a JSON string preserves compatibility across all provider projections while still accepting object payloads from models that send them.

Auto-committed-on: macbook
…jections

The `arguments` field in `tool_call` is now explicitly typed as a JSON string in the schema, ensuring it reaches the model intact under every schema projection. Previously, an open object type was answered as `{}` by schema-constrained providers, and various projections would rewrite or strip `additionalProperties`, causing the field to be lost. The change updates the help text in the search answer to clarify the expected format and adds comprehensive tests verifying that `arguments` remains a string across all supported schema preparations, including Gemini, Anthropic, OpenAI, conservative, and strict variants.

Auto-committed-on: macbook
Reordered the import of `prepare_tool_schema` and `SchemaPreparation` to follow standard Rust convention, and reformatted the long tuple entry for "conservative strict" to improve readability without changing any behavior.

Auto-committed-on: macbook
@coderabbitai

coderabbitai Bot commented Sep 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

  • Run on-demand review

This review includes 2 billable files and costs up to $0.50.

Or wait 29 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: cfef9ddf-8768-4bdf-837b-6bcd877ab2f8

📥 Commits

Reviewing files that changed from the base of the PR and between f4735ff and dbb7246.

📒 Files selected for processing (2)
  • crates/tinyagents-harness/src/tool/discover/bridge.rs
  • crates/tinyagents-harness/src/tool/discover/test.rs

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-24T00:43:24.864919Z dbb7246 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dbb7246480

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 89 to +91
"Invoke a tool found with `{TOOL_SEARCH_NAME}`. `name` is the tool's name \
and `arguments` is its argument object, matching the schema the search \
returned."
and `arguments` is its argument object encoded as a JSON string, matching \
the schema the search returned."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Document the string-encoded bridge argument

This changes the model-facing tool_call contract from an argument object to a JSON-encoded string, but docs/modules/harness/tool-discovery.md still describes only tool_call { name, arguments } without explaining the required encoding. Document the string form and include an example so hosts and prompt integrations relying on the module guide do not implement the old wire shape.

AGENTS.md reference: AGENTS.md:L78-L82

Useful? React with 👍 / 👎.

@tinysweeper

tinysweeper Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

Tiny Sweeper reviewed this change across 6 lane(s) and found 0 active actionable finding(s). Detailed lane evidence and any incomplete work are listed below.

State: Ready for maintainer review
Priority: none
Reviewed head: dbb724648090
Updated: 1790212554 (Unix time)

Review snapshot

Change surface Files Review signal Count
Production 1 Active findings 0
Tests 1 Noted findings 0
Documentation 0 Resolved findings 0
Configuration 0 Pending checks/questions 0

Completeness: Complete
Test assessment: No supported feature-to-test mapping was available; this does not mean tests are absent or passed.

What changed

The review could not produce a supported behavioral summary; inspect the cited changed surface and lane details below.

Features

None identified with supported citations.

Tests

No supported feature-to-test mapping was produced. Test execution is not inferred.

Findings

No active actionable findings.

Before merge

None.

How this fits together

flowchart LR
  n0["answer_tool_search<br/>changed"]:::changed
  n1["tool_call_schema<br/>changed"]:::changed
  n2["tool_search_schema<br/>changed"]:::changed
  n3["catalog"]:::impacted
  n4["catalog_with_families"]:::impacted
  n5["with_ranker"]:::impacted
  n6["bridge_schemas"]:::impacted
  n7["bm25_mode_ignores_an_installed_ranker"]:::impacted
  n8["...ves_the_ranker_and_reports_bm25_alongside"]:::impacted
  n4 -->|calls| n3
  n6 -->|calls| n1
  n6 -->|calls| n2
  n7 -->|calls| n0
  n7 -->|tests| n0
  n7 -->|calls| n4
  n7 -->|tests| n4
  n7 -->|calls| n5
  n7 -->|tests| n5
  n8 -->|calls| n0
  n8 -->|tests| n0
  n8 -->|calls| n4
  n8 -->|tests| n4
  n8 -->|calls| n5
  n8 -->|tests| n5
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading
Agent review details

critique

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: The change keeps the bridge schema provider-compatible while preserving object-form compatibility and decoding stringified arguments correctly. The added behavior and tests look safe to merge; no correctness issues were found in the changed files. _The code index is behind this pull request (indexed at `6ac9a821aadf`), so retrieved context may be out of date._ _Memory was unavailable (model: cortex: v1/recall: timed out after 10s), so this review ran without it._

security

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: The change updates the discovery bridge schema and documentation to encode tool arguments as JSON strings while retaining object compatibility, with focused projection and decoding tests. It looks safe to merge. _The code index is behind this pull request (indexed at `6ac9a821aadf`), so retrieved context may be out of date._ _Memory was unavailable (model: cortex: v1/recall: timed out after 10s), so this review ran without it._

tests

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: The change makes `tool_call.arguments` a JSON string in the schema to survive provider schema projections that strip or rewrite open objects. New tests verify the string type under every projection path and the decode logic in `unwrap_tool_call`, and existing rejection cases still pass. No regression path is left uncovered by this PR. _The code index is behind this pull request (indexed at `6ac9a821aadf`), so retrieved context may be out of date._ _Memory was unavailable (model: cortex: v1/recall: timed out after 10s), so this review ran without it._

commits

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: Nothing sensitive found in what this pull request commits.

description

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: Change `tool_call.arguments` from an open object to a JSON string to survive schema projections that strip or rewrite `additionalProperties`, fixing a provider that returned empty `{}`. The diff is focused, the existing `unwrap_tool_call` already handles both forms, and the new tests cover the string shape under all schema preparations and the decoding logic. No problems introduced.}, _The code index is behind this pull request (indexed at `6ac9a821aadf`), so retrieved context may be out of date._ _Memory was unavailable (model: cortex: v1/recall: timed out after 10s), so this review ran without it._

e2e

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: No end-to-end harness in this repository: no e2e test files and no e2e workflow.
Evidence and run details
  • Models: ladder/vectors, gpt-5.6-luna, deepseek/deepseek-v4-flash
  • Spend: $0.007467
  • Tokens: 115239 input · 8635 output · 20888 cached · 476 embedding
Head State Pass summary
dbb724648090 ready for maintainer review 0 active finding(s), 0 resolved finding(s) (at 1790212554)

tinysweeper 0.1.0

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

tinysweeper found nothing blocking. Approving.

             $0.0075 · 115,239 in / 8,635 out · 20,888 cached (18%) · ladder/vectors, gpt-5.6-luna, deepseek/deepseek-v4-flash · 476 embedded
critique:    $0.0019 · 39,198 in  / 367 out   · 2,118 cached (5%)   · gpt-5.6-luna
security:    $0.0020 · 38,710 in  / 925 out   · 1,874 cached (5%)   · gpt-5.6-luna
tests:       $0.0023 · 28,000 in  / 4,423 out · 14,336 cached (51%) · deepseek/deepseek-v4-flash
description: $0.0004 · 5,682 in   / 478 out   · 2,560 cached (45%)  · deepseek/deepseek-v4-flash

@tinysweeper tinysweeper Bot added the priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. label Sep 24, 2026
@senamakel senamakel self-assigned this Sep 24, 2026
@senamakel
senamakel merged commit c1034c5 into main Sep 24, 2026
16 checks passed
@senamakel
senamakel deleted the tool-call-open-args branch September 30, 2026 06:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant