@librechat/agents 2.4.52 → 2.4.54
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/cjs/graphs/Graph.cjs +8 -7
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +8 -8
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +15 -0
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/main.cjs +2 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/stream.cjs +8 -0
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +8 -2
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +107 -1
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/cjs/tools/search/anthropic.cjs +40 -0
- package/dist/cjs/tools/search/anthropic.cjs.map +1 -0
- package/dist/cjs/tools/search/search.cjs +2 -2
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +8 -7
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +8 -8
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +15 -0
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -1
- package/dist/esm/stream.mjs +9 -1
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +8 -2
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +107 -3
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/esm/tools/search/anthropic.mjs +37 -0
- package/dist/esm/tools/search/anthropic.mjs.map +1 -0
- package/dist/esm/tools/search/search.mjs +2 -2
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +3 -1
- package/dist/types/llm/anthropic/types.d.ts +2 -0
- package/dist/types/llm/anthropic/utils/message_inputs.d.ts +1 -1
- package/dist/types/llm/anthropic/utils/output_parsers.d.ts +3 -3
- package/dist/types/scripts/ant_web_search.d.ts +1 -0
- package/dist/types/tools/CodeExecutor.d.ts +2 -2
- package/dist/types/tools/ToolNode.d.ts +1 -1
- package/dist/types/tools/handlers.d.ts +11 -0
- package/dist/types/tools/search/anthropic.d.ts +16 -0
- package/dist/types/types/llm.d.ts +3 -2
- package/dist/types/types/stream.d.ts +9 -1
- package/package.json +5 -3
- package/src/graphs/Graph.ts +9 -7
- package/src/llm/anthropic/Jacob_Lee_Resume_2023.pdf +0 -0
- package/src/llm/anthropic/index.ts +7 -12
- package/src/llm/anthropic/llm.spec.ts +447 -115
- package/src/llm/anthropic/types.ts +16 -0
- package/src/llm/anthropic/utils/message_inputs.ts +17 -2
- package/src/llm/anthropic/utils/output_parsers.ts +4 -4
- package/src/scripts/ant_web_search.ts +158 -0
- package/src/stream.ts +16 -5
- package/src/tools/ToolNode.ts +17 -3
- package/src/tools/handlers.ts +170 -1
- package/src/tools/search/anthropic.ts +51 -0
- package/src/tools/search/search.ts +2 -1
- package/src/types/llm.ts +4 -2
- package/src/types/stream.ts +14 -0
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
/* eslint-disable no-process-env */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
import { config } from 'dotenv';
|
|
4
|
+
config();
|
|
1
5
|
import { expect, test } from '@jest/globals';
|
|
2
6
|
import * as fs from 'fs/promises';
|
|
3
7
|
import {
|
|
8
|
+
AIMessage,
|
|
4
9
|
AIMessageChunk,
|
|
5
10
|
BaseMessage,
|
|
6
11
|
HumanMessage,
|
|
@@ -16,13 +21,53 @@ import {
|
|
|
16
21
|
} from '@langchain/core/prompts';
|
|
17
22
|
import { CallbackManager } from '@langchain/core/callbacks/manager';
|
|
18
23
|
import { concat } from '@langchain/core/utils/stream';
|
|
24
|
+
import { AnthropicVertex } from '@anthropic-ai/vertex-sdk';
|
|
25
|
+
import { BaseLanguageModelInput } from '@langchain/core/language_models/base';
|
|
26
|
+
import { tool } from '@langchain/core/tools';
|
|
27
|
+
import { z } from 'zod';
|
|
19
28
|
import { CustomAnthropic as ChatAnthropic } from './index';
|
|
20
|
-
import { AnthropicMessageResponse } from './types';
|
|
29
|
+
import { AnthropicMessageResponse, ChatAnthropicContentBlock } from './types';
|
|
21
30
|
jest.setTimeout(120000);
|
|
22
31
|
|
|
32
|
+
async function invoke(
|
|
33
|
+
chat: ChatAnthropic,
|
|
34
|
+
invocationType: string,
|
|
35
|
+
input: BaseLanguageModelInput
|
|
36
|
+
): Promise<AIMessageChunk | AIMessage> {
|
|
37
|
+
if (invocationType === 'stream') {
|
|
38
|
+
let output: AIMessageChunk | undefined;
|
|
39
|
+
|
|
40
|
+
const stream = await chat.stream(input);
|
|
41
|
+
|
|
42
|
+
for await (const chunk of stream) {
|
|
43
|
+
if (!output) {
|
|
44
|
+
output = chunk;
|
|
45
|
+
} else {
|
|
46
|
+
output = output.concat(chunk);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return output!;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return chat.invoke(input);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// use this for tests involving "extended thinking"
|
|
57
|
+
const extendedThinkingModelName = 'claude-3-7-sonnet-20250219';
|
|
58
|
+
|
|
59
|
+
// use this for tests involving citations
|
|
60
|
+
const citationsModelName = 'claude-3-5-sonnet-20241022';
|
|
61
|
+
|
|
62
|
+
// use this for tests involving PDF documents
|
|
63
|
+
const pdfModelName = 'claude-3-5-haiku-20241022';
|
|
64
|
+
|
|
65
|
+
// Use this model for all other tests
|
|
66
|
+
const modelName = 'claude-3-haiku-20240307';
|
|
67
|
+
|
|
23
68
|
test('Test ChatAnthropic', async () => {
|
|
24
69
|
const chat = new ChatAnthropic({
|
|
25
|
-
modelName
|
|
70
|
+
modelName,
|
|
26
71
|
maxRetries: 0,
|
|
27
72
|
});
|
|
28
73
|
const message = new HumanMessage('Hello!');
|
|
@@ -32,7 +77,7 @@ test('Test ChatAnthropic', async () => {
|
|
|
32
77
|
|
|
33
78
|
test('Test ChatAnthropic with a bad API key throws appropriate error', async () => {
|
|
34
79
|
const chat = new ChatAnthropic({
|
|
35
|
-
modelName
|
|
80
|
+
modelName,
|
|
36
81
|
maxRetries: 0,
|
|
37
82
|
apiKey: 'bad',
|
|
38
83
|
});
|
|
@@ -65,7 +110,7 @@ test('Test ChatAnthropic with unknown model throws appropriate error', async ()
|
|
|
65
110
|
|
|
66
111
|
test('Test ChatAnthropic Generate', async () => {
|
|
67
112
|
const chat = new ChatAnthropic({
|
|
68
|
-
modelName
|
|
113
|
+
modelName,
|
|
69
114
|
maxRetries: 0,
|
|
70
115
|
});
|
|
71
116
|
const message = new HumanMessage('Hello!');
|
|
@@ -73,7 +118,6 @@ test('Test ChatAnthropic Generate', async () => {
|
|
|
73
118
|
expect(res.generations.length).toBe(2);
|
|
74
119
|
for (const generation of res.generations) {
|
|
75
120
|
expect(generation.length).toBe(1);
|
|
76
|
-
// @eslint-disable-next-line/@typescript-eslint/ban-ts-comment
|
|
77
121
|
for (const message of generation) {
|
|
78
122
|
// console.log(message.text);
|
|
79
123
|
}
|
|
@@ -83,7 +127,7 @@ test('Test ChatAnthropic Generate', async () => {
|
|
|
83
127
|
|
|
84
128
|
test.skip('Test ChatAnthropic Generate w/ ClientOptions', async () => {
|
|
85
129
|
const chat = new ChatAnthropic({
|
|
86
|
-
modelName
|
|
130
|
+
modelName,
|
|
87
131
|
maxRetries: 0,
|
|
88
132
|
clientOptions: {
|
|
89
133
|
defaultHeaders: {
|
|
@@ -96,7 +140,6 @@ test.skip('Test ChatAnthropic Generate w/ ClientOptions', async () => {
|
|
|
96
140
|
expect(res.generations.length).toBe(2);
|
|
97
141
|
for (const generation of res.generations) {
|
|
98
142
|
expect(generation.length).toBe(1);
|
|
99
|
-
// @eslint-disable-next-line/@typescript-eslint/ban-ts-comment
|
|
100
143
|
for (const message of generation) {
|
|
101
144
|
// console.log(message.text);
|
|
102
145
|
}
|
|
@@ -106,7 +149,7 @@ test.skip('Test ChatAnthropic Generate w/ ClientOptions', async () => {
|
|
|
106
149
|
|
|
107
150
|
test('Test ChatAnthropic Generate with a signal in call options', async () => {
|
|
108
151
|
const chat = new ChatAnthropic({
|
|
109
|
-
modelName
|
|
152
|
+
modelName,
|
|
110
153
|
maxRetries: 0,
|
|
111
154
|
});
|
|
112
155
|
const controller = new AbortController();
|
|
@@ -128,9 +171,8 @@ test('Test ChatAnthropic tokenUsage with a batch', async () => {
|
|
|
128
171
|
const model = new ChatAnthropic({
|
|
129
172
|
temperature: 0,
|
|
130
173
|
maxRetries: 0,
|
|
131
|
-
modelName
|
|
174
|
+
modelName,
|
|
132
175
|
});
|
|
133
|
-
// @eslint-disable-next-line/@typescript-eslint/ban-ts-comment
|
|
134
176
|
const res = await model.generate([
|
|
135
177
|
[new HumanMessage(`Hello!`)],
|
|
136
178
|
[new HumanMessage(`Hi!`)],
|
|
@@ -143,7 +185,7 @@ test('Test ChatAnthropic in streaming mode', async () => {
|
|
|
143
185
|
let streamedCompletion = '';
|
|
144
186
|
|
|
145
187
|
const model = new ChatAnthropic({
|
|
146
|
-
modelName
|
|
188
|
+
modelName,
|
|
147
189
|
maxRetries: 0,
|
|
148
190
|
streaming: true,
|
|
149
191
|
callbacks: CallbackManager.fromHandlers({
|
|
@@ -166,7 +208,7 @@ test('Test ChatAnthropic in streaming mode with a signal', async () => {
|
|
|
166
208
|
let streamedCompletion = '';
|
|
167
209
|
|
|
168
210
|
const model = new ChatAnthropic({
|
|
169
|
-
modelName
|
|
211
|
+
modelName,
|
|
170
212
|
maxRetries: 0,
|
|
171
213
|
streaming: true,
|
|
172
214
|
callbacks: CallbackManager.fromHandlers({
|
|
@@ -195,14 +237,13 @@ test('Test ChatAnthropic in streaming mode with a signal', async () => {
|
|
|
195
237
|
|
|
196
238
|
test.skip('Test ChatAnthropic prompt value', async () => {
|
|
197
239
|
const chat = new ChatAnthropic({
|
|
198
|
-
modelName
|
|
240
|
+
modelName,
|
|
199
241
|
maxRetries: 0,
|
|
200
242
|
});
|
|
201
243
|
const message = new HumanMessage('Hello!');
|
|
202
244
|
const res = await chat.generatePrompt([new ChatPromptValue([message])]);
|
|
203
245
|
expect(res.generations.length).toBe(1);
|
|
204
246
|
for (const generation of res.generations) {
|
|
205
|
-
// @eslint-disable-next-line/@typescript-eslint/ban-ts-comment
|
|
206
247
|
for (const g of generation) {
|
|
207
248
|
// console.log(g.text);
|
|
208
249
|
}
|
|
@@ -212,7 +253,7 @@ test.skip('Test ChatAnthropic prompt value', async () => {
|
|
|
212
253
|
|
|
213
254
|
test.skip('ChatAnthropic, docs, prompt templates', async () => {
|
|
214
255
|
const chat = new ChatAnthropic({
|
|
215
|
-
modelName
|
|
256
|
+
modelName,
|
|
216
257
|
maxRetries: 0,
|
|
217
258
|
temperature: 0,
|
|
218
259
|
});
|
|
@@ -226,7 +267,6 @@ test.skip('ChatAnthropic, docs, prompt templates', async () => {
|
|
|
226
267
|
HumanMessagePromptTemplate.fromTemplate('{text}'),
|
|
227
268
|
]);
|
|
228
269
|
|
|
229
|
-
// @eslint-disable-next-line/@typescript-eslint/ban-ts-comment
|
|
230
270
|
const responseA = await chat.generatePrompt([
|
|
231
271
|
await chatPrompt.formatPromptValue({
|
|
232
272
|
input_language: 'English',
|
|
@@ -240,7 +280,7 @@ test.skip('ChatAnthropic, docs, prompt templates', async () => {
|
|
|
240
280
|
|
|
241
281
|
test.skip('ChatAnthropic, longer chain of messages', async () => {
|
|
242
282
|
const chat = new ChatAnthropic({
|
|
243
|
-
modelName
|
|
283
|
+
modelName,
|
|
244
284
|
maxRetries: 0,
|
|
245
285
|
temperature: 0,
|
|
246
286
|
});
|
|
@@ -251,7 +291,6 @@ test.skip('ChatAnthropic, longer chain of messages', async () => {
|
|
|
251
291
|
HumanMessagePromptTemplate.fromTemplate('{text}'),
|
|
252
292
|
]);
|
|
253
293
|
|
|
254
|
-
// @eslint-disable-next-line/@typescript-eslint/ban-ts-comment
|
|
255
294
|
const responseA = await chat.generatePrompt([
|
|
256
295
|
await chatPrompt.formatPromptValue({
|
|
257
296
|
text: 'What did I just say my name was?',
|
|
@@ -265,12 +304,11 @@ test.skip('ChatAnthropic, Anthropic apiUrl set manually via constructor', async
|
|
|
265
304
|
// Pass the default URL through (should use this, and work as normal)
|
|
266
305
|
const anthropicApiUrl = 'https://api.anthropic.com';
|
|
267
306
|
const chat = new ChatAnthropic({
|
|
268
|
-
modelName
|
|
307
|
+
modelName,
|
|
269
308
|
maxRetries: 0,
|
|
270
309
|
anthropicApiUrl,
|
|
271
310
|
});
|
|
272
311
|
const message = new HumanMessage('Hello!');
|
|
273
|
-
// @eslint-disable-next-line/@typescript-eslint/ban-ts-comment
|
|
274
312
|
const res = await chat.call([message]);
|
|
275
313
|
// console.log({ res });
|
|
276
314
|
});
|
|
@@ -279,10 +317,10 @@ test('Test ChatAnthropic stream method', async () => {
|
|
|
279
317
|
const model = new ChatAnthropic({
|
|
280
318
|
maxTokens: 50,
|
|
281
319
|
maxRetries: 0,
|
|
282
|
-
modelName
|
|
320
|
+
modelName,
|
|
283
321
|
});
|
|
284
322
|
const stream = await model.stream('Print hello world.');
|
|
285
|
-
const chunks:
|
|
323
|
+
const chunks: AIMessageChunk[] = [];
|
|
286
324
|
for await (const chunk of stream) {
|
|
287
325
|
chunks.push(chunk);
|
|
288
326
|
}
|
|
@@ -294,7 +332,7 @@ test('Test ChatAnthropic stream method with abort', async () => {
|
|
|
294
332
|
const model = new ChatAnthropic({
|
|
295
333
|
maxTokens: 500,
|
|
296
334
|
maxRetries: 0,
|
|
297
|
-
modelName
|
|
335
|
+
modelName,
|
|
298
336
|
});
|
|
299
337
|
const stream = await model.stream(
|
|
300
338
|
'How is your day going? Be extremely verbose.',
|
|
@@ -302,7 +340,6 @@ test('Test ChatAnthropic stream method with abort', async () => {
|
|
|
302
340
|
signal: AbortSignal.timeout(1000),
|
|
303
341
|
}
|
|
304
342
|
);
|
|
305
|
-
// @eslint-disable-next-line/@typescript-eslint/ban-ts-comment
|
|
306
343
|
for await (const chunk of stream) {
|
|
307
344
|
// console.log(chunk);
|
|
308
345
|
}
|
|
@@ -313,13 +350,12 @@ test('Test ChatAnthropic stream method with early break', async () => {
|
|
|
313
350
|
const model = new ChatAnthropic({
|
|
314
351
|
maxTokens: 50,
|
|
315
352
|
maxRetries: 0,
|
|
316
|
-
modelName
|
|
353
|
+
modelName,
|
|
317
354
|
});
|
|
318
355
|
const stream = await model.stream(
|
|
319
356
|
'How is your day going? Be extremely verbose.'
|
|
320
357
|
);
|
|
321
358
|
let i = 0;
|
|
322
|
-
// @eslint-disable-next-line/@typescript-eslint/ban-ts-comment
|
|
323
359
|
for await (const chunk of stream) {
|
|
324
360
|
// console.log(chunk);
|
|
325
361
|
i += 1;
|
|
@@ -331,7 +367,7 @@ test('Test ChatAnthropic stream method with early break', async () => {
|
|
|
331
367
|
|
|
332
368
|
test('Test ChatAnthropic headers passed through', async () => {
|
|
333
369
|
const chat = new ChatAnthropic({
|
|
334
|
-
modelName
|
|
370
|
+
modelName,
|
|
335
371
|
maxRetries: 0,
|
|
336
372
|
apiKey: 'NOT_REAL',
|
|
337
373
|
clientOptions: {
|
|
@@ -341,36 +377,142 @@ test('Test ChatAnthropic headers passed through', async () => {
|
|
|
341
377
|
},
|
|
342
378
|
});
|
|
343
379
|
const message = new HumanMessage('Hello!');
|
|
344
|
-
// @eslint-disable-next-line/@typescript-eslint/ban-ts-comment
|
|
345
380
|
const res = await chat.invoke([message]);
|
|
346
381
|
// console.log({ res });
|
|
347
382
|
});
|
|
348
383
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
384
|
+
describe('ChatAnthropic image inputs', () => {
|
|
385
|
+
test.each(['invoke', 'stream'])(
|
|
386
|
+
'Test ChatAnthropic image_url, %s',
|
|
387
|
+
async (invocationType: string) => {
|
|
388
|
+
const chat = new ChatAnthropic({
|
|
389
|
+
modelName,
|
|
390
|
+
maxRetries: 0,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
const dataUrlRes = invoke(chat, invocationType, [
|
|
394
|
+
new HumanMessage({
|
|
395
|
+
content: [
|
|
396
|
+
{
|
|
397
|
+
type: 'image_url',
|
|
398
|
+
image_url: {
|
|
399
|
+
url: 'data:image/jpeg;base64,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',
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
{ type: 'text', text: 'Describe this image.' },
|
|
403
|
+
],
|
|
404
|
+
}),
|
|
405
|
+
]);
|
|
406
|
+
|
|
407
|
+
await expect(dataUrlRes).resolves.toBeDefined();
|
|
408
|
+
|
|
409
|
+
const urlRes = invoke(chat, invocationType, [
|
|
410
|
+
new HumanMessage({
|
|
411
|
+
content: [
|
|
412
|
+
{
|
|
413
|
+
type: 'image_url',
|
|
414
|
+
image_url:
|
|
415
|
+
'https://upload.wikimedia.org/wikipedia/commons/thumb/3/30/RedDisc.svg/24px-RedDisc.svg.png',
|
|
416
|
+
},
|
|
417
|
+
{ type: 'text', text: 'Describe this image.' },
|
|
418
|
+
],
|
|
419
|
+
}),
|
|
420
|
+
]);
|
|
421
|
+
|
|
422
|
+
await expect(urlRes).resolves.toBeDefined();
|
|
423
|
+
|
|
424
|
+
const invalidSchemeRes = invoke(chat, invocationType, [
|
|
425
|
+
new HumanMessage({
|
|
426
|
+
content: [
|
|
427
|
+
{
|
|
428
|
+
type: 'image_url',
|
|
429
|
+
image_url: 'file:///path/to/image.png',
|
|
430
|
+
},
|
|
431
|
+
{ type: 'text', text: 'Describe this image.' },
|
|
432
|
+
],
|
|
433
|
+
}),
|
|
434
|
+
]);
|
|
435
|
+
|
|
436
|
+
await expect(invalidSchemeRes).rejects.toThrow(
|
|
437
|
+
[
|
|
438
|
+
'Invalid image URL protocol: "file:". Anthropic only supports images as http, https, or base64-encoded data URLs on \'image_url\' content blocks.',
|
|
439
|
+
'Example: data:image/png;base64,/9j/4AAQSk...',
|
|
440
|
+
'Example: https://example.com/image.jpg',
|
|
441
|
+
].join('\n\n')
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
const invalidUrlRes = invoke(chat, invocationType, [
|
|
445
|
+
new HumanMessage({
|
|
446
|
+
content: [
|
|
447
|
+
{
|
|
448
|
+
type: 'image_url',
|
|
449
|
+
image_url: "this isn't a valid URL",
|
|
450
|
+
},
|
|
451
|
+
{ type: 'text', text: 'Describe this image.' },
|
|
452
|
+
],
|
|
453
|
+
}),
|
|
454
|
+
]);
|
|
455
|
+
|
|
456
|
+
await expect(invalidUrlRes).rejects.toThrow(
|
|
457
|
+
[
|
|
458
|
+
`Malformed image URL: "this isn't a valid URL". Content blocks of type 'image_url' must be a valid http, https, or base64-encoded data URL.`,
|
|
459
|
+
'Example: data:image/png;base64,/9j/4AAQSk...',
|
|
460
|
+
'Example: https://example.com/image.jpg',
|
|
461
|
+
].join('\n\n')
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
test.each(['invoke', 'stream'])(
|
|
467
|
+
'Test ChatAnthropic Anthropic Image Block, %s',
|
|
468
|
+
async (invocationType: string) => {
|
|
469
|
+
const chat = new ChatAnthropic({
|
|
470
|
+
modelName,
|
|
471
|
+
maxRetries: 0,
|
|
472
|
+
});
|
|
473
|
+
const base64Res = invoke(chat, invocationType, [
|
|
474
|
+
new HumanMessage({
|
|
475
|
+
content: [
|
|
476
|
+
{
|
|
477
|
+
type: 'image',
|
|
478
|
+
source: {
|
|
479
|
+
type: 'base64',
|
|
480
|
+
media_type: 'image/jpeg',
|
|
481
|
+
data: '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',
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
type: 'text',
|
|
486
|
+
text: 'Describe this image',
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
}),
|
|
490
|
+
]);
|
|
491
|
+
|
|
492
|
+
await expect(base64Res).resolves.toBeDefined();
|
|
493
|
+
|
|
494
|
+
const urlRes = chat.invoke([
|
|
495
|
+
new HumanMessage({
|
|
496
|
+
content: [
|
|
497
|
+
{
|
|
498
|
+
type: 'image',
|
|
499
|
+
source: {
|
|
500
|
+
type: 'url',
|
|
501
|
+
url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/3/30/RedDisc.svg/24px-RedDisc.svg.png',
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
],
|
|
505
|
+
}),
|
|
506
|
+
]);
|
|
507
|
+
|
|
508
|
+
await expect(urlRes).resolves.toBeDefined();
|
|
509
|
+
}
|
|
510
|
+
);
|
|
369
511
|
});
|
|
370
512
|
|
|
371
513
|
test('Stream tokens', async () => {
|
|
372
514
|
const model = new ChatAnthropic({
|
|
373
|
-
|
|
515
|
+
modelName,
|
|
374
516
|
temperature: 0,
|
|
375
517
|
maxTokens: 10,
|
|
376
518
|
});
|
|
@@ -397,14 +539,14 @@ test('Stream tokens', async () => {
|
|
|
397
539
|
});
|
|
398
540
|
|
|
399
541
|
test('id is supplied when invoking', async () => {
|
|
400
|
-
const model = new ChatAnthropic();
|
|
542
|
+
const model = new ChatAnthropic({ modelName });
|
|
401
543
|
const result = await model.invoke('Hello');
|
|
402
544
|
expect(result.id).toBeDefined();
|
|
403
545
|
expect(result.id).not.toEqual('');
|
|
404
546
|
});
|
|
405
547
|
|
|
406
548
|
test('id is supplied when streaming', async () => {
|
|
407
|
-
const model = new ChatAnthropic();
|
|
549
|
+
const model = new ChatAnthropic({ modelName });
|
|
408
550
|
let finalChunk: AIMessageChunk | undefined;
|
|
409
551
|
for await (const chunk of await model.stream('Hello')) {
|
|
410
552
|
finalChunk = !finalChunk ? chunk : concat(finalChunk, chunk);
|
|
@@ -678,7 +820,7 @@ The current date is ${new Date().toISOString()}`;
|
|
|
678
820
|
|
|
679
821
|
test('system prompt caching', async () => {
|
|
680
822
|
const model = new ChatAnthropic({
|
|
681
|
-
|
|
823
|
+
modelName,
|
|
682
824
|
clientOptions: {
|
|
683
825
|
defaultHeaders: {
|
|
684
826
|
'anthropic-beta': 'prompt-caching-2024-07-31',
|
|
@@ -724,7 +866,7 @@ test('system prompt caching', async () => {
|
|
|
724
866
|
// TODO: Add proper test with long tool content
|
|
725
867
|
test.skip('tool caching', async () => {
|
|
726
868
|
const model = new ChatAnthropic({
|
|
727
|
-
|
|
869
|
+
modelName,
|
|
728
870
|
clientOptions: {
|
|
729
871
|
defaultHeaders: {
|
|
730
872
|
'anthropic-beta': 'prompt-caching-2024-07-31',
|
|
@@ -769,14 +911,22 @@ test.skip('tool caching', async () => {
|
|
|
769
911
|
);
|
|
770
912
|
});
|
|
771
913
|
|
|
914
|
+
test.skip('Test ChatAnthropic with custom client', async () => {
|
|
915
|
+
const client = new AnthropicVertex();
|
|
916
|
+
const chat = new ChatAnthropic({
|
|
917
|
+
modelName,
|
|
918
|
+
maxRetries: 0,
|
|
919
|
+
createClient: () => client,
|
|
920
|
+
});
|
|
921
|
+
const message = new HumanMessage('Hello!');
|
|
922
|
+
const res = await chat.invoke([message]);
|
|
923
|
+
// console.log({ res });
|
|
924
|
+
expect(res.usage_metadata?.input_token_details).toBeDefined();
|
|
925
|
+
});
|
|
926
|
+
|
|
772
927
|
test('human message caching', async () => {
|
|
773
928
|
const model = new ChatAnthropic({
|
|
774
|
-
|
|
775
|
-
clientOptions: {
|
|
776
|
-
defaultHeaders: {
|
|
777
|
-
'anthropic-beta': 'prompt-caching-2024-07-31',
|
|
778
|
-
},
|
|
779
|
-
},
|
|
929
|
+
modelName,
|
|
780
930
|
});
|
|
781
931
|
|
|
782
932
|
const messages = [
|
|
@@ -784,7 +934,7 @@ test('human message caching', async () => {
|
|
|
784
934
|
content: [
|
|
785
935
|
{
|
|
786
936
|
type: 'text',
|
|
787
|
-
text: `You are a
|
|
937
|
+
text: `You are a scotsman. Always respond in scotsman dialect.\nUse the following as context when answering questions: ${CACHED_TEXT}`,
|
|
788
938
|
},
|
|
789
939
|
],
|
|
790
940
|
}),
|
|
@@ -813,11 +963,10 @@ test('human message caching', async () => {
|
|
|
813
963
|
|
|
814
964
|
test('Can accept PDF documents', async () => {
|
|
815
965
|
const model = new ChatAnthropic({
|
|
816
|
-
|
|
966
|
+
modelName: pdfModelName,
|
|
817
967
|
});
|
|
818
968
|
|
|
819
|
-
const pdfPath =
|
|
820
|
-
'../langchain-community/src/document_loaders/tests/example_data/Jacob_Lee_Resume_2023.pdf';
|
|
969
|
+
const pdfPath = './src/llm/anthropic/Jacob_Lee_Resume_2023.pdf';
|
|
821
970
|
const pdfBase64 = await fs.readFile(pdfPath, 'base64');
|
|
822
971
|
|
|
823
972
|
const response = await model.invoke([
|
|
@@ -844,70 +993,253 @@ test('Can accept PDF documents', async () => {
|
|
|
844
993
|
expect(response.content.length).toBeGreaterThan(10);
|
|
845
994
|
});
|
|
846
995
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
996
|
+
describe('Citations', () => {
|
|
997
|
+
test('document blocks', async () => {
|
|
998
|
+
const citationsModel = new ChatAnthropic({
|
|
999
|
+
model: citationsModelName,
|
|
1000
|
+
});
|
|
1001
|
+
const messages = [
|
|
1002
|
+
{
|
|
1003
|
+
role: 'user',
|
|
1004
|
+
content: [
|
|
1005
|
+
{
|
|
1006
|
+
type: 'document',
|
|
1007
|
+
source: {
|
|
1008
|
+
type: 'text',
|
|
1009
|
+
media_type: 'text/plain',
|
|
1010
|
+
data: "The grass the user is asking about is bluegrass. The sky is orange because it's night.",
|
|
1011
|
+
},
|
|
1012
|
+
title: 'My Document',
|
|
1013
|
+
context: 'This is a trustworthy document.',
|
|
1014
|
+
citations: {
|
|
1015
|
+
enabled: true,
|
|
1016
|
+
},
|
|
1017
|
+
},
|
|
1018
|
+
{
|
|
1019
|
+
type: 'text',
|
|
1020
|
+
text: 'What color is the grass and sky?',
|
|
1021
|
+
},
|
|
1022
|
+
],
|
|
1023
|
+
},
|
|
1024
|
+
];
|
|
1025
|
+
|
|
1026
|
+
const response = await citationsModel.invoke(messages);
|
|
1027
|
+
|
|
1028
|
+
expect(response.content.length).toBeGreaterThan(2);
|
|
1029
|
+
expect(Array.isArray(response.content)).toBe(true);
|
|
1030
|
+
const blocksWithCitations = (response.content as any[]).filter(
|
|
1031
|
+
(block) => block.citations !== undefined
|
|
1032
|
+
);
|
|
1033
|
+
expect(blocksWithCitations.length).toEqual(2);
|
|
1034
|
+
expect(typeof blocksWithCitations[0].citations[0]).toEqual('object');
|
|
1035
|
+
|
|
1036
|
+
const stream = await citationsModel.stream(messages);
|
|
1037
|
+
let aggregated;
|
|
1038
|
+
let chunkHasCitation = false;
|
|
1039
|
+
for await (const chunk of stream) {
|
|
1040
|
+
aggregated = aggregated === undefined ? chunk : concat(aggregated, chunk);
|
|
1041
|
+
if (
|
|
1042
|
+
!chunkHasCitation &&
|
|
1043
|
+
Array.isArray(chunk.content) &&
|
|
1044
|
+
chunk.content.some((c: any) => c.citations !== undefined)
|
|
1045
|
+
) {
|
|
1046
|
+
chunkHasCitation = true;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
expect(chunkHasCitation).toBe(true);
|
|
1050
|
+
expect(Array.isArray(aggregated?.content)).toBe(true);
|
|
1051
|
+
expect(aggregated?.content.length).toBeGreaterThan(2);
|
|
1052
|
+
expect(
|
|
1053
|
+
(aggregated?.content as any[]).some((c) => c.citations !== undefined)
|
|
1054
|
+
).toBe(true);
|
|
850
1055
|
});
|
|
1056
|
+
describe('search result blocks', () => {
|
|
1057
|
+
const citationsModel = new ChatAnthropic({
|
|
1058
|
+
model: citationsModelName,
|
|
1059
|
+
clientOptions: {
|
|
1060
|
+
defaultHeaders: {
|
|
1061
|
+
'anthropic-beta': 'search-results-2025-06-09',
|
|
1062
|
+
},
|
|
1063
|
+
},
|
|
1064
|
+
});
|
|
851
1065
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1066
|
+
const messages = [
|
|
1067
|
+
{
|
|
1068
|
+
role: 'user',
|
|
1069
|
+
content: [
|
|
1070
|
+
{
|
|
1071
|
+
type: 'search_result',
|
|
1072
|
+
title: 'History of France',
|
|
1073
|
+
source: 'https://example.com/france-history',
|
|
1074
|
+
citations: { enabled: true },
|
|
1075
|
+
content: [
|
|
1076
|
+
{
|
|
1077
|
+
type: 'text',
|
|
1078
|
+
text: 'The capital of France is Paris.',
|
|
1079
|
+
},
|
|
1080
|
+
{
|
|
1081
|
+
type: 'text',
|
|
1082
|
+
text: 'The old capital of France was Lyon.',
|
|
1083
|
+
},
|
|
1084
|
+
],
|
|
862
1085
|
},
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1086
|
+
{
|
|
1087
|
+
type: 'search_result',
|
|
1088
|
+
title: 'Geography of France',
|
|
1089
|
+
source: 'https://example.com/france-geography',
|
|
1090
|
+
citations: { enabled: true },
|
|
1091
|
+
content: [
|
|
1092
|
+
{
|
|
1093
|
+
type: 'text',
|
|
1094
|
+
text: 'France is a country in Europe.',
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
type: 'text',
|
|
1098
|
+
text: 'France borders Spain to the south.',
|
|
1099
|
+
},
|
|
1100
|
+
],
|
|
867
1101
|
},
|
|
1102
|
+
{
|
|
1103
|
+
type: 'text',
|
|
1104
|
+
text: 'What is the capital of France and where is it located? You must cite your sources.',
|
|
1105
|
+
},
|
|
1106
|
+
],
|
|
1107
|
+
},
|
|
1108
|
+
];
|
|
1109
|
+
|
|
1110
|
+
test('without streaming', async () => {
|
|
1111
|
+
const response = await citationsModel.invoke(messages);
|
|
1112
|
+
|
|
1113
|
+
expect(Array.isArray(response.content)).toBe(true);
|
|
1114
|
+
expect(response.content.length).toBeGreaterThan(0);
|
|
1115
|
+
|
|
1116
|
+
// Check that we have cited content
|
|
1117
|
+
const blocksWithCitations = (response.content as any[]).filter(
|
|
1118
|
+
(block) => block.citations !== undefined
|
|
1119
|
+
);
|
|
1120
|
+
expect(blocksWithCitations.length).toBeGreaterThan(0);
|
|
1121
|
+
|
|
1122
|
+
// Verify citation structure
|
|
1123
|
+
const citation = blocksWithCitations[0].citations[0];
|
|
1124
|
+
expect(typeof citation).toBe('object');
|
|
1125
|
+
expect(citation.type).toBe('search_result_location');
|
|
1126
|
+
expect(citation.source).toBeDefined();
|
|
1127
|
+
});
|
|
1128
|
+
test('with streaming', async () => {
|
|
1129
|
+
// Test streaming
|
|
1130
|
+
const stream = await citationsModel.stream(messages);
|
|
1131
|
+
let aggregated;
|
|
1132
|
+
let chunkHasCitation = false;
|
|
1133
|
+
for await (const chunk of stream) {
|
|
1134
|
+
aggregated =
|
|
1135
|
+
aggregated === undefined ? chunk : concat(aggregated, chunk);
|
|
1136
|
+
if (
|
|
1137
|
+
!chunkHasCitation &&
|
|
1138
|
+
Array.isArray(chunk.content) &&
|
|
1139
|
+
chunk.content.some((c: any) => c.citations !== undefined)
|
|
1140
|
+
) {
|
|
1141
|
+
chunkHasCitation = true;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
expect(chunkHasCitation).toBe(true);
|
|
1145
|
+
expect(Array.isArray(aggregated?.content)).toBe(true);
|
|
1146
|
+
expect(
|
|
1147
|
+
(aggregated?.content as any[]).some((c) => c.citations !== undefined)
|
|
1148
|
+
).toBe(true);
|
|
1149
|
+
});
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
test('search result blocks from tool', async () => {
|
|
1153
|
+
const ragTool = tool(
|
|
1154
|
+
(): ChatAnthropicContentBlock[] => [
|
|
1155
|
+
{
|
|
1156
|
+
type: 'search_result',
|
|
1157
|
+
title: 'History of France',
|
|
1158
|
+
source: 'https://example.com/france-history',
|
|
1159
|
+
citations: { enabled: true },
|
|
1160
|
+
content: [
|
|
1161
|
+
{
|
|
1162
|
+
type: 'text',
|
|
1163
|
+
text: 'The capital of France is Paris.',
|
|
1164
|
+
},
|
|
1165
|
+
{
|
|
1166
|
+
type: 'text',
|
|
1167
|
+
text: 'France was established as a republic in 1792.',
|
|
1168
|
+
},
|
|
1169
|
+
],
|
|
868
1170
|
},
|
|
869
1171
|
{
|
|
870
|
-
type: '
|
|
871
|
-
|
|
1172
|
+
type: 'search_result',
|
|
1173
|
+
title: 'Geography of France',
|
|
1174
|
+
source: 'https://example.com/france-geography',
|
|
1175
|
+
citations: { enabled: true },
|
|
1176
|
+
content: [
|
|
1177
|
+
{
|
|
1178
|
+
type: 'text',
|
|
1179
|
+
text: 'France is located in Western Europe.',
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
type: 'text',
|
|
1183
|
+
text: 'France has a population of approximately 67 million people.',
|
|
1184
|
+
},
|
|
1185
|
+
],
|
|
872
1186
|
},
|
|
873
1187
|
],
|
|
874
|
-
|
|
875
|
-
|
|
1188
|
+
{
|
|
1189
|
+
name: 'search_knowledge_base',
|
|
1190
|
+
description: 'Search the knowledge base for information about France',
|
|
1191
|
+
schema: z.object({
|
|
1192
|
+
query: z.string().describe('The search query'),
|
|
1193
|
+
}),
|
|
1194
|
+
}
|
|
1195
|
+
);
|
|
876
1196
|
|
|
877
|
-
|
|
1197
|
+
const citationsModel = new ChatAnthropic({
|
|
1198
|
+
model: citationsModelName,
|
|
1199
|
+
clientOptions: {
|
|
1200
|
+
defaultHeaders: {
|
|
1201
|
+
'anthropic-beta': 'search-results-2025-06-09',
|
|
1202
|
+
},
|
|
1203
|
+
},
|
|
1204
|
+
}).bindTools([ragTool]);
|
|
878
1205
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
expect(blocksWithCitations.length).toEqual(2);
|
|
885
|
-
expect(typeof blocksWithCitations[0].citations[0]).toEqual('object');
|
|
1206
|
+
const messages = [
|
|
1207
|
+
new HumanMessage(
|
|
1208
|
+
'Search for information about France and tell me what you find with proper citations.'
|
|
1209
|
+
),
|
|
1210
|
+
];
|
|
886
1211
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1212
|
+
const response = await citationsModel.invoke(messages);
|
|
1213
|
+
messages.push(response);
|
|
1214
|
+
|
|
1215
|
+
expect(Array.isArray(response.content)).toBe(true);
|
|
1216
|
+
expect(response.content.length).toBeGreaterThan(0);
|
|
1217
|
+
|
|
1218
|
+
// Check that the model called the tool
|
|
1219
|
+
expect(response.tool_calls?.length).toBeGreaterThan(0);
|
|
1220
|
+
expect(response.tool_calls?.[0].name).toBe('search_knowledge_base');
|
|
1221
|
+
|
|
1222
|
+
const toolResponse = await ragTool.invoke(response.tool_calls![0]);
|
|
1223
|
+
messages.push(toolResponse);
|
|
1224
|
+
|
|
1225
|
+
const response2 = await citationsModel.invoke(messages);
|
|
1226
|
+
|
|
1227
|
+
expect(Array.isArray(response2.content)).toBe(true);
|
|
1228
|
+
expect(response2.content.length).toBeGreaterThan(0);
|
|
1229
|
+
// Make sure that a citation exists somewhere in the content list
|
|
1230
|
+
const citationBlock = (response2.content as any[]).find(
|
|
1231
|
+
(block: any) =>
|
|
1232
|
+
Array.isArray(block.citations) && block.citations.length > 0
|
|
1233
|
+
);
|
|
1234
|
+
expect(citationBlock).toBeDefined();
|
|
1235
|
+
expect(citationBlock.citations[0].type).toBe('search_result_location');
|
|
1236
|
+
expect(citationBlock.citations[0].source).toBeDefined();
|
|
1237
|
+
});
|
|
906
1238
|
});
|
|
907
1239
|
|
|
908
1240
|
test('Test thinking blocks multiturn invoke', async () => {
|
|
909
1241
|
const model = new ChatAnthropic({
|
|
910
|
-
model:
|
|
1242
|
+
model: extendedThinkingModelName,
|
|
911
1243
|
maxTokens: 5000,
|
|
912
1244
|
thinking: { type: 'enabled', budget_tokens: 2000 },
|
|
913
1245
|
});
|
|
@@ -945,7 +1277,7 @@ test('Test thinking blocks multiturn invoke', async () => {
|
|
|
945
1277
|
|
|
946
1278
|
test('Test thinking blocks multiturn streaming', async () => {
|
|
947
1279
|
const model = new ChatAnthropic({
|
|
948
|
-
model:
|
|
1280
|
+
model: extendedThinkingModelName,
|
|
949
1281
|
maxTokens: 5000,
|
|
950
1282
|
thinking: { type: 'enabled', budget_tokens: 2000 },
|
|
951
1283
|
});
|
|
@@ -986,7 +1318,7 @@ test('Test thinking blocks multiturn streaming', async () => {
|
|
|
986
1318
|
|
|
987
1319
|
test('Test redacted thinking blocks multiturn invoke', async () => {
|
|
988
1320
|
const model = new ChatAnthropic({
|
|
989
|
-
model:
|
|
1321
|
+
model: extendedThinkingModelName,
|
|
990
1322
|
maxTokens: 5000,
|
|
991
1323
|
thinking: { type: 'enabled', budget_tokens: 2000 },
|
|
992
1324
|
});
|
|
@@ -1023,7 +1355,7 @@ test('Test redacted thinking blocks multiturn invoke', async () => {
|
|
|
1023
1355
|
|
|
1024
1356
|
test('Test redacted thinking blocks multiturn streaming', async () => {
|
|
1025
1357
|
const model = new ChatAnthropic({
|
|
1026
|
-
model:
|
|
1358
|
+
model: extendedThinkingModelName,
|
|
1027
1359
|
maxTokens: 5000,
|
|
1028
1360
|
thinking: { type: 'enabled', budget_tokens: 2000 },
|
|
1029
1361
|
});
|