Skip to content

Commit 08ff323

Browse files
authored
anthropic[minor]: Add tool_choice arg (#5416)
* anthropic[minor]: Add tool_choice arg * chore: lint files * release 0.1.19
1 parent a9409a5 commit 08ff323

File tree

6 files changed

+230
-8
lines changed

6 files changed

+230
-8
lines changed

‎docs/core_docs/docs/integrations/chat/anthropic.mdx‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,42 @@ import AnthropicSingleTool from "@examples/models/chat/integration_anthropic_sin
7979
See the LangSmith trace [here](https://smith.langchain.com/public/90c03ed0-154b-4a50-afbf-83dcbf302647/r)
8080
:::
8181

82+
### Forced tool calling
83+
84+
import AnthropicForcedTool from "@examples/models/chat/integration_anthropic_forced_tool.ts";
85+
86+
In this example we'll provide the model with two tools:
87+
88+
- `calculator`
89+
- `get_weather`
90+
91+
Then, when we call `bindTools`, we'll force the model to use the `get_weather` tool by passing the `tool_choice` arg like this:
92+
93+
```typescript
94+
.bindTools({
95+
tools,
96+
tool_choice: {
97+
type: "tool",
98+
name: "get_weather",
99+
}
100+
});
101+
```
102+
103+
Finally, we'll invoke the model, but instead of asking about the weather, we'll ask it to do some math.
104+
Since we explicitly forced the model to use the `get_weather` tool, it will ignore the input and return the weather information (in this case it returned `<UNKNOWN>`, which is expected.)
105+
106+
<CodeBlock language="typescript">{AnthropicForcedTool}</CodeBlock>
107+
108+
The `bind_tools` argument has three possible values:
109+
110+
- `{ type: "tool", name: "tool_name" }` - Forces the model to use the specified tool.
111+
- `"any"` - Allows the model to choose the tool, but still forcing it to choose at least one.
112+
- `"auto"` - The default value. Allows the model to select any tool, or none.
113+
114+
:::tip
115+
See the LangSmith trace [here](https://smith.langchain.com/public/c5cc8fe7-5e76-4607-8c43-1e0b30e4f5ca/r)
116+
:::
117+
82118
### `withStructuredOutput`
83119

84120
import AnthropicWSA from "@examples/models/chat/integration_anthropic_wsa.ts";
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { ChatAnthropic } from "@langchain/anthropic";
2+
import { ChatPromptTemplate } from "@langchain/core/prompts";
3+
import { z } from "zod";
4+
import { zodToJsonSchema } from "zod-to-json-schema";
5+
6+
const calculatorSchema = z.object({
7+
operation: z
8+
.enum(["add", "subtract", "multiply", "divide"])
9+
.describe("The type of operation to execute."),
10+
number1: z.number().describe("The first number to operate on."),
11+
number2: z.number().describe("The second number to operate on."),
12+
});
13+
14+
const weatherSchema = z.object({
15+
city: z.string().describe("The city to get the weather from"),
16+
state: z.string().optional().describe("The state to get the weather from"),
17+
});
18+
19+
const tools = [
20+
{
21+
name: "calculator",
22+
description: "A simple calculator tool",
23+
input_schema: zodToJsonSchema(calculatorSchema),
24+
},
25+
{
26+
name: "get_weather",
27+
description:
28+
"Get the weather of a specific location and return the temperature in Celsius.",
29+
input_schema: zodToJsonSchema(weatherSchema),
30+
},
31+
];
32+
33+
const model = new ChatAnthropic({
34+
apiKey: process.env.ANTHROPIC_API_KEY,
35+
model: "claude-3-haiku-20240307",
36+
}).bind({
37+
tools,
38+
tool_choice: {
39+
type: "tool",
40+
name: "get_weather",
41+
},
42+
});
43+
44+
const prompt = ChatPromptTemplate.fromMessages([
45+
[
46+
"system",
47+
"You are a helpful assistant who always needs to use a calculator.",
48+
],
49+
["human", "{input}"],
50+
]);
51+
52+
// Chain your prompt and model together
53+
const chain = prompt.pipe(model);
54+
55+
const response = await chain.invoke({
56+
input: "What is the sum of 2725 and 273639",
57+
});
58+
console.log(JSON.stringify(response, null, 2));
59+
/*
60+
{
61+
"kwargs": {
62+
"tool_calls": [
63+
{
64+
"name": "get_weather",
65+
"args": {
66+
"city": "<UNKNOWN>",
67+
"state": "<UNKNOWN>"
68+
},
69+
"id": "toolu_01MGRNudJvSDrrCZcPa2WrBX"
70+
}
71+
],
72+
"response_metadata": {
73+
"id": "msg_01RW3R4ctq7q5g4GJuGMmRPR",
74+
"model": "claude-3-haiku-20240307",
75+
"stop_sequence": null,
76+
"usage": {
77+
"input_tokens": 672,
78+
"output_tokens": 52
79+
},
80+
"stop_reason": "tool_use"
81+
}
82+
}
83+
}
84+
*/

‎libs/langchain-anthropic/package.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@langchain/anthropic",
3-
"version": "0.1.18",
3+
"version": "0.1.19",
44
"description": "Anthropic integrations for LangChain.js",
55
"type": "module",
66
"engines": {
@@ -39,7 +39,7 @@
3939
"author": "LangChain",
4040
"license": "MIT",
4141
"dependencies": {
42-
"@anthropic-ai/sdk": "^0.20.1",
42+
"@anthropic-ai/sdk": "^0.21.0",
4343
"@langchain/core": "<0.3.0 || >0.1.0",
4444
"fast-xml-parser": "^4.3.5",
4545
"zod": "^3.22.4",

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,20 @@ type AnthropicStreamingMessageCreateParams =
6060
Anthropic.MessageCreateParamsStreaming;
6161
type AnthropicMessageStreamEvent = Anthropic.MessageStreamEvent;
6262
type AnthropicRequestOptions = Anthropic.RequestOptions;
63-
63+
type AnthropicToolChoice =
64+
| {
65+
type: "tool";
66+
name: string;
67+
}
68+
| "any"
69+
| "auto";
6470
interface ChatAnthropicCallOptions extends BaseLanguageModelCallOptions {
6571
tools?: (StructuredToolInterface | AnthropicTool)[];
72+
/**
73+
* Whether or not to specify what tool the model should use
74+
* @default "auto"
75+
*/
76+
tool_choice?: AnthropicToolChoice;
6677
}
6778

6879
type AnthropicMessageResponse = Anthropic.ContentBlock | AnthropicToolResponse;
@@ -546,6 +557,26 @@ export class ChatAnthropicMessages<
546557
"messages"
547558
> &
548559
Kwargs {
560+
let tool_choice:
561+
| {
562+
type: string;
563+
name?: string;
564+
}
565+
| undefined;
566+
if (options?.tool_choice) {
567+
if (options?.tool_choice === "any") {
568+
tool_choice = {
569+
type: "any",
570+
};
571+
} else if (options?.tool_choice === "auto") {
572+
tool_choice = {
573+
type: "auto",
574+
};
575+
} else {
576+
tool_choice = options?.tool_choice;
577+
}
578+
}
579+
549580
return {
550581
model: this.model,
551582
temperature: this.temperature,
@@ -555,6 +586,7 @@ export class ChatAnthropicMessages<
555586
stream: this.streaming,
556587
max_tokens: this.maxTokens,
557588
tools: this.formatStructuredToolToAnthropic(options?.tools),
589+
tool_choice,
558590
...this.invocationKwargs,
559591
};
560592
}
@@ -910,6 +942,7 @@ export class ChatAnthropicMessages<
910942
}
911943
const llm = this.bind({
912944
tools,
945+
tool_choice: "any",
913946
} as Partial<CallOptions>);
914947

915948
if (!includeRaw) {

‎libs/langchain-anthropic/src/tests/chat_models-tools.int.test.ts‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,72 @@ test("withStructuredOutput JSON Schema only", async () => {
271271
);
272272
expect(typeof result.location).toBe("string");
273273
});
274+
275+
test("Can pass tool_choice", async () => {
276+
const tool1 = {
277+
name: "get_weather",
278+
description:
279+
"Get the weather of a specific location and return the temperature in Celsius.",
280+
input_schema: {
281+
type: "object",
282+
properties: {
283+
location: {
284+
type: "string",
285+
description: "The name of city to get the weather for.",
286+
},
287+
},
288+
required: ["location"],
289+
},
290+
};
291+
const tool2 = {
292+
name: "calculator",
293+
description: "Calculate any math expression and return the result.",
294+
input_schema: {
295+
type: "object",
296+
properties: {
297+
expression: {
298+
type: "string",
299+
description: "The math expression to calculate.",
300+
},
301+
},
302+
required: ["expression"],
303+
},
304+
};
305+
const tools = [tool1, tool2];
306+
307+
const modelWithTools = model.bindTools(tools, {
308+
tool_choice: {
309+
type: "tool",
310+
name: "get_weather",
311+
},
312+
});
313+
314+
const result = await modelWithTools.invoke(
315+
"What is the sum of 272818 and 281818?"
316+
);
317+
console.log(
318+
{
319+
tool_calls: JSON.stringify(result.content, null, 2),
320+
},
321+
"Can bind & invoke StructuredTools"
322+
);
323+
expect(Array.isArray(result.content)).toBeTruthy();
324+
if (!Array.isArray(result.content)) {
325+
throw new Error("Content is not an array");
326+
}
327+
let toolCall: AnthropicToolResponse | undefined;
328+
result.content.forEach((item) => {
329+
if (item.type === "tool_use") {
330+
toolCall = item as AnthropicToolResponse;
331+
}
332+
});
333+
if (!toolCall) {
334+
throw new Error("No tool call found");
335+
}
336+
expect(toolCall).toBeTruthy();
337+
const { name, input } = toolCall;
338+
expect(toolCall.input).toEqual(result.tool_calls?.[0].args);
339+
expect(name).toBe("get_weather");
340+
expect(input).toBeTruthy();
341+
expect(input.location).toBeTruthy();
342+
});

‎yarn.lock‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,9 @@ __metadata:
211211
languageName: node
212212
linkType: hard
213213

214-
"@anthropic-ai/sdk@npm:^0.20.1":
215-
version: 0.20.1
216-
resolution: "@anthropic-ai/sdk@npm:0.20.1"
214+
"@anthropic-ai/sdk@npm:^0.21.0":
215+
version: 0.21.0
216+
resolution: "@anthropic-ai/sdk@npm:0.21.0"
217217
dependencies:
218218
"@types/node": ^18.11.18
219219
"@types/node-fetch": ^2.6.4
@@ -223,7 +223,7 @@ __metadata:
223223
formdata-node: ^4.3.2
224224
node-fetch: ^2.6.7
225225
web-streams-polyfill: ^3.2.1
226-
checksum: a880088ffeb993ea835f3ec250d53bf6ba23e97c3dfc54c915843aa8cb4778849fb7b85de0a359155c36595a5a5cc1db64139d407d2e36a2423284ebfe763cce
226+
checksum: fbed720938487495f1d28822fa6eb3871cf7e7be325c299b69efa78e72e1e0b66d9f564003ae5d7a1e96c7555cc69c817be4b901d1847ae002f782546a4c987d
227227
languageName: node
228228
linkType: hard
229229

@@ -8865,7 +8865,7 @@ __metadata:
88658865
version: 0.0.0-use.local
88668866
resolution: "@langchain/anthropic@workspace:libs/langchain-anthropic"
88678867
dependencies:
8868-
"@anthropic-ai/sdk": ^0.20.1
8868+
"@anthropic-ai/sdk": ^0.21.0
88698869
"@jest/globals": ^29.5.0
88708870
"@langchain/community": "workspace:*"
88718871
"@langchain/core": <0.3.0 || >0.1.0

0 commit comments

Comments
 (0)