fix(ai): strip JSON Schema meta keys for Cloud Code Assist (#3412)
This commit is contained in:
committed by
GitHub
parent
9b28e185db
commit
f732f5e858
@@ -239,6 +239,33 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
|
|||||||
return contents;
|
return contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const JSON_SCHEMA_META_DECLARATIONS = new Set([
|
||||||
|
"$schema",
|
||||||
|
"$id",
|
||||||
|
"$anchor",
|
||||||
|
"$dynamicAnchor",
|
||||||
|
"$vocabulary",
|
||||||
|
"$comment",
|
||||||
|
"$defs",
|
||||||
|
"definitions", // pre-draft-2019-09 equivalent of $defs
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip meta-declarations from a schema obj
|
||||||
|
*/
|
||||||
|
function sanitizeForOpenApi(schema: unknown): unknown {
|
||||||
|
if (typeof schema !== "object" || schema === null || Array.isArray(schema)) {
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: Record<string, unknown> = {};
|
||||||
|
for (const [key, value] of Object.entries(schema)) {
|
||||||
|
if (JSON_SCHEMA_META_DECLARATIONS.has(key)) continue;
|
||||||
|
result[key] = sanitizeForOpenApi(value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert tools to Gemini function declarations format.
|
* Convert tools to Gemini function declarations format.
|
||||||
*
|
*
|
||||||
@@ -257,7 +284,9 @@ export function convertTools(
|
|||||||
functionDeclarations: tools.map((tool) => ({
|
functionDeclarations: tools.map((tool) => ({
|
||||||
name: tool.name,
|
name: tool.name,
|
||||||
description: tool.description,
|
description: tool.description,
|
||||||
...(useParameters ? { parameters: tool.parameters } : { parametersJsonSchema: tool.parameters }),
|
...(useParameters
|
||||||
|
? { parameters: sanitizeForOpenApi(tool.parameters as unknown) }
|
||||||
|
: { parametersJsonSchema: tool.parameters }),
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
187
packages/ai/test/google-shared-convert-tools.test.ts
Normal file
187
packages/ai/test/google-shared-convert-tools.test.ts
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { convertTools } from "../src/providers/google-shared.js";
|
||||||
|
import type { Tool } from "../src/types.js";
|
||||||
|
|
||||||
|
function makeTool(parameters: Record<string, unknown>): Tool {
|
||||||
|
return {
|
||||||
|
name: "test_tool",
|
||||||
|
description: "A test tool",
|
||||||
|
parameters: parameters as Tool["parameters"],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("google-shared convertTools", () => {
|
||||||
|
it("strips JSON Schema meta keys from parameters when useParameters=true", () => {
|
||||||
|
const tools = [
|
||||||
|
makeTool({
|
||||||
|
$schema: "http://json-schema.org/draft-07/schema#",
|
||||||
|
$id: "urn:bash-tool",
|
||||||
|
$comment: "A bash tool for demonstration",
|
||||||
|
$defs: {
|
||||||
|
commandDef: { type: "string" },
|
||||||
|
},
|
||||||
|
definitions: {
|
||||||
|
legacyDef: { type: "number" },
|
||||||
|
},
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
command: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["command"],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = convertTools(tools, true);
|
||||||
|
const decl = result?.[0]?.functionDeclarations?.[0];
|
||||||
|
|
||||||
|
expect(decl).toBeDefined();
|
||||||
|
expect(decl?.parameters).toEqual({
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
command: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["command"],
|
||||||
|
});
|
||||||
|
expect(decl?.parameters).not.toHaveProperty("$schema");
|
||||||
|
expect(decl?.parameters).not.toHaveProperty("$id");
|
||||||
|
expect(decl?.parameters).not.toHaveProperty("$comment");
|
||||||
|
expect(decl?.parameters).not.toHaveProperty("$defs");
|
||||||
|
expect(decl?.parameters).not.toHaveProperty("definitions");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("recursively strips nested JSON Schema meta keys", () => {
|
||||||
|
const tools = [
|
||||||
|
makeTool({
|
||||||
|
$schema: "http://json-schema.org/draft-07/schema#",
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
deep: {
|
||||||
|
$schema: "http://json-schema.org/draft-07/schema#",
|
||||||
|
$id: "urn:nested",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = convertTools(tools, true);
|
||||||
|
const decl = result?.[0]?.functionDeclarations?.[0];
|
||||||
|
|
||||||
|
expect(decl).toBeDefined();
|
||||||
|
expect(decl?.parameters).toEqual({
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
deep: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves $ref while stripping meta keys", () => {
|
||||||
|
const tools = [
|
||||||
|
makeTool({
|
||||||
|
$schema: "http://json-schema.org/draft-07/schema#",
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
refProp: {
|
||||||
|
$ref: "#/$defs/someDef",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = convertTools(tools, true);
|
||||||
|
const decl = result?.[0]?.functionDeclarations?.[0];
|
||||||
|
|
||||||
|
expect(decl).toBeDefined();
|
||||||
|
expect(decl?.parameters).toEqual({
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
refProp: {
|
||||||
|
$ref: "#/$defs/someDef",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not mutate the original Tool.parameters object", () => {
|
||||||
|
const originalParameters = {
|
||||||
|
$schema: "http://json-schema.org/draft-07/schema#",
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
command: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["command"],
|
||||||
|
};
|
||||||
|
const tools = [makeTool(originalParameters)];
|
||||||
|
|
||||||
|
convertTools(tools, true);
|
||||||
|
|
||||||
|
expect(originalParameters).toEqual({
|
||||||
|
$schema: "http://json-schema.org/draft-07/schema#",
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
command: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["command"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves $schema in parametersJsonSchema when useParameters=false", () => {
|
||||||
|
const tools = [
|
||||||
|
makeTool({
|
||||||
|
$schema: "http://json-schema.org/draft-07/schema#",
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
command: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["command"],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = convertTools(tools, false);
|
||||||
|
const decl = result?.[0]?.functionDeclarations?.[0];
|
||||||
|
|
||||||
|
expect(decl).toBeDefined();
|
||||||
|
expect(decl?.parametersJsonSchema).toEqual({
|
||||||
|
$schema: "http://json-schema.org/draft-07/schema#",
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
command: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["command"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles tools without $schema gracefully", () => {
|
||||||
|
const tools = [
|
||||||
|
makeTool({
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
path: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["path"],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = convertTools(tools, true);
|
||||||
|
const decl = result?.[0]?.functionDeclarations?.[0];
|
||||||
|
|
||||||
|
expect(decl).toBeDefined();
|
||||||
|
expect(decl?.parameters).toEqual({
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
path: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["path"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined for empty tool list", () => {
|
||||||
|
expect(convertTools([])).toBeUndefined();
|
||||||
|
expect(convertTools([], true)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user