@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.
package/dist/chat_models.cjs
CHANGED
|
@@ -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 (
|
|
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 (
|
|
277
|
-
|
|
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
|
-
...
|
|
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"
|
|
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
|
package/dist/chat_models.js
CHANGED
|
@@ -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 (
|
|
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 (
|
|
273
|
-
|
|
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
|
-
...
|
|
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"
|
|
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.
|
|
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
|
|
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/
|
|
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
|
-
});
|