mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-27 04:07:05 +00:00
♊ fix: Sanitize MCP Tool Schemas for Gemini/Vertex Compatibility (#13623)
* 🧰 fix: Flatten union schemas for Gemini/Vertex MCP tool compatibility `@langchain/google-common`'s `zod_to_gemini_parameters` throws "Gemini cannot handle union types" on any genuine `anyOf`/`oneOf` (e.g. discriminated unions), so MCP tools shipping union-typed schemas crash on the Google endpoint while working fine on OpenAI/Claude. Add `flattenJsonSchemaUnions` (packages/api) to collapse unions to their first non-null member and multi-entry `type` arrays to a single nullable type, and apply it in `createToolInstance`'s existing `isGoogle` branch so only the Google/Vertex path is affected. Lossy by design, mirroring the existing empty-object fallback. Closes #13612 * 🩹 fix: Address Codex review — preserve fields, strip null enums, cover definitions path - Preserve parent-level `properties`/`required` when collapsing a union: merge the chosen branch into the parent instead of overwriting, so args declared outside the union (e.g. always-required fields) still reach Gemini. - Drop the `null` member from `enum` when a union/type-array makes a field nullable, keeping Gemini's required homogeneous-enum invariant. - Propagate the Google-flattened schema to the definitions/deferred-tool path: thread `provider` into `loadToolDefinitions` and flatten there, and store the flattened schema on `mcpJsonSchema` so `extractMCPToolDefinition` no longer emits raw unions on Google/Vertex. * 🎨 style: Sort imports in tools/definitions per import-order check * ♊ feat: Broaden union flatten into a full Gemini schema sanitizer The union flatten alone wasn't enough — real GitHub MCP tools on Gemini also 400 with `Invalid value ... (TYPE_STRING), true`, because Gemini's function-calling Schema (https://ai.google.dev/api/caching#Schema) accepts only a restricted JSON Schema subset, and `enum` is `Type.STRING`-only. Rename `flattenJsonSchemaUnions` → `sanitizeGeminiSchema` and broaden it (one pass, Gemini-gated) to cover the documented subset: - Keep only string `enum` values; drop the keyword for non-string types (fixes the reported boolean-enum 400, incl. boolean `const` normalized to `enum: [true]`). - `const` → single-value string enum, or drop if non-string. - Merge `allOf` intersections; fold `exclusiveMinimum`/`exclusiveMaximum` into `minimum`/`maximum`. - Strip unsupported keywords: `additionalProperties`, `default`, `$schema`, `$id`. - (Existing) collapse `anyOf`/`oneOf`, multi-entry `type` arrays, nullable. Grounded in Google's Schema docs rather than reverse-engineered from 400s. Verified end-to-end against the real `@langchain/google-common` converter. Complements danny-avila/agents#232 (langchain bump), which defers schema flattening to LibreChat. * 🩹 fix: Gate enum retention on the effective (collapsed) type Codex review: a mixed-type enum like `type: ['integer','string'], enum: [1,'auto']` collapsed the type to `integer` but still kept the string value `'auto'`, yielding `{type:'integer', enum:['auto']}` — a non-string type with an enum, which Gemini rejects. Keep `enum` only when the effective collapsed type is string (or unset), and stamp `type: 'string'` on a surviving typeless enum (e.g. a string `const` discriminator) so it satisfies Gemini's Type.STRING enum requirement.
This commit is contained in:
parent
9db68eeae8
commit
6c36d8038c
6 changed files with 710 additions and 12 deletions
|
|
@ -9,6 +9,7 @@ const {
|
|||
normalizeJsonSchema,
|
||||
GenerationJobManager,
|
||||
resolveJsonSchemaRefs,
|
||||
sanitizeGeminiSchema,
|
||||
buildMCPAuthStepId,
|
||||
buildMCPAuthToolCall,
|
||||
buildMCPAuthRunStepEvent,
|
||||
|
|
@ -648,6 +649,12 @@ function createToolInstance({
|
|||
|
||||
let schema = parameters ? normalizeJsonSchema(resolveJsonSchemaRefs(parameters)) : null;
|
||||
|
||||
if (schema && isGoogle) {
|
||||
// Gemini/Vertex AI accept only a subset of JSON Schema; sanitize so MCP tools with
|
||||
// unions, non-string enums, etc. don't 400 (they work as-is on OpenAI/Claude).
|
||||
schema = sanitizeGeminiSchema(schema);
|
||||
}
|
||||
|
||||
if (!schema || (isGoogle && isEmptyObjectSchema(schema))) {
|
||||
schema = {
|
||||
type: 'object',
|
||||
|
|
@ -779,7 +786,9 @@ function createToolInstance({
|
|||
});
|
||||
toolInstance.mcp = true;
|
||||
toolInstance.mcpRawServerName = serverName;
|
||||
toolInstance.mcpJsonSchema = parameters;
|
||||
// On Google/Vertex, propagate the union-flattened schema so definitions extracted
|
||||
// from this instance don't reach the Gemini converter with unsupported unions.
|
||||
toolInstance.mcpJsonSchema = isGoogle ? schema : parameters;
|
||||
return toolInstance;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -840,6 +840,7 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to
|
|||
deferredToolsEnabled,
|
||||
programmaticToolsEnabled,
|
||||
codeExecutionEnabled,
|
||||
provider: agent.provider,
|
||||
},
|
||||
{
|
||||
isBuiltInTool,
|
||||
|
|
@ -919,6 +920,7 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to
|
|||
deferredToolsEnabled,
|
||||
programmaticToolsEnabled,
|
||||
codeExecutionEnabled,
|
||||
provider: agent.provider,
|
||||
},
|
||||
{
|
||||
isBuiltInTool,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue