@langchain/anthropic 0.2.8 → 0.2.10

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,6 @@ 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");
14
13
  const output_parsers_js_1 = require("./output_parsers.cjs");
15
14
  const utils_js_1 = require("./utils.cjs");
16
15
  function _toolsInParams(params) {
@@ -186,6 +185,26 @@ function _makeMessageChunkFromAnthropicEvent(data, fields) {
186
185
  usageData: usageDataCopy,
187
186
  };
188
187
  }
188
+ else if (data.type === "content_block_start" &&
189
+ data.content_block.type === "text") {
190
+ const content = data.content_block?.text;
191
+ if (content !== undefined) {
192
+ return {
193
+ chunk: new messages_1.AIMessageChunk({
194
+ content: fields.coerceContentToString
195
+ ? content
196
+ : [
197
+ {
198
+ index: data.index,
199
+ ...data.content_block,
200
+ },
201
+ ],
202
+ additional_kwargs: {},
203
+ }),
204
+ usageData: usageDataCopy,
205
+ };
206
+ }
207
+ }
189
208
  return null;
190
209
  }
191
210
  function _mergeMessages(messages) {
@@ -248,6 +267,8 @@ function _convertLangChainToolCallToAnthropic(toolCall) {
248
267
  }
249
268
  exports._convertLangChainToolCallToAnthropic = _convertLangChainToolCallToAnthropic;
250
269
  function _formatContent(content) {
270
+ const toolTypes = ["tool_use", "tool_result", "input_json_delta"];
271
+ const textTypes = ["text", "text_delta"];
251
272
  if (typeof content === "string") {
252
273
  return content;
253
274
  }
@@ -266,18 +287,37 @@ function _formatContent(content) {
266
287
  source,
267
288
  };
268
289
  }
269
- else if (contentPart.type === "text") {
290
+ else if (textTypes.find((t) => t === contentPart.type) &&
291
+ "text" in contentPart) {
270
292
  // Assuming contentPart is of type MessageContentText here
271
293
  return {
272
294
  type: "text",
273
295
  text: contentPart.text,
274
296
  };
275
297
  }
276
- else if (contentPart.type === "tool_use" ||
277
- contentPart.type === "tool_result") {
298
+ else if (toolTypes.find((t) => t === contentPart.type)) {
299
+ const contentPartCopy = { ...contentPart };
300
+ if ("index" in contentPartCopy) {
301
+ // Anthropic does not support passing the index field here, so we remove it.
302
+ delete contentPartCopy.index;
303
+ }
304
+ if (contentPartCopy.type === "input_json_delta") {
305
+ // `input_json_delta` type only represents yielding partial tool inputs
306
+ // and is not a valid type for Anthropic messages.
307
+ contentPartCopy.type = "tool_use";
308
+ }
309
+ if ("input" in contentPartCopy) {
310
+ // Anthropic tool use inputs should be valid objects, when applicable.
311
+ try {
312
+ contentPartCopy.input = JSON.parse(contentPartCopy.input);
313
+ }
314
+ catch {
315
+ // no-op
316
+ }
317
+ }
278
318
  // TODO: Fix when SDK types are fixed
279
319
  return {
280
- ...contentPart,
320
+ ...contentPartCopy,
281
321
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
282
322
  };
283
323
  }
@@ -340,7 +380,9 @@ function _formatMessagesForAnthropic(messages) {
340
380
  }
341
381
  else {
342
382
  const { content } = message;
343
- const hasMismatchedToolCalls = !message.tool_calls.every((toolCall) => content.find((contentPart) => contentPart.type === "tool_use" && contentPart.id === toolCall.id));
383
+ const hasMismatchedToolCalls = !message.tool_calls.every((toolCall) => content.find((contentPart) => (contentPart.type === "tool_use" ||
384
+ contentPart.type === "input_json_delta") &&
385
+ contentPart.id === toolCall.id));
344
386
  if (hasMismatchedToolCalls) {
345
387
  console.warn(`The "tool_calls" field on a message is only respected if content is a string.`);
346
388
  }
@@ -390,6 +432,8 @@ function extractToolCallChunk(chunk) {
390
432
  "input" in inputJsonDeltaChunks) {
391
433
  if (typeof inputJsonDeltaChunks.input === "string") {
392
434
  newToolCallChunk = {
435
+ id: inputJsonDeltaChunks.id,
436
+ name: inputJsonDeltaChunks.name,
393
437
  args: inputJsonDeltaChunks.input,
394
438
  index: inputJsonDeltaChunks.index,
395
439
  type: "tool_call_chunk",
@@ -397,6 +441,8 @@ function extractToolCallChunk(chunk) {
397
441
  }
398
442
  else {
399
443
  newToolCallChunk = {
444
+ id: inputJsonDeltaChunks.id,
445
+ name: inputJsonDeltaChunks.name,
400
446
  args: JSON.stringify(inputJsonDeltaChunks.input, null, 2),
401
447
  index: inputJsonDeltaChunks.index,
402
448
  type: "tool_call_chunk",
@@ -416,43 +462,12 @@ function extractToken(chunk) {
416
462
  ? chunk.content[0].input
417
463
  : JSON.stringify(chunk.content[0].input);
418
464
  }
419
- return undefined;
420
- }
421
- function extractToolUseContent(chunk, concatenatedChunks) {
422
- let newConcatenatedChunks = concatenatedChunks;
423
- // Remove `tool_use` content types until the last chunk.
424
- let toolUseContent;
425
- if (!newConcatenatedChunks) {
426
- newConcatenatedChunks = chunk;
427
- }
428
- else {
429
- newConcatenatedChunks = (0, stream_1.concat)(newConcatenatedChunks, chunk);
430
- }
431
- if (Array.isArray(newConcatenatedChunks.content) &&
432
- newConcatenatedChunks.content.find((c) => c.type === "tool_use")) {
433
- try {
434
- const toolUseMsg = newConcatenatedChunks.content.find((c) => c.type === "tool_use");
435
- if (!toolUseMsg ||
436
- !("input" in toolUseMsg || "name" in toolUseMsg || "id" in toolUseMsg))
437
- return;
438
- const parsedArgs = JSON.parse(toolUseMsg.input);
439
- if (parsedArgs) {
440
- toolUseContent = {
441
- type: "tool_use",
442
- id: toolUseMsg.id,
443
- name: toolUseMsg.name,
444
- input: parsedArgs,
445
- };
446
- }
447
- }
448
- catch (_) {
449
- // no-op
450
- }
465
+ else if (Array.isArray(chunk.content) &&
466
+ chunk.content.length >= 1 &&
467
+ "text" in chunk.content[0]) {
468
+ return chunk.content[0].text;
451
469
  }
452
- return {
453
- toolUseContent,
454
- concatenatedChunks: newConcatenatedChunks,
455
- };
470
+ return undefined;
456
471
  }
457
472
  /**
458
473
  * Wrapper around Anthropic large language models.
@@ -605,6 +620,7 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
605
620
  if (!this.anthropicApiKey) {
606
621
  throw new Error("Anthropic API key not found");
607
622
  }
623
+ this.clientOptions = fields?.clientOptions ?? {};
608
624
  /** Keep anthropicApiKey for backwards compatibility */
609
625
  this.apiKey = this.anthropicApiKey;
610
626
  // Support overriding the default API URL (i.e., https://api.anthropic.com)
@@ -620,7 +636,6 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
620
636
  fields?.maxTokensToSample ?? fields?.maxTokens ?? this.maxTokens;
621
637
  this.stopSequences = fields?.stopSequences ?? this.stopSequences;
622
638
  this.streaming = fields?.streaming ?? false;
623
- this.clientOptions = fields?.clientOptions ?? {};
624
639
  this.streamUsage = fields?.streamUsage ?? this.streamUsage;
625
640
  }
626
641
  getLsParams(options) {
@@ -723,7 +738,6 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
723
738
  stream: true,
724
739
  });
725
740
  let usageData = { input_tokens: 0, output_tokens: 0 };
726
- let concatenatedChunks;
727
741
  for await (const data of stream) {
728
742
  if (options.signal?.aborted) {
729
743
  stream.controller.abort();
@@ -739,28 +753,12 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
739
753
  const { chunk, usageData: updatedUsageData } = result;
740
754
  usageData = updatedUsageData;
741
755
  const newToolCallChunk = extractToolCallChunk(chunk);
742
- // Maintain concatenatedChunks for accessing the complete `tool_use` content block.
743
- concatenatedChunks = concatenatedChunks
744
- ? (0, stream_1.concat)(concatenatedChunks, chunk)
745
- : chunk;
746
- let toolUseContent;
747
- const extractedContent = extractToolUseContent(chunk, concatenatedChunks);
748
- if (extractedContent) {
749
- toolUseContent = extractedContent.toolUseContent;
750
- concatenatedChunks = extractedContent.concatenatedChunks;
751
- }
752
- // Filter partial `tool_use` content, and only add `tool_use` chunks if complete JSON available.
753
- const chunkContent = Array.isArray(chunk.content)
754
- ? chunk.content.filter((c) => c.type !== "tool_use")
755
- : chunk.content;
756
- if (Array.isArray(chunkContent) && toolUseContent) {
757
- chunkContent.push(toolUseContent);
758
- }
759
756
  // Extract the text content token for text field and runManager.
760
757
  const token = extractToken(chunk);
761
758
  yield new outputs_1.ChatGenerationChunk({
762
759
  message: new messages_1.AIMessageChunk({
763
- content: chunkContent,
760
+ // Just yield chunk as it is and tool_use will be concat by BaseChatModel._generateUncached().
761
+ content: chunk.content,
764
762
  additional_kwargs: chunk.additional_kwargs,
765
763
  tool_call_chunks: newToolCallChunk ? [newToolCallChunk] : undefined,
766
764
  usage_metadata: chunk.usage_metadata,
@@ -850,6 +848,7 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
850
848
  /**
851
849
  * Creates a streaming request with retry.
852
850
  * @param request The parameters for creating a completion.
851
+ * @param options
853
852
  * @returns A streaming request.
854
853
  */
855
854
  async createStreamWithRetry(request, options) {
@@ -872,11 +871,11 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
872
871
  }
873
872
  /** @ignore */
874
873
  async completionWithRetry(request, options) {
875
- if (!this.apiKey) {
876
- throw new Error("Missing Anthropic API key.");
877
- }
878
874
  if (!this.batchClient) {
879
875
  const options = this.apiUrl ? { baseURL: this.apiUrl } : undefined;
876
+ if (!this.apiKey) {
877
+ throw new Error("Missing Anthropic API key.");
878
+ }
880
879
  this.batchClient = new sdk_1.Anthropic({
881
880
  ...this.clientOptions,
882
881
  ...options,
@@ -10,8 +10,8 @@ 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
- import { AnthropicToolResponse } from "./types.js";
14
13
  import { AnthropicToolChoice, AnthropicToolTypes } from "./utils.js";
14
+ import { AnthropicToolResponse } from "./types.js";
15
15
  type AnthropicMessageCreateParams = Anthropic.MessageCreateParamsNonStreaming;
16
16
  type AnthropicStreamingMessageCreateParams = Anthropic.MessageCreateParamsStreaming;
17
17
  type AnthropicMessageStreamEvent = Anthropic.MessageStreamEvent;
@@ -201,6 +201,7 @@ export declare class ChatAnthropicMessages<CallOptions extends ChatAnthropicCall
201
201
  /**
202
202
  * Creates a streaming request with retry.
203
203
  * @param request The parameters for creating a completion.
204
+ * @param options
204
205
  * @returns A streaming request.
205
206
  */
206
207
  protected createStreamWithRetry(request: AnthropicStreamingMessageCreateParams & Kwargs, options?: AnthropicRequestOptions): Promise<Stream<AnthropicMessageStreamEvent>>;
@@ -7,7 +7,6 @@ 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";
11
10
  import { AnthropicToolsOutputParser, extractToolCalls, } from "./output_parsers.js";
12
11
  import { handleToolChoice, } from "./utils.js";
13
12
  function _toolsInParams(params) {
@@ -183,6 +182,26 @@ function _makeMessageChunkFromAnthropicEvent(data, fields) {
183
182
  usageData: usageDataCopy,
184
183
  };
185
184
  }
185
+ else if (data.type === "content_block_start" &&
186
+ data.content_block.type === "text") {
187
+ const content = data.content_block?.text;
188
+ if (content !== undefined) {
189
+ return {
190
+ chunk: new AIMessageChunk({
191
+ content: fields.coerceContentToString
192
+ ? content
193
+ : [
194
+ {
195
+ index: data.index,
196
+ ...data.content_block,
197
+ },
198
+ ],
199
+ additional_kwargs: {},
200
+ }),
201
+ usageData: usageDataCopy,
202
+ };
203
+ }
204
+ }
186
205
  return null;
187
206
  }
188
207
  function _mergeMessages(messages) {
@@ -244,6 +263,8 @@ export function _convertLangChainToolCallToAnthropic(toolCall) {
244
263
  };
245
264
  }
246
265
  function _formatContent(content) {
266
+ const toolTypes = ["tool_use", "tool_result", "input_json_delta"];
267
+ const textTypes = ["text", "text_delta"];
247
268
  if (typeof content === "string") {
248
269
  return content;
249
270
  }
@@ -262,18 +283,37 @@ function _formatContent(content) {
262
283
  source,
263
284
  };
264
285
  }
265
- else if (contentPart.type === "text") {
286
+ else if (textTypes.find((t) => t === contentPart.type) &&
287
+ "text" in contentPart) {
266
288
  // Assuming contentPart is of type MessageContentText here
267
289
  return {
268
290
  type: "text",
269
291
  text: contentPart.text,
270
292
  };
271
293
  }
272
- else if (contentPart.type === "tool_use" ||
273
- contentPart.type === "tool_result") {
294
+ else if (toolTypes.find((t) => t === contentPart.type)) {
295
+ const contentPartCopy = { ...contentPart };
296
+ if ("index" in contentPartCopy) {
297
+ // Anthropic does not support passing the index field here, so we remove it.
298
+ delete contentPartCopy.index;
299
+ }
300
+ if (contentPartCopy.type === "input_json_delta") {
301
+ // `input_json_delta` type only represents yielding partial tool inputs
302
+ // and is not a valid type for Anthropic messages.
303
+ contentPartCopy.type = "tool_use";
304
+ }
305
+ if ("input" in contentPartCopy) {
306
+ // Anthropic tool use inputs should be valid objects, when applicable.
307
+ try {
308
+ contentPartCopy.input = JSON.parse(contentPartCopy.input);
309
+ }
310
+ catch {
311
+ // no-op
312
+ }
313
+ }
274
314
  // TODO: Fix when SDK types are fixed
275
315
  return {
276
- ...contentPart,
316
+ ...contentPartCopy,
277
317
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
278
318
  };
279
319
  }
@@ -336,7 +376,9 @@ function _formatMessagesForAnthropic(messages) {
336
376
  }
337
377
  else {
338
378
  const { content } = message;
339
- const hasMismatchedToolCalls = !message.tool_calls.every((toolCall) => content.find((contentPart) => contentPart.type === "tool_use" && contentPart.id === toolCall.id));
379
+ const hasMismatchedToolCalls = !message.tool_calls.every((toolCall) => content.find((contentPart) => (contentPart.type === "tool_use" ||
380
+ contentPart.type === "input_json_delta") &&
381
+ contentPart.id === toolCall.id));
340
382
  if (hasMismatchedToolCalls) {
341
383
  console.warn(`The "tool_calls" field on a message is only respected if content is a string.`);
342
384
  }
@@ -386,6 +428,8 @@ function extractToolCallChunk(chunk) {
386
428
  "input" in inputJsonDeltaChunks) {
387
429
  if (typeof inputJsonDeltaChunks.input === "string") {
388
430
  newToolCallChunk = {
431
+ id: inputJsonDeltaChunks.id,
432
+ name: inputJsonDeltaChunks.name,
389
433
  args: inputJsonDeltaChunks.input,
390
434
  index: inputJsonDeltaChunks.index,
391
435
  type: "tool_call_chunk",
@@ -393,6 +437,8 @@ function extractToolCallChunk(chunk) {
393
437
  }
394
438
  else {
395
439
  newToolCallChunk = {
440
+ id: inputJsonDeltaChunks.id,
441
+ name: inputJsonDeltaChunks.name,
396
442
  args: JSON.stringify(inputJsonDeltaChunks.input, null, 2),
397
443
  index: inputJsonDeltaChunks.index,
398
444
  type: "tool_call_chunk",
@@ -412,43 +458,12 @@ function extractToken(chunk) {
412
458
  ? chunk.content[0].input
413
459
  : JSON.stringify(chunk.content[0].input);
414
460
  }
415
- return undefined;
416
- }
417
- function extractToolUseContent(chunk, concatenatedChunks) {
418
- let newConcatenatedChunks = concatenatedChunks;
419
- // Remove `tool_use` content types until the last chunk.
420
- let toolUseContent;
421
- if (!newConcatenatedChunks) {
422
- newConcatenatedChunks = chunk;
423
- }
424
- else {
425
- newConcatenatedChunks = concat(newConcatenatedChunks, chunk);
426
- }
427
- if (Array.isArray(newConcatenatedChunks.content) &&
428
- newConcatenatedChunks.content.find((c) => c.type === "tool_use")) {
429
- try {
430
- const toolUseMsg = newConcatenatedChunks.content.find((c) => c.type === "tool_use");
431
- if (!toolUseMsg ||
432
- !("input" in toolUseMsg || "name" in toolUseMsg || "id" in toolUseMsg))
433
- return;
434
- const parsedArgs = JSON.parse(toolUseMsg.input);
435
- if (parsedArgs) {
436
- toolUseContent = {
437
- type: "tool_use",
438
- id: toolUseMsg.id,
439
- name: toolUseMsg.name,
440
- input: parsedArgs,
441
- };
442
- }
443
- }
444
- catch (_) {
445
- // no-op
446
- }
461
+ else if (Array.isArray(chunk.content) &&
462
+ chunk.content.length >= 1 &&
463
+ "text" in chunk.content[0]) {
464
+ return chunk.content[0].text;
447
465
  }
448
- return {
449
- toolUseContent,
450
- concatenatedChunks: newConcatenatedChunks,
451
- };
466
+ return undefined;
452
467
  }
453
468
  /**
454
469
  * Wrapper around Anthropic large language models.
@@ -601,6 +616,7 @@ export class ChatAnthropicMessages extends BaseChatModel {
601
616
  if (!this.anthropicApiKey) {
602
617
  throw new Error("Anthropic API key not found");
603
618
  }
619
+ this.clientOptions = fields?.clientOptions ?? {};
604
620
  /** Keep anthropicApiKey for backwards compatibility */
605
621
  this.apiKey = this.anthropicApiKey;
606
622
  // Support overriding the default API URL (i.e., https://api.anthropic.com)
@@ -616,7 +632,6 @@ export class ChatAnthropicMessages extends BaseChatModel {
616
632
  fields?.maxTokensToSample ?? fields?.maxTokens ?? this.maxTokens;
617
633
  this.stopSequences = fields?.stopSequences ?? this.stopSequences;
618
634
  this.streaming = fields?.streaming ?? false;
619
- this.clientOptions = fields?.clientOptions ?? {};
620
635
  this.streamUsage = fields?.streamUsage ?? this.streamUsage;
621
636
  }
622
637
  getLsParams(options) {
@@ -719,7 +734,6 @@ export class ChatAnthropicMessages extends BaseChatModel {
719
734
  stream: true,
720
735
  });
721
736
  let usageData = { input_tokens: 0, output_tokens: 0 };
722
- let concatenatedChunks;
723
737
  for await (const data of stream) {
724
738
  if (options.signal?.aborted) {
725
739
  stream.controller.abort();
@@ -735,28 +749,12 @@ export class ChatAnthropicMessages extends BaseChatModel {
735
749
  const { chunk, usageData: updatedUsageData } = result;
736
750
  usageData = updatedUsageData;
737
751
  const newToolCallChunk = extractToolCallChunk(chunk);
738
- // Maintain concatenatedChunks for accessing the complete `tool_use` content block.
739
- concatenatedChunks = concatenatedChunks
740
- ? concat(concatenatedChunks, chunk)
741
- : chunk;
742
- let toolUseContent;
743
- const extractedContent = extractToolUseContent(chunk, concatenatedChunks);
744
- if (extractedContent) {
745
- toolUseContent = extractedContent.toolUseContent;
746
- concatenatedChunks = extractedContent.concatenatedChunks;
747
- }
748
- // Filter partial `tool_use` content, and only add `tool_use` chunks if complete JSON available.
749
- const chunkContent = Array.isArray(chunk.content)
750
- ? chunk.content.filter((c) => c.type !== "tool_use")
751
- : chunk.content;
752
- if (Array.isArray(chunkContent) && toolUseContent) {
753
- chunkContent.push(toolUseContent);
754
- }
755
752
  // Extract the text content token for text field and runManager.
756
753
  const token = extractToken(chunk);
757
754
  yield new ChatGenerationChunk({
758
755
  message: new AIMessageChunk({
759
- content: chunkContent,
756
+ // Just yield chunk as it is and tool_use will be concat by BaseChatModel._generateUncached().
757
+ content: chunk.content,
760
758
  additional_kwargs: chunk.additional_kwargs,
761
759
  tool_call_chunks: newToolCallChunk ? [newToolCallChunk] : undefined,
762
760
  usage_metadata: chunk.usage_metadata,
@@ -846,6 +844,7 @@ export class ChatAnthropicMessages extends BaseChatModel {
846
844
  /**
847
845
  * Creates a streaming request with retry.
848
846
  * @param request The parameters for creating a completion.
847
+ * @param options
849
848
  * @returns A streaming request.
850
849
  */
851
850
  async createStreamWithRetry(request, options) {
@@ -868,11 +867,11 @@ export class ChatAnthropicMessages extends BaseChatModel {
868
867
  }
869
868
  /** @ignore */
870
869
  async completionWithRetry(request, options) {
871
- if (!this.apiKey) {
872
- throw new Error("Missing Anthropic API key.");
873
- }
874
870
  if (!this.batchClient) {
875
871
  const options = this.apiUrl ? { baseURL: this.apiUrl } : undefined;
872
+ if (!this.apiKey) {
873
+ throw new Error("Missing Anthropic API key.");
874
+ }
876
875
  this.batchClient = new Anthropic({
877
876
  ...this.clientOptions,
878
877
  ...options,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/anthropic",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Anthropic integrations for LangChain.js",
5
5
  "type": "module",
6
6
  "engines": {
@@ -15,7 +15,7 @@
15
15
  "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-anthropic/",
16
16
  "scripts": {
17
17
  "build": "yarn turbo:command build:internal --filter=@langchain/anthropic",
18
- "build:internal": "yarn lc-build:v2 --create-entrypoints --pre --tree-shaking --gen-maps",
18
+ "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking --gen-maps",
19
19
  "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/",
20
20
  "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts",
21
21
  "lint": "yarn lint:eslint && yarn lint:dpdm",
@@ -43,8 +43,7 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "@jest/globals": "^29.5.0",
46
- "@langchain/community": "workspace:*",
47
- "@langchain/scripts": "~0.0.14",
46
+ "@langchain/scripts": "~0.0.20",
48
47
  "@langchain/standard-tests": "0.0.0",
49
48
  "@swc/core": "^1.3.90",
50
49
  "@swc/jest": "^0.2.29",
@@ -1 +0,0 @@
1
- export {};
@@ -1,278 +0,0 @@
1
- /* eslint-disable no-process-env */
2
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
3
- import { test } from "@jest/globals";
4
- import { z } from "zod";
5
- import { zodToJsonSchema } from "zod-to-json-schema";
6
- import { HumanMessage } from "@langchain/core/messages";
7
- import { ChatPromptTemplate } from "@langchain/core/prompts";
8
- import { ChatAnthropicTools } from "../tool_calling.js";
9
- test.skip("Test ChatAnthropicTools", async () => {
10
- const chat = new ChatAnthropicTools({
11
- modelName: "claude-3-sonnet-20240229",
12
- maxRetries: 0,
13
- });
14
- const message = new HumanMessage("Hello!");
15
- const res = await chat.invoke([message]);
16
- console.log(JSON.stringify(res));
17
- });
18
- test.skip("Test ChatAnthropicTools streaming", async () => {
19
- const chat = new ChatAnthropicTools({
20
- modelName: "claude-3-sonnet-20240229",
21
- maxRetries: 0,
22
- });
23
- const message = new HumanMessage("Hello!");
24
- const stream = await chat.stream([message]);
25
- const chunks = [];
26
- for await (const chunk of stream) {
27
- console.log(chunk);
28
- chunks.push(chunk);
29
- }
30
- expect(chunks.length).toBeGreaterThan(1);
31
- });
32
- test.skip("Test ChatAnthropicTools with tools", async () => {
33
- const chat = new ChatAnthropicTools({
34
- modelName: "claude-3-sonnet-20240229",
35
- temperature: 0.1,
36
- maxRetries: 0,
37
- }).bind({
38
- tools: [
39
- {
40
- type: "function",
41
- function: {
42
- name: "get_current_weather",
43
- description: "Get the current weather in a given location",
44
- parameters: {
45
- type: "object",
46
- properties: {
47
- location: {
48
- type: "string",
49
- description: "The city and state, e.g. San Francisco, CA",
50
- },
51
- unit: {
52
- type: "string",
53
- enum: ["celsius", "fahrenheit"],
54
- },
55
- },
56
- required: ["location"],
57
- },
58
- },
59
- },
60
- ],
61
- });
62
- const message = new HumanMessage("What is the weather in San Francisco?");
63
- const res = await chat.invoke([message]);
64
- console.log(JSON.stringify(res));
65
- expect(res.additional_kwargs.tool_calls).toBeDefined();
66
- expect(res.additional_kwargs.tool_calls?.[0].function.name).toEqual("get_current_weather");
67
- });
68
- test.skip("Test ChatAnthropicTools with a forced function call", async () => {
69
- const chat = new ChatAnthropicTools({
70
- modelName: "claude-3-sonnet-20240229",
71
- temperature: 0.1,
72
- maxRetries: 0,
73
- }).bind({
74
- tools: [
75
- {
76
- type: "function",
77
- function: {
78
- name: "extract_data",
79
- description: "Return information about the input",
80
- parameters: {
81
- type: "object",
82
- properties: {
83
- sentiment: {
84
- type: "string",
85
- description: "The city and state, e.g. San Francisco, CA",
86
- },
87
- aggressiveness: {
88
- type: "integer",
89
- description: "How aggressive the input is from 1 to 10",
90
- },
91
- language: {
92
- type: "string",
93
- description: "The language the input is in",
94
- },
95
- },
96
- required: ["sentiment", "aggressiveness"],
97
- },
98
- },
99
- },
100
- ],
101
- tool_choice: { type: "function", function: { name: "extract_data" } },
102
- });
103
- const message = new HumanMessage("Extract the desired information from the following passage:\n\nthis is really cool");
104
- const res = await chat.invoke([message]);
105
- console.log(JSON.stringify(res));
106
- expect(res.additional_kwargs.tool_calls).toBeDefined();
107
- expect(res.additional_kwargs.tool_calls?.[0]?.function.name).toEqual("extract_data");
108
- });
109
- test.skip("ChatAnthropicTools with Zod schema", async () => {
110
- const schema = z.object({
111
- people: z.array(z.object({
112
- name: z.string().describe("The name of a person"),
113
- height: z.number().describe("The person's height"),
114
- hairColor: z.optional(z.string()).describe("The person's hair color"),
115
- })),
116
- });
117
- const chat = new ChatAnthropicTools({
118
- modelName: "claude-3-sonnet-20240229",
119
- temperature: 0.1,
120
- maxRetries: 0,
121
- }).bind({
122
- tools: [
123
- {
124
- type: "function",
125
- function: {
126
- name: "information_extraction",
127
- description: "Extracts the relevant information from the passage.",
128
- parameters: zodToJsonSchema(schema),
129
- },
130
- },
131
- ],
132
- tool_choice: {
133
- type: "function",
134
- function: {
135
- name: "information_extraction",
136
- },
137
- },
138
- });
139
- const message = new HumanMessage("Alex is 5 feet tall. Claudia is 1 foot taller than Alex and jumps higher than him. Claudia is a brunette and Alex is blonde.");
140
- const res = await chat.invoke([message]);
141
- console.log(JSON.stringify(res));
142
- expect(res.additional_kwargs.tool_calls).toBeDefined();
143
- expect(res.additional_kwargs.tool_calls?.[0]?.function.name).toEqual("information_extraction");
144
- expect(JSON.parse(res.additional_kwargs.tool_calls?.[0]?.function.arguments ?? "")).toEqual({
145
- people: expect.arrayContaining([
146
- { name: "Alex", height: 5, hairColor: "blonde" },
147
- { name: "Claudia", height: 6, hairColor: "brunette" },
148
- ]),
149
- });
150
- });
151
- test.skip("ChatAnthropicTools with parallel tool calling", async () => {
152
- const schema = z.object({
153
- name: z.string().describe("The name of a person"),
154
- height: z.number().describe("The person's height"),
155
- hairColor: z.optional(z.string()).describe("The person's hair color"),
156
- });
157
- const chat = new ChatAnthropicTools({
158
- modelName: "claude-3-sonnet-20240229",
159
- temperature: 0.1,
160
- maxRetries: 0,
161
- }).bind({
162
- tools: [
163
- {
164
- type: "function",
165
- function: {
166
- name: "person",
167
- description: "A person mentioned in the passage.",
168
- parameters: zodToJsonSchema(schema),
169
- },
170
- },
171
- ],
172
- tool_choice: {
173
- type: "function",
174
- function: {
175
- name: "person",
176
- },
177
- },
178
- });
179
- console.log(zodToJsonSchema(schema));
180
- const message = new HumanMessage("Alex is 5 feet tall. Claudia is 1 foot taller than Alex and jumps higher than him. Claudia is a brunette and Alex is blonde.");
181
- const res = await chat.invoke([message]);
182
- console.log(JSON.stringify(res));
183
- expect(res.additional_kwargs.tool_calls).toBeDefined();
184
- expect(res.additional_kwargs.tool_calls?.map((toolCall) => JSON.parse(toolCall.function.arguments ?? ""))).toEqual(expect.arrayContaining([
185
- { name: "Alex", height: 5, hairColor: "blonde" },
186
- { name: "Claudia", height: 6, hairColor: "brunette" },
187
- ]));
188
- });
189
- test.skip("Test ChatAnthropic withStructuredOutput", async () => {
190
- const runnable = new ChatAnthropicTools({
191
- modelName: "claude-3-sonnet-20240229",
192
- maxRetries: 0,
193
- }).withStructuredOutput(z.object({
194
- name: z.string().describe("The name of a person"),
195
- height: z.number().describe("The person's height"),
196
- hairColor: z.optional(z.string()).describe("The person's hair color"),
197
- }), {
198
- name: "person",
199
- });
200
- const message = new HumanMessage("Alex is 5 feet tall. Alex is blonde.");
201
- const res = await runnable.invoke([message]);
202
- console.log(JSON.stringify(res, null, 2));
203
- expect(res).toEqual({ name: "Alex", height: 5, hairColor: "blonde" });
204
- });
205
- test.skip("Test ChatAnthropic withStructuredOutput on a single array item", async () => {
206
- const runnable = new ChatAnthropicTools({
207
- modelName: "claude-3-sonnet-20240229",
208
- maxRetries: 0,
209
- }).withStructuredOutput(z.object({
210
- people: z.array(z.object({
211
- name: z.string().describe("The name of a person"),
212
- height: z.number().describe("The person's height"),
213
- hairColor: z.optional(z.string()).describe("The person's hair color"),
214
- })),
215
- }));
216
- const message = new HumanMessage("Alex is 5 feet tall. Alex is blonde.");
217
- const res = await runnable.invoke([message]);
218
- console.log(JSON.stringify(res, null, 2));
219
- expect(res).toEqual({
220
- people: [{ hairColor: "blonde", height: 5, name: "Alex" }],
221
- });
222
- });
223
- test.skip("Test ChatAnthropic withStructuredOutput on a single array item", async () => {
224
- const runnable = new ChatAnthropicTools({
225
- modelName: "claude-3-sonnet-20240229",
226
- maxRetries: 0,
227
- }).withStructuredOutput(z.object({
228
- sender: z
229
- .optional(z.string())
230
- .describe("The sender's name, if available"),
231
- sender_phone_number: z
232
- .optional(z.string())
233
- .describe("The sender's phone number, if available"),
234
- sender_address: z
235
- .optional(z.string())
236
- .describe("The sender's address, if available"),
237
- action_items: z
238
- .array(z.string())
239
- .describe("A list of action items requested by the email"),
240
- topic: z
241
- .string()
242
- .describe("High level description of what the email is about"),
243
- tone: z.enum(["positive", "negative"]).describe("The tone of the email."),
244
- }), {
245
- name: "Email",
246
- });
247
- const prompt = ChatPromptTemplate.fromMessages([
248
- [
249
- "human",
250
- "What can you tell me about the following email? Make sure to answer in the correct format: {email}",
251
- ],
252
- ]);
253
- const extractionChain = prompt.pipe(runnable);
254
- const response = await extractionChain.invoke({
255
- email: "From: Erick. The email is about the new project. The tone is positive. The action items are to send the report and to schedule a meeting.",
256
- });
257
- console.log(JSON.stringify(response, null, 2));
258
- expect(response).toEqual({
259
- sender: "Erick",
260
- action_items: [expect.any(String), expect.any(String)],
261
- topic: expect.any(String),
262
- tone: "positive",
263
- });
264
- });
265
- test.skip("Test ChatAnthropicTools", async () => {
266
- const chat = new ChatAnthropicTools({
267
- modelName: "claude-3-sonnet-20240229",
268
- maxRetries: 0,
269
- });
270
- const structured = chat.withStructuredOutput(z.object({
271
- nested: z.array(z.number()),
272
- }), { force: false });
273
- const res = await structured.invoke("What are the first five natural numbers?");
274
- console.log(res);
275
- expect(res).toEqual({
276
- nested: [1, 2, 3, 4, 5],
277
- });
278
- });