@librechat/agents 3.0.0-rc1 → 3.0.0-rc11
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/common/enum.cjs +1 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +7 -2
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +229 -44
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +21 -2
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/google/index.cjs +3 -0
- package/dist/cjs/llm/google/index.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +13 -0
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/ollama/index.cjs +3 -0
- package/dist/cjs/llm/ollama/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +53 -1
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +6 -1
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +5 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/vertexai/index.cjs +1 -1
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +3 -1
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +5 -1
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +52 -34
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +28 -0
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/run.cjs +28 -15
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +1 -1
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +2 -0
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/search/firecrawl.cjs +3 -1
- package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
- package/dist/cjs/tools/search/rerankers.cjs +8 -6
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs +5 -5
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/serper-scraper.cjs +132 -0
- package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -0
- package/dist/cjs/tools/search/tool.cjs +46 -9
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/utils/handlers.cjs +70 -0
- package/dist/cjs/utils/handlers.cjs.map +1 -0
- package/dist/esm/common/enum.mjs +1 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +7 -2
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +230 -45
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +21 -2
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/google/index.mjs +3 -0
- package/dist/esm/llm/google/index.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +13 -0
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/ollama/index.mjs +3 -0
- package/dist/esm/llm/ollama/index.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +53 -1
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +6 -1
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +5 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/vertexai/index.mjs +1 -1
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -1
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +5 -1
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +52 -34
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +28 -0
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/run.mjs +28 -15
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +1 -1
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +2 -0
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/search/firecrawl.mjs +3 -1
- package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
- package/dist/esm/tools/search/rerankers.mjs +8 -6
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs +5 -5
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/serper-scraper.mjs +129 -0
- package/dist/esm/tools/search/serper-scraper.mjs.map +1 -0
- package/dist/esm/tools/search/tool.mjs +46 -9
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/utils/handlers.mjs +68 -0
- package/dist/esm/utils/handlers.mjs.map +1 -0
- package/dist/types/common/enum.d.ts +2 -1
- package/dist/types/graphs/MultiAgentGraph.d.ts +12 -2
- package/dist/types/llm/anthropic/index.d.ts +3 -0
- package/dist/types/llm/google/index.d.ts +1 -0
- package/dist/types/llm/ollama/index.d.ts +1 -0
- package/dist/types/llm/openai/index.d.ts +14 -0
- package/dist/types/llm/openrouter/index.d.ts +4 -2
- package/dist/types/llm/vertexai/index.d.ts +1 -1
- package/dist/types/messages/format.d.ts +23 -20
- package/dist/types/run.d.ts +1 -1
- package/dist/types/tools/search/firecrawl.d.ts +2 -1
- package/dist/types/tools/search/rerankers.d.ts +4 -1
- package/dist/types/tools/search/search.d.ts +1 -2
- package/dist/types/tools/search/serper-scraper.d.ts +59 -0
- package/dist/types/tools/search/tool.d.ts +25 -4
- package/dist/types/tools/search/types.d.ts +31 -1
- package/dist/types/types/graph.d.ts +38 -4
- package/dist/types/types/llm.d.ts +1 -0
- package/dist/types/types/run.d.ts +5 -1
- package/dist/types/utils/handlers.d.ts +34 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/package.json +11 -3
- package/src/common/enum.ts +1 -0
- package/src/graphs/Graph.ts +8 -2
- package/src/graphs/MultiAgentGraph.ts +267 -50
- package/src/llm/anthropic/index.ts +23 -2
- package/src/llm/google/index.ts +4 -0
- package/src/llm/google/utils/common.ts +14 -0
- package/src/llm/ollama/index.ts +3 -0
- package/src/llm/openai/index.ts +60 -1
- package/src/llm/openai/utils/index.ts +7 -1
- package/src/llm/openrouter/index.ts +15 -6
- package/src/llm/vertexai/index.ts +2 -2
- package/src/messages/core.ts +5 -2
- package/src/messages/format.ts +67 -39
- package/src/messages/formatMessage.test.ts +418 -2
- package/src/messages/prune.ts +51 -0
- package/src/run.ts +38 -27
- package/src/scripts/multi-agent-chain.ts +278 -0
- package/src/scripts/multi-agent-document-review-chain.ts +197 -0
- package/src/scripts/multi-agent-hybrid-flow.ts +310 -0
- package/src/scripts/multi-agent-parallel.ts +27 -23
- package/src/scripts/multi-agent-supervisor.ts +362 -0
- package/src/scripts/search.ts +5 -1
- package/src/scripts/test-custom-prompt-key.ts +145 -0
- package/src/scripts/test-handoff-input.ts +170 -0
- package/src/scripts/test-multi-agent-list-handoff.ts +261 -0
- package/src/scripts/test-tools-before-handoff.ts +233 -0
- package/src/scripts/tools.ts +4 -1
- package/src/stream.ts +4 -1
- package/src/tools/search/firecrawl.ts +5 -2
- package/src/tools/search/jina-reranker.test.ts +126 -0
- package/src/tools/search/rerankers.ts +11 -5
- package/src/tools/search/search.ts +6 -8
- package/src/tools/search/serper-scraper.ts +155 -0
- package/src/tools/search/tool.ts +49 -8
- package/src/tools/search/types.ts +46 -0
- package/src/types/graph.ts +51 -5
- package/src/types/llm.ts +1 -0
- package/src/types/run.ts +6 -1
- package/src/utils/handlers.ts +107 -0
- package/src/utils/index.ts +2 -1
- package/src/utils/llmConfig.ts +35 -1
- package/dist/types/scripts/abort.d.ts +0 -1
- package/dist/types/scripts/ant_web_search.d.ts +0 -1
- package/dist/types/scripts/args.d.ts +0 -7
- package/dist/types/scripts/caching.d.ts +0 -1
- package/dist/types/scripts/cli.d.ts +0 -1
- package/dist/types/scripts/cli2.d.ts +0 -1
- package/dist/types/scripts/cli3.d.ts +0 -1
- package/dist/types/scripts/cli4.d.ts +0 -1
- package/dist/types/scripts/cli5.d.ts +0 -1
- package/dist/types/scripts/code_exec.d.ts +0 -1
- package/dist/types/scripts/code_exec_files.d.ts +0 -1
- package/dist/types/scripts/code_exec_simple.d.ts +0 -1
- package/dist/types/scripts/content.d.ts +0 -1
- package/dist/types/scripts/empty_input.d.ts +0 -1
- package/dist/types/scripts/handoff-test.d.ts +0 -1
- package/dist/types/scripts/image.d.ts +0 -1
- package/dist/types/scripts/memory.d.ts +0 -1
- package/dist/types/scripts/multi-agent-conditional.d.ts +0 -1
- package/dist/types/scripts/multi-agent-parallel.d.ts +0 -1
- package/dist/types/scripts/multi-agent-sequence.d.ts +0 -1
- package/dist/types/scripts/multi-agent-test.d.ts +0 -1
- package/dist/types/scripts/search.d.ts +0 -1
- package/dist/types/scripts/simple.d.ts +0 -1
- package/dist/types/scripts/stream.d.ts +0 -1
- package/dist/types/scripts/thinking.d.ts +0 -1
- package/dist/types/scripts/tools.d.ts +0 -1
- package/dist/types/specs/spec.utils.d.ts +0 -1
- package/src/scripts/multi-agent-example-output.md +0 -110
|
@@ -1,8 +1,43 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
HumanMessage,
|
|
3
|
+
AIMessage,
|
|
4
|
+
SystemMessage,
|
|
5
|
+
} from '@langchain/core/messages';
|
|
6
|
+
import type { MessageContentComplex } from '@/types';
|
|
7
|
+
import {
|
|
8
|
+
formatMessage,
|
|
9
|
+
formatLangChainMessages,
|
|
10
|
+
formatFromLangChain,
|
|
11
|
+
formatMediaMessage,
|
|
12
|
+
} from './format';
|
|
13
|
+
import { Providers } from '@/common';
|
|
3
14
|
|
|
4
15
|
const NO_PARENT = '00000000-0000-0000-0000-000000000000';
|
|
5
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Type for formatted message results with media content
|
|
19
|
+
*/
|
|
20
|
+
interface FormattedMediaMessage {
|
|
21
|
+
role: string;
|
|
22
|
+
content: MessageContentComplex[];
|
|
23
|
+
name?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Type guard to check if result is a FormattedMediaMessage
|
|
28
|
+
*/
|
|
29
|
+
function isFormattedMediaMessage(
|
|
30
|
+
result: unknown
|
|
31
|
+
): result is FormattedMediaMessage {
|
|
32
|
+
return (
|
|
33
|
+
typeof result === 'object' &&
|
|
34
|
+
result !== null &&
|
|
35
|
+
'role' in result &&
|
|
36
|
+
'content' in result &&
|
|
37
|
+
Array.isArray((result as FormattedMediaMessage).content)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
6
41
|
describe('formatMessage', () => {
|
|
7
42
|
it('formats user message', () => {
|
|
8
43
|
const input = {
|
|
@@ -187,6 +222,387 @@ describe('formatMessage', () => {
|
|
|
187
222
|
});
|
|
188
223
|
});
|
|
189
224
|
|
|
225
|
+
describe('formatMediaMessage', () => {
|
|
226
|
+
it('formats message with images for default provider', () => {
|
|
227
|
+
const message = {
|
|
228
|
+
role: 'user',
|
|
229
|
+
content: 'Check out this image',
|
|
230
|
+
name: 'John',
|
|
231
|
+
};
|
|
232
|
+
const mediaParts = [
|
|
233
|
+
{
|
|
234
|
+
type: 'image_url',
|
|
235
|
+
image_url: { url: 'https://example.com/image1.jpg' },
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
type: 'image_url',
|
|
239
|
+
image_url: { url: 'https://example.com/image2.jpg' },
|
|
240
|
+
},
|
|
241
|
+
];
|
|
242
|
+
|
|
243
|
+
const result = formatMediaMessage({ message, mediaParts });
|
|
244
|
+
|
|
245
|
+
expect(result.role).toBe('user');
|
|
246
|
+
expect(result.name).toBe('John');
|
|
247
|
+
expect(Array.isArray(result.content)).toBe(true);
|
|
248
|
+
expect(result.content).toHaveLength(3);
|
|
249
|
+
expect(result.content[0]).toEqual({
|
|
250
|
+
type: 'text',
|
|
251
|
+
text: 'Check out this image',
|
|
252
|
+
});
|
|
253
|
+
expect(result.content[1]).toEqual(mediaParts[0]);
|
|
254
|
+
expect(result.content[2]).toEqual(mediaParts[1]);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('formats message with images for Anthropic (media first)', () => {
|
|
258
|
+
const message = {
|
|
259
|
+
role: 'user',
|
|
260
|
+
content: 'Check out this image',
|
|
261
|
+
};
|
|
262
|
+
const mediaParts = [
|
|
263
|
+
{
|
|
264
|
+
type: 'image_url',
|
|
265
|
+
image_url: { url: 'https://example.com/image.jpg' },
|
|
266
|
+
},
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
const result = formatMediaMessage({
|
|
270
|
+
message,
|
|
271
|
+
mediaParts,
|
|
272
|
+
endpoint: Providers.ANTHROPIC,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
expect(result.content).toHaveLength(2);
|
|
276
|
+
expect(result.content[0]).toEqual(mediaParts[0]);
|
|
277
|
+
expect(result.content[1]).toEqual({
|
|
278
|
+
type: 'text',
|
|
279
|
+
text: 'Check out this image',
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('formats message with multiple media types', () => {
|
|
284
|
+
const message = {
|
|
285
|
+
role: 'user',
|
|
286
|
+
content: 'Check out these files',
|
|
287
|
+
};
|
|
288
|
+
const mediaParts = [
|
|
289
|
+
{ type: 'document', document: { url: 'https://example.com/doc.pdf' } },
|
|
290
|
+
{ type: 'video', video: { url: 'https://example.com/video.mp4' } },
|
|
291
|
+
{ type: 'audio', audio: { url: 'https://example.com/audio.mp3' } },
|
|
292
|
+
{
|
|
293
|
+
type: 'image_url',
|
|
294
|
+
image_url: { url: 'https://example.com/image.jpg' },
|
|
295
|
+
},
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
const result = formatMediaMessage({ message, mediaParts });
|
|
299
|
+
|
|
300
|
+
expect(result.content).toHaveLength(5);
|
|
301
|
+
expect(result.content[0]).toEqual({
|
|
302
|
+
type: 'text',
|
|
303
|
+
text: 'Check out these files',
|
|
304
|
+
});
|
|
305
|
+
expect(result.content[1]).toEqual(mediaParts[0]);
|
|
306
|
+
expect(result.content[2]).toEqual(mediaParts[1]);
|
|
307
|
+
expect(result.content[3]).toEqual(mediaParts[2]);
|
|
308
|
+
expect(result.content[4]).toEqual(mediaParts[3]);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe('formatMessage with media', () => {
|
|
313
|
+
it('formats user message with image_urls (backward compatibility)', () => {
|
|
314
|
+
const input = {
|
|
315
|
+
message: {
|
|
316
|
+
sender: 'user',
|
|
317
|
+
text: 'Check out this image',
|
|
318
|
+
image_urls: [
|
|
319
|
+
{
|
|
320
|
+
type: 'image_url' as const,
|
|
321
|
+
image_url: { url: 'https://example.com/image.jpg' },
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
},
|
|
325
|
+
userName: 'John',
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const result = formatMessage(input);
|
|
329
|
+
|
|
330
|
+
expect(isFormattedMediaMessage(result)).toBe(true);
|
|
331
|
+
if (isFormattedMediaMessage(result)) {
|
|
332
|
+
expect(result.role).toBe('user');
|
|
333
|
+
expect(result.name).toBe('John');
|
|
334
|
+
expect(Array.isArray(result.content)).toBe(true);
|
|
335
|
+
expect(result.content).toHaveLength(2);
|
|
336
|
+
expect(result.content[0]).toEqual({
|
|
337
|
+
type: 'text',
|
|
338
|
+
text: 'Check out this image',
|
|
339
|
+
});
|
|
340
|
+
expect(result.content[1]).toEqual(input.message.image_urls[0]);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('formats user message with documents', () => {
|
|
345
|
+
const input = {
|
|
346
|
+
message: {
|
|
347
|
+
role: 'user',
|
|
348
|
+
content: 'Review this document',
|
|
349
|
+
documents: [
|
|
350
|
+
{
|
|
351
|
+
type: 'document',
|
|
352
|
+
document: { url: 'https://example.com/report.pdf' },
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const result = formatMessage(input);
|
|
359
|
+
|
|
360
|
+
expect(isFormattedMediaMessage(result)).toBe(true);
|
|
361
|
+
if (isFormattedMediaMessage(result)) {
|
|
362
|
+
expect(result.role).toBe('user');
|
|
363
|
+
expect(Array.isArray(result.content)).toBe(true);
|
|
364
|
+
expect(result.content).toHaveLength(2);
|
|
365
|
+
expect(result.content[0]).toEqual({
|
|
366
|
+
type: 'text',
|
|
367
|
+
text: 'Review this document',
|
|
368
|
+
});
|
|
369
|
+
expect(result.content[1]).toEqual(input.message.documents[0]);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('formats user message with videos', () => {
|
|
374
|
+
const input = {
|
|
375
|
+
message: {
|
|
376
|
+
role: 'user',
|
|
377
|
+
content: 'Watch this video',
|
|
378
|
+
videos: [
|
|
379
|
+
{ type: 'video', video: { url: 'https://example.com/demo.mp4' } },
|
|
380
|
+
],
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const result = formatMessage(input);
|
|
385
|
+
|
|
386
|
+
expect(isFormattedMediaMessage(result)).toBe(true);
|
|
387
|
+
if (isFormattedMediaMessage(result)) {
|
|
388
|
+
expect(result.role).toBe('user');
|
|
389
|
+
expect(Array.isArray(result.content)).toBe(true);
|
|
390
|
+
expect(result.content).toHaveLength(2);
|
|
391
|
+
expect(result.content[0]).toEqual({
|
|
392
|
+
type: 'text',
|
|
393
|
+
text: 'Watch this video',
|
|
394
|
+
});
|
|
395
|
+
expect(result.content[1]).toEqual(input.message.videos[0]);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('formats user message with audios', () => {
|
|
400
|
+
const input = {
|
|
401
|
+
message: {
|
|
402
|
+
role: 'user',
|
|
403
|
+
content: 'Listen to this',
|
|
404
|
+
audios: [
|
|
405
|
+
{ type: 'audio', audio: { url: 'https://example.com/podcast.mp3' } },
|
|
406
|
+
],
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const result = formatMessage(input);
|
|
411
|
+
|
|
412
|
+
expect(isFormattedMediaMessage(result)).toBe(true);
|
|
413
|
+
if (isFormattedMediaMessage(result)) {
|
|
414
|
+
expect(result.role).toBe('user');
|
|
415
|
+
expect(Array.isArray(result.content)).toBe(true);
|
|
416
|
+
expect(result.content).toHaveLength(2);
|
|
417
|
+
expect(result.content[0]).toEqual({
|
|
418
|
+
type: 'text',
|
|
419
|
+
text: 'Listen to this',
|
|
420
|
+
});
|
|
421
|
+
expect(result.content[1]).toEqual(input.message.audios[0]);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('formats user message with all media types in correct order', () => {
|
|
426
|
+
const input = {
|
|
427
|
+
message: {
|
|
428
|
+
role: 'user',
|
|
429
|
+
content: 'Check out all these files',
|
|
430
|
+
documents: [
|
|
431
|
+
{
|
|
432
|
+
type: 'document',
|
|
433
|
+
document: { url: 'https://example.com/doc.pdf' },
|
|
434
|
+
},
|
|
435
|
+
],
|
|
436
|
+
videos: [
|
|
437
|
+
{ type: 'video', video: { url: 'https://example.com/video.mp4' } },
|
|
438
|
+
],
|
|
439
|
+
audios: [
|
|
440
|
+
{ type: 'audio', audio: { url: 'https://example.com/audio.mp3' } },
|
|
441
|
+
],
|
|
442
|
+
image_urls: [
|
|
443
|
+
{
|
|
444
|
+
type: 'image_url' as const,
|
|
445
|
+
image_url: { url: 'https://example.com/image.jpg' },
|
|
446
|
+
},
|
|
447
|
+
],
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const result = formatMessage(input);
|
|
452
|
+
|
|
453
|
+
expect(isFormattedMediaMessage(result)).toBe(true);
|
|
454
|
+
if (isFormattedMediaMessage(result)) {
|
|
455
|
+
expect(result.role).toBe('user');
|
|
456
|
+
expect(Array.isArray(result.content)).toBe(true);
|
|
457
|
+
expect(result.content).toHaveLength(5);
|
|
458
|
+
// Text first
|
|
459
|
+
expect(result.content[0]).toEqual({
|
|
460
|
+
type: 'text',
|
|
461
|
+
text: 'Check out all these files',
|
|
462
|
+
});
|
|
463
|
+
// Then documents, videos, audios, images
|
|
464
|
+
expect(result.content[1]).toEqual(input.message.documents[0]);
|
|
465
|
+
expect(result.content[2]).toEqual(input.message.videos[0]);
|
|
466
|
+
expect(result.content[3]).toEqual(input.message.audios[0]);
|
|
467
|
+
expect(result.content[4]).toEqual(input.message.image_urls[0]);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it('formats user message with multiple files of the same type', () => {
|
|
472
|
+
const input = {
|
|
473
|
+
message: {
|
|
474
|
+
role: 'user',
|
|
475
|
+
content: 'Review these documents',
|
|
476
|
+
documents: [
|
|
477
|
+
{
|
|
478
|
+
type: 'document',
|
|
479
|
+
document: { url: 'https://example.com/doc1.pdf' },
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
type: 'document',
|
|
483
|
+
document: { url: 'https://example.com/doc2.pdf' },
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
type: 'document',
|
|
487
|
+
document: { url: 'https://example.com/doc3.pdf' },
|
|
488
|
+
},
|
|
489
|
+
],
|
|
490
|
+
},
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
const result = formatMessage(input);
|
|
494
|
+
|
|
495
|
+
expect(isFormattedMediaMessage(result)).toBe(true);
|
|
496
|
+
if (isFormattedMediaMessage(result)) {
|
|
497
|
+
expect(result.content).toHaveLength(4);
|
|
498
|
+
expect(result.content[0].type).toBe('text');
|
|
499
|
+
expect(result.content[1]).toEqual(input.message.documents[0]);
|
|
500
|
+
expect(result.content[2]).toEqual(input.message.documents[1]);
|
|
501
|
+
expect(result.content[3]).toEqual(input.message.documents[2]);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('respects Anthropic provider ordering (media before text)', () => {
|
|
506
|
+
const input = {
|
|
507
|
+
message: {
|
|
508
|
+
role: 'user',
|
|
509
|
+
content: 'Check this out',
|
|
510
|
+
documents: [
|
|
511
|
+
{
|
|
512
|
+
type: 'document',
|
|
513
|
+
document: { url: 'https://example.com/doc.pdf' },
|
|
514
|
+
},
|
|
515
|
+
],
|
|
516
|
+
image_urls: [
|
|
517
|
+
{
|
|
518
|
+
type: 'image_url' as const,
|
|
519
|
+
image_url: { url: 'https://example.com/image.jpg' },
|
|
520
|
+
},
|
|
521
|
+
],
|
|
522
|
+
},
|
|
523
|
+
endpoint: Providers.ANTHROPIC,
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const result = formatMessage(input);
|
|
527
|
+
|
|
528
|
+
expect(isFormattedMediaMessage(result)).toBe(true);
|
|
529
|
+
if (isFormattedMediaMessage(result)) {
|
|
530
|
+
expect(result.content).toHaveLength(3);
|
|
531
|
+
// Media first for Anthropic
|
|
532
|
+
expect(result.content[0]).toEqual(input.message.documents[0]);
|
|
533
|
+
expect(result.content[1]).toEqual(input.message.image_urls[0]);
|
|
534
|
+
expect(result.content[2]).toEqual({
|
|
535
|
+
type: 'text',
|
|
536
|
+
text: 'Check this out',
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('does not format media for assistant messages', () => {
|
|
542
|
+
const input = {
|
|
543
|
+
message: {
|
|
544
|
+
role: 'assistant',
|
|
545
|
+
content: 'Here is a response',
|
|
546
|
+
documents: [
|
|
547
|
+
{
|
|
548
|
+
type: 'document',
|
|
549
|
+
document: { url: 'https://example.com/doc.pdf' },
|
|
550
|
+
},
|
|
551
|
+
],
|
|
552
|
+
},
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
const result = formatMessage(input);
|
|
556
|
+
|
|
557
|
+
expect(result).toMatchObject({
|
|
558
|
+
role: 'assistant',
|
|
559
|
+
content: 'Here is a response',
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it('handles empty media arrays gracefully', () => {
|
|
564
|
+
const input = {
|
|
565
|
+
message: {
|
|
566
|
+
role: 'user',
|
|
567
|
+
content: 'Just text',
|
|
568
|
+
documents: [],
|
|
569
|
+
videos: [],
|
|
570
|
+
audios: [],
|
|
571
|
+
image_urls: [],
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const result = formatMessage(input);
|
|
576
|
+
|
|
577
|
+
expect(result).toMatchObject({
|
|
578
|
+
role: 'user',
|
|
579
|
+
content: 'Just text',
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it('formats media with langChain flag', () => {
|
|
584
|
+
const input = {
|
|
585
|
+
message: {
|
|
586
|
+
role: 'user',
|
|
587
|
+
content: 'Check this image',
|
|
588
|
+
image_urls: [
|
|
589
|
+
{
|
|
590
|
+
type: 'image_url' as const,
|
|
591
|
+
image_url: { url: 'https://example.com/image.jpg' },
|
|
592
|
+
},
|
|
593
|
+
],
|
|
594
|
+
},
|
|
595
|
+
langChain: true,
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
const result = formatMessage(input);
|
|
599
|
+
|
|
600
|
+
expect(result).toBeInstanceOf(HumanMessage);
|
|
601
|
+
expect(Array.isArray(result.lc_kwargs.content)).toBe(true);
|
|
602
|
+
expect(result.lc_kwargs.content).toHaveLength(2);
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
|
|
190
606
|
describe('formatLangChainMessages', () => {
|
|
191
607
|
it('formats an array of messages for LangChain', () => {
|
|
192
608
|
const messages = [
|
package/src/messages/prune.ts
CHANGED
|
@@ -389,6 +389,14 @@ export function checkValidNumber(value: unknown): value is number {
|
|
|
389
389
|
return typeof value === 'number' && !isNaN(value) && value > 0;
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
+
type ThinkingBlocks = {
|
|
393
|
+
thinking_blocks?: Array<{
|
|
394
|
+
type: 'thinking';
|
|
395
|
+
thinking: string;
|
|
396
|
+
signature: string;
|
|
397
|
+
}>;
|
|
398
|
+
};
|
|
399
|
+
|
|
392
400
|
export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
|
|
393
401
|
const indexTokenCountMap = { ...factoryParams.indexTokenCountMap };
|
|
394
402
|
let lastTurnStartIndex = factoryParams.startIndex;
|
|
@@ -402,6 +410,49 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
|
|
|
402
410
|
context: BaseMessage[];
|
|
403
411
|
indexTokenCountMap: Record<string, number | undefined>;
|
|
404
412
|
} {
|
|
413
|
+
if (
|
|
414
|
+
factoryParams.provider === Providers.OPENAI &&
|
|
415
|
+
factoryParams.thinkingEnabled === true
|
|
416
|
+
) {
|
|
417
|
+
for (let i = lastTurnStartIndex; i < params.messages.length; i++) {
|
|
418
|
+
const m = params.messages[i];
|
|
419
|
+
if (
|
|
420
|
+
m.getType() === 'ai' &&
|
|
421
|
+
typeof m.additional_kwargs.reasoning_content === 'string' &&
|
|
422
|
+
Array.isArray(
|
|
423
|
+
(
|
|
424
|
+
m.additional_kwargs.provider_specific_fields as
|
|
425
|
+
| ThinkingBlocks
|
|
426
|
+
| undefined
|
|
427
|
+
)?.thinking_blocks
|
|
428
|
+
) &&
|
|
429
|
+
(m as AIMessage).tool_calls &&
|
|
430
|
+
((m as AIMessage).tool_calls?.length ?? 0) > 0
|
|
431
|
+
) {
|
|
432
|
+
const message = m as AIMessage;
|
|
433
|
+
const thinkingBlocks = (
|
|
434
|
+
message.additional_kwargs.provider_specific_fields as ThinkingBlocks
|
|
435
|
+
).thinking_blocks;
|
|
436
|
+
const signature =
|
|
437
|
+
thinkingBlocks?.[thinkingBlocks.length - 1].signature;
|
|
438
|
+
const thinkingBlock: ThinkingContentText = {
|
|
439
|
+
signature,
|
|
440
|
+
type: ContentTypes.THINKING,
|
|
441
|
+
thinking: message.additional_kwargs.reasoning_content as string,
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
params.messages[i] = new AIMessage({
|
|
445
|
+
...message,
|
|
446
|
+
content: [thinkingBlock],
|
|
447
|
+
additional_kwargs: {
|
|
448
|
+
...message.additional_kwargs,
|
|
449
|
+
reasoning_content: undefined,
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
405
456
|
let currentUsage: UsageMetadata | undefined;
|
|
406
457
|
if (
|
|
407
458
|
params.usageMetadata &&
|
package/src/run.ts
CHANGED
|
@@ -34,7 +34,7 @@ export const defaultOmitOptions = new Set([
|
|
|
34
34
|
export class Run<_T extends t.BaseGraphState> {
|
|
35
35
|
id: string;
|
|
36
36
|
private tokenCounter?: t.TokenCounter;
|
|
37
|
-
private handlerRegistry
|
|
37
|
+
private handlerRegistry?: HandlerRegistry;
|
|
38
38
|
private indexTokenCountMap?: Record<string, number>;
|
|
39
39
|
graphRunnable?: t.CompiledStateWorkflow;
|
|
40
40
|
Graph: StandardGraph | MultiAgentGraph | undefined;
|
|
@@ -73,7 +73,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
73
73
|
this.Graph.handlerRegistry = handlerRegistry;
|
|
74
74
|
}
|
|
75
75
|
} else {
|
|
76
|
-
|
|
76
|
+
/** Default to legacy graph for 'standard' or undefined type */
|
|
77
77
|
this.graphRunnable = this.createLegacyGraph(config.graphConfig);
|
|
78
78
|
if (this.Graph) {
|
|
79
79
|
this.Graph.compileOptions =
|
|
@@ -86,25 +86,38 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
private createLegacyGraph(
|
|
89
|
-
config: t.LegacyGraphConfig
|
|
89
|
+
config: t.LegacyGraphConfig | t.StandardGraphConfig
|
|
90
90
|
): t.CompiledStateWorkflow {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
91
|
+
let agentConfig: t.AgentInputs;
|
|
92
|
+
let signal: AbortSignal | undefined;
|
|
93
|
+
|
|
94
|
+
/** Check if this is a multi-agent style config (has agents array) */
|
|
95
|
+
if ('agents' in config && Array.isArray(config.agents)) {
|
|
96
|
+
if (config.agents.length === 0) {
|
|
97
|
+
throw new Error('At least one agent must be provided');
|
|
98
|
+
}
|
|
99
|
+
agentConfig = config.agents[0];
|
|
100
|
+
signal = config.signal;
|
|
101
|
+
} else {
|
|
102
|
+
/** Legacy path: build agent config from llmConfig */
|
|
103
|
+
const {
|
|
104
|
+
type: _type,
|
|
105
|
+
llmConfig,
|
|
106
|
+
signal: legacySignal,
|
|
107
|
+
tools = [],
|
|
108
|
+
...agentInputs
|
|
109
|
+
} = config as t.LegacyGraphConfig;
|
|
110
|
+
const { provider, ...clientOptions } = llmConfig;
|
|
111
|
+
|
|
112
|
+
agentConfig = {
|
|
113
|
+
...agentInputs,
|
|
114
|
+
tools,
|
|
115
|
+
provider,
|
|
116
|
+
clientOptions,
|
|
117
|
+
agentId: 'default',
|
|
118
|
+
};
|
|
119
|
+
signal = legacySignal;
|
|
120
|
+
}
|
|
108
121
|
|
|
109
122
|
const standardGraph = new StandardGraph({
|
|
110
123
|
signal,
|
|
@@ -113,10 +126,8 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
113
126
|
tokenCounter: this.tokenCounter,
|
|
114
127
|
indexTokenCountMap: this.indexTokenCountMap,
|
|
115
128
|
});
|
|
116
|
-
|
|
117
|
-
standardGraph.compileOptions =
|
|
118
|
-
config as t.LegacyGraphConfig
|
|
119
|
-
).compileOptions;
|
|
129
|
+
/** Propagate compile options from graph config */
|
|
130
|
+
standardGraph.compileOptions = config.compileOptions;
|
|
120
131
|
this.Graph = standardGraph;
|
|
121
132
|
return standardGraph.createWorkflow();
|
|
122
133
|
}
|
|
@@ -145,7 +156,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
145
156
|
static async create<T extends t.BaseGraphState>(
|
|
146
157
|
config: t.RunConfig
|
|
147
158
|
): Promise<Run<T>> {
|
|
148
|
-
|
|
159
|
+
/** Create tokenCounter if indexTokenCountMap is provided but tokenCounter is not */
|
|
149
160
|
if (config.indexTokenCountMap && !config.tokenCounter) {
|
|
150
161
|
config.tokenCounter = await createTokenCounter();
|
|
151
162
|
}
|
|
@@ -179,7 +190,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
179
190
|
) {
|
|
180
191
|
return;
|
|
181
192
|
}
|
|
182
|
-
const handler = this.handlerRegistry
|
|
193
|
+
const handler = this.handlerRegistry?.getHandler(eventName);
|
|
183
194
|
if (handler && this.Graph) {
|
|
184
195
|
await handler.handle(
|
|
185
196
|
eventName,
|
|
@@ -251,7 +262,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
251
262
|
continue;
|
|
252
263
|
}
|
|
253
264
|
|
|
254
|
-
const handler = this.handlerRegistry
|
|
265
|
+
const handler = this.handlerRegistry?.getHandler(eventName);
|
|
255
266
|
if (handler) {
|
|
256
267
|
await handler.handle(eventName, data, metadata, this.Graph);
|
|
257
268
|
}
|