Skip to content

fix(gemini): strip unsupported JSON Schema fields from tool declarations#374

Merged
kevincodex1 merged 1 commit into
mainfrom
fix/gemini-tool-schema-sanitize
Jul 2, 2026
Merged

fix(gemini): strip unsupported JSON Schema fields from tool declarations#374
kevincodex1 merged 1 commit into
mainfrom
fix/gemini-tool-schema-sanitize

Conversation

@gnanam1990

@gnanam1990 gnanam1990 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Problem

Fixes #373. Every zero exec against the Google/Gemini provider fails as soon as tools are advertised (i.e. essentially all agentic use). Zero serializes tool parameter schemas in OpenAI's JSON Schema dialect — schemaToRuntimeMap writes additionalProperties on every tool unconditionally — and Google's functionDeclarations schema doesn't recognize that keyword:

Invalid JSON payload received. Unknown name "additionalProperties"
at "tools[0].function_declarations[N].parameters": Cannot find field

The request is 400-rejected for every declaration, so the call never reaches the model. Reported against v0.1.0 with gemini-2.5-pro.

Root cause

  • internal/agent/loop.goschemaToRuntimeMap always emits "additionalProperties" into a tool's runtime parameter map.
  • internal/providers/gemini/provider.go — the mapper passed tool.Parameters verbatim into functionDeclarations[].parameters.

Fix

Sanitize tool parameters in the Gemini mapper onlyadditionalProperties: false is valid and wanted for OpenAI strict mode and harmless for Anthropic, so this must not be stripped globally.

  • sanitizeGeminiSchema keeps only the keywords Google's Schema type supports (type, description, enum, properties, required, items, anyOf, default, nullable, format, and the min/max/length constraints) via an allowlist, so any future OpenAI-ism or MCP-supplied field can't silently leak into a Gemini payload.
  • Recurses through properties, items, and anyOf, so nested schemas are cleaned at every depth — not just the top level.
  • nil parameters stay nil (parameterless tools unaffected).

Tests

  • Unit test on sanitizeGeminiSchema: nested stripping, legitimate fields (type/description/enum/required/default) preserved, nil-safe.
  • End-to-end test asserting the real outgoing request body's functionDeclarations[].parameters carry no additionalProperties while the actual schema survives.
  • Red-green: bypassing the sanitizer reproduces the exact reported payload and fails the E2E.

Full suite go test ./... passes (73/73 packages); change is isolated to the Gemini provider.

Summary by CodeRabbit

  • Bug Fixes
    • Improved Gemini tool calls to send cleaner JSON schemas, reducing compatibility issues with unsupported schema fields.
    • Preserved supported parameter details while removing invalid extras from tool definitions.
  • Tests
    • Added coverage for schema sanitization across nested fields and end-to-end Gemini request payloads.

Every exec against the Google/Gemini provider failed: Zero emits tool
parameter schemas in OpenAI's JSON Schema dialect, which always includes
`additionalProperties` (schemaToRuntimeMap writes it unconditionally).
Google's functionDeclarations schema doesn't recognize that keyword and
400s the whole request — "Unknown name \"additionalProperties\" …
Cannot find field" — so no tool-using call ever reached the model.

Sanitize tool parameters in the Gemini mapper (only there — the field is
valid and wanted for OpenAI strict mode and harmless for Anthropic):

- sanitizeGeminiSchema keeps only the keywords Gemini's Schema type
  supports (type, description, enum, properties, required, items, anyOf,
  default, nullable, format, and the min/max/length constraints) via an
  allowlist, so any future OpenAI-ism or MCP-supplied field can't leak.
- Recurses through properties, items, and anyOf so nested schemas are
  cleaned at every depth, not just the top level.

Tests: a unit test on the sanitizer (nesting, legit fields preserved,
nil-safe) and an end-to-end test asserting the outgoing request body's
functionDeclarations[].parameters carry no additionalProperties while the
real schema survives. Bypassing the sanitizer reproduces the reported
payload, so the coverage is red-green.

Fixes #373
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Zero automated PR review

Verdict: No blockers found

Blockers

  • None found.

Validation

  • [pass] Diff hygiene: git diff --check
  • [pass] Tests: go test ./...
  • [pass] Build: go run ./cmd/zero-release build
  • [pass] Smoke build: go run ./cmd/zero-release smoke

Scope

Head: c941eff66f91
Changed files (2): internal/providers/gemini/provider.go, internal/providers/gemini/provider_test.go

This deterministic review checks validation status and basic diff hygiene. A human reviewer still owns product judgment and design quality.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds a sanitizeGeminiSchema function and geminiSchemaFields allowlist to filter unsupported JSON Schema keywords (e.g., additionalProperties, $schema) from Gemini tool parameter schemas before sending requests. Includes new tests verifying sanitization behavior and end-to-end request payloads.

Changes

Gemini Tool Schema Sanitization

Layer / File(s) Summary
Schema allowlist and recursive sanitizer
internal/providers/gemini/provider.go
Adds geminiSchemaFields keyword allowlist and sanitizeGeminiSchema, a recursive function that strips unsupported JSON Schema keys from properties, items, and anyOf, returning nil for nil input; wires this into tool function declaration building so tool.Parameters is sanitized before being sent.
Sanitization and request tests
internal/providers/gemini/provider_test.go
Adds TestSanitizeGeminiSchemaStripsUnsupportedFields and a recursive assertNoAdditionalProps helper to confirm unsupported fields are removed at all nesting depths while legitimate fields remain, plus TestGeminiRequestOmitsAdditionalPropertiesInToolSchema, an end-to-end test verifying the outgoing request payload excludes additionalProperties.

Estimated code review effort: 2 (Simple) | ~12 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant geminiRequest
  participant sanitizeGeminiSchema
  participant GeminiAPI

  Caller->>geminiRequest: build request with tools
  geminiRequest->>sanitizeGeminiSchema: tool.Parameters
  sanitizeGeminiSchema->>sanitizeGeminiSchema: recursively strip unsupported keys
  sanitizeGeminiSchema-->>geminiRequest: sanitized schema
  geminiRequest->>GeminiAPI: send functionDeclarations with sanitized Parameters
Loading

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Clearly matches the main change: Gemini tool schemas now strip unsupported JSON Schema fields before being sent.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/gemini-tool-schema-sanitize

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

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
internal/providers/gemini/provider.go (1)

353-368: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Allowlist matches Gemini's documented Schema fields.

Cross-checked against Gemini's Schema object docs — type, format, nullable, enum, items, properties, required, anyOf, propertyOrdering, pattern, minLength/maxLength, minProperties/maxProperties, example are all genuinely supported fields, and the allowlist approach correctly guards against other unsupported keywords (const, propertyNames, exclusiveMinimum/Maximum, prefixItems) that real-world bug reports show Gemini also rejects at arbitrary nesting depths.

One note: oneOf (common for discriminated unions in some JSON Schema generators) isn't in the allowlist, so it's silently dropped rather than preserved/recursed like anyOf. Likely fine given Zero emits anyOf per the tests, but worth keeping in mind if a future tool schema source uses oneOf.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/providers/gemini/provider.go` around lines 353 - 368, The Gemini
schema allowlist in geminiSchemaFields omits oneOf, so schemas using
discriminated unions will lose that keyword during payload filtering. Update the
schema handling in provider.go so oneOf is treated consistently with anyOf
(preserved or recursively processed where appropriate), while keeping the
existing allowlist behavior for unsupported fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/providers/gemini/provider.go`:
- Around line 353-368: The Gemini schema allowlist in geminiSchemaFields omits
oneOf, so schemas using discriminated unions will lose that keyword during
payload filtering. Update the schema handling in provider.go so oneOf is treated
consistently with anyOf (preserved or recursively processed where appropriate),
while keeping the existing allowlist behavior for unsupported fields.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8b1f6d97-6d59-4a9a-be2f-f71e38cdd3b6

📥 Commits

Reviewing files that changed from the base of the PR and between bd89a1f and c941eff.

📒 Files selected for processing (2)
  • internal/providers/gemini/provider.go
  • internal/providers/gemini/provider_test.go

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict: Approve ✅

Fixes a launch-blocking bug — with tools advertised (i.e. essentially every agentic zero exec), Gemini 400-rejects every functionDeclaration on the OpenAI-ism additionalProperties. Checked out, built, vetted, and ran the tests on the branch.

Correctness — solid

  • Right scope. Sanitizing in the Gemini mapper only is the correct call — additionalProperties: false is wanted for OpenAI strict mode and harmless for Anthropic, so a global strip would be wrong. The base runtime schema is left intact for the other providers. ✔
  • Allowlist is complete and accurate. I checked it against Google's Generative-Language Schema type: the 22 kept keywords match exactly (type/format/title/description/nullable/default/items/minItems/maxItems/enum/properties/propertyOrdering/required/minProperties/maxProperties/minimum/maximum/minLength/maxLength/pattern/example/anyOf). Allowlist (vs denylist) is the right choice — a future OpenAI-ism or MCP-supplied field can't silently leak. ✔
  • Recursion is right. properties recurses into the sub-schema values (not the property names), items/anyOf handled, non-map values passed through, nilnil. Confirmed the reported nested additionalProperties is stripped at depth by the test. ✔
  • No leak path. geminiRequest is the single site that builds functionDeclarations (one completion path at provider.go:107), so there's no second unsanitized route. ✔

Local verification (branch checked out)

go build ./... clean · go vet ./internal/providers/gemini/... clean · new TestSanitizeGeminiSchemaStripsUnsupportedFields + TestGeminiRequestOmitsAdditionalPropertiesInToolSchema pass · full gemini package green. The E2E test asserting the real outgoing body carries no additionalProperties while the schema survives is exactly the right regression guard.

Nits (non-blocking)

  • items in JSON Schema can be an array (tuple form); only the object form recurses. Immaterial here — Gemini doesn't support tuple items anyway, so an array would be equally unusable with or without this PR.
  • No CHANGELOG entry.

Neither blocks merge. This one is worth prioritizing — Gemini is completely unusable for tool calls without it. Thanks @gnanam1990.

@kevincodex1 kevincodex1 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.

LGTM

@kevincodex1
kevincodex1 merged commit 39e7100 into main Jul 2, 2026
9 checks passed
eli-l pushed a commit to eli-l/zero that referenced this pull request Jul 2, 2026
…ons (Gitlawb#374)

Every exec against the Google/Gemini provider failed: Zero emits tool
parameter schemas in OpenAI's JSON Schema dialect, which always includes
`additionalProperties` (schemaToRuntimeMap writes it unconditionally).
Google's functionDeclarations schema doesn't recognize that keyword and
400s the whole request — "Unknown name \"additionalProperties\" …
Cannot find field" — so no tool-using call ever reached the model.

Sanitize tool parameters in the Gemini mapper (only there — the field is
valid and wanted for OpenAI strict mode and harmless for Anthropic):

- sanitizeGeminiSchema keeps only the keywords Gemini's Schema type
  supports (type, description, enum, properties, required, items, anyOf,
  default, nullable, format, and the min/max/length constraints) via an
  allowlist, so any future OpenAI-ism or MCP-supplied field can't leak.
- Recurses through properties, items, and anyOf so nested schemas are
  cleaned at every depth, not just the top level.

Tests: a unit test on the sanitizer (nesting, legit fields preserved,
nil-safe) and an end-to-end test asserting the outgoing request body's
functionDeclarations[].parameters carry no additionalProperties while the
real schema survives. Bypassing the sanitizer reproduces the reported
payload, so the coverage is red-green.

Fixes Gitlawb#373
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.

Gemini provider: tool schema emits unsupported 'additionalProperties' field (12 tools rejected, all exec calls fail)

3 participants