Skip to content

fix(ai): apply Gemini and Kimi schema handling by model name - #51017

Merged
rekram1-node merged 6 commits into
v2from
gemini-model-detection
Sep 24, 2026
Merged

rekram1-node merged 6 commits into
v2from
gemini-model-detection

Conversation

@rekram1-node

@rekram1-node rekram1-node commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Follow-up to #51009, which switched the native Gemini protocol to parametersJsonSchema with a small normalizer (GeminiJsonSchema.normalize).

Problem

Gemini reached through OpenAI-compatible endpoints and gateways (OpenRouter, Vercel AI Gateway, Google's and Vertex's OpenAI-compatible endpoints) never gets Gemini schema handling:

  • The toolSchema: "gemini" compatibility mode still used the legacy OpenAPI projection (utils/gemini-tool-schema.ts). That projection makes Gemini send wrong argument types: integer enums as "3", and referenced objects and oneOf unions as JSON strings.
  • Nothing in the repo ever enabled the mode, and core's model config cannot set it. So these routes always sent schemas unchanged.

Change

  • Rename compatibility.toolSchema to compatibility.sanitizer: "gemini" | "moonshot" | "none". It is only set by the Moonshot provider and tests in this repo, and core config does not expose it.
  • Delete the legacy projection. sanitizer: "gemini" now applies GeminiJsonSchema.normalize.
  • ToolSchemaProjection.modelCompatibility takes the model instead of just its compatibility setting. An explicit compatibility.sanitizer wins. Otherwise the model name selects the sanitizer (case-insensitive): "gemini" selects gemini, and "kimi" selects moonshot, the same projection the direct Moonshot provider already sets explicitly. Kimi reached through Go, Zen, OpenRouter, Bedrock, and other hosts previously got schemas unchanged. Every non-Gemini protocol resolves the mode here: OpenAI Chat, Open Responses, OpenAI Responses (including namespace leaves), Meta Responses, Anthropic Messages, and Bedrock Converse.
  • The Gemini API (native generateContent protocol) defaults to the Gemini mode, which covers tuned Vertex endpoints whose IDs do not name Gemini.
  • sanitizer: "none" opts out of both defaults, matching other opt-outs in the package such as cachePolicy, toolChoice, and reasoning effort.

Resolution order, the same for every protocol: an explicit compatibility.sanitizer (including none), then the protocol default (gemini for the Gemini API), then the model name, then unchanged.

ToolSchemaProjection is not re-exported from the package barrels, and nothing outside packages/ai imports it.

Verification

The live results below cover the Gemini matching. Kimi matching is covered by unit tests. Improvements to the Moonshot projection itself are left for a follow-up.

Live through the V2 OpenAI-compatible route with gemini-3.8-flash, using plain models with no explicit setting. The corpus is 45 schemas plus Vercel's 169 MCP tools in 5 chunks, and 12 forced tool calls were validated against the original schema (accepted of 50 / correct calls of 12):

Endpoint Before (unchanged) Old gemini projection This PR (auto-detected)
Google OpenAI-compatible API 43 / 12 47 / 4 47 / 12
Vertex OpenAI-compatible 43 / 12 47 / 4 47 / 12
OpenRouter 39 / 2 43 / 0 39 / 2
Vercel AI Gateway 35 / 8 41 / 0 37 / 8
  • Google's own endpoints: Google converts OpenAI-compatible tool schemas to parametersJsonSchema internally, so this PR gives the same results as the native protocol.
  • OpenRouter and Vercel: they rebuild Google's legacy schema with their own converters, which limit what any client-side handling can fix. This PR is never worse than sending schemas unchanged.
  • Remaining rejections: the only schemas still rejected on Google's endpoints are synthetic 24+ level nesting.

Tests:

  • tool-schema-projection.test.ts covers selecting the mode from a mixed-case model name, an explicit gemini setting on a non-Gemini ID, an explicit moonshot setting winning on a Gemini ID, a Kimi name selecting moonshot, and a non-matching model staying unchanged.
  • google-vertex.test.ts covers the Gemini API default on a tuned endpoint (endpoints/1234567890), opting it out with sanitizer: "none", a Gemini model on Vertex chat completions getting Gemini rules, and non-Gemini models on Vertex chat completions (DeepSeek), Responses (Grok), and Messages (Claude) sending schemas unchanged.
  • tool-schema-projection.test.ts also covers sanitizer: "none" opting out a Gemini and a Kimi model.
  • bun test in packages/ai: 1506 pass. bun run check: 35/35.

The toolSchema "gemini" mode still used the legacy OpenAPI projection,
and nothing enabled it, so Gemini reached through OpenAI-compatible
endpoints and gateways got schemas unchanged. Point the mode at the JSON
Schema normalizer, delete the old projection, and select the mode from
the model name when no tool schema mode is set explicitly.

The native Gemini protocol always applies the normalizer, including for
tuned endpoints whose IDs do not name Gemini.
Select the moonshot tool schema mode for model names containing "kimi"
when no mode is set explicitly, so Kimi reached through gateways and
OpenAI-compatible endpoints gets the same handling as the direct Moonshot
provider.
@rekram1-node rekram1-node changed the title fix(ai): apply Gemini schema handling to Gemini on any route fix(ai): apply Gemini and Kimi schema handling by model name Sep 24, 2026
Replace the tuned-endpoint test with one in the Vertex suite that uses
the real tuned endpoint form and checks that only the Gemini API applies
Gemini's schema rules, while Vertex chat completions, Responses, and
Messages routes send non-matching models' schemas unchanged.
Add a `none` tool schema mode that opts out of the defaults, and resolve
the Gemini API through the same order as other protocols: an explicit
setting wins, then the protocol default (Gemini rules for the Gemini API,
including tuned endpoints), then the model name.
`compatibility.sanitizer` selects the tool schema sanitizer for a model
family (`gemini` or `moonshot`), and `false` opts out of the protocol
and model-name defaults.
Match the other opt-out values in the package, such as cachePolicy,
toolChoice, and reasoning effort.
@rekram1-node
rekram1-node merged commit b098b0e into v2 Sep 24, 2026
10 checks passed
@rekram1-node
rekram1-node deleted the gemini-model-detection branch September 24, 2026 05:16
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