fix(ai): declare Cloudflare Responses strict tools (#7934)

Fixes #7896
This commit is contained in:
muyiyr 2026-08-11 15:07:12 +08:00 • committed by GitHub
parent 9cb7f493e7
commit 75c7fd6623
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 56 additions and 1 deletions

View file

@ -732,7 +732,10 @@ function applyOpenAICompletionsCompatMetadata(model: Model<Api>): void {
}
function applyStrictToolCompatMetadata(model: Model<Api>): void {
if (model.provider === "openai" && model.api === "openai-responses") {
if (
(model.provider === "openai" || model.provider === "cloudflare-ai-gateway") &&
model.api === "openai-responses"
) {
model.compat = { ...(model.compat as OpenAIResponsesCompat | undefined), supportsStrictMode: true };
} else if (model.provider === "anthropic" && model.api === "anthropic-messages") {
mergeAnthropicMessagesCompat(model, { supportsStrictTools: true });

View file

@ -9,6 +9,7 @@ type CapturedHeaders = Headers | string[][] | Record<string, string | readonly s
interface CapturedResponsesPayload {
prompt_cache_key?: string;
session_id?: string;
tools?: Array<{ name?: string; strict?: boolean }>;
}
function getHeader(headers: CapturedHeaders, name: string): string | null {
@ -153,6 +154,57 @@ describe("openai-responses provider defaults", () => {
});
});
it("sets strict mode explicitly for Cloudflare OpenAI Responses tools", async () => {
const model = getModel("cloudflare-ai-gateway", "gpt-5.6-sol");
let capturedPayload: CapturedResponsesPayload | undefined;
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response("data: [DONE]\n\n", {
status: 200,
headers: { "content-type": "text/event-stream" },
}),
);
const stream = streamOpenAIResponses(
model,
{
messages: [{ role: "user", content: "Use a tool.", timestamp: Date.now() }],
tools: [
{
name: "ordinary",
description: "An ordinary tool",
parameters: Type.Object({
path: Type.String(),
offset: Type.Optional(Type.Number()),
}),
},
{
name: "constrained",
description: "A constrained tool",
parameters: Type.Object({ value: Type.String() }),
constrainedSampling: { type: "json_schema", strict: "prefer" },
},
],
},
{
apiKey: "test-key",
onPayload: (payload) => {
capturedPayload = payload as CapturedResponsesPayload;
},
},
);
for await (const event of stream) {
if (event.type === "done" || event.type === "error") break;
}
expect(model.compat?.supportsStrictMode).toBe(true);
expect(capturedPayload?.tools).toEqual([
expect.objectContaining({ name: "ordinary", strict: false }),
expect.objectContaining({ name: "constrained", strict: true }),
]);
});
it.each([
"gpt-5.1",
"gpt-5.2",