Skip to content

Commit ab1313e

Browse files
committed
Adapt langchain-anthropic to chat model changes
1 parent e115165 commit ab1313e

File tree

2 files changed

+42
-61
lines changed

2 files changed

+42
-61
lines changed

libs/providers/langchain-anthropic/src/chat_models.ts

Lines changed: 33 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,11 @@ import {
1313
} from "@langchain/core/language_models/chat_models";
1414
import {
1515
type StructuredOutputMethodOptions,
16-
type BaseLanguageModelInput,
1716
isOpenAITool,
1817
} from "@langchain/core/language_models/base";
1918
import { toJsonSchema } from "@langchain/core/utils/json_schema";
2019
import { BaseLLMOutputParser } from "@langchain/core/output_parsers";
21-
import {
22-
Runnable,
23-
RunnablePassthrough,
24-
RunnableSequence,
25-
} from "@langchain/core/runnables";
20+
import { RunnableLambda } from "@langchain/core/runnables";
2621
import {
2722
InteropZodType,
2823
isInteropZodSchema,
@@ -622,9 +617,10 @@ function extractToken(chunk: AIMessageChunk): string | undefined {
622617
* <br />
623618
*/
624619
export class ChatAnthropicMessages<
625-
CallOptions extends ChatAnthropicCallOptions = ChatAnthropicCallOptions
620+
CallOptions extends ChatAnthropicCallOptions = ChatAnthropicCallOptions,
621+
RunOutput = AIMessageChunk
626622
>
627-
extends BaseChatModel<CallOptions, AIMessageChunk>
623+
extends BaseChatModel<CallOptions, RunOutput>
628624
implements AnthropicInput
629625
{
630626
static lc_name() {
@@ -791,7 +787,7 @@ export class ChatAnthropicMessages<
791787
override bindTools(
792788
tools: ChatAnthropicToolType[],
793789
kwargs?: Partial<CallOptions>
794-
): Runnable<BaseLanguageModelInput, AIMessageChunk, CallOptions> {
790+
): this {
795791
return this.withConfig({
796792
tools: this.formatStructuredToolToAnthropic(tools),
797793
...kwargs,
@@ -1093,7 +1089,7 @@ export class ChatAnthropicMessages<
10931089
// eslint-disable-next-line @typescript-eslint/no-explicit-any
10941090
| Record<string, any>,
10951091
config?: StructuredOutputMethodOptions<false>
1096-
): Runnable<BaseLanguageModelInput, RunOutput>;
1092+
): ChatAnthropicMessages<CallOptions, RunOutput>;
10971093

10981094
withStructuredOutput<
10991095
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -1104,7 +1100,10 @@ export class ChatAnthropicMessages<
11041100
// eslint-disable-next-line @typescript-eslint/no-explicit-any
11051101
| Record<string, any>,
11061102
config?: StructuredOutputMethodOptions<true>
1107-
): Runnable<BaseLanguageModelInput, { raw: BaseMessage; parsed: RunOutput }>;
1103+
): ChatAnthropicMessages<
1104+
CallOptions,
1105+
{ raw: AIMessageChunk; parsed: RunOutput }
1106+
>;
11081107

11091108
withStructuredOutput<
11101109
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -1116,10 +1115,10 @@ export class ChatAnthropicMessages<
11161115
| Record<string, any>,
11171116
config?: StructuredOutputMethodOptions<boolean>
11181117
):
1119-
| Runnable<BaseLanguageModelInput, RunOutput>
1120-
| Runnable<
1121-
BaseLanguageModelInput,
1122-
{ raw: BaseMessage; parsed: RunOutput }
1118+
| BaseChatModel<CallOptions, RunOutput>
1119+
| BaseChatModel<
1120+
CallOptions,
1121+
{ raw: AIMessageChunk; parsed: RunOutput | null }
11231122
> {
11241123
// eslint-disable-next-line @typescript-eslint/no-explicit-any
11251124
const schema: InteropZodType<RunOutput> | Record<string, any> =
@@ -1130,6 +1129,12 @@ export class ChatAnthropicMessages<
11301129
if (method === "jsonMode") {
11311130
throw new Error(`Anthropic only supports "functionCalling" as a method.`);
11321131
}
1132+
const thinkingAdmonition =
1133+
"Anthropic structured output relies on forced tool calling, " +
1134+
"which is not supported when `thinking` is enabled. This method will raise " +
1135+
"OutputParserException if tool calls are not " +
1136+
"generated. Consider disabling `thinking` or adjust your prompt to ensure " +
1137+
"the tool is called.";
11331138

11341139
let functionName = name ?? "extract";
11351140
let outputParser: BaseLLMOutputParser<RunOutput>;
@@ -1148,6 +1153,8 @@ export class ChatAnthropicMessages<
11481153
returnSingle: true,
11491154
keyName: functionName,
11501155
zodSchema: schema,
1156+
errorMsgIfNoToolCalls:
1157+
this.thinking?.type === "enabled" ? thinkingAdmonition : undefined,
11511158
});
11521159
} else {
11531160
let anthropicTools: Anthropic.Messages.Tool;
@@ -1170,35 +1177,20 @@ export class ChatAnthropicMessages<
11701177
outputParser = new AnthropicToolsOutputParser<RunOutput>({
11711178
returnSingle: true,
11721179
keyName: functionName,
1180+
errorMsgIfNoToolCalls:
1181+
this.thinking?.type === "enabled" ? thinkingAdmonition : undefined,
11731182
});
11741183
}
1175-
let llm;
1184+
let llm: this;
11761185
if (this.thinking?.type === "enabled") {
1177-
const thinkingAdmonition =
1178-
"Anthropic structured output relies on forced tool calling, " +
1179-
"which is not supported when `thinking` is enabled. This method will raise " +
1180-
"OutputParserException if tool calls are not " +
1181-
"generated. Consider disabling `thinking` or adjust your prompt to ensure " +
1182-
"the tool is called.";
1183-
11841186
console.warn(thinkingAdmonition);
1185-
11861187
llm = this.withConfig({
11871188
tools,
11881189
ls_structured_output_format: {
11891190
kwargs: { method: "functionCalling" },
11901191
schema: toJsonSchema(schema),
11911192
},
11921193
} as Partial<CallOptions>);
1193-
1194-
const raiseIfNoToolCalls = (message: AIMessageChunk) => {
1195-
if (!message.tool_calls || message.tool_calls.length === 0) {
1196-
throw new Error(thinkingAdmonition);
1197-
}
1198-
return message;
1199-
};
1200-
1201-
llm = llm.pipe(raiseIfNoToolCalls);
12021194
} else {
12031195
llm = this.withConfig({
12041196
tools,
@@ -1214,32 +1206,16 @@ export class ChatAnthropicMessages<
12141206
}
12151207

12161208
if (!includeRaw) {
1217-
return llm.pipe(outputParser).withConfig({
1218-
runName: "ChatAnthropicStructuredOutput",
1219-
}) as Runnable<BaseLanguageModelInput, RunOutput>;
1209+
return llm.withOutputParser(outputParser);
12201210
}
12211211

1222-
const parserAssign = RunnablePassthrough.assign({
1223-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1224-
parsed: (input: any, config) => outputParser.invoke(input.raw, config),
1225-
});
1226-
const parserNone = RunnablePassthrough.assign({
1227-
parsed: () => null,
1228-
});
1229-
const parsedWithFallback = parserAssign.withFallbacks({
1230-
fallbacks: [parserNone],
1231-
});
1232-
return RunnableSequence.from<
1233-
BaseLanguageModelInput,
1234-
{ raw: BaseMessage; parsed: RunOutput }
1235-
>([
1236-
{
1237-
raw: llm,
1238-
},
1239-
parsedWithFallback,
1240-
]).withConfig({
1241-
runName: "StructuredOutputRunnable",
1242-
});
1212+
const parserWithRaw = RunnableLambda.from(
1213+
async (message: AIMessageChunk, config) => ({
1214+
raw: message,
1215+
parsed: await outputParser.invoke(message, config).catch(() => null),
1216+
})
1217+
);
1218+
return llm.withOutputParser(parserWithRaw);
12431219
}
12441220
}
12451221

libs/providers/langchain-anthropic/src/output_parsers.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import {
1212

1313
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1414
interface AnthropicToolsOutputParserParams<T extends Record<string, any>>
15-
extends JsonOutputKeyToolsParserParamsInterop<T> {}
15+
extends JsonOutputKeyToolsParserParamsInterop<T> {
16+
errorMsgIfNoToolCalls?: string;
17+
}
1618

1719
export class AnthropicToolsOutputParser<
1820
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -34,11 +36,16 @@ export class AnthropicToolsOutputParser<
3436

3537
zodSchema?: InteropZodType<T>;
3638

39+
errorMsgIfNoToolCalls =
40+
"No parseable tool calls provided to AnthropicToolsOutputParser.";
41+
3742
constructor(params: AnthropicToolsOutputParserParams<T>) {
3843
super(params);
3944
this.keyName = params.keyName;
4045
this.returnSingle = params.returnSingle ?? this.returnSingle;
4146
this.zodSchema = params.zodSchema;
47+
this.errorMsgIfNoToolCalls =
48+
params.errorMsgIfNoToolCalls ?? this.errorMsgIfNoToolCalls;
4249
}
4350

4451
protected async _validateResult(result: unknown): Promise<T> {
@@ -91,9 +98,7 @@ export class AnthropicToolsOutputParser<
9198
return tool;
9299
});
93100
if (tools[0] === undefined) {
94-
throw new Error(
95-
"No parseable tool calls provided to AnthropicToolsOutputParser."
96-
);
101+
throw new Error(this.errorMsgIfNoToolCalls);
97102
}
98103
const [tool] = tools;
99104
const validatedResult = await this._validateResult(tool.args);

0 commit comments

Comments
 (0)