@langchain/anthropic 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,11 @@ const base_1 = require("@langchain/core/language_models/base");
10
10
  const zod_to_json_schema_1 = require("zod-to-json-schema");
11
11
  const runnables_1 = require("@langchain/core/runnables");
12
12
  const types_1 = require("@langchain/core/utils/types");
13
+ const stream_1 = require("@langchain/core/utils/stream");
13
14
  const output_parsers_js_1 = require("./output_parsers.cjs");
15
+ function _toolsInParams(params) {
16
+ return !!(params.tools && params.tools.length > 0);
17
+ }
14
18
  function _formatImage(imageUrl) {
15
19
  const regex = /^data:(image\/.+);base64,(.+)$/;
16
20
  const match = imageUrl.match(regex);
@@ -44,6 +48,7 @@ function anthropicResponseToChatMessages(messages, additionalKwargs) {
44
48
  content: messages[0].text,
45
49
  additional_kwargs: additionalKwargs,
46
50
  usage_metadata: usageMetadata,
51
+ response_metadata: additionalKwargs,
47
52
  }),
48
53
  },
49
54
  ];
@@ -59,6 +64,7 @@ function anthropicResponseToChatMessages(messages, additionalKwargs) {
59
64
  additional_kwargs: additionalKwargs,
60
65
  tool_calls: toolCalls,
61
66
  usage_metadata: usageMetadata,
67
+ response_metadata: additionalKwargs,
62
68
  }),
63
69
  },
64
70
  ];
@@ -69,6 +75,115 @@ function anthropicResponseToChatMessages(messages, additionalKwargs) {
69
75
  function isAnthropicTool(tool) {
70
76
  return "input_schema" in tool;
71
77
  }
78
+ function _makeMessageChunkFromAnthropicEvent(data, fields) {
79
+ let usageDataCopy = { ...fields.usageData };
80
+ if (data.type === "message_start") {
81
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
82
+ const { content, usage, ...additionalKwargs } = data.message;
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ const filteredAdditionalKwargs = {};
85
+ for (const [key, value] of Object.entries(additionalKwargs)) {
86
+ if (value !== undefined && value !== null) {
87
+ filteredAdditionalKwargs[key] = value;
88
+ }
89
+ }
90
+ usageDataCopy = usage;
91
+ let usageMetadata;
92
+ if (fields.streamUsage) {
93
+ usageMetadata = {
94
+ input_tokens: usage.input_tokens,
95
+ output_tokens: usage.output_tokens,
96
+ total_tokens: usage.input_tokens + usage.output_tokens,
97
+ };
98
+ }
99
+ return {
100
+ chunk: new messages_1.AIMessageChunk({
101
+ content: fields.coerceContentToString ? "" : [],
102
+ additional_kwargs: filteredAdditionalKwargs,
103
+ usage_metadata: usageMetadata,
104
+ }),
105
+ usageData: usageDataCopy,
106
+ };
107
+ }
108
+ else if (data.type === "message_delta") {
109
+ let usageMetadata;
110
+ if (fields.streamUsage) {
111
+ usageMetadata = {
112
+ input_tokens: data.usage.output_tokens,
113
+ output_tokens: 0,
114
+ total_tokens: data.usage.output_tokens,
115
+ };
116
+ }
117
+ if (data?.usage !== undefined) {
118
+ usageDataCopy.output_tokens += data.usage.output_tokens;
119
+ }
120
+ return {
121
+ chunk: new messages_1.AIMessageChunk({
122
+ content: fields.coerceContentToString ? "" : [],
123
+ additional_kwargs: { ...data.delta },
124
+ usage_metadata: usageMetadata,
125
+ }),
126
+ usageData: usageDataCopy,
127
+ };
128
+ }
129
+ else if (data.type === "content_block_start" &&
130
+ data.content_block.type === "tool_use") {
131
+ return {
132
+ chunk: new messages_1.AIMessageChunk({
133
+ content: fields.coerceContentToString
134
+ ? ""
135
+ : [
136
+ {
137
+ index: data.index,
138
+ ...data.content_block,
139
+ input: "",
140
+ },
141
+ ],
142
+ additional_kwargs: {},
143
+ }),
144
+ usageData: usageDataCopy,
145
+ };
146
+ }
147
+ else if (data.type === "content_block_delta" &&
148
+ data.delta.type === "text_delta") {
149
+ const content = data.delta?.text;
150
+ if (content !== undefined) {
151
+ return {
152
+ chunk: new messages_1.AIMessageChunk({
153
+ content: fields.coerceContentToString
154
+ ? content
155
+ : [
156
+ {
157
+ index: data.index,
158
+ ...data.delta,
159
+ },
160
+ ],
161
+ additional_kwargs: {},
162
+ }),
163
+ usageData: usageDataCopy,
164
+ };
165
+ }
166
+ }
167
+ else if (data.type === "content_block_delta" &&
168
+ data.delta.type === "input_json_delta") {
169
+ return {
170
+ chunk: new messages_1.AIMessageChunk({
171
+ content: fields.coerceContentToString
172
+ ? ""
173
+ : [
174
+ {
175
+ index: data.index,
176
+ input: data.delta.partial_json,
177
+ type: data.delta.type,
178
+ },
179
+ ],
180
+ additional_kwargs: {},
181
+ }),
182
+ usageData: usageDataCopy,
183
+ };
184
+ }
185
+ return null;
186
+ }
72
187
  function _mergeMessages(messages) {
73
188
  // Merge runs of human/tool messages into single human messages with content blocks.
74
189
  const merged = [];
@@ -243,6 +358,90 @@ function _formatMessagesForAnthropic(messages) {
243
358
  system,
244
359
  };
245
360
  }
361
+ function extractToolCallChunk(chunk) {
362
+ let newToolCallChunk;
363
+ // Initial chunk for tool calls from anthropic contains identifying information like ID and name.
364
+ // This chunk does not contain any input JSON.
365
+ const toolUseChunks = Array.isArray(chunk.content)
366
+ ? chunk.content.find((c) => c.type === "tool_use")
367
+ : undefined;
368
+ if (toolUseChunks &&
369
+ "index" in toolUseChunks &&
370
+ "name" in toolUseChunks &&
371
+ "id" in toolUseChunks) {
372
+ newToolCallChunk = {
373
+ args: "",
374
+ id: toolUseChunks.id,
375
+ name: toolUseChunks.name,
376
+ index: toolUseChunks.index,
377
+ type: "tool_call_chunk",
378
+ };
379
+ }
380
+ // Chunks after the initial chunk only contain the index and partial JSON.
381
+ const inputJsonDeltaChunks = Array.isArray(chunk.content)
382
+ ? chunk.content.find((c) => c.type === "input_json_delta")
383
+ : undefined;
384
+ if (inputJsonDeltaChunks &&
385
+ "index" in inputJsonDeltaChunks &&
386
+ "input" in inputJsonDeltaChunks) {
387
+ if (typeof inputJsonDeltaChunks.input === "string") {
388
+ newToolCallChunk = {
389
+ args: inputJsonDeltaChunks.input,
390
+ index: inputJsonDeltaChunks.index,
391
+ type: "tool_call_chunk",
392
+ };
393
+ }
394
+ else {
395
+ newToolCallChunk = {
396
+ args: JSON.stringify(inputJsonDeltaChunks.input, null, 2),
397
+ index: inputJsonDeltaChunks.index,
398
+ type: "tool_call_chunk",
399
+ };
400
+ }
401
+ }
402
+ return newToolCallChunk;
403
+ }
404
+ function extractToken(chunk) {
405
+ return typeof chunk.content === "string" && chunk.content !== ""
406
+ ? chunk.content
407
+ : undefined;
408
+ }
409
+ function extractToolUseContent(chunk, concatenatedChunks) {
410
+ let newConcatenatedChunks = concatenatedChunks;
411
+ // Remove `tool_use` content types until the last chunk.
412
+ let toolUseContent;
413
+ if (!newConcatenatedChunks) {
414
+ newConcatenatedChunks = chunk;
415
+ }
416
+ else {
417
+ newConcatenatedChunks = (0, stream_1.concat)(newConcatenatedChunks, chunk);
418
+ }
419
+ if (Array.isArray(newConcatenatedChunks.content) &&
420
+ newConcatenatedChunks.content.find((c) => c.type === "tool_use")) {
421
+ try {
422
+ const toolUseMsg = newConcatenatedChunks.content.find((c) => c.type === "tool_use");
423
+ if (!toolUseMsg ||
424
+ !("input" in toolUseMsg || "name" in toolUseMsg || "id" in toolUseMsg))
425
+ return;
426
+ const parsedArgs = JSON.parse(toolUseMsg.input);
427
+ if (parsedArgs) {
428
+ toolUseContent = {
429
+ type: "tool_use",
430
+ id: toolUseMsg.id,
431
+ name: toolUseMsg.name,
432
+ input: parsedArgs,
433
+ };
434
+ }
435
+ }
436
+ catch (_) {
437
+ // no-op
438
+ }
439
+ }
440
+ return {
441
+ toolUseContent,
442
+ concatenatedChunks: newConcatenatedChunks,
443
+ };
444
+ }
246
445
  /**
247
446
  * Wrapper around Anthropic large language models.
248
447
  *
@@ -516,119 +715,82 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
516
715
  async *_streamResponseChunks(messages, options, runManager) {
517
716
  const params = this.invocationParams(options);
518
717
  const formattedMessages = _formatMessagesForAnthropic(messages);
519
- if (options.tools !== undefined && options.tools.length > 0) {
520
- const { generations } = await this._generateNonStreaming(messages, params, {
521
- signal: options.signal,
522
- });
523
- const result = generations[0].message;
524
- const toolCallChunks = result.tool_calls?.map((toolCall, index) => ({
525
- name: toolCall.name,
526
- args: JSON.stringify(toolCall.args),
527
- id: toolCall.id,
528
- index,
529
- }));
530
- yield new outputs_1.ChatGenerationChunk({
531
- message: new messages_1.AIMessageChunk({
532
- content: result.content,
533
- additional_kwargs: result.additional_kwargs,
534
- tool_call_chunks: toolCallChunks,
535
- }),
536
- text: generations[0].text,
537
- });
538
- }
539
- else {
540
- const stream = await this.createStreamWithRetry({
541
- ...params,
542
- ...formattedMessages,
543
- stream: true,
718
+ const coerceContentToString = !_toolsInParams({
719
+ ...params,
720
+ ...formattedMessages,
721
+ stream: false,
722
+ });
723
+ const stream = await this.createStreamWithRetry({
724
+ ...params,
725
+ ...formattedMessages,
726
+ stream: true,
727
+ });
728
+ let usageData = { input_tokens: 0, output_tokens: 0 };
729
+ let concatenatedChunks;
730
+ for await (const data of stream) {
731
+ if (options.signal?.aborted) {
732
+ stream.controller.abort();
733
+ throw new Error("AbortError: User aborted the request.");
734
+ }
735
+ const result = _makeMessageChunkFromAnthropicEvent(data, {
736
+ streamUsage: !!(this.streamUsage || options.streamUsage),
737
+ coerceContentToString,
738
+ usageData,
544
739
  });
545
- let usageData = { input_tokens: 0, output_tokens: 0 };
546
- for await (const data of stream) {
547
- if (options.signal?.aborted) {
548
- stream.controller.abort();
549
- throw new Error("AbortError: User aborted the request.");
550
- }
551
- if (data.type === "message_start") {
552
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
553
- const { content, usage, ...additionalKwargs } = data.message;
554
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
555
- const filteredAdditionalKwargs = {};
556
- for (const [key, value] of Object.entries(additionalKwargs)) {
557
- if (value !== undefined && value !== null) {
558
- filteredAdditionalKwargs[key] = value;
559
- }
560
- }
561
- usageData = usage;
562
- let usageMetadata;
563
- if (this.streamUsage || options.streamUsage) {
564
- usageMetadata = {
565
- input_tokens: usage.input_tokens,
566
- output_tokens: usage.output_tokens,
567
- total_tokens: usage.input_tokens + usage.output_tokens,
568
- };
569
- }
570
- yield new outputs_1.ChatGenerationChunk({
571
- message: new messages_1.AIMessageChunk({
572
- content: "",
573
- additional_kwargs: filteredAdditionalKwargs,
574
- usage_metadata: usageMetadata,
575
- }),
576
- text: "",
577
- });
578
- }
579
- else if (data.type === "message_delta") {
580
- let usageMetadata;
581
- if (this.streamUsage || options.streamUsage) {
582
- usageMetadata = {
583
- input_tokens: data.usage.output_tokens,
584
- output_tokens: 0,
585
- total_tokens: data.usage.output_tokens,
586
- };
587
- }
588
- yield new outputs_1.ChatGenerationChunk({
589
- message: new messages_1.AIMessageChunk({
590
- content: "",
591
- additional_kwargs: { ...data.delta },
592
- usage_metadata: usageMetadata,
593
- }),
594
- text: "",
595
- });
596
- if (data?.usage !== undefined) {
597
- usageData.output_tokens += data.usage.output_tokens;
598
- }
599
- }
600
- else if (data.type === "content_block_delta" &&
601
- data.delta.type === "text_delta") {
602
- const content = data.delta?.text;
603
- if (content !== undefined) {
604
- yield new outputs_1.ChatGenerationChunk({
605
- message: new messages_1.AIMessageChunk({
606
- content,
607
- additional_kwargs: {},
608
- }),
609
- text: content,
610
- });
611
- await runManager?.handleLLMNewToken(content);
612
- }
613
- }
740
+ if (!result)
741
+ continue;
742
+ const { chunk, usageData: updatedUsageData } = result;
743
+ usageData = updatedUsageData;
744
+ const newToolCallChunk = extractToolCallChunk(chunk);
745
+ // Maintain concatenatedChunks for accessing the complete `tool_use` content block.
746
+ concatenatedChunks = concatenatedChunks
747
+ ? (0, stream_1.concat)(concatenatedChunks, chunk)
748
+ : chunk;
749
+ let toolUseContent;
750
+ const extractedContent = extractToolUseContent(chunk, concatenatedChunks);
751
+ if (extractedContent) {
752
+ toolUseContent = extractedContent.toolUseContent;
753
+ concatenatedChunks = extractedContent.concatenatedChunks;
614
754
  }
615
- let usageMetadata;
616
- if (this.streamUsage || options.streamUsage) {
617
- usageMetadata = {
618
- input_tokens: usageData.input_tokens,
619
- output_tokens: usageData.output_tokens,
620
- total_tokens: usageData.input_tokens + usageData.output_tokens,
621
- };
755
+ // Filter partial `tool_use` content, and only add `tool_use` chunks if complete JSON available.
756
+ const chunkContent = Array.isArray(chunk.content)
757
+ ? chunk.content.filter((c) => c.type !== "tool_use")
758
+ : chunk.content;
759
+ if (Array.isArray(chunkContent) && toolUseContent) {
760
+ chunkContent.push(toolUseContent);
622
761
  }
762
+ // Extract the text content token for text field and runManager.
763
+ const token = extractToken(chunk);
623
764
  yield new outputs_1.ChatGenerationChunk({
624
765
  message: new messages_1.AIMessageChunk({
625
- content: "",
626
- additional_kwargs: { usage: usageData },
627
- usage_metadata: usageMetadata,
766
+ content: chunkContent,
767
+ additional_kwargs: chunk.additional_kwargs,
768
+ tool_call_chunks: newToolCallChunk ? [newToolCallChunk] : undefined,
769
+ usage_metadata: chunk.usage_metadata,
770
+ response_metadata: chunk.response_metadata,
628
771
  }),
629
- text: "",
772
+ text: token ?? "",
630
773
  });
774
+ if (token) {
775
+ await runManager?.handleLLMNewToken(token);
776
+ }
777
+ }
778
+ let usageMetadata;
779
+ if (this.streamUsage || options.streamUsage) {
780
+ usageMetadata = {
781
+ input_tokens: usageData.input_tokens,
782
+ output_tokens: usageData.output_tokens,
783
+ total_tokens: usageData.input_tokens + usageData.output_tokens,
784
+ };
631
785
  }
786
+ yield new outputs_1.ChatGenerationChunk({
787
+ message: new messages_1.AIMessageChunk({
788
+ content: coerceContentToString ? "" : [],
789
+ additional_kwargs: { usage: usageData },
790
+ usage_metadata: usageMetadata,
791
+ }),
792
+ text: "",
793
+ });
632
794
  }
633
795
  /** @ignore */
634
796
  async _generateNonStreaming(messages, params, requestOptions) {
@@ -784,7 +946,10 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
784
946
  }
785
947
  const llm = this.bind({
786
948
  tools,
787
- tool_choice: "any",
949
+ tool_choice: {
950
+ type: "tool",
951
+ name: functionName,
952
+ },
788
953
  });
789
954
  if (!includeRaw) {
790
955
  return llm.pipe(outputParser).withConfig({
@@ -6,7 +6,7 @@ import { ChatGeneration, ChatGenerationChunk, type ChatResult } from "@langchain
6
6
  import { BaseChatModel, LangSmithParams, type BaseChatModelParams } from "@langchain/core/language_models/chat_models";
7
7
  import { type StructuredOutputMethodOptions, type BaseLanguageModelCallOptions, type BaseLanguageModelInput, type ToolDefinition } from "@langchain/core/language_models/base";
8
8
  import { StructuredToolInterface } from "@langchain/core/tools";
9
- import { Runnable } from "@langchain/core/runnables";
9
+ import { Runnable, RunnableToolLike } from "@langchain/core/runnables";
10
10
  import { ToolCall } from "@langchain/core/messages/tool";
11
11
  import { z } from "zod";
12
12
  import type { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index.mjs";
@@ -20,7 +20,7 @@ type AnthropicToolChoice = {
20
20
  name: string;
21
21
  } | "any" | "auto";
22
22
  export interface ChatAnthropicCallOptions extends BaseLanguageModelCallOptions, Pick<AnthropicInput, "streamUsage"> {
23
- tools?: (StructuredToolInterface | AnthropicTool | Record<string, unknown> | ToolDefinition)[];
23
+ tools?: (StructuredToolInterface | AnthropicTool | Record<string, unknown> | ToolDefinition | RunnableToolLike)[];
24
24
  /**
25
25
  * Whether or not to specify what tool the model should use
26
26
  * @default "auto"
@@ -150,7 +150,7 @@ export declare class ChatAnthropicMessages<CallOptions extends ChatAnthropicCall
150
150
  * @throws {Error} If a mix of AnthropicTools and StructuredTools are passed.
151
151
  */
152
152
  formatStructuredToolToAnthropic(tools: ChatAnthropicCallOptions["tools"]): AnthropicTool[] | undefined;
153
- bindTools(tools: (AnthropicTool | Record<string, unknown> | StructuredToolInterface | ToolDefinition)[], kwargs?: Partial<CallOptions>): Runnable<BaseLanguageModelInput, AIMessageChunk, CallOptions>;
153
+ bindTools(tools: (AnthropicTool | Record<string, unknown> | StructuredToolInterface | ToolDefinition | RunnableToolLike)[], kwargs?: Partial<CallOptions>): Runnable<BaseLanguageModelInput, AIMessageChunk, CallOptions>;
154
154
  /**
155
155
  * Get the parameters used to invoke the model
156
156
  */
@@ -163,8 +163,8 @@ export declare class ChatAnthropicMessages<CallOptions extends ChatAnthropicCall
163
163
  temperature?: number | undefined;
164
164
  model: "claude-2.1" | (string & {}) | "claude-3-opus-20240229" | "claude-3-sonnet-20240229" | "claude-3-haiku-20240307" | "claude-2.0" | "claude-instant-1.2";
165
165
  system?: string | undefined;
166
- stream?: boolean | undefined;
167
166
  max_tokens: number;
167
+ stream?: boolean | undefined;
168
168
  stop_sequences?: string[] | undefined;
169
169
  top_k?: number | undefined;
170
170
  top_p?: number | undefined;
@@ -180,8 +180,8 @@ export declare class ChatAnthropicMessages<CallOptions extends ChatAnthropicCall
180
180
  temperature?: number | undefined;
181
181
  model: "claude-2.1" | (string & {}) | "claude-3-opus-20240229" | "claude-3-sonnet-20240229" | "claude-3-haiku-20240307" | "claude-2.0" | "claude-instant-1.2";
182
182
  system?: string | undefined;
183
- stream?: boolean | undefined;
184
183
  max_tokens: number;
184
+ stream?: boolean | undefined;
185
185
  stop_sequences?: string[] | undefined;
186
186
  top_k?: number | undefined;
187
187
  top_p?: number | undefined;
@@ -194,7 +194,7 @@ export declare class ChatAnthropicMessages<CallOptions extends ChatAnthropicCall
194
194
  llmOutput: {
195
195
  id: string;
196
196
  model: string;
197
- stop_reason: "tool_use" | "max_tokens" | "stop_sequence" | "end_turn" | null;
197
+ stop_reason: "tool_use" | "stop_sequence" | "end_turn" | "max_tokens" | null;
198
198
  stop_sequence: string | null;
199
199
  usage: Anthropic.Messages.Usage;
200
200
  };
@@ -7,7 +7,11 @@ import { isOpenAITool, } from "@langchain/core/language_models/base";
7
7
  import { zodToJsonSchema } from "zod-to-json-schema";
8
8
  import { RunnablePassthrough, RunnableSequence, } from "@langchain/core/runnables";
9
9
  import { isZodSchema } from "@langchain/core/utils/types";
10
+ import { concat } from "@langchain/core/utils/stream";
10
11
  import { AnthropicToolsOutputParser, extractToolCalls, } from "./output_parsers.js";
12
+ function _toolsInParams(params) {
13
+ return !!(params.tools && params.tools.length > 0);
14
+ }
11
15
  function _formatImage(imageUrl) {
12
16
  const regex = /^data:(image\/.+);base64,(.+)$/;
13
17
  const match = imageUrl.match(regex);
@@ -41,6 +45,7 @@ function anthropicResponseToChatMessages(messages, additionalKwargs) {
41
45
  content: messages[0].text,
42
46
  additional_kwargs: additionalKwargs,
43
47
  usage_metadata: usageMetadata,
48
+ response_metadata: additionalKwargs,
44
49
  }),
45
50
  },
46
51
  ];
@@ -56,6 +61,7 @@ function anthropicResponseToChatMessages(messages, additionalKwargs) {
56
61
  additional_kwargs: additionalKwargs,
57
62
  tool_calls: toolCalls,
58
63
  usage_metadata: usageMetadata,
64
+ response_metadata: additionalKwargs,
59
65
  }),
60
66
  },
61
67
  ];
@@ -66,6 +72,115 @@ function anthropicResponseToChatMessages(messages, additionalKwargs) {
66
72
  function isAnthropicTool(tool) {
67
73
  return "input_schema" in tool;
68
74
  }
75
+ function _makeMessageChunkFromAnthropicEvent(data, fields) {
76
+ let usageDataCopy = { ...fields.usageData };
77
+ if (data.type === "message_start") {
78
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
79
+ const { content, usage, ...additionalKwargs } = data.message;
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ const filteredAdditionalKwargs = {};
82
+ for (const [key, value] of Object.entries(additionalKwargs)) {
83
+ if (value !== undefined && value !== null) {
84
+ filteredAdditionalKwargs[key] = value;
85
+ }
86
+ }
87
+ usageDataCopy = usage;
88
+ let usageMetadata;
89
+ if (fields.streamUsage) {
90
+ usageMetadata = {
91
+ input_tokens: usage.input_tokens,
92
+ output_tokens: usage.output_tokens,
93
+ total_tokens: usage.input_tokens + usage.output_tokens,
94
+ };
95
+ }
96
+ return {
97
+ chunk: new AIMessageChunk({
98
+ content: fields.coerceContentToString ? "" : [],
99
+ additional_kwargs: filteredAdditionalKwargs,
100
+ usage_metadata: usageMetadata,
101
+ }),
102
+ usageData: usageDataCopy,
103
+ };
104
+ }
105
+ else if (data.type === "message_delta") {
106
+ let usageMetadata;
107
+ if (fields.streamUsage) {
108
+ usageMetadata = {
109
+ input_tokens: data.usage.output_tokens,
110
+ output_tokens: 0,
111
+ total_tokens: data.usage.output_tokens,
112
+ };
113
+ }
114
+ if (data?.usage !== undefined) {
115
+ usageDataCopy.output_tokens += data.usage.output_tokens;
116
+ }
117
+ return {
118
+ chunk: new AIMessageChunk({
119
+ content: fields.coerceContentToString ? "" : [],
120
+ additional_kwargs: { ...data.delta },
121
+ usage_metadata: usageMetadata,
122
+ }),
123
+ usageData: usageDataCopy,
124
+ };
125
+ }
126
+ else if (data.type === "content_block_start" &&
127
+ data.content_block.type === "tool_use") {
128
+ return {
129
+ chunk: new AIMessageChunk({
130
+ content: fields.coerceContentToString
131
+ ? ""
132
+ : [
133
+ {
134
+ index: data.index,
135
+ ...data.content_block,
136
+ input: "",
137
+ },
138
+ ],
139
+ additional_kwargs: {},
140
+ }),
141
+ usageData: usageDataCopy,
142
+ };
143
+ }
144
+ else if (data.type === "content_block_delta" &&
145
+ data.delta.type === "text_delta") {
146
+ const content = data.delta?.text;
147
+ if (content !== undefined) {
148
+ return {
149
+ chunk: new AIMessageChunk({
150
+ content: fields.coerceContentToString
151
+ ? content
152
+ : [
153
+ {
154
+ index: data.index,
155
+ ...data.delta,
156
+ },
157
+ ],
158
+ additional_kwargs: {},
159
+ }),
160
+ usageData: usageDataCopy,
161
+ };
162
+ }
163
+ }
164
+ else if (data.type === "content_block_delta" &&
165
+ data.delta.type === "input_json_delta") {
166
+ return {
167
+ chunk: new AIMessageChunk({
168
+ content: fields.coerceContentToString
169
+ ? ""
170
+ : [
171
+ {
172
+ index: data.index,
173
+ input: data.delta.partial_json,
174
+ type: data.delta.type,
175
+ },
176
+ ],
177
+ additional_kwargs: {},
178
+ }),
179
+ usageData: usageDataCopy,
180
+ };
181
+ }
182
+ return null;
183
+ }
69
184
  function _mergeMessages(messages) {
70
185
  // Merge runs of human/tool messages into single human messages with content blocks.
71
186
  const merged = [];
@@ -239,6 +354,90 @@ function _formatMessagesForAnthropic(messages) {
239
354
  system,
240
355
  };
241
356
  }
357
+ function extractToolCallChunk(chunk) {
358
+ let newToolCallChunk;
359
+ // Initial chunk for tool calls from anthropic contains identifying information like ID and name.
360
+ // This chunk does not contain any input JSON.
361
+ const toolUseChunks = Array.isArray(chunk.content)
362
+ ? chunk.content.find((c) => c.type === "tool_use")
363
+ : undefined;
364
+ if (toolUseChunks &&
365
+ "index" in toolUseChunks &&
366
+ "name" in toolUseChunks &&
367
+ "id" in toolUseChunks) {
368
+ newToolCallChunk = {
369
+ args: "",
370
+ id: toolUseChunks.id,
371
+ name: toolUseChunks.name,
372
+ index: toolUseChunks.index,
373
+ type: "tool_call_chunk",
374
+ };
375
+ }
376
+ // Chunks after the initial chunk only contain the index and partial JSON.
377
+ const inputJsonDeltaChunks = Array.isArray(chunk.content)
378
+ ? chunk.content.find((c) => c.type === "input_json_delta")
379
+ : undefined;
380
+ if (inputJsonDeltaChunks &&
381
+ "index" in inputJsonDeltaChunks &&
382
+ "input" in inputJsonDeltaChunks) {
383
+ if (typeof inputJsonDeltaChunks.input === "string") {
384
+ newToolCallChunk = {
385
+ args: inputJsonDeltaChunks.input,
386
+ index: inputJsonDeltaChunks.index,
387
+ type: "tool_call_chunk",
388
+ };
389
+ }
390
+ else {
391
+ newToolCallChunk = {
392
+ args: JSON.stringify(inputJsonDeltaChunks.input, null, 2),
393
+ index: inputJsonDeltaChunks.index,
394
+ type: "tool_call_chunk",
395
+ };
396
+ }
397
+ }
398
+ return newToolCallChunk;
399
+ }
400
+ function extractToken(chunk) {
401
+ return typeof chunk.content === "string" && chunk.content !== ""
402
+ ? chunk.content
403
+ : undefined;
404
+ }
405
+ function extractToolUseContent(chunk, concatenatedChunks) {
406
+ let newConcatenatedChunks = concatenatedChunks;
407
+ // Remove `tool_use` content types until the last chunk.
408
+ let toolUseContent;
409
+ if (!newConcatenatedChunks) {
410
+ newConcatenatedChunks = chunk;
411
+ }
412
+ else {
413
+ newConcatenatedChunks = concat(newConcatenatedChunks, chunk);
414
+ }
415
+ if (Array.isArray(newConcatenatedChunks.content) &&
416
+ newConcatenatedChunks.content.find((c) => c.type === "tool_use")) {
417
+ try {
418
+ const toolUseMsg = newConcatenatedChunks.content.find((c) => c.type === "tool_use");
419
+ if (!toolUseMsg ||
420
+ !("input" in toolUseMsg || "name" in toolUseMsg || "id" in toolUseMsg))
421
+ return;
422
+ const parsedArgs = JSON.parse(toolUseMsg.input);
423
+ if (parsedArgs) {
424
+ toolUseContent = {
425
+ type: "tool_use",
426
+ id: toolUseMsg.id,
427
+ name: toolUseMsg.name,
428
+ input: parsedArgs,
429
+ };
430
+ }
431
+ }
432
+ catch (_) {
433
+ // no-op
434
+ }
435
+ }
436
+ return {
437
+ toolUseContent,
438
+ concatenatedChunks: newConcatenatedChunks,
439
+ };
440
+ }
242
441
  /**
243
442
  * Wrapper around Anthropic large language models.
244
443
  *
@@ -512,119 +711,82 @@ export class ChatAnthropicMessages extends BaseChatModel {
512
711
  async *_streamResponseChunks(messages, options, runManager) {
513
712
  const params = this.invocationParams(options);
514
713
  const formattedMessages = _formatMessagesForAnthropic(messages);
515
- if (options.tools !== undefined && options.tools.length > 0) {
516
- const { generations } = await this._generateNonStreaming(messages, params, {
517
- signal: options.signal,
518
- });
519
- const result = generations[0].message;
520
- const toolCallChunks = result.tool_calls?.map((toolCall, index) => ({
521
- name: toolCall.name,
522
- args: JSON.stringify(toolCall.args),
523
- id: toolCall.id,
524
- index,
525
- }));
526
- yield new ChatGenerationChunk({
527
- message: new AIMessageChunk({
528
- content: result.content,
529
- additional_kwargs: result.additional_kwargs,
530
- tool_call_chunks: toolCallChunks,
531
- }),
532
- text: generations[0].text,
533
- });
534
- }
535
- else {
536
- const stream = await this.createStreamWithRetry({
537
- ...params,
538
- ...formattedMessages,
539
- stream: true,
714
+ const coerceContentToString = !_toolsInParams({
715
+ ...params,
716
+ ...formattedMessages,
717
+ stream: false,
718
+ });
719
+ const stream = await this.createStreamWithRetry({
720
+ ...params,
721
+ ...formattedMessages,
722
+ stream: true,
723
+ });
724
+ let usageData = { input_tokens: 0, output_tokens: 0 };
725
+ let concatenatedChunks;
726
+ for await (const data of stream) {
727
+ if (options.signal?.aborted) {
728
+ stream.controller.abort();
729
+ throw new Error("AbortError: User aborted the request.");
730
+ }
731
+ const result = _makeMessageChunkFromAnthropicEvent(data, {
732
+ streamUsage: !!(this.streamUsage || options.streamUsage),
733
+ coerceContentToString,
734
+ usageData,
540
735
  });
541
- let usageData = { input_tokens: 0, output_tokens: 0 };
542
- for await (const data of stream) {
543
- if (options.signal?.aborted) {
544
- stream.controller.abort();
545
- throw new Error("AbortError: User aborted the request.");
546
- }
547
- if (data.type === "message_start") {
548
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
549
- const { content, usage, ...additionalKwargs } = data.message;
550
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
551
- const filteredAdditionalKwargs = {};
552
- for (const [key, value] of Object.entries(additionalKwargs)) {
553
- if (value !== undefined && value !== null) {
554
- filteredAdditionalKwargs[key] = value;
555
- }
556
- }
557
- usageData = usage;
558
- let usageMetadata;
559
- if (this.streamUsage || options.streamUsage) {
560
- usageMetadata = {
561
- input_tokens: usage.input_tokens,
562
- output_tokens: usage.output_tokens,
563
- total_tokens: usage.input_tokens + usage.output_tokens,
564
- };
565
- }
566
- yield new ChatGenerationChunk({
567
- message: new AIMessageChunk({
568
- content: "",
569
- additional_kwargs: filteredAdditionalKwargs,
570
- usage_metadata: usageMetadata,
571
- }),
572
- text: "",
573
- });
574
- }
575
- else if (data.type === "message_delta") {
576
- let usageMetadata;
577
- if (this.streamUsage || options.streamUsage) {
578
- usageMetadata = {
579
- input_tokens: data.usage.output_tokens,
580
- output_tokens: 0,
581
- total_tokens: data.usage.output_tokens,
582
- };
583
- }
584
- yield new ChatGenerationChunk({
585
- message: new AIMessageChunk({
586
- content: "",
587
- additional_kwargs: { ...data.delta },
588
- usage_metadata: usageMetadata,
589
- }),
590
- text: "",
591
- });
592
- if (data?.usage !== undefined) {
593
- usageData.output_tokens += data.usage.output_tokens;
594
- }
595
- }
596
- else if (data.type === "content_block_delta" &&
597
- data.delta.type === "text_delta") {
598
- const content = data.delta?.text;
599
- if (content !== undefined) {
600
- yield new ChatGenerationChunk({
601
- message: new AIMessageChunk({
602
- content,
603
- additional_kwargs: {},
604
- }),
605
- text: content,
606
- });
607
- await runManager?.handleLLMNewToken(content);
608
- }
609
- }
736
+ if (!result)
737
+ continue;
738
+ const { chunk, usageData: updatedUsageData } = result;
739
+ usageData = updatedUsageData;
740
+ const newToolCallChunk = extractToolCallChunk(chunk);
741
+ // Maintain concatenatedChunks for accessing the complete `tool_use` content block.
742
+ concatenatedChunks = concatenatedChunks
743
+ ? concat(concatenatedChunks, chunk)
744
+ : chunk;
745
+ let toolUseContent;
746
+ const extractedContent = extractToolUseContent(chunk, concatenatedChunks);
747
+ if (extractedContent) {
748
+ toolUseContent = extractedContent.toolUseContent;
749
+ concatenatedChunks = extractedContent.concatenatedChunks;
610
750
  }
611
- let usageMetadata;
612
- if (this.streamUsage || options.streamUsage) {
613
- usageMetadata = {
614
- input_tokens: usageData.input_tokens,
615
- output_tokens: usageData.output_tokens,
616
- total_tokens: usageData.input_tokens + usageData.output_tokens,
617
- };
751
+ // Filter partial `tool_use` content, and only add `tool_use` chunks if complete JSON available.
752
+ const chunkContent = Array.isArray(chunk.content)
753
+ ? chunk.content.filter((c) => c.type !== "tool_use")
754
+ : chunk.content;
755
+ if (Array.isArray(chunkContent) && toolUseContent) {
756
+ chunkContent.push(toolUseContent);
618
757
  }
758
+ // Extract the text content token for text field and runManager.
759
+ const token = extractToken(chunk);
619
760
  yield new ChatGenerationChunk({
620
761
  message: new AIMessageChunk({
621
- content: "",
622
- additional_kwargs: { usage: usageData },
623
- usage_metadata: usageMetadata,
762
+ content: chunkContent,
763
+ additional_kwargs: chunk.additional_kwargs,
764
+ tool_call_chunks: newToolCallChunk ? [newToolCallChunk] : undefined,
765
+ usage_metadata: chunk.usage_metadata,
766
+ response_metadata: chunk.response_metadata,
624
767
  }),
625
- text: "",
768
+ text: token ?? "",
626
769
  });
770
+ if (token) {
771
+ await runManager?.handleLLMNewToken(token);
772
+ }
773
+ }
774
+ let usageMetadata;
775
+ if (this.streamUsage || options.streamUsage) {
776
+ usageMetadata = {
777
+ input_tokens: usageData.input_tokens,
778
+ output_tokens: usageData.output_tokens,
779
+ total_tokens: usageData.input_tokens + usageData.output_tokens,
780
+ };
627
781
  }
782
+ yield new ChatGenerationChunk({
783
+ message: new AIMessageChunk({
784
+ content: coerceContentToString ? "" : [],
785
+ additional_kwargs: { usage: usageData },
786
+ usage_metadata: usageMetadata,
787
+ }),
788
+ text: "",
789
+ });
628
790
  }
629
791
  /** @ignore */
630
792
  async _generateNonStreaming(messages, params, requestOptions) {
@@ -780,7 +942,10 @@ export class ChatAnthropicMessages extends BaseChatModel {
780
942
  }
781
943
  const llm = this.bind({
782
944
  tools,
783
- tool_choice: "any",
945
+ tool_choice: {
946
+ type: "tool",
947
+ name: functionName,
948
+ },
784
949
  });
785
950
  if (!includeRaw) {
786
951
  return llm.pipe(outputParser).withConfig({
@@ -79,7 +79,12 @@ function extractToolCalls(content) {
79
79
  const toolCalls = [];
80
80
  for (const block of content) {
81
81
  if (block.type === "tool_use") {
82
- toolCalls.push({ name: block.name, args: block.input, id: block.id });
82
+ toolCalls.push({
83
+ name: block.name,
84
+ args: block.input,
85
+ id: block.id,
86
+ type: "tool_call",
87
+ });
83
88
  }
84
89
  }
85
90
  return toolCalls;
@@ -75,7 +75,12 @@ export function extractToolCalls(content) {
75
75
  const toolCalls = [];
76
76
  for (const block of content) {
77
77
  if (block.type === "tool_use") {
78
- toolCalls.push({ name: block.name, args: block.input, id: block.id });
78
+ toolCalls.push({
79
+ name: block.name,
80
+ args: block.input,
81
+ id: block.id,
82
+ type: "tool_call",
83
+ });
79
84
  }
80
85
  }
81
86
  return toolCalls;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/anthropic",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Anthropic integrations for LangChain.js",
5
5
  "type": "module",
6
6
  "engines": {
@@ -36,7 +36,7 @@
36
36
  "license": "MIT",
37
37
  "dependencies": {
38
38
  "@anthropic-ai/sdk": "^0.22.0",
39
- "@langchain/core": ">=0.2.9 <0.3.0",
39
+ "@langchain/core": ">=0.2.16 <0.3.0",
40
40
  "fast-xml-parser": "^4.3.5",
41
41
  "zod": "^3.22.4",
42
42
  "zod-to-json-schema": "^3.22.4"