@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.
package/dist/chat_models.cjs
CHANGED
|
@@ -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 (
|
|
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 (
|
|
277
|
-
|
|
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
|
-
...
|
|
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"
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
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,
|
package/dist/chat_models.d.ts
CHANGED
|
@@ -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>>;
|
package/dist/chat_models.js
CHANGED
|
@@ -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 (
|
|
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 (
|
|
273
|
-
|
|
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
|
-
...
|
|
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"
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
});
|