diff --git a/libs/langchain-anthropic/package.json b/libs/langchain-anthropic/package.json index 8b96391a42ec..b3729c114f2d 100644 --- a/libs/langchain-anthropic/package.json +++ b/libs/langchain-anthropic/package.json @@ -35,7 +35,7 @@ "author": "LangChain", "license": "MIT", "dependencies": { - "@anthropic-ai/sdk": "^0.39.0", + "@anthropic-ai/sdk": "^0.52.0", "fast-xml-parser": "^4.4.1", "zod": "^3.22.4", "zod-to-json-schema": "^3.22.4" diff --git a/libs/langchain-anthropic/src/chat_models.ts b/libs/langchain-anthropic/src/chat_models.ts index 10483d0f29b7..7423a374d93b 100644 --- a/libs/langchain-anthropic/src/chat_models.ts +++ b/libs/langchain-anthropic/src/chat_models.ts @@ -35,6 +35,7 @@ import { anthropicResponseToChatMessages, } from "./utils/message_outputs.js"; import { + AnthropicBuiltInToolUnion, AnthropicMessageCreateParams, AnthropicMessageStreamEvent, AnthropicRequestOptions, @@ -100,6 +101,20 @@ function isAnthropicTool(tool: any): tool is Anthropic.Messages.Tool { return "input_schema" in tool; } +function isBuiltinTool(tool: unknown): tool is AnthropicBuiltInToolUnion { + return ( + typeof tool === "object" && + tool !== null && + "type" in tool && + "name" in tool && + typeof tool.type === "string" && + typeof tool.name === "string" && + (tool.name === "bash" || + tool.name === "str_replace_editor" || + tool.name === "web_search") + ); +} + /** * Input to AnthropicChat class. */ @@ -720,11 +735,14 @@ export class ChatAnthropicMessages< */ formatStructuredToolToAnthropic( tools: ChatAnthropicCallOptions["tools"] - ): Anthropic.Messages.Tool[] | undefined { + ): Anthropic.Messages.ToolUnion[] | undefined { if (!tools || !tools.length) { return undefined; } return tools.map((tool) => { + if (isBuiltinTool(tool)) { + return tool; + } if (isAnthropicTool(tool)) { return tool; } diff --git a/libs/langchain-anthropic/src/output_parsers.ts b/libs/langchain-anthropic/src/output_parsers.ts index b4a08be4d6ee..e2d0caf3c2dc 100644 --- a/libs/langchain-anthropic/src/output_parsers.ts +++ b/libs/langchain-anthropic/src/output_parsers.ts @@ -98,6 +98,7 @@ export class AnthropicToolsOutputParser< // eslint-disable-next-line @typescript-eslint/no-explicit-any export function extractToolCalls(content: Record[]) { const toolCalls: ToolCall[] = []; + for (const block of content) { if (block.type === "tool_use") { toolCalls.push({ @@ -106,7 +107,19 @@ export function extractToolCalls(content: Record[]) { id: block.id, type: "tool_call", }); + } else if ( + block.type === "server_tool_use" && + block.name === "web_search" + ) { + // Handle Anthropic built-in web search tool + toolCalls.push({ + name: block.name, + args: block.input, + id: block.id, + type: "tool_call", + }); } } + return toolCalls; } diff --git a/libs/langchain-anthropic/src/tests/chat_models-web_search.int.test.ts b/libs/langchain-anthropic/src/tests/chat_models-web_search.int.test.ts new file mode 100644 index 000000000000..e46c52d68d19 --- /dev/null +++ b/libs/langchain-anthropic/src/tests/chat_models-web_search.int.test.ts @@ -0,0 +1,49 @@ +import { test, expect } from "@jest/globals"; +import { HumanMessage, AIMessage } from "@langchain/core/messages"; +import { ChatAnthropic } from "../chat_models.js"; + +const model = new ChatAnthropic({ + model: "claude-3-5-sonnet-20241022", + temperature: 0, +}).bindTools([ + { + type: "web_search_20250305", + name: "web_search", + max_uses: 1, + }, +]); + +test("Web search single turn", async () => { + const result = await model.invoke([ + new HumanMessage("What is Claude Shannon's birth date?"), + ]); + + expect(result).toBeInstanceOf(AIMessage); + expect( + result.tool_calls?.find((tc) => tc.name === "web_search") + ).toBeTruthy(); +}, 30000); + +test("Web search multi-turn conversation", async () => { + const firstResponse = await model.invoke([ + new HumanMessage("What is Claude Shannon's birth date?"), + ]); + + const secondResponse = await model.invoke([ + new HumanMessage("What is Claude Shannon's birth date?"), + firstResponse, + new HumanMessage("What year did he die?"), + ]); + + expect(firstResponse).toBeInstanceOf(AIMessage); + expect(secondResponse).toBeInstanceOf(AIMessage); +}, 45000); + +test("Web search with unusual query", async () => { + const result = await model.invoke([ + new HumanMessage("What is the population of Mars?"), + ]); + + expect(result).toBeInstanceOf(AIMessage); + expect(result.content).toBeDefined(); +}, 30000); diff --git a/libs/langchain-anthropic/src/tests/chat_models-web_search.test.ts b/libs/langchain-anthropic/src/tests/chat_models-web_search.test.ts new file mode 100644 index 000000000000..b727f317bfef --- /dev/null +++ b/libs/langchain-anthropic/src/tests/chat_models-web_search.test.ts @@ -0,0 +1,211 @@ +import Anthropic from "@anthropic-ai/sdk"; +import { test, expect } from "@jest/globals"; +import { AIMessage, HumanMessage } from "@langchain/core/messages"; +import { anthropicResponseToChatMessages } from "../utils/message_outputs.js"; +import { _convertMessagesToAnthropicPayload } from "../utils/message_inputs.js"; + +test("Web Search Tool - Anthropic response to LangChain format", () => { + // What Anthropic returns + const anthropicResponse: Anthropic.ContentBlock[] = [ + { + type: "text", + text: "I'll search for that information.", + citations: null, + }, + { + type: "server_tool_use", + id: "toolu_01ABC123", + name: "web_search", + input: { query: "Claude Shannon birth date" }, + }, + { + type: "web_search_tool_result", + tool_use_id: "toolu_01ABC123", + content: [ + { + type: "web_search_result", + title: "Claude Shannon - Wikipedia", + url: "https://en.wikipedia.org/wiki/Claude_Shannon", + encrypted_content: + "eyJjb250ZW50IjoiQ2xhdWRlIEVsd29vZCBTaGFubm9uIChBcHJpbCAzMCwgMTkxNiDigJMgRmVicnVhcnkgMjQsIDIwMDEpIHdhcyBhbiBBbWVyaWNhbiBtYXRoZW1hdGljaWFuLCBlbGVjdHJpY2FsIGVuZ2luZWVyLCBjb21wdXRlciBzY2llbnRpc3QgYW5kIGNyeXB0b2dyYXBoZXIga25vd24gYXMgdGhlIGZhdGhlciBvZiBpbmZvcm1hdGlvbiB0aGVvcnkuIn0=", + page_age: "April 30, 2025", + }, + ], + }, + { + type: "text", + text: "Claude Shannon was born on April 30, 1916.", + citations: [ + { + type: "web_search_result_location", + url: "https://en.wikipedia.org/wiki/Claude_Shannon", + title: "Claude Shannon - Wikipedia", + cited_text: + "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)...", + encrypted_index: + "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm1qLTQxYWUtOGVkYi1hNTc3MGZkZDllOGYSKENsYXVkZSBFbHdvb2QgU2hhbm5vbiAoQXByaWwgMzAsIDE5MTYgXu+/vSk=", + }, + ], + }, + ]; + + const result = anthropicResponseToChatMessages(anthropicResponse, { + id: "msg_01ABC123", + }); + + // What LangChain should produce + expect(result[0].message).toEqual( + new AIMessage({ + content: anthropicResponse, + tool_calls: [ + { + name: "web_search", + args: { query: "Claude Shannon birth date" }, + id: "toolu_01ABC123", + type: "tool_call", + }, + ], + additional_kwargs: { id: "msg_01ABC123" }, + response_metadata: { id: "msg_01ABC123" }, + id: "msg_01ABC123", + }) + ); +}); + +test("Web Search Tool - Only web_search server tools extracted", () => { + // What Anthropic returns (multiple server tools) + const anthropicResponse: Anthropic.ContentBlock[] = [ + { + type: "server_tool_use", + id: "toolu_web_001", + name: "web_search", + input: { query: "latest AI developments" }, + }, + { + type: "server_tool_use", + id: "toolu_web_002", + name: "web_search", + input: { query: "machine learning trends 2024" }, + }, + ]; + + const result = anthropicResponseToChatMessages(anthropicResponse, {}); + + // What LangChain should produce (only web_search extracted) + expect(result[0].message).toEqual( + new AIMessage({ + content: anthropicResponse, + tool_calls: [ + { + name: "web_search", + args: { query: "latest AI developments" }, + id: "toolu_web_001", + type: "tool_call", + }, + { + name: "web_search", + args: { query: "machine learning trends 2024" }, + id: "toolu_web_002", + type: "tool_call", + }, + ], + additional_kwargs: {}, + response_metadata: {}, + id: undefined, + }) + ); +}); + +test("Web Search Tool - LangChain message to Anthropic format", () => { + // What LangChain has (after a web search response) + const langChainMessage = new AIMessage({ + content: [ + { + type: "text", + text: "Based on my search, Claude Shannon was born in 1916 and made foundational contributions to information theory.", + citations: [ + { + type: "web_search_result_location", + url: "https://en.wikipedia.org/wiki/Claude_Shannon", + title: "Claude Shannon - Wikipedia", + cited_text: + "Claude Elwood Shannon (April 30, 1916 – February 24, 2001) was an American mathematician...", + encrypted_index: + "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm1qLWJkZGUtNDI0YS1hMjZlLWNmOTNjMGEzNGE2YxIkQ2xhdWRlIEVsd29vZCBTaGFubm9uIChBcHJpbCAzMCwgMTkxNiA+z+/fSk=", + }, + ], + }, + { + type: "web_search_tool_result", + tool_use_id: "toolu_01DEF456", + content: [ + { + type: "web_search_result", + title: "Claude Shannon - Wikipedia", + url: "https://en.wikipedia.org/wiki/Claude_Shannon", + encrypted_content: + "eyJjb250ZW50IjoiQ2xhdWRlIEVsd29vZCBTaGFubm9uIChBcHJpbCAzMCwgMTkxNiDigJMgRmVicnVhcnkgMjQsIDIwMDEpIHdhcyBhbiBBbWVyaWNhbiBtYXRoZW1hdGljaWFuLCBlbGVjdHJpY2FsIGVuZ2luZWVyLCBjb21wdXRlciBzY2llbnRpc3QgYW5kIGNyeXB0b2dyYXBoZXIga25vd24gYXMgdGhlIGZhdGhlciBvZiBpbmZvcm1hdGlvbiB0aGVvcnkuIn0=", + page_age: "April 30, 2025", + }, + { + type: "web_search_result", + title: "Information Theory - Britannica", + url: "https://www.britannica.com/science/information-theory", + encrypted_content: + "eyJjb250ZW50IjoiSW5mb3JtYXRpb24gdGhlb3J5LCBhIG1hdGhlbWF0aWNhbCByZXByZXNlbnRhdGlvbiBvZiB0aGUgY29uZGl0aW9ucyBhbmQgcGFyYW1ldGVycyBhZmZlY3RpbmcgdGhlIHRyYW5zbWlzc2lvbiBhbmQgcHJvY2Vzc2luZyBvZiBpbmZvcm1hdGlvbi4ifQ==", + page_age: "April 30, 2025", + }, + ], + }, + ], + }); + + const result = _convertMessagesToAnthropicPayload([ + new HumanMessage("Follow up question about information theory"), + langChainMessage, + ]); + + // What should be sent to Anthropic (preserving encrypted content for multi-turn) + expect(result.messages[1]).toEqual({ + role: "assistant", + content: [ + { + type: "text", + text: "Based on my search, Claude Shannon was born in 1916 and made foundational contributions to information theory.", + citations: [ + { + type: "web_search_result_location", + url: "https://en.wikipedia.org/wiki/Claude_Shannon", + title: "Claude Shannon - Wikipedia", + cited_text: + "Claude Elwood Shannon (April 30, 1916 – February 24, 2001) was an American mathematician...", + encrypted_index: + "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm1qLWJkZGUtNDI0YS1hMjZlLWNmOTNjMGEzNGE2YxIkQ2xhdWRlIEVsd29vZCBTaGFubm9uIChBcHJpbCAzMCwgMTkxNiA+z+/fSk=", + }, + ], + }, + { + type: "web_search_tool_result", + tool_use_id: "toolu_01DEF456", + content: [ + { + type: "web_search_result", + title: "Claude Shannon - Wikipedia", + url: "https://en.wikipedia.org/wiki/Claude_Shannon", + encrypted_content: + "eyJjb250ZW50IjoiQ2xhdWRlIEVsd29vZCBTaGFubm9uIChBcHJpbCAzMCwgMTkxNiDigJMgRmVicnVhcnkgMjQsIDIwMDEpIHdhcyBhbiBBbWVyaWNhbiBtYXRoZW1hdGljaWFuLCBlbGVjdHJpY2FsIGVuZ2luZWVyLCBjb21wdXRlciBzY2llbnRpc3QgYW5kIGNyeXB0b2dyYXBoZXIga25vd24gYXMgdGhlIGZhdGhlciBvZiBpbmZvcm1hdGlvbiB0aGVvcnkuIn0=", + page_age: "April 30, 2025", + }, + { + type: "web_search_result", + title: "Information Theory - Britannica", + url: "https://www.britannica.com/science/information-theory", + encrypted_content: + "eyJjb250ZW50IjoiSW5mb3JtYXRpb24gdGhlb3J5LCBhIG1hdGhlbWF0aWNhbCByZXByZXNlbnRhdGlvbiBvZiB0aGUgY29uZGl0aW9ucyBhbmQgcGFyYW1ldGVycyBhZmZlY3RpbmcgdGhlIHRyYW5zbWlzc2lvbiBhbmQgcHJvY2Vzc2luZyBvZiBpbmZvcm1hdGlvbi4ifQ==", + page_age: "April 30, 2025", + }, + ], + }, + ], + }); +}); diff --git a/libs/langchain-anthropic/src/types.ts b/libs/langchain-anthropic/src/types.ts index f8ab0424e5c9..6708aae04f8c 100644 --- a/libs/langchain-anthropic/src/types.ts +++ b/libs/langchain-anthropic/src/types.ts @@ -38,6 +38,25 @@ export type AnthropicDocumentBlockParam = Anthropic.Messages.DocumentBlockParam; export type AnthropicThinkingBlockParam = Anthropic.Messages.ThinkingBlockParam; export type AnthropicRedactedThinkingBlockParam = Anthropic.Messages.RedactedThinkingBlockParam; +export type AnthropicServerToolUseBlockParam = + Anthropic.Messages.ServerToolUseBlockParam; +export type AnthropicWebSearchToolResultBlockParam = + Anthropic.Messages.WebSearchToolResultBlockParam; +export type AnthropicWebSearchResultBlockParam = + Anthropic.Messages.WebSearchResultBlockParam; + +// Union of all possible content block types including server tool use +export type AnthropicContentBlock = + | AnthropicTextBlockParam + | AnthropicImageBlockParam + | AnthropicToolUseBlockParam + | AnthropicToolResultBlockParam + | AnthropicDocumentBlockParam + | AnthropicThinkingBlockParam + | AnthropicRedactedThinkingBlockParam + | AnthropicServerToolUseBlockParam + | AnthropicWebSearchToolResultBlockParam + | AnthropicWebSearchResultBlockParam; export function isAnthropicImageBlockParam( block: unknown @@ -98,3 +117,9 @@ export function isAnthropicImageBlockParam( return false; } + +// Type for built-in tools only (excludes custom Tool type) +export type AnthropicBuiltInToolUnion = Exclude< + Anthropic.Messages.ToolUnion, + Anthropic.Messages.Tool +>; diff --git a/libs/langchain-anthropic/src/utils/message_inputs.ts b/libs/langchain-anthropic/src/utils/message_inputs.ts index d2426056d054..94a35a7cb5c0 100644 --- a/libs/langchain-anthropic/src/utils/message_inputs.ts +++ b/libs/langchain-anthropic/src/utils/message_inputs.ts @@ -29,6 +29,8 @@ import { AnthropicDocumentBlockParam, AnthropicThinkingBlockParam, AnthropicRedactedThinkingBlockParam, + AnthropicServerToolUseBlockParam, + AnthropicWebSearchToolResultBlockParam, isAnthropicImageBlockParam, } from "../types.js"; @@ -344,7 +346,14 @@ const standardContentBlockConverter: StandardContentBlockConverter<{ }; function _formatContent(content: MessageContent) { - const toolTypes = ["tool_use", "tool_result", "input_json_delta"]; + const toolTypes = [ + "tool_use", + "tool_result", + "input_json_delta", + "server_tool_use", + "web_search_tool_result", + "web_search_result", + ]; const textTypes = ["text", "text_delta"]; if (typeof content === "string") { @@ -405,6 +414,9 @@ function _formatContent(content: MessageContent) { type: "text" as const, // Explicitly setting the type as "text" text: contentPart.text, ...(cacheControl ? { cache_control: cacheControl } : {}), + ...("citations" in contentPart && contentPart.citations + ? { citations: contentPart.citations } + : {}), }; } else if (toolTypes.find((t) => t === contentPart.type)) { const contentPartCopy = { ...contentPart }; @@ -499,7 +511,8 @@ export function _convertMessagesToAnthropicPayload( content.find( (contentPart) => (contentPart.type === "tool_use" || - contentPart.type === "input_json_delta") && + contentPart.type === "input_json_delta" || + contentPart.type === "server_tool_use") && contentPart.id === toolCall.id ) ); @@ -545,6 +558,8 @@ function mergeMessages(messages: AnthropicMessageCreateParams["messages"]) { | AnthropicDocumentBlockParam | AnthropicThinkingBlockParam | AnthropicRedactedThinkingBlockParam + | AnthropicServerToolUseBlockParam + | AnthropicWebSearchToolResultBlockParam > ): Array< | AnthropicTextBlockParam @@ -554,6 +569,8 @@ function mergeMessages(messages: AnthropicMessageCreateParams["messages"]) { | AnthropicDocumentBlockParam | AnthropicThinkingBlockParam | AnthropicRedactedThinkingBlockParam + | AnthropicServerToolUseBlockParam + | AnthropicWebSearchToolResultBlockParam > => { if (typeof content === "string") { return [ diff --git a/libs/langchain-anthropic/src/utils/message_outputs.ts b/libs/langchain-anthropic/src/utils/message_outputs.ts index 17d784456428..745f607a975f 100644 --- a/libs/langchain-anthropic/src/utils/message_outputs.ts +++ b/libs/langchain-anthropic/src/utils/message_outputs.ts @@ -77,7 +77,12 @@ export function _makeMessageChunkFromAnthropicEvent( }; } else if ( data.type === "content_block_start" && - ["tool_use", "document"].includes(data.content_block.type) + [ + "tool_use", + "document", + "server_tool_use", + "web_search_tool_result", + ].includes(data.content_block.type) ) { const contentBlock = data.content_block; let toolCallChunks: ToolCallChunk[]; @@ -90,6 +95,16 @@ export function _makeMessageChunkFromAnthropicEvent( args: "", }, ]; + } else if (contentBlock.type === "server_tool_use") { + // Handle anthropic built-in server tool use (like web search) + toolCallChunks = [ + { + id: contentBlock.id, + index: data.index, + name: contentBlock.name, + args: "", + }, + ]; } else { toolCallChunks = []; } @@ -101,7 +116,11 @@ export function _makeMessageChunkFromAnthropicEvent( { index: data.index, ...data.content_block, - input: "", + input: + contentBlock.type === "server_tool_use" || + contentBlock.type === "tool_use" + ? "" + : undefined, }, ], additional_kwargs: {}, diff --git a/libs/langchain-anthropic/src/utils/tools.ts b/libs/langchain-anthropic/src/utils/tools.ts index 157caa1b7f11..c22ff4758431 100644 --- a/libs/langchain-anthropic/src/utils/tools.ts +++ b/libs/langchain-anthropic/src/utils/tools.ts @@ -1,12 +1,12 @@ -import type { MessageCreateParams } from "@anthropic-ai/sdk/resources/index.mjs"; +import type { Anthropic } from "@anthropic-ai/sdk"; import { AnthropicToolChoice } from "../types.js"; export function handleToolChoice( toolChoice?: AnthropicToolChoice ): - | MessageCreateParams.ToolChoiceAuto - | MessageCreateParams.ToolChoiceAny - | MessageCreateParams.ToolChoiceTool + | Anthropic.Messages.ToolChoiceAuto + | Anthropic.Messages.ToolChoiceAny + | Anthropic.Messages.ToolChoiceTool | undefined { if (!toolChoice) { return undefined; diff --git a/yarn.lock b/yarn.lock index 3492632469c2..782ed95e79e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -211,7 +211,7 @@ __metadata: languageName: node linkType: hard -"@anthropic-ai/sdk@npm:>=0.14 <1, @anthropic-ai/sdk@npm:^0.39.0": +"@anthropic-ai/sdk@npm:>=0.14 <1": version: 0.39.0 resolution: "@anthropic-ai/sdk@npm:0.39.0" dependencies: @@ -241,6 +241,15 @@ __metadata: languageName: node linkType: hard +"@anthropic-ai/sdk@npm:^0.52.0": + version: 0.52.0 + resolution: "@anthropic-ai/sdk@npm:0.52.0" + bin: + anthropic-ai-sdk: bin/cli + checksum: 09b8edc517d05c76bfa63f72537aad4e292d08560daf7a179e9f7ab648278a74e41ef34688e9711d54e6219e57d92a6b22105b9a0648a227dd80fdc15dd80abd + languageName: node + linkType: hard + "@anthropic-ai/vertex-sdk@npm:^0.4.1": version: 0.4.1 resolution: "@anthropic-ai/vertex-sdk@npm:0.4.1" @@ -6902,7 +6911,7 @@ __metadata: version: 0.0.0-use.local resolution: "@langchain/anthropic@workspace:libs/langchain-anthropic" dependencies: - "@anthropic-ai/sdk": ^0.39.0 + "@anthropic-ai/sdk": ^0.52.0 "@anthropic-ai/vertex-sdk": ^0.4.1 "@jest/globals": ^29.5.0 "@langchain/core": "workspace:*"