Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions packages/proxy/src/providers/anthropic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -575,3 +575,33 @@ it("should use 128000 max_tokens and add beta header for claude-3-7-sonnet when
"output-128k-2025-02-19",
);
});

it("should translate json_object response format to tool-based structured output", async () => {
// This test verifies BRA-3896: json_object response format should be translated
// to a tool-based workaround for Anthropic, similar to how json_schema is handled.
const { fetch, requests } = createCapturingFetch({ captureOnly: true });

await callProxyV1<OpenAIChatCompletionCreateParams, OpenAIChatCompletion>({
body: {
model: "claude-3-haiku-20240307",
messages: [{ role: "user", content: "Return JSON" }],
response_format: { type: "json_object" },
stream: false,
max_tokens: 150,
},
fetch,
});

expect(requests).toHaveLength(1);
// Verify the proxy created a tool with generic object schema
expect(requests[0].body).toMatchObject({
tools: [
{
name: "json",
description: "Output the result in JSON format",
input_schema: { type: "object" },
},
],
tool_choice: { type: "tool", name: "json" },
});
});
10 changes: 8 additions & 2 deletions packages/proxy/src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2597,7 +2597,10 @@ async function fetchAnthropicChatCompletions({

let isStructuredOutput = false;
const parsed = responseFormatSchema.safeParse(oaiParams.response_format);
if (parsed.success && parsed.data.type === "json_schema") {
if (
parsed.success &&
(parsed.data.type === "json_schema" || parsed.data.type === "json_object")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit]: code might be more defensive if we match up-front for the data.type. currently, if someone adds another parsed.data.type, it defaults to type: "object".

i think this is fine since we are migrating away though.

) {
isStructuredOutput = true;
if (params.tools || params.tool_choice) {
throw new ProxyBadRequestError(
Expand All @@ -2608,7 +2611,10 @@ async function fetchAnthropicChatCompletions({
{
name: "json",
description: "Output the result in JSON format",
input_schema: parsed.data.json_schema.schema,
input_schema:
parsed.data.type === "json_schema"
? parsed.data.json_schema.schema
: { type: "object" },
},
];

Expand Down