@langchain/anthropic 0.2.8 → 0.2.9

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.
@@ -146,6 +146,10 @@ function _makeMessageChunkFromAnthropicEvent(data, fields) {
146
146
  additional_kwargs: {},
147
147
  }),
148
148
  usageData: usageDataCopy,
149
+ toolUse: {
150
+ id: data.content_block.id,
151
+ name: data.content_block.name,
152
+ },
149
153
  };
150
154
  }
151
155
  else if (data.type === "content_block_delta" &&
@@ -186,6 +190,26 @@ function _makeMessageChunkFromAnthropicEvent(data, fields) {
186
190
  usageData: usageDataCopy,
187
191
  };
188
192
  }
193
+ else if (data.type === "content_block_stop" && fields.toolUse) {
194
+ // Only yield the ID & name when the tool_use block is complete.
195
+ // This is so the names & IDs do not get concatenated.
196
+ return {
197
+ chunk: new messages_1.AIMessageChunk({
198
+ content: fields.coerceContentToString
199
+ ? ""
200
+ : [
201
+ {
202
+ id: fields.toolUse.id,
203
+ name: fields.toolUse.name,
204
+ index: data.index,
205
+ type: "input_json_delta",
206
+ },
207
+ ],
208
+ additional_kwargs: {},
209
+ }),
210
+ usageData: usageDataCopy,
211
+ };
212
+ }
189
213
  return null;
190
214
  }
191
215
  function _mergeMessages(messages) {
@@ -248,6 +272,8 @@ function _convertLangChainToolCallToAnthropic(toolCall) {
248
272
  }
249
273
  exports._convertLangChainToolCallToAnthropic = _convertLangChainToolCallToAnthropic;
250
274
  function _formatContent(content) {
275
+ const toolTypes = ["tool_use", "tool_result", "input_json_delta"];
276
+ const textTypes = ["text", "text_delta"];
251
277
  if (typeof content === "string") {
252
278
  return content;
253
279
  }
@@ -266,18 +292,37 @@ function _formatContent(content) {
266
292
  source,
267
293
  };
268
294
  }
269
- else if (contentPart.type === "text") {
295
+ else if (textTypes.find((t) => t === contentPart.type) &&
296
+ "text" in contentPart) {
270
297
  // Assuming contentPart is of type MessageContentText here
271
298
  return {
272
299
  type: "text",
273
300
  text: contentPart.text,
274
301
  };
275
302
  }
276
- else if (contentPart.type === "tool_use" ||
277
- contentPart.type === "tool_result") {
303
+ else if (toolTypes.find((t) => t === contentPart.type)) {
304
+ const contentPartCopy = { ...contentPart };
305
+ if ("index" in contentPartCopy) {
306
+ // Anthropic does not support passing the index field here, so we remove it.
307
+ delete contentPartCopy.index;
308
+ }
309
+ if (contentPartCopy.type === "input_json_delta") {
310
+ // `input_json_delta` type only represents yielding partial tool inputs
311
+ // and is not a valid type for Anthropic messages.
312
+ contentPartCopy.type = "tool_use";
313
+ }
314
+ if ("input" in contentPartCopy) {
315
+ // Anthropic tool use inputs should be valid objects, when applicable.
316
+ try {
317
+ contentPartCopy.input = JSON.parse(contentPartCopy.input);
318
+ }
319
+ catch {
320
+ // no-op
321
+ }
322
+ }
278
323
  // TODO: Fix when SDK types are fixed
279
324
  return {
280
- ...contentPart,
325
+ ...contentPartCopy,
281
326
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
282
327
  };
283
328
  }
@@ -340,7 +385,9 @@ function _formatMessagesForAnthropic(messages) {
340
385
  }
341
386
  else {
342
387
  const { content } = message;
343
- const hasMismatchedToolCalls = !message.tool_calls.every((toolCall) => content.find((contentPart) => contentPart.type === "tool_use" && contentPart.id === toolCall.id));
388
+ const hasMismatchedToolCalls = !message.tool_calls.every((toolCall) => content.find((contentPart) => (contentPart.type === "tool_use" ||
389
+ contentPart.type === "input_json_delta") &&
390
+ contentPart.id === toolCall.id));
344
391
  if (hasMismatchedToolCalls) {
345
392
  console.warn(`The "tool_calls" field on a message is only respected if content is a string.`);
346
393
  }
@@ -390,6 +437,8 @@ function extractToolCallChunk(chunk) {
390
437
  "input" in inputJsonDeltaChunks) {
391
438
  if (typeof inputJsonDeltaChunks.input === "string") {
392
439
  newToolCallChunk = {
440
+ id: inputJsonDeltaChunks.id,
441
+ name: inputJsonDeltaChunks.name,
393
442
  args: inputJsonDeltaChunks.input,
394
443
  index: inputJsonDeltaChunks.index,
395
444
  type: "tool_call_chunk",
@@ -397,6 +446,8 @@ function extractToolCallChunk(chunk) {
397
446
  }
398
447
  else {
399
448
  newToolCallChunk = {
449
+ id: inputJsonDeltaChunks.id,
450
+ name: inputJsonDeltaChunks.name,
400
451
  args: JSON.stringify(inputJsonDeltaChunks.input, null, 2),
401
452
  index: inputJsonDeltaChunks.index,
402
453
  type: "tool_call_chunk",
@@ -724,6 +775,9 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
724
775
  });
725
776
  let usageData = { input_tokens: 0, output_tokens: 0 };
726
777
  let concatenatedChunks;
778
+ // Anthropic only yields the tool name and id once, so we need to save those
779
+ // so we can yield them with the rest of the tool_use content.
780
+ let toolUse;
727
781
  for await (const data of stream) {
728
782
  if (options.signal?.aborted) {
729
783
  stream.controller.abort();
@@ -733,11 +787,20 @@ class ChatAnthropicMessages extends chat_models_1.BaseChatModel {
733
787
  streamUsage: !!(this.streamUsage || options.streamUsage),
734
788
  coerceContentToString,
735
789
  usageData,
790
+ toolUse: toolUse
791
+ ? {
792
+ id: toolUse.id,
793
+ name: toolUse.name,
794
+ }
795
+ : undefined,
736
796
  });
737
797
  if (!result)
738
798
  continue;
739
- const { chunk, usageData: updatedUsageData } = result;
799
+ const { chunk, usageData: updatedUsageData, toolUse: updatedToolUse, } = result;
740
800
  usageData = updatedUsageData;
801
+ if (updatedToolUse) {
802
+ toolUse = updatedToolUse;
803
+ }
741
804
  const newToolCallChunk = extractToolCallChunk(chunk);
742
805
  // Maintain concatenatedChunks for accessing the complete `tool_use` content block.
743
806
  concatenatedChunks = concatenatedChunks
@@ -143,6 +143,10 @@ function _makeMessageChunkFromAnthropicEvent(data, fields) {
143
143
  additional_kwargs: {},
144
144
  }),
145
145
  usageData: usageDataCopy,
146
+ toolUse: {
147
+ id: data.content_block.id,
148
+ name: data.content_block.name,
149
+ },
146
150
  };
147
151
  }
148
152
  else if (data.type === "content_block_delta" &&
@@ -183,6 +187,26 @@ function _makeMessageChunkFromAnthropicEvent(data, fields) {
183
187
  usageData: usageDataCopy,
184
188
  };
185
189
  }
190
+ else if (data.type === "content_block_stop" && fields.toolUse) {
191
+ // Only yield the ID & name when the tool_use block is complete.
192
+ // This is so the names & IDs do not get concatenated.
193
+ return {
194
+ chunk: new AIMessageChunk({
195
+ content: fields.coerceContentToString
196
+ ? ""
197
+ : [
198
+ {
199
+ id: fields.toolUse.id,
200
+ name: fields.toolUse.name,
201
+ index: data.index,
202
+ type: "input_json_delta",
203
+ },
204
+ ],
205
+ additional_kwargs: {},
206
+ }),
207
+ usageData: usageDataCopy,
208
+ };
209
+ }
186
210
  return null;
187
211
  }
188
212
  function _mergeMessages(messages) {
@@ -244,6 +268,8 @@ export function _convertLangChainToolCallToAnthropic(toolCall) {
244
268
  };
245
269
  }
246
270
  function _formatContent(content) {
271
+ const toolTypes = ["tool_use", "tool_result", "input_json_delta"];
272
+ const textTypes = ["text", "text_delta"];
247
273
  if (typeof content === "string") {
248
274
  return content;
249
275
  }
@@ -262,18 +288,37 @@ function _formatContent(content) {
262
288
  source,
263
289
  };
264
290
  }
265
- else if (contentPart.type === "text") {
291
+ else if (textTypes.find((t) => t === contentPart.type) &&
292
+ "text" in contentPart) {
266
293
  // Assuming contentPart is of type MessageContentText here
267
294
  return {
268
295
  type: "text",
269
296
  text: contentPart.text,
270
297
  };
271
298
  }
272
- else if (contentPart.type === "tool_use" ||
273
- contentPart.type === "tool_result") {
299
+ else if (toolTypes.find((t) => t === contentPart.type)) {
300
+ const contentPartCopy = { ...contentPart };
301
+ if ("index" in contentPartCopy) {
302
+ // Anthropic does not support passing the index field here, so we remove it.
303
+ delete contentPartCopy.index;
304
+ }
305
+ if (contentPartCopy.type === "input_json_delta") {
306
+ // `input_json_delta` type only represents yielding partial tool inputs
307
+ // and is not a valid type for Anthropic messages.
308
+ contentPartCopy.type = "tool_use";
309
+ }
310
+ if ("input" in contentPartCopy) {
311
+ // Anthropic tool use inputs should be valid objects, when applicable.
312
+ try {
313
+ contentPartCopy.input = JSON.parse(contentPartCopy.input);
314
+ }
315
+ catch {
316
+ // no-op
317
+ }
318
+ }
274
319
  // TODO: Fix when SDK types are fixed
275
320
  return {
276
- ...contentPart,
321
+ ...contentPartCopy,
277
322
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
278
323
  };
279
324
  }
@@ -336,7 +381,9 @@ function _formatMessagesForAnthropic(messages) {
336
381
  }
337
382
  else {
338
383
  const { content } = message;
339
- const hasMismatchedToolCalls = !message.tool_calls.every((toolCall) => content.find((contentPart) => contentPart.type === "tool_use" && contentPart.id === toolCall.id));
384
+ const hasMismatchedToolCalls = !message.tool_calls.every((toolCall) => content.find((contentPart) => (contentPart.type === "tool_use" ||
385
+ contentPart.type === "input_json_delta") &&
386
+ contentPart.id === toolCall.id));
340
387
  if (hasMismatchedToolCalls) {
341
388
  console.warn(`The "tool_calls" field on a message is only respected if content is a string.`);
342
389
  }
@@ -386,6 +433,8 @@ function extractToolCallChunk(chunk) {
386
433
  "input" in inputJsonDeltaChunks) {
387
434
  if (typeof inputJsonDeltaChunks.input === "string") {
388
435
  newToolCallChunk = {
436
+ id: inputJsonDeltaChunks.id,
437
+ name: inputJsonDeltaChunks.name,
389
438
  args: inputJsonDeltaChunks.input,
390
439
  index: inputJsonDeltaChunks.index,
391
440
  type: "tool_call_chunk",
@@ -393,6 +442,8 @@ function extractToolCallChunk(chunk) {
393
442
  }
394
443
  else {
395
444
  newToolCallChunk = {
445
+ id: inputJsonDeltaChunks.id,
446
+ name: inputJsonDeltaChunks.name,
396
447
  args: JSON.stringify(inputJsonDeltaChunks.input, null, 2),
397
448
  index: inputJsonDeltaChunks.index,
398
449
  type: "tool_call_chunk",
@@ -720,6 +771,9 @@ export class ChatAnthropicMessages extends BaseChatModel {
720
771
  });
721
772
  let usageData = { input_tokens: 0, output_tokens: 0 };
722
773
  let concatenatedChunks;
774
+ // Anthropic only yields the tool name and id once, so we need to save those
775
+ // so we can yield them with the rest of the tool_use content.
776
+ let toolUse;
723
777
  for await (const data of stream) {
724
778
  if (options.signal?.aborted) {
725
779
  stream.controller.abort();
@@ -729,11 +783,20 @@ export class ChatAnthropicMessages extends BaseChatModel {
729
783
  streamUsage: !!(this.streamUsage || options.streamUsage),
730
784
  coerceContentToString,
731
785
  usageData,
786
+ toolUse: toolUse
787
+ ? {
788
+ id: toolUse.id,
789
+ name: toolUse.name,
790
+ }
791
+ : undefined,
732
792
  });
733
793
  if (!result)
734
794
  continue;
735
- const { chunk, usageData: updatedUsageData } = result;
795
+ const { chunk, usageData: updatedUsageData, toolUse: updatedToolUse, } = result;
736
796
  usageData = updatedUsageData;
797
+ if (updatedToolUse) {
798
+ toolUse = updatedToolUse;
799
+ }
737
800
  const newToolCallChunk = extractToolCallChunk(chunk);
738
801
  // Maintain concatenatedChunks for accessing the complete `tool_use` content block.
739
802
  concatenatedChunks = concatenatedChunks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/anthropic",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
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
- });