@langchain/anthropic 0.1.12 → 0.1.14
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/README.md +3 -3
- package/dist/chat_models.cjs +240 -139
- package/dist/chat_models.d.ts +15 -16
- package/dist/chat_models.js +240 -140
- package/dist/output_parsers.cjs +14 -7
- package/dist/output_parsers.d.ts +2 -0
- package/dist/output_parsers.js +12 -6
- package/dist/tests/agent.int.test.d.ts +1 -0
- package/dist/tests/agent.int.test.js +39 -0
- package/dist/tests/chat_models-tools.int.test.d.ts +1 -0
- package/dist/tests/chat_models-tools.int.test.js +218 -0
- package/dist/tests/chat_models.int.test.js +1 -179
- package/package.json +2 -2
package/dist/chat_models.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Anthropic } from "@anthropic-ai/sdk";
|
|
2
|
-
import { AIMessage, AIMessageChunk, } from "@langchain/core/messages";
|
|
2
|
+
import { AIMessage, AIMessageChunk, HumanMessage, isAIMessage, } from "@langchain/core/messages";
|
|
3
3
|
import { ChatGenerationChunk, } from "@langchain/core/outputs";
|
|
4
4
|
import { getEnvironmentVariable } from "@langchain/core/utils/env";
|
|
5
5
|
import { BaseChatModel, } from "@langchain/core/language_models/chat_models";
|
|
6
6
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
7
7
|
import { RunnablePassthrough, RunnableSequence, } from "@langchain/core/runnables";
|
|
8
8
|
import { isZodSchema } from "@langchain/core/utils/types";
|
|
9
|
-
import { AnthropicToolsOutputParser } from "./output_parsers.js";
|
|
9
|
+
import { AnthropicToolsOutputParser, extractToolCalls, } from "./output_parsers.js";
|
|
10
10
|
function _formatImage(imageUrl) {
|
|
11
11
|
const regex = /^data:(image\/.+);base64,(.+)$/;
|
|
12
12
|
const match = imageUrl.match(regex);
|
|
@@ -33,14 +33,15 @@ function anthropicResponseToChatMessages(messages, additionalKwargs) {
|
|
|
33
33
|
];
|
|
34
34
|
}
|
|
35
35
|
else {
|
|
36
|
-
|
|
37
|
-
const castMessage = messages;
|
|
36
|
+
const toolCalls = extractToolCalls(messages);
|
|
38
37
|
const generations = [
|
|
39
38
|
{
|
|
40
39
|
text: "",
|
|
41
40
|
message: new AIMessage({
|
|
42
|
-
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
|
+
content: messages,
|
|
43
43
|
additional_kwargs: additionalKwargs,
|
|
44
|
+
tool_calls: toolCalls,
|
|
44
45
|
}),
|
|
45
46
|
},
|
|
46
47
|
];
|
|
@@ -51,6 +52,175 @@ function anthropicResponseToChatMessages(messages, additionalKwargs) {
|
|
|
51
52
|
function isAnthropicTool(tool) {
|
|
52
53
|
return "input_schema" in tool;
|
|
53
54
|
}
|
|
55
|
+
function _mergeMessages(messages) {
|
|
56
|
+
// Merge runs of human/tool messages into single human messages with content blocks.
|
|
57
|
+
const merged = [];
|
|
58
|
+
for (const message of messages) {
|
|
59
|
+
if (message._getType() === "tool") {
|
|
60
|
+
if (typeof message.content === "string") {
|
|
61
|
+
merged.push(new HumanMessage({
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: "tool_result",
|
|
65
|
+
content: message.content,
|
|
66
|
+
tool_use_id: message.tool_call_id,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
merged.push(new HumanMessage({ content: message.content }));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const previousMessage = merged[merged.length - 1];
|
|
77
|
+
if (previousMessage?._getType() === "human" &&
|
|
78
|
+
message._getType() === "human") {
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
80
|
+
let combinedContent;
|
|
81
|
+
if (typeof previousMessage.content === "string") {
|
|
82
|
+
combinedContent = [{ type: "text", text: previousMessage.content }];
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
combinedContent = previousMessage.content;
|
|
86
|
+
}
|
|
87
|
+
if (typeof message.content === "string") {
|
|
88
|
+
combinedContent.push({ type: "text", text: message.content });
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
combinedContent = combinedContent.concat(message.content);
|
|
92
|
+
}
|
|
93
|
+
previousMessage.content = combinedContent;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
merged.push(message);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return merged;
|
|
101
|
+
}
|
|
102
|
+
export function _convertLangChainToolCallToAnthropic(toolCall) {
|
|
103
|
+
if (toolCall.id === undefined) {
|
|
104
|
+
throw new Error(`Anthropic requires all tool calls to have an "id".`);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
type: "tool_use",
|
|
108
|
+
id: toolCall.id,
|
|
109
|
+
name: toolCall.name,
|
|
110
|
+
input: toolCall.args,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function _formatContent(content) {
|
|
114
|
+
if (typeof content === "string") {
|
|
115
|
+
return content;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const contentBlocks = content.map((contentPart) => {
|
|
119
|
+
if (contentPart.type === "image_url") {
|
|
120
|
+
let source;
|
|
121
|
+
if (typeof contentPart.image_url === "string") {
|
|
122
|
+
source = _formatImage(contentPart.image_url);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
source = _formatImage(contentPart.image_url.url);
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
type: "image",
|
|
129
|
+
source,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
else if (contentPart.type === "text") {
|
|
133
|
+
// Assuming contentPart is of type MessageContentText here
|
|
134
|
+
return {
|
|
135
|
+
type: "text",
|
|
136
|
+
text: contentPart.text,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
else if (contentPart.type === "tool_use" ||
|
|
140
|
+
contentPart.type === "tool_result") {
|
|
141
|
+
// TODO: Fix when SDK types are fixed
|
|
142
|
+
return {
|
|
143
|
+
...contentPart,
|
|
144
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
throw new Error("Unsupported message content format");
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
return contentBlocks;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Formats messages as a prompt for the model.
|
|
156
|
+
* @param messages The base messages to format as a prompt.
|
|
157
|
+
* @returns The formatted prompt.
|
|
158
|
+
*/
|
|
159
|
+
function _formatMessagesForAnthropic(messages) {
|
|
160
|
+
const mergedMessages = _mergeMessages(messages);
|
|
161
|
+
let system;
|
|
162
|
+
if (mergedMessages.length > 0 && mergedMessages[0]._getType() === "system") {
|
|
163
|
+
if (typeof messages[0].content !== "string") {
|
|
164
|
+
throw new Error("System message content must be a string.");
|
|
165
|
+
}
|
|
166
|
+
system = messages[0].content;
|
|
167
|
+
}
|
|
168
|
+
const conversationMessages = system !== undefined ? mergedMessages.slice(1) : mergedMessages;
|
|
169
|
+
const formattedMessages = conversationMessages.map((message) => {
|
|
170
|
+
let role;
|
|
171
|
+
if (message._getType() === "human") {
|
|
172
|
+
role = "user";
|
|
173
|
+
}
|
|
174
|
+
else if (message._getType() === "ai") {
|
|
175
|
+
role = "assistant";
|
|
176
|
+
}
|
|
177
|
+
else if (message._getType() === "tool") {
|
|
178
|
+
role = "user";
|
|
179
|
+
}
|
|
180
|
+
else if (message._getType() === "system") {
|
|
181
|
+
throw new Error("System messages are only permitted as the first passed message.");
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
throw new Error(`Message type "${message._getType()}" is not supported.`);
|
|
185
|
+
}
|
|
186
|
+
if (isAIMessage(message) && !!message.tool_calls?.length) {
|
|
187
|
+
if (message.content === "") {
|
|
188
|
+
return {
|
|
189
|
+
role,
|
|
190
|
+
content: message.tool_calls.map(_convertLangChainToolCallToAnthropic),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
else if (typeof message.content === "string") {
|
|
194
|
+
console.warn(`The "tool_calls" field on a message is only respected if content is an empty string.`);
|
|
195
|
+
return {
|
|
196
|
+
role,
|
|
197
|
+
content: _formatContent(message.content),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
const { content } = message;
|
|
202
|
+
const hasMismatchedToolCalls = !message.tool_calls.every((toolCall) => content.find((contentPart) => contentPart.type === "tool_use" && contentPart.id === toolCall.id));
|
|
203
|
+
if (hasMismatchedToolCalls) {
|
|
204
|
+
console.warn(`The "tool_calls" field on a message is only respected if content is an empty string.`);
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
role,
|
|
208
|
+
content: _formatContent(message.content),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
return {
|
|
214
|
+
role,
|
|
215
|
+
content: _formatContent(message.content),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
return {
|
|
220
|
+
messages: formattedMessages,
|
|
221
|
+
system,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
54
224
|
/**
|
|
55
225
|
* Wrapper around Anthropic large language models.
|
|
56
226
|
*
|
|
@@ -68,7 +238,7 @@ function isAnthropicTool(tool) {
|
|
|
68
238
|
*
|
|
69
239
|
* const model = new ChatAnthropic({
|
|
70
240
|
* temperature: 0.9,
|
|
71
|
-
*
|
|
241
|
+
* apiKey: 'YOUR-API-KEY',
|
|
72
242
|
* });
|
|
73
243
|
* const res = await model.invoke({ input: 'Hello!' });
|
|
74
244
|
* console.log(res);
|
|
@@ -81,6 +251,7 @@ export class ChatAnthropicMessages extends BaseChatModel {
|
|
|
81
251
|
get lc_secrets() {
|
|
82
252
|
return {
|
|
83
253
|
anthropicApiKey: "ANTHROPIC_API_KEY",
|
|
254
|
+
apiKey: "ANTHROPIC_API_KEY",
|
|
84
255
|
};
|
|
85
256
|
}
|
|
86
257
|
get lc_aliases() {
|
|
@@ -102,6 +273,12 @@ export class ChatAnthropicMessages extends BaseChatModel {
|
|
|
102
273
|
writable: true,
|
|
103
274
|
value: void 0
|
|
104
275
|
});
|
|
276
|
+
Object.defineProperty(this, "apiKey", {
|
|
277
|
+
enumerable: true,
|
|
278
|
+
configurable: true,
|
|
279
|
+
writable: true,
|
|
280
|
+
value: void 0
|
|
281
|
+
});
|
|
105
282
|
Object.defineProperty(this, "apiUrl", {
|
|
106
283
|
enumerable: true,
|
|
107
284
|
configurable: true,
|
|
@@ -138,6 +315,12 @@ export class ChatAnthropicMessages extends BaseChatModel {
|
|
|
138
315
|
writable: true,
|
|
139
316
|
value: "claude-2.1"
|
|
140
317
|
});
|
|
318
|
+
Object.defineProperty(this, "model", {
|
|
319
|
+
enumerable: true,
|
|
320
|
+
configurable: true,
|
|
321
|
+
writable: true,
|
|
322
|
+
value: "claude-2.1"
|
|
323
|
+
});
|
|
141
324
|
Object.defineProperty(this, "invocationKwargs", {
|
|
142
325
|
enumerable: true,
|
|
143
326
|
configurable: true,
|
|
@@ -177,13 +360,19 @@ export class ChatAnthropicMessages extends BaseChatModel {
|
|
|
177
360
|
value: void 0
|
|
178
361
|
});
|
|
179
362
|
this.anthropicApiKey =
|
|
180
|
-
fields?.
|
|
363
|
+
fields?.apiKey ??
|
|
364
|
+
fields?.anthropicApiKey ??
|
|
365
|
+
getEnvironmentVariable("ANTHROPIC_API_KEY");
|
|
181
366
|
if (!this.anthropicApiKey) {
|
|
182
367
|
throw new Error("Anthropic API key not found");
|
|
183
368
|
}
|
|
369
|
+
/** Keep anthropicApiKey for backwards compatibility */
|
|
370
|
+
this.apiKey = this.anthropicApiKey;
|
|
184
371
|
// Support overriding the default API URL (i.e., https://api.anthropic.com)
|
|
185
372
|
this.apiUrl = fields?.anthropicApiUrl;
|
|
186
|
-
|
|
373
|
+
/** Keep modelName for backwards compatibility */
|
|
374
|
+
this.modelName = fields?.model ?? fields?.modelName ?? this.model;
|
|
375
|
+
this.model = this.modelName;
|
|
187
376
|
this.invocationKwargs = fields?.invocationKwargs ?? {};
|
|
188
377
|
this.temperature = fields?.temperature ?? this.temperature;
|
|
189
378
|
this.topK = fields?.topK ?? this.topK;
|
|
@@ -220,46 +409,32 @@ export class ChatAnthropicMessages extends BaseChatModel {
|
|
|
220
409
|
input_schema: zodToJsonSchema(tool.schema),
|
|
221
410
|
}));
|
|
222
411
|
}
|
|
412
|
+
bindTools(tools, kwargs) {
|
|
413
|
+
return this.bind({
|
|
414
|
+
tools: this.formatStructuredToolToAnthropic(tools),
|
|
415
|
+
...kwargs,
|
|
416
|
+
});
|
|
417
|
+
}
|
|
223
418
|
/**
|
|
224
419
|
* Get the parameters used to invoke the model
|
|
225
420
|
*/
|
|
226
421
|
invocationParams(options) {
|
|
227
422
|
return {
|
|
228
|
-
model: this.
|
|
423
|
+
model: this.model,
|
|
229
424
|
temperature: this.temperature,
|
|
230
425
|
top_k: this.topK,
|
|
231
426
|
top_p: this.topP,
|
|
232
427
|
stop_sequences: options?.stop ?? this.stopSequences,
|
|
233
428
|
stream: this.streaming,
|
|
234
429
|
max_tokens: this.maxTokens,
|
|
430
|
+
tools: this.formatStructuredToolToAnthropic(options?.tools),
|
|
235
431
|
...this.invocationKwargs,
|
|
236
432
|
};
|
|
237
433
|
}
|
|
238
|
-
invocationOptions(request, options) {
|
|
239
|
-
const toolUseBetaHeader = {
|
|
240
|
-
"anthropic-beta": "tools-2024-04-04",
|
|
241
|
-
};
|
|
242
|
-
const tools = this.formatStructuredToolToAnthropic(options?.tools);
|
|
243
|
-
// If tools are present, populate the body with the message request params.
|
|
244
|
-
// This is because Anthropic overwrites the message request params if a body
|
|
245
|
-
// is passed.
|
|
246
|
-
const body = tools
|
|
247
|
-
? {
|
|
248
|
-
...request,
|
|
249
|
-
tools,
|
|
250
|
-
}
|
|
251
|
-
: undefined;
|
|
252
|
-
const headers = tools ? toolUseBetaHeader : undefined;
|
|
253
|
-
return {
|
|
254
|
-
signal: options.signal,
|
|
255
|
-
...(body ? { body } : {}),
|
|
256
|
-
...(headers ? { headers } : {}),
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
434
|
/** @ignore */
|
|
260
435
|
_identifyingParams() {
|
|
261
436
|
return {
|
|
262
|
-
model_name: this.
|
|
437
|
+
model_name: this.model,
|
|
263
438
|
...this.invocationParams(),
|
|
264
439
|
};
|
|
265
440
|
}
|
|
@@ -268,28 +443,29 @@ export class ChatAnthropicMessages extends BaseChatModel {
|
|
|
268
443
|
*/
|
|
269
444
|
identifyingParams() {
|
|
270
445
|
return {
|
|
271
|
-
model_name: this.
|
|
446
|
+
model_name: this.model,
|
|
272
447
|
...this.invocationParams(),
|
|
273
448
|
};
|
|
274
449
|
}
|
|
275
450
|
async *_streamResponseChunks(messages, options, runManager) {
|
|
276
451
|
const params = this.invocationParams(options);
|
|
277
|
-
const
|
|
278
|
-
...params,
|
|
279
|
-
stream: false,
|
|
280
|
-
...this.formatMessagesForAnthropic(messages),
|
|
281
|
-
}, options);
|
|
452
|
+
const formattedMessages = _formatMessagesForAnthropic(messages);
|
|
282
453
|
if (options.tools !== undefined && options.tools.length > 0) {
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
454
|
+
const generations = await this._generateNonStreaming(messages, params, {
|
|
455
|
+
signal: options.signal,
|
|
456
|
+
});
|
|
457
|
+
const result = generations[0].message;
|
|
458
|
+
const toolCallChunks = result.tool_calls?.map((toolCall, index) => ({
|
|
459
|
+
name: toolCall.name,
|
|
460
|
+
args: JSON.stringify(toolCall.args),
|
|
461
|
+
id: toolCall.id,
|
|
462
|
+
index,
|
|
463
|
+
}));
|
|
289
464
|
yield new ChatGenerationChunk({
|
|
290
465
|
message: new AIMessageChunk({
|
|
291
|
-
content:
|
|
292
|
-
additional_kwargs:
|
|
466
|
+
content: result.content,
|
|
467
|
+
additional_kwargs: result.additional_kwargs,
|
|
468
|
+
tool_call_chunks: toolCallChunks,
|
|
293
469
|
}),
|
|
294
470
|
text: generations[0].text,
|
|
295
471
|
});
|
|
@@ -297,9 +473,9 @@ export class ChatAnthropicMessages extends BaseChatModel {
|
|
|
297
473
|
else {
|
|
298
474
|
const stream = await this.createStreamWithRetry({
|
|
299
475
|
...params,
|
|
300
|
-
...
|
|
476
|
+
...formattedMessages,
|
|
301
477
|
stream: true,
|
|
302
|
-
}
|
|
478
|
+
});
|
|
303
479
|
let usageData = { input_tokens: 0, output_tokens: 0 };
|
|
304
480
|
for await (const data of stream) {
|
|
305
481
|
if (options.signal?.aborted) {
|
|
@@ -360,95 +536,22 @@ export class ChatAnthropicMessages extends BaseChatModel {
|
|
|
360
536
|
});
|
|
361
537
|
}
|
|
362
538
|
}
|
|
363
|
-
/**
|
|
364
|
-
* Formats messages as a prompt for the model.
|
|
365
|
-
* @param messages The base messages to format as a prompt.
|
|
366
|
-
* @returns The formatted prompt.
|
|
367
|
-
*/
|
|
368
|
-
formatMessagesForAnthropic(messages) {
|
|
369
|
-
let system;
|
|
370
|
-
if (messages.length > 0 && messages[0]._getType() === "system") {
|
|
371
|
-
if (typeof messages[0].content !== "string") {
|
|
372
|
-
throw new Error("System message content must be a string.");
|
|
373
|
-
}
|
|
374
|
-
system = messages[0].content;
|
|
375
|
-
}
|
|
376
|
-
const conversationMessages = system !== undefined ? messages.slice(1) : messages;
|
|
377
|
-
const formattedMessages = conversationMessages.map((message) => {
|
|
378
|
-
let role;
|
|
379
|
-
if (message._getType() === "human") {
|
|
380
|
-
role = "user";
|
|
381
|
-
}
|
|
382
|
-
else if (message._getType() === "ai") {
|
|
383
|
-
role = "assistant";
|
|
384
|
-
}
|
|
385
|
-
else if (message._getType() === "tool") {
|
|
386
|
-
role = "user";
|
|
387
|
-
}
|
|
388
|
-
else if (message._getType() === "system") {
|
|
389
|
-
throw new Error("System messages are only permitted as the first passed message.");
|
|
390
|
-
}
|
|
391
|
-
else {
|
|
392
|
-
throw new Error(`Message type "${message._getType()}" is not supported.`);
|
|
393
|
-
}
|
|
394
|
-
if (typeof message.content === "string") {
|
|
395
|
-
return {
|
|
396
|
-
role,
|
|
397
|
-
content: message.content,
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
const contentBlocks = message.content.map((contentPart) => {
|
|
402
|
-
if (contentPart.type === "image_url") {
|
|
403
|
-
let source;
|
|
404
|
-
if (typeof contentPart.image_url === "string") {
|
|
405
|
-
source = _formatImage(contentPart.image_url);
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
source = _formatImage(contentPart.image_url.url);
|
|
409
|
-
}
|
|
410
|
-
return {
|
|
411
|
-
type: "image",
|
|
412
|
-
source,
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
else if (contentPart.type === "text") {
|
|
416
|
-
// Assuming contentPart is of type MessageContentText here
|
|
417
|
-
return {
|
|
418
|
-
type: "text",
|
|
419
|
-
text: contentPart.text,
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
else if (contentPart.type === "tool_use" ||
|
|
423
|
-
contentPart.type === "tool_result") {
|
|
424
|
-
// TODO: Fix when SDK types are fixed
|
|
425
|
-
return {
|
|
426
|
-
...contentPart,
|
|
427
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
else {
|
|
431
|
-
throw new Error("Unsupported message content format");
|
|
432
|
-
}
|
|
433
|
-
});
|
|
434
|
-
return {
|
|
435
|
-
role,
|
|
436
|
-
content: contentBlocks,
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
return {
|
|
441
|
-
messages: formattedMessages,
|
|
442
|
-
system,
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
539
|
/** @ignore */
|
|
446
540
|
async _generateNonStreaming(messages, params, requestOptions) {
|
|
541
|
+
const options = params.tools !== undefined
|
|
542
|
+
? {
|
|
543
|
+
...requestOptions,
|
|
544
|
+
headers: {
|
|
545
|
+
...requestOptions.headers,
|
|
546
|
+
"anthropic-beta": "tools-2024-04-04",
|
|
547
|
+
},
|
|
548
|
+
}
|
|
549
|
+
: requestOptions;
|
|
447
550
|
const response = await this.completionWithRetry({
|
|
448
551
|
...params,
|
|
449
552
|
stream: false,
|
|
450
|
-
...
|
|
451
|
-
},
|
|
553
|
+
..._formatMessagesForAnthropic(messages),
|
|
554
|
+
}, options);
|
|
452
555
|
const { content, ...additionalKwargs } = response;
|
|
453
556
|
const generations = anthropicResponseToChatMessages(content, additionalKwargs);
|
|
454
557
|
return generations;
|
|
@@ -483,12 +586,9 @@ export class ChatAnthropicMessages extends BaseChatModel {
|
|
|
483
586
|
};
|
|
484
587
|
}
|
|
485
588
|
else {
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
...this.formatMessagesForAnthropic(messages),
|
|
490
|
-
}, options);
|
|
491
|
-
const generations = await this._generateNonStreaming(messages, params, requestOptions);
|
|
589
|
+
const generations = await this._generateNonStreaming(messages, params, {
|
|
590
|
+
signal: options.signal,
|
|
591
|
+
});
|
|
492
592
|
return {
|
|
493
593
|
generations,
|
|
494
594
|
};
|
|
@@ -505,7 +605,7 @@ export class ChatAnthropicMessages extends BaseChatModel {
|
|
|
505
605
|
this.streamingClient = new Anthropic({
|
|
506
606
|
...this.clientOptions,
|
|
507
607
|
...options_,
|
|
508
|
-
apiKey: this.
|
|
608
|
+
apiKey: this.apiKey,
|
|
509
609
|
// Prefer LangChain built-in retries
|
|
510
610
|
maxRetries: 0,
|
|
511
611
|
});
|
|
@@ -519,7 +619,7 @@ export class ChatAnthropicMessages extends BaseChatModel {
|
|
|
519
619
|
}
|
|
520
620
|
/** @ignore */
|
|
521
621
|
async completionWithRetry(request, options) {
|
|
522
|
-
if (!this.
|
|
622
|
+
if (!this.apiKey) {
|
|
523
623
|
throw new Error("Missing Anthropic API key.");
|
|
524
624
|
}
|
|
525
625
|
if (!this.batchClient) {
|
|
@@ -527,7 +627,7 @@ export class ChatAnthropicMessages extends BaseChatModel {
|
|
|
527
627
|
this.batchClient = new Anthropic({
|
|
528
628
|
...this.clientOptions,
|
|
529
629
|
...options,
|
|
530
|
-
apiKey: this.
|
|
630
|
+
apiKey: this.apiKey,
|
|
531
631
|
maxRetries: 0,
|
|
532
632
|
});
|
|
533
633
|
}
|
package/dist/output_parsers.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AnthropicToolsOutputParser = void 0;
|
|
3
|
+
exports.extractToolCalls = exports.AnthropicToolsOutputParser = void 0;
|
|
4
4
|
const output_parsers_1 = require("@langchain/core/output_parsers");
|
|
5
5
|
class AnthropicToolsOutputParser extends output_parsers_1.BaseLLMOutputParser {
|
|
6
6
|
static lc_name() {
|
|
@@ -56,25 +56,32 @@ class AnthropicToolsOutputParser extends output_parsers_1.BaseLLMOutputParser {
|
|
|
56
56
|
throw new output_parsers_1.OutputParserException(`Failed to parse. Text: "${JSON.stringify(result, null, 2)}". Error: ${JSON.stringify(zodParsedResult.error.errors)}`, JSON.stringify(result, null, 2));
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
59
|
async parseResult(generations) {
|
|
61
60
|
const tools = generations.flatMap((generation) => {
|
|
62
61
|
const { message } = generation;
|
|
63
|
-
if (typeof message === "string") {
|
|
64
|
-
return [];
|
|
65
|
-
}
|
|
66
62
|
if (!Array.isArray(message.content)) {
|
|
67
63
|
return [];
|
|
68
64
|
}
|
|
69
|
-
const tool = message.content
|
|
65
|
+
const tool = extractToolCalls(message.content)[0];
|
|
70
66
|
return tool;
|
|
71
67
|
});
|
|
72
68
|
if (tools[0] === undefined) {
|
|
73
69
|
throw new Error("No parseable tool calls provided to AnthropicToolsOutputParser.");
|
|
74
70
|
}
|
|
75
71
|
const [tool] = tools;
|
|
76
|
-
const validatedResult = await this._validateResult(tool.
|
|
72
|
+
const validatedResult = await this._validateResult(tool.args);
|
|
77
73
|
return validatedResult;
|
|
78
74
|
}
|
|
79
75
|
}
|
|
80
76
|
exports.AnthropicToolsOutputParser = AnthropicToolsOutputParser;
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
+
function extractToolCalls(content) {
|
|
79
|
+
const toolCalls = [];
|
|
80
|
+
for (const block of content) {
|
|
81
|
+
if (block.type === "tool_use") {
|
|
82
|
+
toolCalls.push({ name: block.name, args: block.input, id: block.id });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return toolCalls;
|
|
86
|
+
}
|
|
87
|
+
exports.extractToolCalls = extractToolCalls;
|
package/dist/output_parsers.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { BaseLLMOutputParser } from "@langchain/core/output_parsers";
|
|
3
3
|
import { JsonOutputKeyToolsParserParams } from "@langchain/core/output_parsers/openai_tools";
|
|
4
4
|
import { ChatGeneration } from "@langchain/core/outputs";
|
|
5
|
+
import { ToolCall } from "@langchain/core/messages/tool";
|
|
5
6
|
interface AnthropicToolsOutputParserParams<T extends Record<string, any>> extends JsonOutputKeyToolsParserParams<T> {
|
|
6
7
|
}
|
|
7
8
|
export declare class AnthropicToolsOutputParser<T extends Record<string, any> = Record<string, any>> extends BaseLLMOutputParser<T> {
|
|
@@ -17,4 +18,5 @@ export declare class AnthropicToolsOutputParser<T extends Record<string, any> =
|
|
|
17
18
|
protected _validateResult(result: unknown): Promise<T>;
|
|
18
19
|
parseResult(generations: ChatGeneration[]): Promise<T>;
|
|
19
20
|
}
|
|
21
|
+
export declare function extractToolCalls(content: Record<string, any>[]): ToolCall[];
|
|
20
22
|
export {};
|
package/dist/output_parsers.js
CHANGED
|
@@ -53,24 +53,30 @@ export class AnthropicToolsOutputParser extends BaseLLMOutputParser {
|
|
|
53
53
|
throw new OutputParserException(`Failed to parse. Text: "${JSON.stringify(result, null, 2)}". Error: ${JSON.stringify(zodParsedResult.error.errors)}`, JSON.stringify(result, null, 2));
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
56
|
async parseResult(generations) {
|
|
58
57
|
const tools = generations.flatMap((generation) => {
|
|
59
58
|
const { message } = generation;
|
|
60
|
-
if (typeof message === "string") {
|
|
61
|
-
return [];
|
|
62
|
-
}
|
|
63
59
|
if (!Array.isArray(message.content)) {
|
|
64
60
|
return [];
|
|
65
61
|
}
|
|
66
|
-
const tool = message.content
|
|
62
|
+
const tool = extractToolCalls(message.content)[0];
|
|
67
63
|
return tool;
|
|
68
64
|
});
|
|
69
65
|
if (tools[0] === undefined) {
|
|
70
66
|
throw new Error("No parseable tool calls provided to AnthropicToolsOutputParser.");
|
|
71
67
|
}
|
|
72
68
|
const [tool] = tools;
|
|
73
|
-
const validatedResult = await this._validateResult(tool.
|
|
69
|
+
const validatedResult = await this._validateResult(tool.args);
|
|
74
70
|
return validatedResult;
|
|
75
71
|
}
|
|
76
72
|
}
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
|
+
export function extractToolCalls(content) {
|
|
75
|
+
const toolCalls = [];
|
|
76
|
+
for (const block of content) {
|
|
77
|
+
if (block.type === "tool_use") {
|
|
78
|
+
toolCalls.push({ name: block.name, args: block.input, id: block.id });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return toolCalls;
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// import { test, expect } from "@jest/globals";
|
|
2
|
+
// import { ChatPromptTemplate } from "@langchain/core/prompts";
|
|
3
|
+
// import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
|
|
4
|
+
// import { AgentExecutor, createToolCallingAgent } from "langchain/agents";
|
|
5
|
+
// import { ChatAnthropic } from "../index.js";
|
|
6
|
+
// const tools = [new TavilySearchResults({ maxResults: 1 })];
|
|
7
|
+
// TODO: This test breaks CI build due to dependencies. Figure out a way around it.
|
|
8
|
+
test("createToolCallingAgent works", async () => {
|
|
9
|
+
// const prompt = ChatPromptTemplate.fromMessages([
|
|
10
|
+
// ["system", "You are a helpful assistant"],
|
|
11
|
+
// ["placeholder", "{chat_history}"],
|
|
12
|
+
// ["human", "{input}"],
|
|
13
|
+
// ["placeholder", "{agent_scratchpad}"],
|
|
14
|
+
// ]);
|
|
15
|
+
// const llm = new ChatAnthropic({
|
|
16
|
+
// modelName: "claude-3-sonnet-20240229",
|
|
17
|
+
// temperature: 0,
|
|
18
|
+
// });
|
|
19
|
+
// const agent = await createToolCallingAgent({
|
|
20
|
+
// llm,
|
|
21
|
+
// tools,
|
|
22
|
+
// prompt,
|
|
23
|
+
// });
|
|
24
|
+
// const agentExecutor = new AgentExecutor({
|
|
25
|
+
// agent,
|
|
26
|
+
// tools,
|
|
27
|
+
// });
|
|
28
|
+
// const input = "what is the current weather in SF?";
|
|
29
|
+
// const result = await agentExecutor.invoke({
|
|
30
|
+
// input,
|
|
31
|
+
// });
|
|
32
|
+
// console.log(result);
|
|
33
|
+
// expect(result.input).toBe(input);
|
|
34
|
+
// expect(typeof result.output).toBe("string");
|
|
35
|
+
// // Length greater than 10 because any less than that would warrant
|
|
36
|
+
// // an investigation into why such a short generation was returned.
|
|
37
|
+
// expect(result.output.length).toBeGreaterThan(10);
|
|
38
|
+
});
|
|
39
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|