Repository navigation
fix(ai): apply Gemini and Kimi schema handling by model name - #51017
Merged
Merged
Conversation
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.
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.
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.
Follow-up to #51009, which switched the native Gemini protocol to
parametersJsonSchemawith 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:
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 andoneOfunions as JSON strings.Change
compatibility.toolSchematocompatibility.sanitizer:"gemini" | "moonshot" | "none". It is only set by the Moonshot provider and tests in this repo, and core config does not expose it.sanitizer: "gemini"now appliesGeminiJsonSchema.normalize.ToolSchemaProjection.modelCompatibilitytakes the model instead of just its compatibility setting. An explicitcompatibility.sanitizerwins. Otherwise the model name selects the sanitizer (case-insensitive): "gemini" selectsgemini, and "kimi" selectsmoonshot, 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.generateContentprotocol) 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 ascachePolicy,toolChoice, and reasoning effort.Resolution order, the same for every protocol: an explicit
compatibility.sanitizer(includingnone), then the protocol default (geminifor the Gemini API), then the model name, then unchanged.ToolSchemaProjectionis not re-exported from the package barrels, and nothing outsidepackages/aiimports 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):geminiprojectionparametersJsonSchemainternally, so this PR gives the same results as the native protocol.Tests:
tool-schema-projection.test.tscovers selecting the mode from a mixed-case model name, an explicitgeminisetting on a non-Gemini ID, an explicitmoonshotsetting winning on a Gemini ID, a Kimi name selectingmoonshot, and a non-matching model staying unchanged.google-vertex.test.tscovers the Gemini API default on a tuned endpoint (endpoints/1234567890), opting it out withsanitizer: "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.tsalso coverssanitizer: "none"opting out a Gemini and a Kimi model.bun testinpackages/ai: 1506 pass.bun run check: 35/35.