Skip to content

Commit e6d798b

Browse files
feat(anthropic): support websearch tool (#9407)
1 parent ac8b1a8 commit e6d798b

File tree

5 files changed

+282
-1
lines changed

5 files changed

+282
-1
lines changed

libs/providers/langchain-anthropic/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,50 @@ const response = await llmWithMemory.invoke(
134134

135135
For more information, see [Anthropic's Memory Tool documentation](https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/memory-tool).
136136

137+
### Web Search Tool
138+
139+
The web search tool (`webSearch_20250305`) gives Claude direct access to real-time web content, allowing it to answer questions with up-to-date information beyond its knowledge cutoff. Claude automatically cites sources from search results as part of its answer.
140+
141+
```typescript
142+
import { ChatAnthropic, tools } from "@langchain/anthropic";
143+
144+
const llm = new ChatAnthropic({
145+
model: "claude-sonnet-4-5-20250929",
146+
});
147+
148+
// Basic usage
149+
const response = await llm.invoke("What is the weather in NYC?", {
150+
tools: [tools.webSearch_20250305()],
151+
});
152+
```
153+
154+
The web search tool supports several configuration options:
155+
156+
```typescript
157+
const response = await llm.invoke("Latest news about AI?", {
158+
tools: [
159+
tools.webSearch_20250305({
160+
// Maximum number of times the tool can be used in the API request
161+
maxUses: 5,
162+
// Only include results from these domains
163+
allowedDomains: ["reuters.com", "bbc.com"],
164+
// Or block specific domains (cannot be used with allowedDomains)
165+
// blockedDomains: ["example.com"],
166+
// Provide user location for more relevant results
167+
userLocation: {
168+
type: "approximate",
169+
city: "San Francisco",
170+
region: "California",
171+
country: "US",
172+
timezone: "America/Los_Angeles",
173+
},
174+
}),
175+
],
176+
});
177+
```
178+
179+
For more information, see [Anthropic's Web Search Tool documentation](https://docs.anthropic.com/en/docs/build-with-claude/tool-use/web-search-tool).
180+
137181
## Development
138182

139183
To develop the Anthropic package, you'll need to follow these instructions:
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
1-
export { memory_20250818 } from "./memory.js";
1+
import { memory_20250818 } from "./memory.js";
2+
import { webSearch_20250305 } from "./webSearch.js";
3+
4+
export const tools = {
5+
memory_20250818,
6+
webSearch_20250305,
7+
};
8+
29
export type * from "./types.js";
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { expect, it, describe } from "vitest";
2+
import {
3+
AIMessage,
4+
AIMessageChunk,
5+
HumanMessage,
6+
} from "@langchain/core/messages";
7+
import { concat } from "@langchain/core/utils/stream";
8+
9+
import { ChatAnthropic } from "../../chat_models.js";
10+
import { webSearch_20250305 } from "../webSearch.js";
11+
12+
const createModel = () =>
13+
new ChatAnthropic({
14+
model: "claude-sonnet-4-5-20250929",
15+
temperature: 0,
16+
});
17+
18+
describe("Anthropic Web Search Tool Integration Tests", () => {
19+
it("web search tool can be bound to ChatAnthropic and triggers server tool use", async () => {
20+
const llm = createModel();
21+
const llmWithWebSearch = llm.bindTools([
22+
webSearch_20250305({ maxUses: 1 }),
23+
]);
24+
25+
const response = await llmWithWebSearch.invoke([
26+
new HumanMessage("What is the current weather in San Francisco?"),
27+
]);
28+
29+
expect(response).toBeInstanceOf(AIMessage);
30+
expect(Array.isArray(response.content)).toBe(true);
31+
32+
const contentBlocks = response.content as Array<{ type: string }>;
33+
const hasServerToolUse = contentBlocks.some(
34+
(block) => block.type === "server_tool_use"
35+
);
36+
const hasWebSearchResult = contentBlocks.some(
37+
(block) => block.type === "web_search_tool_result"
38+
);
39+
40+
expect(hasServerToolUse).toBe(true);
41+
expect(hasWebSearchResult).toBe(true);
42+
}, 30000);
43+
44+
it("web search tool streaming works correctly", async () => {
45+
const llm = createModel();
46+
const llmWithWebSearch = llm.bindTools([
47+
webSearch_20250305({ maxUses: 1 }),
48+
]);
49+
50+
const stream = await llmWithWebSearch.stream([
51+
new HumanMessage(
52+
"What is the capital of France according to a web search?"
53+
),
54+
]);
55+
56+
let finalChunk: AIMessageChunk | undefined;
57+
for await (const chunk of stream) {
58+
if (!finalChunk) {
59+
finalChunk = chunk;
60+
} else {
61+
finalChunk = concat(finalChunk, chunk);
62+
}
63+
}
64+
65+
expect(finalChunk).toBeDefined();
66+
expect(finalChunk).toBeInstanceOf(AIMessageChunk);
67+
expect(Array.isArray(finalChunk?.content)).toBe(true);
68+
const contentBlocks = finalChunk?.content as Array<{ type: string }>;
69+
const hasServerToolUse = contentBlocks.some(
70+
(block) => block.type === "server_tool_use"
71+
);
72+
const hasWebSearchResult = contentBlocks.some(
73+
(block) => block.type === "web_search_tool_result"
74+
);
75+
76+
expect(hasServerToolUse).toBe(true);
77+
expect(hasWebSearchResult).toBe(true);
78+
}, 30000);
79+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { expect, it, describe } from "vitest";
2+
import { webSearch_20250305 } from "../webSearch.js";
3+
4+
describe("Anthropic Web Search Tool Integration Tests", () => {
5+
it("webSearch_20250305 creates a valid tool definition", () => {
6+
expect(webSearch_20250305()).toMatchInlineSnapshot(`
7+
{
8+
"allowed_domains": undefined,
9+
"blocked_domains": undefined,
10+
"cache_control": undefined,
11+
"defer_loading": undefined,
12+
"max_uses": undefined,
13+
"name": "web_search",
14+
"strict": undefined,
15+
"type": "web_search_20250305",
16+
"user_location": undefined,
17+
}
18+
`);
19+
20+
expect(
21+
webSearch_20250305({
22+
maxUses: 3,
23+
allowedDomains: ["example.com", "docs.example.com"],
24+
cacheControl: { type: "ephemeral" },
25+
deferLoading: true,
26+
strict: true,
27+
userLocation: {
28+
type: "approximate",
29+
country: "US",
30+
region: "California",
31+
city: "San Francisco",
32+
},
33+
})
34+
).toMatchInlineSnapshot(`
35+
{
36+
"allowed_domains": [
37+
"example.com",
38+
"docs.example.com",
39+
],
40+
"blocked_domains": undefined,
41+
"cache_control": {
42+
"type": "ephemeral",
43+
},
44+
"defer_loading": true,
45+
"max_uses": 3,
46+
"name": "web_search",
47+
"strict": true,
48+
"type": "web_search_20250305",
49+
"user_location": {
50+
"city": "San Francisco",
51+
"country": "US",
52+
"region": "California",
53+
"type": "approximate",
54+
},
55+
}
56+
`);
57+
});
58+
});
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import Anthropic from "@anthropic-ai/sdk";
2+
3+
/**
4+
* Options for the web search tool.
5+
*/
6+
interface WebSearch20250305Options {
7+
/**
8+
* Maximum number of times the tool can be used in the API request.
9+
*/
10+
maxUses?: number;
11+
/**
12+
* If provided, only these domains will be included in results. Cannot be used
13+
* alongside `blocked_domains`.
14+
*/
15+
allowedDomains?: string[];
16+
/**
17+
* If provided, these domains will never appear in results. Cannot be used
18+
* alongside `allowed_domains`.
19+
*/
20+
blockedDomains?: string[];
21+
/**
22+
* Create a cache control breakpoint at this content block.
23+
*/
24+
cacheControl?: Anthropic.Beta.BetaCacheControlEphemeral;
25+
/**
26+
* Parameters for the user's location. Used to provide more relevant search
27+
* results.
28+
*/
29+
userLocation?: Anthropic.Beta.BetaWebSearchTool20250305.UserLocation;
30+
/**
31+
* If true, tool will not be included in initial system prompt. Only loaded when
32+
* returned via tool_reference from tool search.
33+
*/
34+
deferLoading?: boolean;
35+
/**
36+
* If true, tool will only return results from the allowed domains.
37+
*/
38+
strict?: boolean;
39+
}
40+
41+
/**
42+
* Creates a web search tool that gives Claude direct access to real-time web content,
43+
* allowing it to answer questions with up-to-date information beyond its knowledge cutoff.
44+
* Claude automatically cites sources from search results as part of its answer.
45+
*
46+
* @see {@link https://docs.anthropic.com/en/docs/build-with-claude/tool-use/web-search-tool | Anthropic Web Search Documentation}
47+
* @param options - Configuration options for the web search tool
48+
* @returns A web search tool definition to be passed to the Anthropic API
49+
*
50+
* @example
51+
* ```typescript
52+
* import { ChatAnthropic, tools } from "@langchain/anthropic";
53+
*
54+
* const model = new ChatAnthropic({
55+
* model: "claude-sonnet-4-5-20250929",
56+
* });
57+
*
58+
* // Basic usage
59+
* const response = await model.invoke("What is the weather in NYC?", {
60+
* tools: [tools.webSearch_20250305()],
61+
* });
62+
*
63+
* // With options
64+
* const responseWithOptions = await model.invoke("Latest news about AI?", {
65+
* tools: [tools.webSearch_20250305({
66+
* maxUses: 5,
67+
* allowedDomains: ["reuters.com", "bbc.com"],
68+
* userLocation: {
69+
* type: "approximate",
70+
* city: "San Francisco",
71+
* region: "California",
72+
* country: "US",
73+
* timezone: "America/Los_Angeles",
74+
* },
75+
* })],
76+
* });
77+
* ```
78+
*/
79+
export function webSearch_20250305(
80+
options?: WebSearch20250305Options
81+
): Anthropic.Beta.BetaWebSearchTool20250305 {
82+
return {
83+
type: "web_search_20250305",
84+
name: "web_search",
85+
max_uses: options?.maxUses,
86+
allowed_domains: options?.allowedDomains,
87+
blocked_domains: options?.blockedDomains,
88+
cache_control: options?.cacheControl,
89+
defer_loading: options?.deferLoading,
90+
strict: options?.strict,
91+
user_location: options?.userLocation,
92+
};
93+
}

0 commit comments

Comments
 (0)