Skip to content

Commit bf6eac4

Browse files
fix(langchain): fix structured output on opus (#10010)
1 parent 2d05e3a commit bf6eac4

5 files changed

Lines changed: 84 additions & 14 deletions

File tree

.changeset/thirty-readers-sniff.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"langchain": patch
3+
---
4+
5+
fix(langchain): fix structured output on opus

.github/workflows/benchmark-tests.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
name: Benchmark Tests (LangChain)
55

66
on:
7-
push:
8-
branches: ["main"]
9-
pull_request:
10-
# Only run when LangChain, LangChain Core, or LangChain Classic code changes.
11-
paths:
12-
- "libs/langchain/**"
13-
- "libs/langchain-core/**"
7+
# push:
8+
# branches: ["main"]
9+
# pull_request:
10+
# # Only run when LangChain, LangChain Core, or LangChain Classic code changes.
11+
# paths:
12+
# - "libs/langchain/**"
13+
# - "libs/langchain-core/**"
1414
workflow_dispatch: # Allows triggering the workflow manually in GitHub UI
1515

1616
# If another push to the same PR or branch happens while this workflow is still running,

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@
3636
},
3737
"[typescript]": {
3838
"editor.defaultFormatter": "esbenp.prettier-vscode"
39-
}
39+
},
40+
"deno.enable": false
4041
}

libs/langchain/src/agents/nodes/AgentNode.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -909,17 +909,28 @@ export class AgentNode<
909909
};
910910

911911
Object.assign(options, {
912+
/**
913+
* OpenAI-style options
914+
* Used by ChatOpenAI, ChatXAI, and other OpenAI-compatible providers.
915+
*/
912916
response_format: {
913917
type: "json_schema",
914918
json_schema: jsonSchemaParams,
915919
},
916-
output_format: {
917-
type: "json_schema",
918-
schema: structuredResponseFormat.strategy.schema,
919-
},
920-
headers: {
921-
"anthropic-beta": "structured-outputs-2025-11-13",
920+
921+
/**
922+
* Anthropic-style options
923+
*/
924+
outputConfig: {
925+
format: {
926+
type: "json_schema",
927+
schema: structuredResponseFormat.strategy.schema,
928+
},
922929
},
930+
931+
/**
932+
* for LangSmith structured output tracing
933+
*/
923934
ls_structured_output_format: {
924935
kwargs: { method: "json_schema" },
925936
schema: structuredResponseFormat.strategy.schema,

libs/langchain/src/agents/tests/responses.int.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,59 @@ describe("structured output handling", () => {
264264
expect(result.messages.at(-1)?.content).toContain('{"answer": "Paris"}');
265265
expect((result.messages.at(-1) as AIMessage).tool_calls?.length).toBe(0);
266266
});
267+
268+
it("should return structured output with Claude Opus 4.6 end-to-end", async () => {
269+
const model = new ChatAnthropic({
270+
model: "claude-opus-4-6",
271+
});
272+
273+
const { tool: weatherTool, mock: weatherMock } = makeTool(
274+
({ city }: { city: string }) =>
275+
`The weather in ${city} is 22°C and partly cloudy.`,
276+
{
277+
name: "get_weather",
278+
description: "Get the current weather for a city",
279+
schema: z.object({
280+
city: z.string().describe("The city name"),
281+
}),
282+
}
283+
);
284+
285+
const ResponseSchema = z.object({
286+
city: z.string().describe("The city that was queried"),
287+
temperature: z.number().describe("The temperature"),
288+
summary: z.string().describe("A brief weather summary"),
289+
});
290+
291+
const agent = createAgent({
292+
model,
293+
tools: [weatherTool],
294+
systemPrompt:
295+
"You are a weather assistant. Use the get_weather tool to answer questions about weather. Return exact values from the tool.",
296+
responseFormat: providerStrategy(ResponseSchema),
297+
});
298+
299+
const result = await agent.invoke({
300+
messages: [new HumanMessage("What is the weather in Berlin?")],
301+
});
302+
303+
// Verify tool was called
304+
expect(weatherMock).toHaveBeenCalledTimes(1);
305+
306+
// Verify structured response is present and correctly parsed
307+
expect(result.structuredResponse).toBeDefined();
308+
expect(result.structuredResponse.temperature).toBe(22);
309+
expect(result.structuredResponse.city).toBe("Berlin");
310+
expect(typeof result.structuredResponse.summary).toBe("string");
311+
312+
// Verify no extract- tool calls (providerStrategy uses native structured output)
313+
const hasExtractToolCalls = result.messages.some(
314+
(msg) =>
315+
AIMessage.isInstance(msg) &&
316+
msg.tool_calls?.some((tc) => tc.name.startsWith("extract-"))
317+
);
318+
expect(hasExtractToolCalls).toBe(false);
319+
});
267320
});
268321
});
269322

0 commit comments

Comments
 (0)