From 0a99c4961943593db255127237320dca95871590 Mon Sep 17 00:00:00 2001 From: Brandon Leafman <17711952+bleafman@users.noreply.github.coms> Date: Fri, 23 May 2025 12:44:11 -0700 Subject: [PATCH 01/12] chore(langchain-anthropic): update @anthropic-ai/sdk to version 0.52.0 --- libs/langchain-anthropic/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From f2ef55fceb67c09668bf8adc06be4823e7ecfa64 Mon Sep 17 00:00:00 2001 From: Brandon Leafman <17711952+bleafman@users.noreply.github.coms> Date: Fri, 23 May 2025 12:44:27 -0700 Subject: [PATCH 02/12] feat(langchain-anthropic): add isBuiltinTool function to validate built-in Anthropic tools --- libs/langchain-anthropic/src/chat_models.ts | 25 ++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/libs/langchain-anthropic/src/chat_models.ts b/libs/langchain-anthropic/src/chat_models.ts index 10483d0f29b7..2dbe791b09f9 100644 --- a/libs/langchain-anthropic/src/chat_models.ts +++ b/libs/langchain-anthropic/src/chat_models.ts @@ -100,6 +100,26 @@ function isAnthropicTool(tool: any): tool is Anthropic.Messages.Tool { return "input_schema" in tool; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function isBuiltinTool( + tool: any +): tool is + | Anthropic.Messages.ToolBash20250124 + | Anthropic.Messages.ToolTextEditor20250124 + | Anthropic.Messages.WebSearchTool20250305 { + return ( + typeof tool === "object" && + tool !== null && + typeof tool.type === "string" && + typeof tool.name === "string" && + ((tool.type === "bash_20250124" && tool.name === "bash_20250124") || + (tool.type === "text_editor_20250124" && + tool.name === "text_editor_20250124") || + (tool.type === "web_search_20250305" && + tool.name === "web_search_20250305")) + ); +} + /** * Input to AnthropicChat class. */ @@ -720,11 +740,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; } From 127e1539d371792e89c28e152c4a9992ca892f62 Mon Sep 17 00:00:00 2001 From: Brandon Leafman <17711952+bleafman@users.noreply.github.coms> Date: Fri, 23 May 2025 13:01:48 -0700 Subject: [PATCH 03/12] feat(langchain-anthropic): Update types and utilities with built-in server tools and web search tool --- libs/langchain-anthropic/src/types.ts | 16 ++++++++++++++++ .../src/utils/message_inputs.ts | 14 +++++++++++++- libs/langchain-anthropic/src/utils/tools.ts | 8 ++++---- yarn.lock | 13 +++++++++++-- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/libs/langchain-anthropic/src/types.ts b/libs/langchain-anthropic/src/types.ts index f8ab0424e5c9..d26742c6b1fe 100644 --- a/libs/langchain-anthropic/src/types.ts +++ b/libs/langchain-anthropic/src/types.ts @@ -38,6 +38,22 @@ 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; + +// Union of all possible content block types including server tool use +export type AnthropicContentBlock = + | AnthropicTextBlockParam + | AnthropicImageBlockParam + | AnthropicToolUseBlockParam + | AnthropicToolResultBlockParam + | AnthropicDocumentBlockParam + | AnthropicThinkingBlockParam + | AnthropicRedactedThinkingBlockParam + | AnthropicServerToolUseBlockParam + | AnthropicWebSearchToolResultBlockParam; export function isAnthropicImageBlockParam( block: unknown diff --git a/libs/langchain-anthropic/src/utils/message_inputs.ts b/libs/langchain-anthropic/src/utils/message_inputs.ts index d2426056d054..3abbfc6caf2b 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,13 @@ 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", + ]; const textTypes = ["text", "text_delta"]; if (typeof content === "string") { @@ -545,6 +553,8 @@ function mergeMessages(messages: AnthropicMessageCreateParams["messages"]) { | AnthropicDocumentBlockParam | AnthropicThinkingBlockParam | AnthropicRedactedThinkingBlockParam + | AnthropicServerToolUseBlockParam + | AnthropicWebSearchToolResultBlockParam > ): Array< | AnthropicTextBlockParam @@ -554,6 +564,8 @@ function mergeMessages(messages: AnthropicMessageCreateParams["messages"]) { | AnthropicDocumentBlockParam | AnthropicThinkingBlockParam | AnthropicRedactedThinkingBlockParam + | AnthropicServerToolUseBlockParam + | AnthropicWebSearchToolResultBlockParam > => { if (typeof content === "string") { return [ 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:*" From 38ada7afac10081d0d8f18fd54089f27f4ac5a30 Mon Sep 17 00:00:00 2001 From: Brandon Leafman <17711952+bleafman@users.noreply.github.coms> Date: Fri, 23 May 2025 14:24:54 -0700 Subject: [PATCH 04/12] feat(langchain-anthropic): add support for web search result blocks and enhance server tool integration tests --- .../chat_models-built-in-tools.int.test.ts | 187 ++++++++++++++ .../tests/chat_models-built-in-tools.test.ts | 232 ++++++++++++++++++ libs/langchain-anthropic/src/types.ts | 5 +- .../src/utils/message_inputs.ts | 1 + .../src/utils/message_outputs.ts | 23 +- 5 files changed, 445 insertions(+), 3 deletions(-) create mode 100644 libs/langchain-anthropic/src/tests/chat_models-built-in-tools.int.test.ts create mode 100644 libs/langchain-anthropic/src/tests/chat_models-built-in-tools.test.ts diff --git a/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.int.test.ts b/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.int.test.ts new file mode 100644 index 000000000000..ffa5cb1014bb --- /dev/null +++ b/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.int.test.ts @@ -0,0 +1,187 @@ +import { test, expect } from "@jest/globals"; +import { ChatAnthropic } from "../chat_models.js"; +import { HumanMessage, AIMessage } from "@langchain/core/messages"; +import { _convertMessagesToAnthropicPayload } from "../utils/message_inputs.js"; + +const chatModel = new ChatAnthropic({ + model: "claude-3-5-sonnet-20241022", + temperature: 0, + // Enable built-in tools (web search, text editor, bash) + // Note: This requires API access to server tools +}); + +test("Server Tools Integration - Web Search", async () => { + // Test that we can handle a conversation with web search tool usage + const messages = [ + new HumanMessage({ + content: + "Search for the latest news about TypeScript 5.7 release and summarize what you find.", + }), + ]; + + const response = await chatModel.invoke(messages); + + console.log("Response content:", JSON.stringify(response.content, null, 2)); + + // The response should be an AIMessage + expect(response).toBeInstanceOf(AIMessage); + expect(response.content).toBeDefined(); + + // The response should contain meaningful content about TypeScript + expect( + typeof response.content === "string" + ? response.content + : JSON.stringify(response.content) + ).toMatch(/TypeScript|typescript/i); + + console.log("✅ Successfully handled web search request"); +}, 30000); // 30 second timeout for API call + +test("Server Tools Integration - Message Round Trip", async () => { + // Test that we can properly parse messages with server tool content blocks + const conversation = [ + new HumanMessage({ + content: + "What are the latest developments in AI research? Please search for current information.", + }), + ]; + + try { + const response1 = await chatModel.invoke(conversation); + + console.log("First response:", JSON.stringify(response1.content, null, 2)); + + // Add the AI response to conversation + conversation.push(response1); + + // Continue the conversation + conversation.push( + new HumanMessage({ + content: + "Based on your search, what are the most promising areas for future research?", + }) + ); + + const response2 = await chatModel.invoke(conversation); + + console.log("Second response:", JSON.stringify(response2.content, null, 2)); + + // Both responses should be valid + expect(response1).toBeInstanceOf(AIMessage); + expect(response2).toBeInstanceOf(AIMessage); + + console.log( + "✅ Successfully completed multi-turn conversation with server tools" + ); + } catch (error) { + // If server tools aren't available, the test should still not crash due to unsupported content format + if ( + error instanceof Error && + error.message.includes("Unsupported message content format") + ) { + throw new Error( + "❌ REGRESSION: 'Unsupported message content format' error returned - this should be fixed!" + ); + } + + // Other errors (like API access issues) are expected and should not fail the test + console.log( + "⚠️ Server tools may not be available for this API key, but no format errors occurred" + ); + } +}, 45000); // 45 second timeout for longer conversation + +test("Server Tools Integration - Content Block Parsing", async () => { + // Test parsing of messages that contain server tool content blocks + const messageWithServerTool = new AIMessage({ + content: [ + { + type: "text", + text: "I'll search for that information.", + }, + { + type: "server_tool_use", + id: "toolu_01ABC123", + name: "web_search", + input: { + query: "latest AI developments", + }, + }, + ], + }); + + const messageWithSearchResult = new HumanMessage({ + content: [ + { + type: "web_search_tool_result", + tool_use_id: "toolu_01ABC123", + content: [ + { + type: "web_search_result", + title: "AI Breakthrough 2024", + url: "https://example.com/ai-news", + content: "Recent developments in AI...", + }, + ], + }, + ], + }); + + // This should not throw an "Unsupported message content format" error + expect(() => { + const messages = [messageWithServerTool, messageWithSearchResult]; + // Try to format these messages - this should work now + const formatted = _convertMessagesToAnthropicPayload(messages); + expect(formatted.messages).toHaveLength(2); + + // Verify server_tool_use is preserved + const aiContent = formatted.messages[0].content as any[]; + expect( + aiContent.find((block) => block.type === "server_tool_use") + ).toBeDefined(); + + // Verify web_search_tool_result is preserved + const userContent = formatted.messages[1].content as any[]; + expect( + userContent.find((block) => block.type === "web_search_tool_result") + ).toBeDefined(); + }).not.toThrow(); + + console.log( + "✅ Successfully parsed server tool content blocks without errors" + ); +}); + +test("Server Tools Integration - Error Handling", async () => { + // Test that malformed server tool content doesn't crash the system + const messageWithMalformedContent = new AIMessage({ + content: [ + { + type: "text", + text: "Testing error handling", + }, + { + type: "server_tool_use", + id: "test_id", + name: "web_search", + input: "malformed input", // This should be converted to object + }, + ], + }); + + // This should handle the malformed input gracefully + expect(() => { + const formatted = _convertMessagesToAnthropicPayload([ + messageWithMalformedContent, + ]); + expect(formatted.messages).toHaveLength(1); + + const content = formatted.messages[0].content as any[]; + const toolUse = content.find((block) => block.type === "server_tool_use"); + expect(toolUse).toBeDefined(); + // The malformed string input should be converted to an empty object + expect(typeof toolUse.input).toBe("object"); + }).not.toThrow(); + + console.log("✅ Successfully handled malformed server tool content"); +}); diff --git a/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.test.ts b/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.test.ts new file mode 100644 index 000000000000..ded2498ad4ee --- /dev/null +++ b/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.test.ts @@ -0,0 +1,232 @@ +import { describe, it, expect } from "@jest/globals"; +import { HumanMessage, AIMessage } from "@langchain/core/messages"; +import { _convertMessagesToAnthropicPayload } from "../utils/message_inputs.js"; + +describe("Anthropic Server Tools Content Blocks", () => { + it("should handle server_tool_use content blocks", () => { + const message = new AIMessage({ + content: [ + { + type: "text", + text: "I'll search for information about that topic.", + }, + { + type: "server_tool_use", + id: "toolu_01ABC123", + name: "web_search", + input: { + query: "latest developments in AI", + }, + }, + ], + }); + + const result = _convertMessagesToAnthropicPayload([message]); + + expect(result.messages).toHaveLength(1); + expect(result.messages[0].role).toBe("assistant"); + expect(Array.isArray(result.messages[0].content)).toBe(true); + + const content = result.messages[0].content as any[]; + expect(content).toHaveLength(2); + expect(content[0]).toEqual({ + type: "text", + text: "I'll search for information about that topic.", + }); + expect(content[1]).toEqual({ + type: "server_tool_use", + id: "toolu_01ABC123", + name: "web_search", + input: { + query: "latest developments in AI", + }, + }); + }); + + it("should handle web_search_tool_result content blocks", () => { + const message = new HumanMessage({ + content: [ + { + type: "web_search_tool_result", + tool_use_id: "toolu_01ABC123", + content: [ + { + type: "web_search_result", + title: "Latest AI Developments", + url: "https://example.com/ai-news", + content: "Recent breakthroughs in artificial intelligence...", + }, + { + type: "web_search_result", + title: "AI Research Updates", + url: "https://example.com/research", + content: "New research papers and findings...", + }, + ], + }, + ], + }); + + const result = _convertMessagesToAnthropicPayload([message]); + + expect(result.messages).toHaveLength(1); + expect(result.messages[0].role).toBe("user"); + expect(Array.isArray(result.messages[0].content)).toBe(true); + + const content = result.messages[0].content as any[]; + expect(content).toHaveLength(1); + expect(content[0]).toEqual({ + type: "web_search_tool_result", + tool_use_id: "toolu_01ABC123", + content: [ + { + type: "web_search_result", + title: "Latest AI Developments", + url: "https://example.com/ai-news", + content: "Recent breakthroughs in artificial intelligence...", + }, + { + type: "web_search_result", + title: "AI Research Updates", + url: "https://example.com/research", + content: "New research papers and findings...", + }, + ], + }); + }); + + it("should handle mixed content with server tools", () => { + const messages = [ + new HumanMessage({ + content: "Can you search for recent AI developments?", + }), + new AIMessage({ + content: [ + { + type: "text", + text: "I'll search for that information.", + }, + { + type: "server_tool_use", + id: "toolu_01ABC123", + name: "web_search", + input: { + query: "recent AI developments 2024", + }, + }, + ], + }), + new HumanMessage({ + content: [ + { + type: "web_search_tool_result", + tool_use_id: "toolu_01ABC123", + content: [ + { + type: "web_search_result", + title: "AI Breakthrough 2024", + url: "https://example.com/ai-breakthrough", + content: "Major AI breakthrough announced...", + }, + ], + }, + ], + }), + new AIMessage({ + content: + "Based on the search results, here are the latest AI developments...", + }), + ]; + + const result = _convertMessagesToAnthropicPayload(messages); + + expect(result.messages).toHaveLength(4); + + // Check human message + expect(result.messages[0].role).toBe("user"); + expect(result.messages[0].content).toBe( + "Can you search for recent AI developments?" + ); + + // Check AI message with server_tool_use + expect(result.messages[1].role).toBe("assistant"); + const aiContent = result.messages[1].content as any[]; + expect(aiContent).toHaveLength(2); + expect(aiContent[0].type).toBe("text"); + expect(aiContent[1].type).toBe("server_tool_use"); + + // Check human message with web_search_tool_result + expect(result.messages[2].role).toBe("user"); + const userContent = result.messages[2].content as any[]; + expect(userContent).toHaveLength(1); + expect(userContent[0].type).toBe("web_search_tool_result"); + + // Check final AI response + expect(result.messages[3].role).toBe("assistant"); + expect(result.messages[3].content).toBe( + "Based on the search results, here are the latest AI developments..." + ); + }); + + it("should preserve cache_control in server tool content blocks", () => { + const message = new AIMessage({ + content: [ + { + type: "server_tool_use", + id: "toolu_01ABC123", + name: "web_search", + input: { + query: "test query", + }, + cache_control: { type: "ephemeral" }, + }, + ], + }); + + const result = _convertMessagesToAnthropicPayload([message]); + + const content = result.messages[0].content as any[]; + expect(content[0]).toEqual({ + type: "server_tool_use", + id: "toolu_01ABC123", + name: "web_search", + input: { + query: "test query", + }, + cache_control: { type: "ephemeral" }, + }); + }); + + it("should handle web_search_result with all optional fields", () => { + const message = new HumanMessage({ + content: [ + { + type: "web_search_tool_result", + tool_use_id: "toolu_01ABC123", + content: [ + { + type: "web_search_result", + title: "Complete Example", + url: "https://example.com/full", + content: "Full content here...", + publishedDate: "2024-01-15", + snippet: "This is a snippet...", + }, + ], + }, + ], + }); + + const result = _convertMessagesToAnthropicPayload([message]); + + const content = result.messages[0].content as any[]; + expect(content[0].content[0]).toEqual({ + type: "web_search_result", + title: "Complete Example", + url: "https://example.com/full", + content: "Full content here...", + publishedDate: "2024-01-15", + snippet: "This is a snippet...", + }); + }); +}); diff --git a/libs/langchain-anthropic/src/types.ts b/libs/langchain-anthropic/src/types.ts index d26742c6b1fe..c569cecce033 100644 --- a/libs/langchain-anthropic/src/types.ts +++ b/libs/langchain-anthropic/src/types.ts @@ -42,6 +42,8 @@ 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 = @@ -53,7 +55,8 @@ export type AnthropicContentBlock = | AnthropicThinkingBlockParam | AnthropicRedactedThinkingBlockParam | AnthropicServerToolUseBlockParam - | AnthropicWebSearchToolResultBlockParam; + | AnthropicWebSearchToolResultBlockParam + | AnthropicWebSearchResultBlockParam; export function isAnthropicImageBlockParam( block: unknown diff --git a/libs/langchain-anthropic/src/utils/message_inputs.ts b/libs/langchain-anthropic/src/utils/message_inputs.ts index 3abbfc6caf2b..cfade17f6664 100644 --- a/libs/langchain-anthropic/src/utils/message_inputs.ts +++ b/libs/langchain-anthropic/src/utils/message_inputs.ts @@ -352,6 +352,7 @@ function _formatContent(content: MessageContent) { "input_json_delta", "server_tool_use", "web_search_tool_result", + "web_search_result", ]; const textTypes = ["text", "text_delta"]; 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: {}, From ce00f9073a8062d2c8e53a7814d5bef864c45c7d Mon Sep 17 00:00:00 2001 From: Brandon Leafman <17711952+bleafman@users.noreply.github.coms> Date: Sun, 25 May 2025 18:48:35 -0700 Subject: [PATCH 05/12] refactor(langchain-anthropic): change parameter type in isBuiltinTool function from 'any' to 'unknown' for improved type safety --- libs/langchain-anthropic/src/chat_models.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/langchain-anthropic/src/chat_models.ts b/libs/langchain-anthropic/src/chat_models.ts index 2dbe791b09f9..8eaa618919a7 100644 --- a/libs/langchain-anthropic/src/chat_models.ts +++ b/libs/langchain-anthropic/src/chat_models.ts @@ -100,9 +100,8 @@ function isAnthropicTool(tool: any): tool is Anthropic.Messages.Tool { return "input_schema" in tool; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any function isBuiltinTool( - tool: any + tool: unknown ): tool is | Anthropic.Messages.ToolBash20250124 | Anthropic.Messages.ToolTextEditor20250124 @@ -110,6 +109,8 @@ function isBuiltinTool( return ( typeof tool === "object" && tool !== null && + "type" in tool && + "name" in tool && typeof tool.type === "string" && typeof tool.name === "string" && ((tool.type === "bash_20250124" && tool.name === "bash_20250124") || From a4e1bf81a7d8094ad9f3d1e99e0e9b09865028f1 Mon Sep 17 00:00:00 2001 From: Brandon Leafman <17711952+bleafman@users.noreply.github.coms> Date: Wed, 28 May 2025 10:32:10 -0700 Subject: [PATCH 06/12] refactor(langchain-anthropic): add AnthropicBuiltInToolUnion type and simplify isBuiltinTool --- libs/langchain-anthropic/src/chat_models.ts | 16 +++++----------- libs/langchain-anthropic/src/types.ts | 6 ++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/libs/langchain-anthropic/src/chat_models.ts b/libs/langchain-anthropic/src/chat_models.ts index 8eaa618919a7..bc5bd69537c1 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,12 +101,7 @@ function isAnthropicTool(tool: any): tool is Anthropic.Messages.Tool { return "input_schema" in tool; } -function isBuiltinTool( - tool: unknown -): tool is - | Anthropic.Messages.ToolBash20250124 - | Anthropic.Messages.ToolTextEditor20250124 - | Anthropic.Messages.WebSearchTool20250305 { +function isBuiltinTool(tool: unknown): tool is AnthropicBuiltInToolUnion { return ( typeof tool === "object" && tool !== null && @@ -113,11 +109,9 @@ function isBuiltinTool( "name" in tool && typeof tool.type === "string" && typeof tool.name === "string" && - ((tool.type === "bash_20250124" && tool.name === "bash_20250124") || - (tool.type === "text_editor_20250124" && - tool.name === "text_editor_20250124") || - (tool.type === "web_search_20250305" && - tool.name === "web_search_20250305")) + (tool.name === "bash" || + tool.name === "text_editor" || + tool.name === "web_search") ); } diff --git a/libs/langchain-anthropic/src/types.ts b/libs/langchain-anthropic/src/types.ts index c569cecce033..6708aae04f8c 100644 --- a/libs/langchain-anthropic/src/types.ts +++ b/libs/langchain-anthropic/src/types.ts @@ -117,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 +>; From cde9d3206a63873a5edc44919067de79f175b54a Mon Sep 17 00:00:00 2001 From: Brandon Leafman <17711952+bleafman@users.noreply.github.coms> Date: Wed, 28 May 2025 12:14:51 -0700 Subject: [PATCH 07/12] fix(langchain-anthropic): update built-in tools integration to use the correct 'str_replace_editor' name, enhance test cases for server tools --- libs/langchain-anthropic/src/chat_models.ts | 2 +- .../chat_models-built-in-tools.int.test.ts | 47 ++++++++++++------- .../tests/chat_models-built-in-tools.test.ts | 17 ++++--- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/libs/langchain-anthropic/src/chat_models.ts b/libs/langchain-anthropic/src/chat_models.ts index bc5bd69537c1..7423a374d93b 100644 --- a/libs/langchain-anthropic/src/chat_models.ts +++ b/libs/langchain-anthropic/src/chat_models.ts @@ -110,7 +110,7 @@ function isBuiltinTool(tool: unknown): tool is AnthropicBuiltInToolUnion { typeof tool.type === "string" && typeof tool.name === "string" && (tool.name === "bash" || - tool.name === "text_editor" || + tool.name === "str_replace_editor" || tool.name === "web_search") ); } diff --git a/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.int.test.ts b/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.int.test.ts index ffa5cb1014bb..0dc7c1f563cc 100644 --- a/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.int.test.ts +++ b/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.int.test.ts @@ -1,25 +1,38 @@ import { test, expect } from "@jest/globals"; -import { ChatAnthropic } from "../chat_models.js"; import { HumanMessage, AIMessage } from "@langchain/core/messages"; +import Anthropic from "@anthropic-ai/sdk"; +import { ChatAnthropic } from "../chat_models.js"; import { _convertMessagesToAnthropicPayload } from "../utils/message_inputs.js"; -const chatModel = new ChatAnthropic({ +const chatModelWithBuiltInTools = new ChatAnthropic({ model: "claude-3-5-sonnet-20241022", temperature: 0, - // Enable built-in tools (web search, text editor, bash) - // Note: This requires API access to server tools -}); +}).bindTools([ + { + type: "web_search_20250305", + name: "web_search", + max_uses: 1, + }, + { + type: "text_editor_20250124", + name: "str_replace_editor", + }, + { + type: "bash_20250124", + name: "bash", + }, +]); test("Server Tools Integration - Web Search", async () => { // Test that we can handle a conversation with web search tool usage const messages = [ new HumanMessage({ content: - "Search for the latest news about TypeScript 5.7 release and summarize what you find.", + "Search the web to find the name(s) of the original creator(s) of TypeScript", }), ]; - const response = await chatModel.invoke(messages); + const response = await chatModelWithBuiltInTools.invoke(messages); console.log("Response content:", JSON.stringify(response.content, null, 2)); @@ -42,12 +55,12 @@ test("Server Tools Integration - Message Round Trip", async () => { const conversation = [ new HumanMessage({ content: - "What are the latest developments in AI research? Please search for current information.", + "Search the web to find the name(s) of the original creator(s) of TypeScript", }), ]; try { - const response1 = await chatModel.invoke(conversation); + const response1 = await chatModelWithBuiltInTools.invoke(conversation); console.log("First response:", JSON.stringify(response1.content, null, 2)); @@ -58,11 +71,11 @@ test("Server Tools Integration - Message Round Trip", async () => { conversation.push( new HumanMessage({ content: - "Based on your search, what are the most promising areas for future research?", + "Based on your search, what is the name of the original creator(s) of TypeScript?", }) ); - const response2 = await chatModel.invoke(conversation); + const response2 = await chatModelWithBuiltInTools.invoke(conversation); console.log("Second response:", JSON.stringify(response2.content, null, 2)); @@ -76,6 +89,7 @@ test("Server Tools Integration - Message Round Trip", async () => { } catch (error) { // If server tools aren't available, the test should still not crash due to unsupported content format if ( + // eslint-disable-next-line no-instanceof/no-instanceof error instanceof Error && error.message.includes("Unsupported message content format") ) { @@ -135,13 +149,15 @@ test("Server Tools Integration - Content Block Parsing", async () => { expect(formatted.messages).toHaveLength(2); // Verify server_tool_use is preserved - const aiContent = formatted.messages[0].content as any[]; + const aiContent = formatted.messages[0] + .content as Anthropic.ContentBlockParam[]; expect( aiContent.find((block) => block.type === "server_tool_use") ).toBeDefined(); // Verify web_search_tool_result is preserved - const userContent = formatted.messages[1].content as any[]; + const userContent = formatted.messages[1] + .content as Anthropic.ContentBlockParam[]; expect( userContent.find((block) => block.type === "web_search_tool_result") ).toBeDefined(); @@ -176,11 +192,10 @@ test("Server Tools Integration - Error Handling", async () => { ]); expect(formatted.messages).toHaveLength(1); - const content = formatted.messages[0].content as any[]; + const content = formatted.messages[0] + .content as Anthropic.ContentBlockParam[]; const toolUse = content.find((block) => block.type === "server_tool_use"); expect(toolUse).toBeDefined(); - // The malformed string input should be converted to an empty object - expect(typeof toolUse.input).toBe("object"); }).not.toThrow(); console.log("✅ Successfully handled malformed server tool content"); diff --git a/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.test.ts b/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.test.ts index ded2498ad4ee..6d7ab688a038 100644 --- a/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.test.ts +++ b/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect } from "@jest/globals"; +import Anthropic from "@anthropic-ai/sdk"; import { HumanMessage, AIMessage } from "@langchain/core/messages"; import { _convertMessagesToAnthropicPayload } from "../utils/message_inputs.js"; @@ -27,7 +28,7 @@ describe("Anthropic Server Tools Content Blocks", () => { expect(result.messages[0].role).toBe("assistant"); expect(Array.isArray(result.messages[0].content)).toBe(true); - const content = result.messages[0].content as any[]; + const content = result.messages[0].content as Anthropic.ContentBlockParam[]; expect(content).toHaveLength(2); expect(content[0]).toEqual({ type: "text", @@ -73,7 +74,7 @@ describe("Anthropic Server Tools Content Blocks", () => { expect(result.messages[0].role).toBe("user"); expect(Array.isArray(result.messages[0].content)).toBe(true); - const content = result.messages[0].content as any[]; + const content = result.messages[0].content as Anthropic.ContentBlockParam[]; expect(content).toHaveLength(1); expect(content[0]).toEqual({ type: "web_search_tool_result", @@ -150,14 +151,16 @@ describe("Anthropic Server Tools Content Blocks", () => { // Check AI message with server_tool_use expect(result.messages[1].role).toBe("assistant"); - const aiContent = result.messages[1].content as any[]; + const aiContent = result.messages[1] + .content as Anthropic.Messages.ContentBlockParam[]; expect(aiContent).toHaveLength(2); expect(aiContent[0].type).toBe("text"); expect(aiContent[1].type).toBe("server_tool_use"); // Check human message with web_search_tool_result expect(result.messages[2].role).toBe("user"); - const userContent = result.messages[2].content as any[]; + const userContent = result.messages[2] + .content as Anthropic.ContentBlockParam[]; expect(userContent).toHaveLength(1); expect(userContent[0].type).toBe("web_search_tool_result"); @@ -185,7 +188,7 @@ describe("Anthropic Server Tools Content Blocks", () => { const result = _convertMessagesToAnthropicPayload([message]); - const content = result.messages[0].content as any[]; + const content = result.messages[0].content as Anthropic.ContentBlockParam[]; expect(content[0]).toEqual({ type: "server_tool_use", id: "toolu_01ABC123", @@ -219,8 +222,8 @@ describe("Anthropic Server Tools Content Blocks", () => { const result = _convertMessagesToAnthropicPayload([message]); - const content = result.messages[0].content as any[]; - expect(content[0].content[0]).toEqual({ + const content = result.messages[0].content as Anthropic.ContentBlockParam[]; + expect(content[0]).toEqual({ type: "web_search_result", title: "Complete Example", url: "https://example.com/full", From 108f394176b832720380ffa60d7d2adcbb3e0a53 Mon Sep 17 00:00:00 2001 From: Brandon Leafman <17711952+bleafman@users.noreply.github.coms> Date: Fri, 6 Jun 2025 16:11:13 -0700 Subject: [PATCH 08/12] feat(langchain-anthropic): add support for citations in message formatting and extend tool type handling --- libs/langchain-anthropic/src/utils/message_inputs.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/langchain-anthropic/src/utils/message_inputs.ts b/libs/langchain-anthropic/src/utils/message_inputs.ts index cfade17f6664..fc383504bfab 100644 --- a/libs/langchain-anthropic/src/utils/message_inputs.ts +++ b/libs/langchain-anthropic/src/utils/message_inputs.ts @@ -414,6 +414,7 @@ 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 }; @@ -508,7 +509,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 ) ); From 3608e606f76c65f3b207fa2ac1de775d3d4ae7da Mon Sep 17 00:00:00 2001 From: Brandon Leafman <17711952+bleafman@users.noreply.github.coms> Date: Fri, 6 Jun 2025 16:11:35 -0700 Subject: [PATCH 09/12] feat(langchain-anthropic): enhance tool call extraction to support 'server_tool_use' for web search --- libs/langchain-anthropic/src/output_parsers.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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; } From bdd8cd02fc210a8003b10ea26afa4ed949968ec8 Mon Sep 17 00:00:00 2001 From: Brandon Leafman <17711952+bleafman@users.noreply.github.coms> Date: Fri, 6 Jun 2025 16:22:05 -0700 Subject: [PATCH 10/12] test(langchain-anthropic): add integration tests for web search functionality in ChatAnthropic model --- .../tests/chat_models-web_search.int.test.ts | 47 +++++ .../src/tests/chat_models-web_search.test.ts | 172 ++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 libs/langchain-anthropic/src/tests/chat_models-web_search.int.test.ts create mode 100644 libs/langchain-anthropic/src/tests/chat_models-web_search.test.ts 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..9b723345e494 --- /dev/null +++ b/libs/langchain-anthropic/src/tests/chat_models-web_search.int.test.ts @@ -0,0 +1,47 @@ +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..79672902bbd0 --- /dev/null +++ b/libs/langchain-anthropic/src/tests/chat_models-web_search.test.ts @@ -0,0 +1,172 @@ +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 = [ + { + type: "text", + text: "I'll search for that information.", + }, + { + 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", + content: "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)..." + } + ] + }, + { + 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)..." + } + ] + } + ]; + + 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 = [ + { + type: "server_tool_use", + id: "toolu_web", + name: "web_search", + input: { query: "test" } + }, + { + type: "server_tool_use", + id: "toolu_bash", + name: "bash", + input: { command: "ls" } + } + ]; + + 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: "test" }, + id: "toolu_web", + 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.", + 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..." + } + ] + }, + { + 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", + content: "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)...", + encrypted_index: "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm.." + } + ] + } + ] + }); + + const result = _convertMessagesToAnthropicPayload([ + new HumanMessage("Follow up question"), + 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.", + 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..." + } + ] + }, + { + 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", + content: "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)...", + encrypted_index: "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm.." + } + ] + } + ] + }); +}); \ No newline at end of file From b9d17fcb26699fdd2d78dfe809e5741025af3029 Mon Sep 17 00:00:00 2001 From: Brandon Leafman <17711952+bleafman@users.noreply.github.coms> Date: Fri, 6 Jun 2025 16:23:57 -0700 Subject: [PATCH 11/12] chore(langchain-anthropic): formatting --- .../tests/chat_models-web_search.int.test.ts | 12 +-- .../src/tests/chat_models-web_search.test.ts | 82 ++++++++++--------- .../src/utils/message_inputs.ts | 4 +- 3 files changed, 54 insertions(+), 44 deletions(-) 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 index 9b723345e494..e46c52d68d19 100644 --- 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 @@ -15,22 +15,24 @@ const model = new ChatAnthropic({ test("Web search single turn", async () => { const result = await model.invoke([ - new HumanMessage("What is Claude Shannon's birth date?") + new HumanMessage("What is Claude Shannon's birth date?"), ]); expect(result).toBeInstanceOf(AIMessage); - expect(result.tool_calls?.find(tc => tc.name === "web_search")).toBeTruthy(); + 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?") + 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?") + new HumanMessage("What year did he die?"), ]); expect(firstResponse).toBeInstanceOf(AIMessage); @@ -39,7 +41,7 @@ test("Web search multi-turn conversation", async () => { test("Web search with unusual query", async () => { const result = await model.invoke([ - new HumanMessage("What is the population of Mars?") + new HumanMessage("What is the population of Mars?"), ]); expect(result).toBeInstanceOf(AIMessage); 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 index 79672902bbd0..73f715a80972 100644 --- a/libs/langchain-anthropic/src/tests/chat_models-web_search.test.ts +++ b/libs/langchain-anthropic/src/tests/chat_models-web_search.test.ts @@ -14,7 +14,7 @@ test("Web Search Tool - Anthropic response to LangChain format", () => { type: "server_tool_use", id: "toolu_01ABC123", name: "web_search", - input: { query: "Claude Shannon birth date" } + input: { query: "Claude Shannon birth date" }, }, { type: "web_search_tool_result", @@ -24,9 +24,10 @@ test("Web Search Tool - Anthropic response to LangChain format", () => { type: "web_search_result", title: "Claude Shannon - Wikipedia", url: "https://en.wikipedia.org/wiki/Claude_Shannon", - content: "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)..." - } - ] + content: + "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)...", + }, + ], }, { type: "text", @@ -36,13 +37,16 @@ test("Web Search Tool - Anthropic response to LangChain format", () => { 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)..." - } - ] - } + cited_text: + "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)...", + }, + ], + }, ]; - const result = anthropicResponseToChatMessages(anthropicResponse, { id: "msg_01ABC123" }); + const result = anthropicResponseToChatMessages(anthropicResponse, { + id: "msg_01ABC123", + }); // What LangChain should produce expect(result[0].message).toEqual( @@ -53,12 +57,12 @@ test("Web Search Tool - Anthropic response to LangChain format", () => { name: "web_search", args: { query: "Claude Shannon birth date" }, id: "toolu_01ABC123", - type: "tool_call" - } + type: "tool_call", + }, ], additional_kwargs: { id: "msg_01ABC123" }, response_metadata: { id: "msg_01ABC123" }, - id: "msg_01ABC123" + id: "msg_01ABC123", }) ); }); @@ -70,14 +74,14 @@ test("Web Search Tool - Only web_search server tools extracted", () => { type: "server_tool_use", id: "toolu_web", name: "web_search", - input: { query: "test" } + input: { query: "test" }, }, { type: "server_tool_use", id: "toolu_bash", name: "bash", - input: { command: "ls" } - } + input: { command: "ls" }, + }, ]; const result = anthropicResponseToChatMessages(anthropicResponse, {}); @@ -91,12 +95,12 @@ test("Web Search Tool - Only web_search server tools extracted", () => { name: "web_search", args: { query: "test" }, id: "toolu_web", - type: "tool_call" - } + type: "tool_call", + }, ], additional_kwargs: {}, response_metadata: {}, - id: undefined + id: undefined, }) ); }); @@ -113,9 +117,9 @@ test("Web Search Tool - LangChain message to Anthropic format", () => { 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..." - } - ] + cited_text: "Claude Elwood Shannon (April 30, 1916...", + }, + ], }, { type: "web_search_tool_result", @@ -125,17 +129,18 @@ test("Web Search Tool - LangChain message to Anthropic format", () => { type: "web_search_result", title: "Claude Shannon - Wikipedia", url: "https://en.wikipedia.org/wiki/Claude_Shannon", - content: "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)...", - encrypted_index: "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm.." - } - ] - } - ] + content: + "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)...", + encrypted_index: "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm..", + }, + ], + }, + ], }); const result = _convertMessagesToAnthropicPayload([ new HumanMessage("Follow up question"), - langChainMessage + langChainMessage, ]); // What should be sent to Anthropic (preserving encrypted content for multi-turn) @@ -150,9 +155,9 @@ test("Web Search Tool - LangChain message to Anthropic format", () => { 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..." - } - ] + cited_text: "Claude Elwood Shannon (April 30, 1916...", + }, + ], }, { type: "web_search_tool_result", @@ -162,11 +167,12 @@ test("Web Search Tool - LangChain message to Anthropic format", () => { type: "web_search_result", title: "Claude Shannon - Wikipedia", url: "https://en.wikipedia.org/wiki/Claude_Shannon", - content: "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)...", - encrypted_index: "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm.." - } - ] - } - ] + content: + "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)...", + encrypted_index: "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm..", + }, + ], + }, + ], }); -}); \ No newline at end of file +}); diff --git a/libs/langchain-anthropic/src/utils/message_inputs.ts b/libs/langchain-anthropic/src/utils/message_inputs.ts index fc383504bfab..94a35a7cb5c0 100644 --- a/libs/langchain-anthropic/src/utils/message_inputs.ts +++ b/libs/langchain-anthropic/src/utils/message_inputs.ts @@ -414,7 +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 } : {}), + ...("citations" in contentPart && contentPart.citations + ? { citations: contentPart.citations } + : {}), }; } else if (toolTypes.find((t) => t === contentPart.type)) { const contentPartCopy = { ...contentPart }; From 1aa35b00efb87a55c33e1e555d9fa52a3b214057 Mon Sep 17 00:00:00 2001 From: Brandon Leafman <17711952+bleafman@users.noreply.github.coms> Date: Sun, 8 Jun 2025 12:28:54 -0700 Subject: [PATCH 12/12] tests(langchain-anthropic): fix web_search tests to reflect anthropic responses, remove outdated integration tests for built-in tools and server tools --- .../chat_models-built-in-tools.int.test.ts | 202 --------------- .../tests/chat_models-built-in-tools.test.ts | 235 ------------------ .../src/tests/chat_models-web_search.test.ts | 81 ++++-- 3 files changed, 57 insertions(+), 461 deletions(-) delete mode 100644 libs/langchain-anthropic/src/tests/chat_models-built-in-tools.int.test.ts delete mode 100644 libs/langchain-anthropic/src/tests/chat_models-built-in-tools.test.ts diff --git a/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.int.test.ts b/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.int.test.ts deleted file mode 100644 index 0dc7c1f563cc..000000000000 --- a/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.int.test.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { test, expect } from "@jest/globals"; -import { HumanMessage, AIMessage } from "@langchain/core/messages"; -import Anthropic from "@anthropic-ai/sdk"; -import { ChatAnthropic } from "../chat_models.js"; -import { _convertMessagesToAnthropicPayload } from "../utils/message_inputs.js"; - -const chatModelWithBuiltInTools = new ChatAnthropic({ - model: "claude-3-5-sonnet-20241022", - temperature: 0, -}).bindTools([ - { - type: "web_search_20250305", - name: "web_search", - max_uses: 1, - }, - { - type: "text_editor_20250124", - name: "str_replace_editor", - }, - { - type: "bash_20250124", - name: "bash", - }, -]); - -test("Server Tools Integration - Web Search", async () => { - // Test that we can handle a conversation with web search tool usage - const messages = [ - new HumanMessage({ - content: - "Search the web to find the name(s) of the original creator(s) of TypeScript", - }), - ]; - - const response = await chatModelWithBuiltInTools.invoke(messages); - - console.log("Response content:", JSON.stringify(response.content, null, 2)); - - // The response should be an AIMessage - expect(response).toBeInstanceOf(AIMessage); - expect(response.content).toBeDefined(); - - // The response should contain meaningful content about TypeScript - expect( - typeof response.content === "string" - ? response.content - : JSON.stringify(response.content) - ).toMatch(/TypeScript|typescript/i); - - console.log("✅ Successfully handled web search request"); -}, 30000); // 30 second timeout for API call - -test("Server Tools Integration - Message Round Trip", async () => { - // Test that we can properly parse messages with server tool content blocks - const conversation = [ - new HumanMessage({ - content: - "Search the web to find the name(s) of the original creator(s) of TypeScript", - }), - ]; - - try { - const response1 = await chatModelWithBuiltInTools.invoke(conversation); - - console.log("First response:", JSON.stringify(response1.content, null, 2)); - - // Add the AI response to conversation - conversation.push(response1); - - // Continue the conversation - conversation.push( - new HumanMessage({ - content: - "Based on your search, what is the name of the original creator(s) of TypeScript?", - }) - ); - - const response2 = await chatModelWithBuiltInTools.invoke(conversation); - - console.log("Second response:", JSON.stringify(response2.content, null, 2)); - - // Both responses should be valid - expect(response1).toBeInstanceOf(AIMessage); - expect(response2).toBeInstanceOf(AIMessage); - - console.log( - "✅ Successfully completed multi-turn conversation with server tools" - ); - } catch (error) { - // If server tools aren't available, the test should still not crash due to unsupported content format - if ( - // eslint-disable-next-line no-instanceof/no-instanceof - error instanceof Error && - error.message.includes("Unsupported message content format") - ) { - throw new Error( - "❌ REGRESSION: 'Unsupported message content format' error returned - this should be fixed!" - ); - } - - // Other errors (like API access issues) are expected and should not fail the test - console.log( - "⚠️ Server tools may not be available for this API key, but no format errors occurred" - ); - } -}, 45000); // 45 second timeout for longer conversation - -test("Server Tools Integration - Content Block Parsing", async () => { - // Test parsing of messages that contain server tool content blocks - const messageWithServerTool = new AIMessage({ - content: [ - { - type: "text", - text: "I'll search for that information.", - }, - { - type: "server_tool_use", - id: "toolu_01ABC123", - name: "web_search", - input: { - query: "latest AI developments", - }, - }, - ], - }); - - const messageWithSearchResult = new HumanMessage({ - content: [ - { - type: "web_search_tool_result", - tool_use_id: "toolu_01ABC123", - content: [ - { - type: "web_search_result", - title: "AI Breakthrough 2024", - url: "https://example.com/ai-news", - content: "Recent developments in AI...", - }, - ], - }, - ], - }); - - // This should not throw an "Unsupported message content format" error - expect(() => { - const messages = [messageWithServerTool, messageWithSearchResult]; - // Try to format these messages - this should work now - const formatted = _convertMessagesToAnthropicPayload(messages); - expect(formatted.messages).toHaveLength(2); - - // Verify server_tool_use is preserved - const aiContent = formatted.messages[0] - .content as Anthropic.ContentBlockParam[]; - expect( - aiContent.find((block) => block.type === "server_tool_use") - ).toBeDefined(); - - // Verify web_search_tool_result is preserved - const userContent = formatted.messages[1] - .content as Anthropic.ContentBlockParam[]; - expect( - userContent.find((block) => block.type === "web_search_tool_result") - ).toBeDefined(); - }).not.toThrow(); - - console.log( - "✅ Successfully parsed server tool content blocks without errors" - ); -}); - -test("Server Tools Integration - Error Handling", async () => { - // Test that malformed server tool content doesn't crash the system - const messageWithMalformedContent = new AIMessage({ - content: [ - { - type: "text", - text: "Testing error handling", - }, - { - type: "server_tool_use", - id: "test_id", - name: "web_search", - input: "malformed input", // This should be converted to object - }, - ], - }); - - // This should handle the malformed input gracefully - expect(() => { - const formatted = _convertMessagesToAnthropicPayload([ - messageWithMalformedContent, - ]); - expect(formatted.messages).toHaveLength(1); - - const content = formatted.messages[0] - .content as Anthropic.ContentBlockParam[]; - const toolUse = content.find((block) => block.type === "server_tool_use"); - expect(toolUse).toBeDefined(); - }).not.toThrow(); - - console.log("✅ Successfully handled malformed server tool content"); -}); diff --git a/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.test.ts b/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.test.ts deleted file mode 100644 index 6d7ab688a038..000000000000 --- a/libs/langchain-anthropic/src/tests/chat_models-built-in-tools.test.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { describe, it, expect } from "@jest/globals"; -import Anthropic from "@anthropic-ai/sdk"; -import { HumanMessage, AIMessage } from "@langchain/core/messages"; -import { _convertMessagesToAnthropicPayload } from "../utils/message_inputs.js"; - -describe("Anthropic Server Tools Content Blocks", () => { - it("should handle server_tool_use content blocks", () => { - const message = new AIMessage({ - content: [ - { - type: "text", - text: "I'll search for information about that topic.", - }, - { - type: "server_tool_use", - id: "toolu_01ABC123", - name: "web_search", - input: { - query: "latest developments in AI", - }, - }, - ], - }); - - const result = _convertMessagesToAnthropicPayload([message]); - - expect(result.messages).toHaveLength(1); - expect(result.messages[0].role).toBe("assistant"); - expect(Array.isArray(result.messages[0].content)).toBe(true); - - const content = result.messages[0].content as Anthropic.ContentBlockParam[]; - expect(content).toHaveLength(2); - expect(content[0]).toEqual({ - type: "text", - text: "I'll search for information about that topic.", - }); - expect(content[1]).toEqual({ - type: "server_tool_use", - id: "toolu_01ABC123", - name: "web_search", - input: { - query: "latest developments in AI", - }, - }); - }); - - it("should handle web_search_tool_result content blocks", () => { - const message = new HumanMessage({ - content: [ - { - type: "web_search_tool_result", - tool_use_id: "toolu_01ABC123", - content: [ - { - type: "web_search_result", - title: "Latest AI Developments", - url: "https://example.com/ai-news", - content: "Recent breakthroughs in artificial intelligence...", - }, - { - type: "web_search_result", - title: "AI Research Updates", - url: "https://example.com/research", - content: "New research papers and findings...", - }, - ], - }, - ], - }); - - const result = _convertMessagesToAnthropicPayload([message]); - - expect(result.messages).toHaveLength(1); - expect(result.messages[0].role).toBe("user"); - expect(Array.isArray(result.messages[0].content)).toBe(true); - - const content = result.messages[0].content as Anthropic.ContentBlockParam[]; - expect(content).toHaveLength(1); - expect(content[0]).toEqual({ - type: "web_search_tool_result", - tool_use_id: "toolu_01ABC123", - content: [ - { - type: "web_search_result", - title: "Latest AI Developments", - url: "https://example.com/ai-news", - content: "Recent breakthroughs in artificial intelligence...", - }, - { - type: "web_search_result", - title: "AI Research Updates", - url: "https://example.com/research", - content: "New research papers and findings...", - }, - ], - }); - }); - - it("should handle mixed content with server tools", () => { - const messages = [ - new HumanMessage({ - content: "Can you search for recent AI developments?", - }), - new AIMessage({ - content: [ - { - type: "text", - text: "I'll search for that information.", - }, - { - type: "server_tool_use", - id: "toolu_01ABC123", - name: "web_search", - input: { - query: "recent AI developments 2024", - }, - }, - ], - }), - new HumanMessage({ - content: [ - { - type: "web_search_tool_result", - tool_use_id: "toolu_01ABC123", - content: [ - { - type: "web_search_result", - title: "AI Breakthrough 2024", - url: "https://example.com/ai-breakthrough", - content: "Major AI breakthrough announced...", - }, - ], - }, - ], - }), - new AIMessage({ - content: - "Based on the search results, here are the latest AI developments...", - }), - ]; - - const result = _convertMessagesToAnthropicPayload(messages); - - expect(result.messages).toHaveLength(4); - - // Check human message - expect(result.messages[0].role).toBe("user"); - expect(result.messages[0].content).toBe( - "Can you search for recent AI developments?" - ); - - // Check AI message with server_tool_use - expect(result.messages[1].role).toBe("assistant"); - const aiContent = result.messages[1] - .content as Anthropic.Messages.ContentBlockParam[]; - expect(aiContent).toHaveLength(2); - expect(aiContent[0].type).toBe("text"); - expect(aiContent[1].type).toBe("server_tool_use"); - - // Check human message with web_search_tool_result - expect(result.messages[2].role).toBe("user"); - const userContent = result.messages[2] - .content as Anthropic.ContentBlockParam[]; - expect(userContent).toHaveLength(1); - expect(userContent[0].type).toBe("web_search_tool_result"); - - // Check final AI response - expect(result.messages[3].role).toBe("assistant"); - expect(result.messages[3].content).toBe( - "Based on the search results, here are the latest AI developments..." - ); - }); - - it("should preserve cache_control in server tool content blocks", () => { - const message = new AIMessage({ - content: [ - { - type: "server_tool_use", - id: "toolu_01ABC123", - name: "web_search", - input: { - query: "test query", - }, - cache_control: { type: "ephemeral" }, - }, - ], - }); - - const result = _convertMessagesToAnthropicPayload([message]); - - const content = result.messages[0].content as Anthropic.ContentBlockParam[]; - expect(content[0]).toEqual({ - type: "server_tool_use", - id: "toolu_01ABC123", - name: "web_search", - input: { - query: "test query", - }, - cache_control: { type: "ephemeral" }, - }); - }); - - it("should handle web_search_result with all optional fields", () => { - const message = new HumanMessage({ - content: [ - { - type: "web_search_tool_result", - tool_use_id: "toolu_01ABC123", - content: [ - { - type: "web_search_result", - title: "Complete Example", - url: "https://example.com/full", - content: "Full content here...", - publishedDate: "2024-01-15", - snippet: "This is a snippet...", - }, - ], - }, - ], - }); - - const result = _convertMessagesToAnthropicPayload([message]); - - const content = result.messages[0].content as Anthropic.ContentBlockParam[]; - expect(content[0]).toEqual({ - type: "web_search_result", - title: "Complete Example", - url: "https://example.com/full", - content: "Full content here...", - publishedDate: "2024-01-15", - snippet: "This is a snippet...", - }); - }); -}); 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 index 73f715a80972..b727f317bfef 100644 --- a/libs/langchain-anthropic/src/tests/chat_models-web_search.test.ts +++ b/libs/langchain-anthropic/src/tests/chat_models-web_search.test.ts @@ -1,3 +1,4 @@ +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"; @@ -5,10 +6,11 @@ import { _convertMessagesToAnthropicPayload } from "../utils/message_inputs.js"; test("Web Search Tool - Anthropic response to LangChain format", () => { // What Anthropic returns - const anthropicResponse = [ + const anthropicResponse: Anthropic.ContentBlock[] = [ { type: "text", text: "I'll search for that information.", + citations: null, }, { type: "server_tool_use", @@ -24,8 +26,9 @@ test("Web Search Tool - Anthropic response to LangChain format", () => { type: "web_search_result", title: "Claude Shannon - Wikipedia", url: "https://en.wikipedia.org/wiki/Claude_Shannon", - content: - "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)...", + encrypted_content: + "eyJjb250ZW50IjoiQ2xhdWRlIEVsd29vZCBTaGFubm9uIChBcHJpbCAzMCwgMTkxNiDigJMgRmVicnVhcnkgMjQsIDIwMDEpIHdhcyBhbiBBbWVyaWNhbiBtYXRoZW1hdGljaWFuLCBlbGVjdHJpY2FsIGVuZ2luZWVyLCBjb21wdXRlciBzY2llbnRpc3QgYW5kIGNyeXB0b2dyYXBoZXIga25vd24gYXMgdGhlIGZhdGhlciBvZiBpbmZvcm1hdGlvbiB0aGVvcnkuIn0=", + page_age: "April 30, 2025", }, ], }, @@ -39,6 +42,8 @@ test("Web Search Tool - Anthropic response to LangChain format", () => { title: "Claude Shannon - Wikipedia", cited_text: "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)...", + encrypted_index: + "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm1qLTQxYWUtOGVkYi1hNTc3MGZkZDllOGYSKENsYXVkZSBFbHdvb2QgU2hhbm5vbiAoQXByaWwgMzAsIDE5MTYgXu+/vSk=", }, ], }, @@ -69,18 +74,18 @@ test("Web Search Tool - Anthropic response to LangChain format", () => { test("Web Search Tool - Only web_search server tools extracted", () => { // What Anthropic returns (multiple server tools) - const anthropicResponse = [ + const anthropicResponse: Anthropic.ContentBlock[] = [ { type: "server_tool_use", - id: "toolu_web", + id: "toolu_web_001", name: "web_search", - input: { query: "test" }, + input: { query: "latest AI developments" }, }, { type: "server_tool_use", - id: "toolu_bash", - name: "bash", - input: { command: "ls" }, + id: "toolu_web_002", + name: "web_search", + input: { query: "machine learning trends 2024" }, }, ]; @@ -93,8 +98,14 @@ test("Web Search Tool - Only web_search server tools extracted", () => { tool_calls: [ { name: "web_search", - args: { query: "test" }, - id: "toolu_web", + 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", }, ], @@ -111,27 +122,38 @@ test("Web Search Tool - LangChain message to Anthropic format", () => { content: [ { type: "text", - text: "Based on my search, Claude Shannon was born in 1916.", + 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...", + 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_01ABC123", + tool_use_id: "toolu_01DEF456", content: [ { type: "web_search_result", title: "Claude Shannon - Wikipedia", url: "https://en.wikipedia.org/wiki/Claude_Shannon", - content: - "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)...", - encrypted_index: "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm..", + 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", }, ], }, @@ -139,7 +161,7 @@ test("Web Search Tool - LangChain message to Anthropic format", () => { }); const result = _convertMessagesToAnthropicPayload([ - new HumanMessage("Follow up question"), + new HumanMessage("Follow up question about information theory"), langChainMessage, ]); @@ -149,27 +171,38 @@ test("Web Search Tool - LangChain message to Anthropic format", () => { content: [ { type: "text", - text: "Based on my search, Claude Shannon was born in 1916.", + 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...", + 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_01ABC123", + tool_use_id: "toolu_01DEF456", content: [ { type: "web_search_result", title: "Claude Shannon - Wikipedia", url: "https://en.wikipedia.org/wiki/Claude_Shannon", - content: - "Claude Elwood Shannon (April 30, 1916 – February 24, 2001)...", - encrypted_index: "Eo8BCioIAhgBIiQyYjQ0OWJmZi1lNm..", + 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", }, ], },