🎛️ fix: Google JSON Schema Normalization/Resolution Logic (#11804)

- Updated `resolveJsonSchemaRefs` to prevent `` and `definitions` from appearing in the resolved output, ensuring compatibility with LLM APIs.
- Improved `normalizeJsonSchema` to strip vendor extension fields (e.g., `x-*` prefixed keys) and leftover ``/`definitions` blocks, enhancing schema normalization for Google/Gemini API.
- Added comprehensive tests to validate the stripping of ``, vendor extensions, and proper normalization across various schema structures.
This commit is contained in:
Danny Avila 2026-02-15 21:31:16 -05:00 committed by GitHub
parent 12f45c76ee
commit 2ea72a0f87
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 190 additions and 7 deletions

View file

@ -203,9 +203,9 @@ export function resolveJsonSchemaRefs<T extends Record<string, unknown>>(
const result: Record<string, unknown> = {};
for (const [key, value] of Object.entries(schema)) {
// Skip $defs/definitions at root level to avoid infinite recursion
if ((key === '$defs' || key === 'definitions') && !visited.size) {
result[key] = value;
// Skip $defs/definitions — they are only used for resolving $ref and
// should not appear in the resolved output (e.g. Google/Gemini API rejects them).
if (key === '$defs' || key === 'definitions') {
continue;
}
@ -249,12 +249,15 @@ export function resolveJsonSchemaRefs<T extends Record<string, unknown>>(
}
/**
* Recursively normalizes a JSON schema by converting `const` values to `enum` arrays.
* Gemini/Vertex AI does not support the `const` keyword in function declarations,
* but `const: X` is semantically equivalent to `enum: [X]` per the JSON Schema spec.
* Recursively normalizes a JSON schema for LLM API compatibility.
*
* Transformations applied:
* - Converts `const` values to `enum` arrays (Gemini/Vertex AI rejects `const`)
* - Strips vendor extension fields (`x-*` prefixed keys, e.g. `x-google-enum-descriptions`)
* - Strips leftover `$defs`/`definitions` blocks that may survive ref resolution
*
* @param schema - The JSON schema to normalize
* @returns The normalized schema with `const` converted to `enum`
* @returns The normalized schema
*/
export function normalizeJsonSchema<T extends Record<string, unknown>>(schema: T): T {
if (!schema || typeof schema !== 'object') {
@ -270,6 +273,18 @@ export function normalizeJsonSchema<T extends Record<string, unknown>>(schema: T
const result: Record<string, unknown> = {};
for (const [key, value] of Object.entries(schema)) {
// Strip vendor extension fields (e.g. x-google-enum-descriptions) —
// these are valid in JSON Schema but rejected by Google/Gemini API.
if (key.startsWith('x-')) {
continue;
}
// Strip leftover $defs/definitions (should already be resolved by resolveJsonSchemaRefs,
// but strip as a safety net for schemas that bypass ref resolution).
if (key === '$defs' || key === 'definitions') {
continue;
}
if (key === 'const' && !('enum' in schema)) {
result['enum'] = [value];
continue;