diff --git a/libs/langchain-anthropic/package.json b/libs/langchain-anthropic/package.json index b7baf1e8feed..c4bc6e83a000 100644 --- a/libs/langchain-anthropic/package.json +++ b/libs/langchain-anthropic/package.json @@ -35,8 +35,8 @@ "author": "LangChain", "license": "MIT", "dependencies": { - "@anthropic-ai/sdk": "^0.21.0", - "@langchain/core": ">=0.2.5 <0.3.0", + "@anthropic-ai/sdk": "^0.22.0", + "@langchain/core": ">=0.2.9 <0.3.0", "fast-xml-parser": "^4.3.5", "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 036b21515671..f93ce4e6b15e 100644 --- a/libs/langchain-anthropic/src/chat_models.ts +++ b/libs/langchain-anthropic/src/chat_models.ts @@ -25,9 +25,11 @@ import { type BaseChatModelParams, } from "@langchain/core/language_models/chat_models"; import { - StructuredOutputMethodOptions, + type StructuredOutputMethodOptions, type BaseLanguageModelCallOptions, - BaseLanguageModelInput, + type BaseLanguageModelInput, + type ToolDefinition, + isOpenAITool, } from "@langchain/core/language_models/base"; import { StructuredToolInterface } from "@langchain/core/tools"; import { zodToJsonSchema } from "zod-to-json-schema"; @@ -40,21 +42,16 @@ import { import { isZodSchema } from "@langchain/core/utils/types"; import { ToolCall } from "@langchain/core/messages/tool"; import { z } from "zod"; +import type { + MessageCreateParams, + Tool as AnthropicTool, +} from "@anthropic-ai/sdk/resources/index.mjs"; import { AnthropicToolsOutputParser, extractToolCalls, } from "./output_parsers.js"; import { AnthropicToolResponse } from "./types.js"; -type AnthropicTool = { - name: string; - description: string; - /** - * JSON schema. - */ - input_schema: Record<string, unknown>; -}; - type AnthropicMessage = Anthropic.MessageParam; type AnthropicMessageCreateParams = Anthropic.MessageCreateParamsNonStreaming; type AnthropicStreamingMessageCreateParams = @@ -71,7 +68,12 @@ type AnthropicToolChoice = export interface ChatAnthropicCallOptions extends BaseLanguageModelCallOptions, Pick<AnthropicInput, "streamUsage"> { - tools?: (StructuredToolInterface | AnthropicTool)[]; + tools?: ( + | StructuredToolInterface + | AnthropicTool + | Record<string, unknown> + | ToolDefinition + )[]; /** * Whether or not to specify what tool the model should use * @default "auto" @@ -562,22 +564,35 @@ export class ChatAnthropicMessages< return tools as AnthropicTool[]; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((tools as any[]).every((tool) => isOpenAITool(tool))) { + // Formatted as OpenAI tool, convert to Anthropic tool + return (tools as ToolDefinition[]).map((tc) => ({ + name: tc.function.name, + description: tc.function.description, + input_schema: tc.function.parameters as AnthropicTool.InputSchema, + })); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((tools as any[]).some((tool) => isAnthropicTool(tool))) { - throw new Error( - `Can not pass in a mix of AnthropicTools and StructuredTools` - ); + throw new Error(`Can not pass in a mix of tool schemas to ChatAnthropic`); } return (tools as StructuredToolInterface[]).map((tool) => ({ name: tool.name, description: tool.description, - input_schema: zodToJsonSchema(tool.schema), + input_schema: zodToJsonSchema(tool.schema) as AnthropicTool.InputSchema, })); } override bindTools( - tools: (AnthropicTool | StructuredToolInterface)[], + tools: ( + | AnthropicTool + | Record<string, unknown> + | StructuredToolInterface + | ToolDefinition + )[], kwargs?: Partial<CallOptions> ): Runnable<BaseLanguageModelInput, AIMessageChunk, CallOptions> { return this.bind({ @@ -597,10 +612,9 @@ export class ChatAnthropicMessages< > & Kwargs { let tool_choice: - | { - type: string; - name?: string; - } + | MessageCreateParams.ToolChoiceAuto + | MessageCreateParams.ToolChoiceAny + | MessageCreateParams.ToolChoiceTool | undefined; if (options?.tool_choice) { if (options?.tool_choice === "any") { @@ -739,7 +753,10 @@ export class ChatAnthropicMessages< if (data?.usage !== undefined) { usageData.output_tokens += data.usage.output_tokens; } - } else if (data.type === "content_block_delta") { + } else if ( + data.type === "content_block_delta" && + data.delta.type === "text_delta" + ) { const content = data.delta?.text; if (content !== undefined) { yield new ChatGenerationChunk({ @@ -976,7 +993,7 @@ export class ChatAnthropicMessages< name: functionName, description: jsonSchema.description ?? "A function available to call.", - input_schema: jsonSchema, + input_schema: jsonSchema as AnthropicTool.InputSchema, }, ]; outputParser = new AnthropicToolsOutputParser({ @@ -998,7 +1015,7 @@ export class ChatAnthropicMessages< anthropicTools = { name: functionName, description: schema.description ?? "", - input_schema: schema, + input_schema: schema as AnthropicTool.InputSchema, }; } tools = [anthropicTools]; diff --git a/libs/langchain-anthropic/src/tests/chat_models-tools.int.test.ts b/libs/langchain-anthropic/src/tests/chat_models-tools.int.test.ts index 153a6e00ea34..04ac8900a772 100644 --- a/libs/langchain-anthropic/src/tests/chat_models-tools.int.test.ts +++ b/libs/langchain-anthropic/src/tests/chat_models-tools.int.test.ts @@ -340,3 +340,25 @@ test("Can pass tool_choice", async () => { expect(input).toBeTruthy(); expect(input.location).toBeTruthy(); }); + +test("bindTools accepts openai formatted tool", async () => { + const openaiTool = { + type: "function", + function: { + name: "get_weather", + description: + "Get the weather of a specific location and return the temperature in Celsius.", + parameters: zodToJsonSchema(zodSchema), + }, + }; + const modelWithTools = model.bindTools([openaiTool]); + const response = await modelWithTools.invoke( + "Whats the weather like in san francisco?" + ); + expect(response.tool_calls).toHaveLength(1); + const { tool_calls } = response; + if (!tool_calls) { + return; + } + expect(tool_calls[0].name).toBe("get_weather"); +}); diff --git a/yarn.lock b/yarn.lock index 6c6d038dfc8c..5709f50f0600 100644 --- a/yarn.lock +++ b/yarn.lock @@ -211,9 +211,9 @@ __metadata: languageName: node linkType: hard -"@anthropic-ai/sdk@npm:^0.21.0": - version: 0.21.0 - resolution: "@anthropic-ai/sdk@npm:0.21.0" +"@anthropic-ai/sdk@npm:^0.22.0": + version: 0.22.0 + resolution: "@anthropic-ai/sdk@npm:0.22.0" dependencies: "@types/node": ^18.11.18 "@types/node-fetch": ^2.6.4 @@ -223,7 +223,7 @@ __metadata: formdata-node: ^4.3.2 node-fetch: ^2.6.7 web-streams-polyfill: ^3.2.1 - checksum: fbed720938487495f1d28822fa6eb3871cf7e7be325c299b69efa78e72e1e0b66d9f564003ae5d7a1e96c7555cc69c817be4b901d1847ae002f782546a4c987d + checksum: f09fc6ea1f5f68483fd2dbdc1ab78a0914d7a3f39fd4a921f5a04b8959ae82aa210372fa2dbff2ee78143d37fb408e7b7d61e0023e7ea31d5fa1f4873f371af8 languageName: node linkType: hard @@ -9399,10 +9399,10 @@ __metadata: version: 0.0.0-use.local resolution: "@langchain/anthropic@workspace:libs/langchain-anthropic" dependencies: - "@anthropic-ai/sdk": ^0.21.0 + "@anthropic-ai/sdk": ^0.22.0 "@jest/globals": ^29.5.0 "@langchain/community": "workspace:*" - "@langchain/core": ">=0.2.5 <0.3.0" + "@langchain/core": ">=0.2.9 <0.3.0" "@langchain/scripts": ~0.0.14 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90