@langchain/anthropic 0.2.3 → 0.2.5

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,12 @@ 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
+ const utils_js_1 = require("./utils.cjs");
16
+ function _toolsInParams(params) {
17
+ return !!(params.tools && params.tools.length > 0);
18
+ }
14
19
  function _formatImage(imageUrl) {
15
20
  const regex = /^data:(image\/.+);base64,(.+)$/;
16
21
  const match = imageUrl.match(regex);
@@ -44,6 +49,8 @@ function anthropicResponseToChatMessages(messages, additionalKwargs) {
44
49
  content: messages[0].text,
45
50
  additional_kwargs: additionalKwargs,
46
51
  usage_metadata: usageMetadata,
52
+ response_metadata: additionalKwargs,
53
+ id: additionalKwargs.id,
47
54
  }),
48
55
  },
49
56
  ];
@@ -59,6 +66,8 @@ function anthropicResponseToChatMessages(messages, additionalKwargs) {
59
66
  additional_kwargs: additionalKwargs,
60
67
  tool_calls: toolCalls,
61
68
  usage_metadata: usageMetadata,
69
+ response_metadata: additionalKwargs,
70
+ id: additionalKwargs.id,
62
71
  }),
63
72
  },
64
73
  ];
@@ -69,6 +78,117 @@ function anthropicResponseToChatMessages(messages, additionalKwargs) {
69
78
  function isAnthropicTool(tool) {
70
79
  return "input_schema" in tool;
71
80
  }
81
+ function _makeMessageChunkFromAnthropicEvent(data, fields) {
82
+ let usageDataCopy = { ...fields.usageData };
83
+ if (data.type === "message_start") {
84
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
85
+ const { content, usage, ...additionalKwargs } = data.message;
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ const filteredAdditionalKwargs = {};
88
+ for (const [key, value] of Object.entries(additionalKwargs)) {
89
+ if (value !== undefined && value !== null) {
90
+ filteredAdditionalKwargs[key] = value;
91
+ }
92
+ }
93
+ usageDataCopy = usage;
94
+ let usageMetadata;
95
+ if (fields.streamUsage) {
96
+ usageMetadata = {
97
+ input_tokens: usage.input_tokens,
98
+ output_tokens: usage.output_tokens,
99
+ total_tokens: usage.input_tokens + usage.output_tokens,
100
+ };
101
+ }
102
+ console.log("data.message", data.message);
103
+ return {
104
+ chunk: new messages_1.AIMessageChunk({
105
+ content: fields.coerceContentToString ? "" : [],
106
+ additional_kwargs: filteredAdditionalKwargs,
107
+ usage_metadata: usageMetadata,
108
+ id: data.message.id,
109
+ }),
110
+ usageData: usageDataCopy,
111
+ };
112
+ }
113
+ else if (data.type === "message_delta") {
114
+ let usageMetadata;
115
+ if (fields.streamUsage) {
116
+ usageMetadata = {
117
+ input_tokens: data.usage.output_tokens,
118
+ output_tokens: 0,
119
+ total_tokens: data.usage.output_tokens,
120
+ };
121
+ }
122
+ if (data?.usage !== undefined) {
123
+ usageDataCopy.output_tokens += data.usage.output_tokens;
124
+ }
125
+ return {
126
+ chunk: new messages_1.AIMessageChunk({
127
+ content: fields.coerceContentToString ? "" : [],
128
+ additional_kwargs: { ...data.delta },
129
+ usage_metadata: usageMetadata,
130
+ }),
131
+ usageData: usageDataCopy,
132
+ };
133
+ }
134
+ else if (data.type === "content_block_start" &&
135
+ data.content_block.type === "tool_use") {
136
+ return {
137
+ chunk: new messages_1.AIMessageChunk({
138
+ content: fields.coerceContentToString
139
+ ? ""
140
+ : [
141
+ {
142
+ index: data.index,
143
+ ...data.content_block,
144
+ input: "",
145
+ },
146
+ ],
147
+ additional_kwargs: {},
148
+ }),
149
+ usageData: usageDataCopy,
150
+ };
151
+ }
152
+ else if (data.type === "content_block_delta" &&
153
+ data.delta.type === "text_delta") {
154
+ const content = data.delta?.text;
155
+ if (content !== undefined) {
156
+ return {
157
+ chunk: new messages_1.AIMessageChunk({
158
+ content: fields.coerceContentToString
159
+ ? content
160
+ : [
161
+ {
162
+ index: data.index,
163
+ ...data.delta,
164
+ },
165
+ ],
166
+ additional_kwargs: {},
167
+ }),
168
+ usageData: usageDataCopy,
169
+ };
170
+ }
171
+ }
172
+ else if (data.type === "content_block_delta" &&
173
+ data.delta.type === "input_json_delta") {
174
+ return {
175
+ chunk: new messages_1.AIMessageChunk({
176
+ content: fields.coerceContentToString
177
+ ? ""
178
+ : [
179
+ {
180
+ index: data.index,
181
+ input: data.delta.partial_json,
182
+ type: data.delta.type,
183
+ },
184
+ ],
185
+ additional_kwargs: {},
186
+ }),
187
+ usageData: usageDataCopy,
188
+ };
189
+ }
190
+ return null;
191
+ }
72
192
  function _mergeMessages(messages) {
73
193
  // Merge runs of human/tool messages into single human messages with content blocks.
74
194
  const merged = [];
@@ -243,6 +363,90 @@ function _formatMessagesForAnthropic(messages) {
243
363
  system,
244
364
  };
245
365
  }
366
+ function extractToolCallChunk(chunk) {
367
+ let newToolCallChunk;
368
+ // Initial chunk for tool calls from anthropic contains identifying information like ID and name.
369
+ // This chunk does not contain any input JSON.
370
+ const toolUseChunks = Array.isArray(chunk.content)
371
+ ? chunk.content.find((c) => c.type === "tool_use")
372
+ : undefined;
373
+ if (toolUseChunks &&
374
+ "index" in toolUseChunks &&
375
+ "name" in toolUseChunks &&
376
+ "id" in toolUseChunks) {
377
+ newToolCallChunk = {
378
+ args: "",
379
+ id: toolUseChunks.id,
380
+ name: toolUseChunks.name,
381
+ index: toolUseChunks.index,
382
+ type: "tool_call_chunk",
383
+ };
384
+ }
385
+ // Chunks after the initial chunk only contain the index and partial JSON.
386
+ const inputJsonDeltaChunks = Array.isArray(chunk.content)
387
+ ? chunk.content.find((c) => c.type === "input_json_delta")
388
+ : undefined;
389
+ if (inputJsonDeltaChunks &&
390
+ "index" in inputJsonDeltaChunks &&
391
+ "input" in inputJsonDeltaChunks) {
392
+ if (typeof inputJsonDeltaChunks.input === "string") {
393
+ newToolCallChunk = {
394
+ args: inputJsonDeltaChunks.input,
395
+ index: inputJsonDeltaChunks.index,
396
+ type: "tool_call_chunk",
397
+ };
398
+ }
399
+ else {
400
+ newToolCallChunk = {
401
+ args: JSON.stringify(inputJsonDeltaChunks.input, null, 2),
402
+ index: inputJsonDeltaChunks.index,
403
+ type: "tool_call_chunk",
404
+ };
405
+ }
406
+ }
407
+ return newToolCallChunk;
408
+ }
409
+ function extractToken(chunk) {
410
+ return typeof chunk.content === "string" && chunk.content !== ""
411
+ ? chunk.content
412
+ : undefined;
413
+ }
414
+ function extractToolUseContent(chunk, concatenatedChunks) {
415
+ let newConcatenatedChunks = concatenatedChunks;
416
+ // Remove `tool_use` content types until the last chunk.
417
+ let toolUseContent;
418
+ if (!newConcatenatedChunks) {
419
+ newConcatenatedChunks = chunk;
420
+ }
421
+ else {
422
+ newConcatenatedChunks = (0, stream_1.concat)(newConcatenatedChunks, chunk);
423
+ }
424
+ if (Array.isArray(newConcatenatedChunks.content) &&
425
+ newConcatenatedChunks.content.find((c) => c.type === "tool_use")) {
426
+ try {
427
+ const toolUseMsg = newConcatenatedChunks.content.find((c) => c.type === "tool_use");
428
+ if (!toolUseMsg ||
429
+ !("input" in toolUseMsg || "name" in toolUseMsg || "id" in toolUseMsg))
430
+ return;
431
+ const parsedArgs = JSON.parse(toolUseMsg.input);
432
+ if (parsedArgs) {
433
+ toolUseContent = {
434
+ type: "tool_use",
435
+ id: toolUseMsg.id,
436
+ name: toolUseMsg.name,
437
+ input: parsedArgs,
438
+ };
439
+ }
440
+ }
441
+ catch (_) {
442
+ // no-op
443
+ }
444
+ }
445
+ return {
446
+ toolUseContent,
447
+ concatenatedChunks: newConcatenatedChunks,
448
+ };
449
+ }
246
450
  /**
247
451
  * Wrapper around Anthropic large language models.
248
452
  *
@@ -468,22 +672,7 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
468
672
  * Get the parameters used to invoke the model
469
673
  */
470
674
  invocationParams(options) {
471
- let tool_choice;
472
- if (options?.tool_choice) {
473
- if (options?.tool_choice === "any") {
474
- tool_choice = {
475
- type: "any",
476
- };
477
- }
478
- else if (options?.tool_choice === "auto") {
479
- tool_choice = {
480
- type: "auto",
481
- };
482
- }
483
- else {
484
- tool_choice = options?.tool_choice;
485
- }
486
- }
675
+ const tool_choice = (0, utils_js_1.handleToolChoice)(options?.tool_choice);
487
676
  return {
488
677
  model: this.model,
489
678
  temperature: this.temperature,
@@ -516,119 +705,83 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
516
705
  async *_streamResponseChunks(messages, options, runManager) {
517
706
  const params = this.invocationParams(options);
518
707
  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,
708
+ const coerceContentToString = !_toolsInParams({
709
+ ...params,
710
+ ...formattedMessages,
711
+ stream: false,
712
+ });
713
+ const stream = await this.createStreamWithRetry({
714
+ ...params,
715
+ ...formattedMessages,
716
+ stream: true,
717
+ });
718
+ let usageData = { input_tokens: 0, output_tokens: 0 };
719
+ let concatenatedChunks;
720
+ for await (const data of stream) {
721
+ if (options.signal?.aborted) {
722
+ stream.controller.abort();
723
+ throw new Error("AbortError: User aborted the request.");
724
+ }
725
+ const result = _makeMessageChunkFromAnthropicEvent(data, {
726
+ streamUsage: !!(this.streamUsage || options.streamUsage),
727
+ coerceContentToString,
728
+ usageData,
544
729
  });
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
- }
730
+ if (!result)
731
+ continue;
732
+ const { chunk, usageData: updatedUsageData } = result;
733
+ usageData = updatedUsageData;
734
+ const newToolCallChunk = extractToolCallChunk(chunk);
735
+ // Maintain concatenatedChunks for accessing the complete `tool_use` content block.
736
+ concatenatedChunks = concatenatedChunks
737
+ ? (0, stream_1.concat)(concatenatedChunks, chunk)
738
+ : chunk;
739
+ let toolUseContent;
740
+ const extractedContent = extractToolUseContent(chunk, concatenatedChunks);
741
+ if (extractedContent) {
742
+ toolUseContent = extractedContent.toolUseContent;
743
+ concatenatedChunks = extractedContent.concatenatedChunks;
614
744
  }
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
- };
745
+ // Filter partial `tool_use` content, and only add `tool_use` chunks if complete JSON available.
746
+ const chunkContent = Array.isArray(chunk.content)
747
+ ? chunk.content.filter((c) => c.type !== "tool_use")
748
+ : chunk.content;
749
+ if (Array.isArray(chunkContent) && toolUseContent) {
750
+ chunkContent.push(toolUseContent);
622
751
  }
752
+ // Extract the text content token for text field and runManager.
753
+ const token = extractToken(chunk);
623
754
  yield new outputs_1.ChatGenerationChunk({
624
755
  message: new messages_1.AIMessageChunk({
625
- content: "",
626
- additional_kwargs: { usage: usageData },
627
- usage_metadata: usageMetadata,
756
+ content: chunkContent,
757
+ additional_kwargs: chunk.additional_kwargs,
758
+ tool_call_chunks: newToolCallChunk ? [newToolCallChunk] : undefined,
759
+ usage_metadata: chunk.usage_metadata,
760
+ response_metadata: chunk.response_metadata,
761
+ id: chunk.id,
628
762
  }),
629
- text: "",
763
+ text: token ?? "",
630
764
  });
765
+ if (token) {
766
+ await runManager?.handleLLMNewToken(token);
767
+ }
631
768
  }
769
+ let usageMetadata;
770
+ if (this.streamUsage || options.streamUsage) {
771
+ usageMetadata = {
772
+ input_tokens: usageData.input_tokens,
773
+ output_tokens: usageData.output_tokens,
774
+ total_tokens: usageData.input_tokens + usageData.output_tokens,
775
+ };
776
+ }
777
+ yield new outputs_1.ChatGenerationChunk({
778
+ message: new messages_1.AIMessageChunk({
779
+ content: coerceContentToString ? "" : [],
780
+ additional_kwargs: { usage: usageData },
781
+ usage_metadata: usageMetadata,
782
+ }),
783
+ text: "",
784
+ });
632
785
  }
633
786
  /** @ignore */
634
787
  async _generateNonStreaming(messages, params, requestOptions) {
@@ -3,24 +3,21 @@ import type { Stream } from "@anthropic-ai/sdk/streaming";
3
3
  import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager";
4
4
  import { AIMessageChunk, type BaseMessage } from "@langchain/core/messages";
5
5
  import { ChatGeneration, ChatGenerationChunk, type ChatResult } from "@langchain/core/outputs";
6
- import { BaseChatModel, LangSmithParams, type BaseChatModelParams } from "@langchain/core/language_models/chat_models";
7
- import { type StructuredOutputMethodOptions, type BaseLanguageModelCallOptions, type BaseLanguageModelInput, type ToolDefinition } from "@langchain/core/language_models/base";
6
+ import { BaseChatModel, BaseChatModelCallOptions, LangSmithParams, type BaseChatModelParams } from "@langchain/core/language_models/chat_models";
7
+ import { type StructuredOutputMethodOptions, 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";
13
13
  import { AnthropicToolResponse } from "./types.js";
14
+ import { AnthropicToolChoice, AnthropicToolTypes } from "./utils.js";
14
15
  type AnthropicMessageCreateParams = Anthropic.MessageCreateParamsNonStreaming;
15
16
  type AnthropicStreamingMessageCreateParams = Anthropic.MessageCreateParamsStreaming;
16
17
  type AnthropicMessageStreamEvent = Anthropic.MessageStreamEvent;
17
18
  type AnthropicRequestOptions = Anthropic.RequestOptions;
18
- type AnthropicToolChoice = {
19
- type: "tool";
20
- name: string;
21
- } | "any" | "auto";
22
- export interface ChatAnthropicCallOptions extends BaseLanguageModelCallOptions, Pick<AnthropicInput, "streamUsage"> {
23
- tools?: (StructuredToolInterface | AnthropicTool | Record<string, unknown> | ToolDefinition)[];
19
+ export interface ChatAnthropicCallOptions extends BaseChatModelCallOptions, Pick<AnthropicInput, "streamUsage"> {
20
+ tools?: AnthropicToolTypes[];
24
21
  /**
25
22
  * Whether or not to specify what tool the model should use
26
23
  * @default "auto"
@@ -150,7 +147,7 @@ export declare class ChatAnthropicMessages<CallOptions extends ChatAnthropicCall
150
147
  * @throws {Error} If a mix of AnthropicTools and StructuredTools are passed.
151
148
  */
152
149
  formatStructuredToolToAnthropic(tools: ChatAnthropicCallOptions["tools"]): AnthropicTool[] | undefined;
153
- bindTools(tools: (AnthropicTool | Record<string, unknown> | StructuredToolInterface | ToolDefinition)[], kwargs?: Partial<CallOptions>): Runnable<BaseLanguageModelInput, AIMessageChunk, CallOptions>;
150
+ bindTools(tools: (AnthropicTool | Record<string, unknown> | StructuredToolInterface | ToolDefinition | RunnableToolLike)[], kwargs?: Partial<CallOptions>): Runnable<BaseLanguageModelInput, AIMessageChunk, CallOptions>;
154
151
  /**
155
152
  * Get the parameters used to invoke the model
156
153
  */
@@ -163,8 +160,8 @@ export declare class ChatAnthropicMessages<CallOptions extends ChatAnthropicCall
163
160
  temperature?: number | undefined;
164
161
  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
162
  system?: string | undefined;
166
- stream?: boolean | undefined;
167
163
  max_tokens: number;
164
+ stream?: boolean | undefined;
168
165
  stop_sequences?: string[] | undefined;
169
166
  top_k?: number | undefined;
170
167
  top_p?: number | undefined;
@@ -180,8 +177,8 @@ export declare class ChatAnthropicMessages<CallOptions extends ChatAnthropicCall
180
177
  temperature?: number | undefined;
181
178
  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
179
  system?: string | undefined;
183
- stream?: boolean | undefined;
184
180
  max_tokens: number;
181
+ stream?: boolean | undefined;
185
182
  stop_sequences?: string[] | undefined;
186
183
  top_k?: number | undefined;
187
184
  top_p?: number | undefined;
@@ -194,7 +191,7 @@ export declare class ChatAnthropicMessages<CallOptions extends ChatAnthropicCall
194
191
  llmOutput: {
195
192
  id: string;
196
193
  model: string;
197
- stop_reason: "tool_use" | "max_tokens" | "stop_sequence" | "end_turn" | null;
194
+ stop_reason: "tool_use" | "stop_sequence" | "end_turn" | "max_tokens" | null;
198
195
  stop_sequence: string | null;
199
196
  usage: Anthropic.Messages.Usage;
200
197
  };
@@ -7,7 +7,12 @@ 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
+ import { handleToolChoice, } from "./utils.js";
13
+ function _toolsInParams(params) {
14
+ return !!(params.tools && params.tools.length > 0);
15
+ }
11
16
  function _formatImage(imageUrl) {
12
17
  const regex = /^data:(image\/.+);base64,(.+)$/;
13
18
  const match = imageUrl.match(regex);
@@ -41,6 +46,8 @@ function anthropicResponseToChatMessages(messages, additionalKwargs) {
41
46
  content: messages[0].text,
42
47
  additional_kwargs: additionalKwargs,
43
48
  usage_metadata: usageMetadata,
49
+ response_metadata: additionalKwargs,
50
+ id: additionalKwargs.id,
44
51
  }),
45
52
  },
46
53
  ];
@@ -56,6 +63,8 @@ function anthropicResponseToChatMessages(messages, additionalKwargs) {
56
63
  additional_kwargs: additionalKwargs,
57
64
  tool_calls: toolCalls,
58
65
  usage_metadata: usageMetadata,
66
+ response_metadata: additionalKwargs,
67
+ id: additionalKwargs.id,
59
68
  }),
60
69
  },
61
70
  ];
@@ -66,6 +75,117 @@ function anthropicResponseToChatMessages(messages, additionalKwargs) {
66
75
  function isAnthropicTool(tool) {
67
76
  return "input_schema" in tool;
68
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
+ console.log("data.message", data.message);
100
+ return {
101
+ chunk: new AIMessageChunk({
102
+ content: fields.coerceContentToString ? "" : [],
103
+ additional_kwargs: filteredAdditionalKwargs,
104
+ usage_metadata: usageMetadata,
105
+ id: data.message.id,
106
+ }),
107
+ usageData: usageDataCopy,
108
+ };
109
+ }
110
+ else if (data.type === "message_delta") {
111
+ let usageMetadata;
112
+ if (fields.streamUsage) {
113
+ usageMetadata = {
114
+ input_tokens: data.usage.output_tokens,
115
+ output_tokens: 0,
116
+ total_tokens: data.usage.output_tokens,
117
+ };
118
+ }
119
+ if (data?.usage !== undefined) {
120
+ usageDataCopy.output_tokens += data.usage.output_tokens;
121
+ }
122
+ return {
123
+ chunk: new AIMessageChunk({
124
+ content: fields.coerceContentToString ? "" : [],
125
+ additional_kwargs: { ...data.delta },
126
+ usage_metadata: usageMetadata,
127
+ }),
128
+ usageData: usageDataCopy,
129
+ };
130
+ }
131
+ else if (data.type === "content_block_start" &&
132
+ data.content_block.type === "tool_use") {
133
+ return {
134
+ chunk: new AIMessageChunk({
135
+ content: fields.coerceContentToString
136
+ ? ""
137
+ : [
138
+ {
139
+ index: data.index,
140
+ ...data.content_block,
141
+ input: "",
142
+ },
143
+ ],
144
+ additional_kwargs: {},
145
+ }),
146
+ usageData: usageDataCopy,
147
+ };
148
+ }
149
+ else if (data.type === "content_block_delta" &&
150
+ data.delta.type === "text_delta") {
151
+ const content = data.delta?.text;
152
+ if (content !== undefined) {
153
+ return {
154
+ chunk: new AIMessageChunk({
155
+ content: fields.coerceContentToString
156
+ ? content
157
+ : [
158
+ {
159
+ index: data.index,
160
+ ...data.delta,
161
+ },
162
+ ],
163
+ additional_kwargs: {},
164
+ }),
165
+ usageData: usageDataCopy,
166
+ };
167
+ }
168
+ }
169
+ else if (data.type === "content_block_delta" &&
170
+ data.delta.type === "input_json_delta") {
171
+ return {
172
+ chunk: new AIMessageChunk({
173
+ content: fields.coerceContentToString
174
+ ? ""
175
+ : [
176
+ {
177
+ index: data.index,
178
+ input: data.delta.partial_json,
179
+ type: data.delta.type,
180
+ },
181
+ ],
182
+ additional_kwargs: {},
183
+ }),
184
+ usageData: usageDataCopy,
185
+ };
186
+ }
187
+ return null;
188
+ }
69
189
  function _mergeMessages(messages) {
70
190
  // Merge runs of human/tool messages into single human messages with content blocks.
71
191
  const merged = [];
@@ -239,6 +359,90 @@ function _formatMessagesForAnthropic(messages) {
239
359
  system,
240
360
  };
241
361
  }
362
+ function extractToolCallChunk(chunk) {
363
+ let newToolCallChunk;
364
+ // Initial chunk for tool calls from anthropic contains identifying information like ID and name.
365
+ // This chunk does not contain any input JSON.
366
+ const toolUseChunks = Array.isArray(chunk.content)
367
+ ? chunk.content.find((c) => c.type === "tool_use")
368
+ : undefined;
369
+ if (toolUseChunks &&
370
+ "index" in toolUseChunks &&
371
+ "name" in toolUseChunks &&
372
+ "id" in toolUseChunks) {
373
+ newToolCallChunk = {
374
+ args: "",
375
+ id: toolUseChunks.id,
376
+ name: toolUseChunks.name,
377
+ index: toolUseChunks.index,
378
+ type: "tool_call_chunk",
379
+ };
380
+ }
381
+ // Chunks after the initial chunk only contain the index and partial JSON.
382
+ const inputJsonDeltaChunks = Array.isArray(chunk.content)
383
+ ? chunk.content.find((c) => c.type === "input_json_delta")
384
+ : undefined;
385
+ if (inputJsonDeltaChunks &&
386
+ "index" in inputJsonDeltaChunks &&
387
+ "input" in inputJsonDeltaChunks) {
388
+ if (typeof inputJsonDeltaChunks.input === "string") {
389
+ newToolCallChunk = {
390
+ args: inputJsonDeltaChunks.input,
391
+ index: inputJsonDeltaChunks.index,
392
+ type: "tool_call_chunk",
393
+ };
394
+ }
395
+ else {
396
+ newToolCallChunk = {
397
+ args: JSON.stringify(inputJsonDeltaChunks.input, null, 2),
398
+ index: inputJsonDeltaChunks.index,
399
+ type: "tool_call_chunk",
400
+ };
401
+ }
402
+ }
403
+ return newToolCallChunk;
404
+ }
405
+ function extractToken(chunk) {
406
+ return typeof chunk.content === "string" && chunk.content !== ""
407
+ ? chunk.content
408
+ : undefined;
409
+ }
410
+ function extractToolUseContent(chunk, concatenatedChunks) {
411
+ let newConcatenatedChunks = concatenatedChunks;
412
+ // Remove `tool_use` content types until the last chunk.
413
+ let toolUseContent;
414
+ if (!newConcatenatedChunks) {
415
+ newConcatenatedChunks = chunk;
416
+ }
417
+ else {
418
+ newConcatenatedChunks = concat(newConcatenatedChunks, chunk);
419
+ }
420
+ if (Array.isArray(newConcatenatedChunks.content) &&
421
+ newConcatenatedChunks.content.find((c) => c.type === "tool_use")) {
422
+ try {
423
+ const toolUseMsg = newConcatenatedChunks.content.find((c) => c.type === "tool_use");
424
+ if (!toolUseMsg ||
425
+ !("input" in toolUseMsg || "name" in toolUseMsg || "id" in toolUseMsg))
426
+ return;
427
+ const parsedArgs = JSON.parse(toolUseMsg.input);
428
+ if (parsedArgs) {
429
+ toolUseContent = {
430
+ type: "tool_use",
431
+ id: toolUseMsg.id,
432
+ name: toolUseMsg.name,
433
+ input: parsedArgs,
434
+ };
435
+ }
436
+ }
437
+ catch (_) {
438
+ // no-op
439
+ }
440
+ }
441
+ return {
442
+ toolUseContent,
443
+ concatenatedChunks: newConcatenatedChunks,
444
+ };
445
+ }
242
446
  /**
243
447
  * Wrapper around Anthropic large language models.
244
448
  *
@@ -464,22 +668,7 @@ export class ChatAnthropicMessages extends BaseChatModel {
464
668
  * Get the parameters used to invoke the model
465
669
  */
466
670
  invocationParams(options) {
467
- let tool_choice;
468
- if (options?.tool_choice) {
469
- if (options?.tool_choice === "any") {
470
- tool_choice = {
471
- type: "any",
472
- };
473
- }
474
- else if (options?.tool_choice === "auto") {
475
- tool_choice = {
476
- type: "auto",
477
- };
478
- }
479
- else {
480
- tool_choice = options?.tool_choice;
481
- }
482
- }
671
+ const tool_choice = handleToolChoice(options?.tool_choice);
483
672
  return {
484
673
  model: this.model,
485
674
  temperature: this.temperature,
@@ -512,119 +701,83 @@ export class ChatAnthropicMessages extends BaseChatModel {
512
701
  async *_streamResponseChunks(messages, options, runManager) {
513
702
  const params = this.invocationParams(options);
514
703
  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,
704
+ const coerceContentToString = !_toolsInParams({
705
+ ...params,
706
+ ...formattedMessages,
707
+ stream: false,
708
+ });
709
+ const stream = await this.createStreamWithRetry({
710
+ ...params,
711
+ ...formattedMessages,
712
+ stream: true,
713
+ });
714
+ let usageData = { input_tokens: 0, output_tokens: 0 };
715
+ let concatenatedChunks;
716
+ for await (const data of stream) {
717
+ if (options.signal?.aborted) {
718
+ stream.controller.abort();
719
+ throw new Error("AbortError: User aborted the request.");
720
+ }
721
+ const result = _makeMessageChunkFromAnthropicEvent(data, {
722
+ streamUsage: !!(this.streamUsage || options.streamUsage),
723
+ coerceContentToString,
724
+ usageData,
540
725
  });
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
- }
726
+ if (!result)
727
+ continue;
728
+ const { chunk, usageData: updatedUsageData } = result;
729
+ usageData = updatedUsageData;
730
+ const newToolCallChunk = extractToolCallChunk(chunk);
731
+ // Maintain concatenatedChunks for accessing the complete `tool_use` content block.
732
+ concatenatedChunks = concatenatedChunks
733
+ ? concat(concatenatedChunks, chunk)
734
+ : chunk;
735
+ let toolUseContent;
736
+ const extractedContent = extractToolUseContent(chunk, concatenatedChunks);
737
+ if (extractedContent) {
738
+ toolUseContent = extractedContent.toolUseContent;
739
+ concatenatedChunks = extractedContent.concatenatedChunks;
610
740
  }
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
- };
741
+ // Filter partial `tool_use` content, and only add `tool_use` chunks if complete JSON available.
742
+ const chunkContent = Array.isArray(chunk.content)
743
+ ? chunk.content.filter((c) => c.type !== "tool_use")
744
+ : chunk.content;
745
+ if (Array.isArray(chunkContent) && toolUseContent) {
746
+ chunkContent.push(toolUseContent);
618
747
  }
748
+ // Extract the text content token for text field and runManager.
749
+ const token = extractToken(chunk);
619
750
  yield new ChatGenerationChunk({
620
751
  message: new AIMessageChunk({
621
- content: "",
622
- additional_kwargs: { usage: usageData },
623
- usage_metadata: usageMetadata,
752
+ content: chunkContent,
753
+ additional_kwargs: chunk.additional_kwargs,
754
+ tool_call_chunks: newToolCallChunk ? [newToolCallChunk] : undefined,
755
+ usage_metadata: chunk.usage_metadata,
756
+ response_metadata: chunk.response_metadata,
757
+ id: chunk.id,
624
758
  }),
625
- text: "",
759
+ text: token ?? "",
626
760
  });
761
+ if (token) {
762
+ await runManager?.handleLLMNewToken(token);
763
+ }
627
764
  }
765
+ let usageMetadata;
766
+ if (this.streamUsage || options.streamUsage) {
767
+ usageMetadata = {
768
+ input_tokens: usageData.input_tokens,
769
+ output_tokens: usageData.output_tokens,
770
+ total_tokens: usageData.input_tokens + usageData.output_tokens,
771
+ };
772
+ }
773
+ yield new ChatGenerationChunk({
774
+ message: new AIMessageChunk({
775
+ content: coerceContentToString ? "" : [],
776
+ additional_kwargs: { usage: usageData },
777
+ usage_metadata: usageMetadata,
778
+ }),
779
+ text: "",
780
+ });
628
781
  }
629
782
  /** @ignore */
630
783
  async _generateNonStreaming(messages, params, requestOptions) {
@@ -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/dist/utils.cjs ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleToolChoice = void 0;
4
+ function handleToolChoice(toolChoice) {
5
+ if (!toolChoice) {
6
+ return undefined;
7
+ }
8
+ else if (toolChoice === "any") {
9
+ return {
10
+ type: "any",
11
+ };
12
+ }
13
+ else if (toolChoice === "auto") {
14
+ return {
15
+ type: "auto",
16
+ };
17
+ }
18
+ else if (typeof toolChoice === "string") {
19
+ return {
20
+ type: "tool",
21
+ name: toolChoice,
22
+ };
23
+ }
24
+ else {
25
+ return toolChoice;
26
+ }
27
+ }
28
+ exports.handleToolChoice = handleToolChoice;
@@ -0,0 +1,10 @@
1
+ import type { MessageCreateParams, Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index.mjs";
2
+ import { ToolDefinition } from "@langchain/core/language_models/base";
3
+ import { RunnableToolLike } from "@langchain/core/runnables";
4
+ import { StructuredToolInterface } from "@langchain/core/tools";
5
+ export type AnthropicToolChoice = {
6
+ type: "tool";
7
+ name: string;
8
+ } | "any" | "auto" | "none" | string;
9
+ export type AnthropicToolTypes = StructuredToolInterface | AnthropicTool | Record<string, unknown> | ToolDefinition | RunnableToolLike;
10
+ export declare function handleToolChoice(toolChoice?: AnthropicToolChoice): MessageCreateParams.ToolChoiceAuto | MessageCreateParams.ToolChoiceAny | MessageCreateParams.ToolChoiceTool | undefined;
package/dist/utils.js ADDED
@@ -0,0 +1,24 @@
1
+ export function handleToolChoice(toolChoice) {
2
+ if (!toolChoice) {
3
+ return undefined;
4
+ }
5
+ else if (toolChoice === "any") {
6
+ return {
7
+ type: "any",
8
+ };
9
+ }
10
+ else if (toolChoice === "auto") {
11
+ return {
12
+ type: "auto",
13
+ };
14
+ }
15
+ else if (typeof toolChoice === "string") {
16
+ return {
17
+ type: "tool",
18
+ name: toolChoice,
19
+ };
20
+ }
21
+ else {
22
+ return toolChoice;
23
+ }
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/anthropic",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
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"