Skip to content

Commit a9773e2

Browse files
committed
fix(agents): materialize OpenAI tool schemas
1 parent db5bb1c commit a9773e2

2 files changed

Lines changed: 65 additions & 3 deletions

File tree

src/agents/openai-tool-schema.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it } from "vitest";
22
import {
33
clearOpenAIToolSchemaCacheForTest,
44
isStrictOpenAIJsonSchemaCompatible,
5+
normalizeOpenAIStrictToolParameters,
56
normalizeStrictOpenAIJsonSchema,
67
resolveOpenAIStrictToolFlagForInventory,
78
} from "./openai-tool-schema.js";
@@ -91,4 +92,43 @@ describe("OpenAI strict tool schema normalization", () => {
9192
}),
9293
).toBe(third);
9394
});
95+
96+
it("shapes OpenAI tool schemas from their JSON view before raw object traversal", () => {
97+
const schema = new Proxy(
98+
{
99+
toJSON() {
100+
return {
101+
type: "object",
102+
properties: {
103+
query: { type: "string" },
104+
},
105+
required: ["query"],
106+
};
107+
},
108+
},
109+
{
110+
ownKeys() {
111+
throw new Error("raw schema traversal");
112+
},
113+
},
114+
);
115+
116+
expect(normalizeStrictOpenAIJsonSchema(schema)).toEqual({
117+
type: "object",
118+
properties: {
119+
query: { type: "string" },
120+
},
121+
required: ["query"],
122+
additionalProperties: false,
123+
});
124+
expect(normalizeOpenAIStrictToolParameters(schema, false)).toMatchObject({
125+
type: "object",
126+
properties: {
127+
query: { type: "string" },
128+
},
129+
});
130+
expect(
131+
resolveOpenAIStrictToolFlagForInventory([{ name: "lookup", parameters: schema }], true),
132+
).toBe(true);
133+
});
94134
});

src/agents/openai-tool-schema.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type ToolWithParameters = {
1414

1515
const MAX_STRICT_SCHEMA_CACHE_ENTRIES_PER_SCHEMA = 8;
1616
let strictOpenAISchemaCache = new WeakMap<object, Array<{ key: string; value: unknown }>>();
17+
let openAISchemaInputCache = new WeakMap<object, unknown>();
1718

1819
function resolveToolSchemaModelCompat(
1920
compat: ToolSchemaCompatInput | null | undefined,
@@ -63,13 +64,33 @@ function rememberStrictOpenAISchema(schema: object, key: string, value: unknown)
6364

6465
export function clearOpenAIToolSchemaCacheForTest(): void {
6566
strictOpenAISchemaCache = new WeakMap();
67+
openAISchemaInputCache = new WeakMap();
68+
}
69+
70+
function materializeOpenAIToolSchemaInput(schema: unknown): unknown {
71+
const schemaInput = schema ?? {};
72+
if (!schemaInput || typeof schemaInput !== "object") {
73+
return schemaInput;
74+
}
75+
if (openAISchemaInputCache.has(schemaInput)) {
76+
return openAISchemaInputCache.get(schemaInput);
77+
}
78+
let materialized: unknown;
79+
try {
80+
const text = JSON.stringify(schemaInput);
81+
materialized = text ? JSON.parse(text) : {};
82+
} catch {
83+
materialized = {};
84+
}
85+
openAISchemaInputCache.set(schemaInput, materialized);
86+
return materialized;
6687
}
6788

6889
export function normalizeStrictOpenAIJsonSchema(
6990
schema: unknown,
7091
modelCompat?: ToolSchemaCompatInput | null,
7192
): unknown {
72-
const schemaInput = schema ?? {};
93+
const schemaInput = materializeOpenAIToolSchemaInput(schema);
7394
if (!schemaInput || typeof schemaInput !== "object") {
7495
return normalizeStrictOpenAIJsonSchemaRecursive(
7596
normalizeToolParameterSchema(schemaInput, {
@@ -147,10 +168,11 @@ export function normalizeOpenAIStrictToolParameters<T>(
147168
modelCompat?: ToolSchemaCompatInput | null,
148169
): T {
149170
const toolSchemaCompat = resolveToolSchemaModelCompat(modelCompat);
171+
const schemaInput = materializeOpenAIToolSchemaInput(schema);
150172
if (!strict) {
151-
return normalizeToolParameterSchema(schema ?? {}, { modelCompat: toolSchemaCompat }) as T;
173+
return normalizeToolParameterSchema(schemaInput ?? {}, { modelCompat: toolSchemaCompat }) as T;
152174
}
153-
return normalizeStrictOpenAIJsonSchema(schema, toolSchemaCompat) as T;
175+
return normalizeStrictOpenAIJsonSchema(schemaInput, toolSchemaCompat) as T;
154176
}
155177

156178
export function isStrictOpenAIJsonSchemaCompatible(schema: unknown): boolean {

0 commit comments

Comments
 (0)