@librechat/agents 3.0.26 → 3.0.28
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/llm/anthropic/utils/message_inputs.cjs +35 -2
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +0 -13
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/stream.cjs +4 -1
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +10 -1
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +35 -2
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +0 -13
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/stream.mjs +4 -1
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +10 -1
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/package.json +2 -1
- package/src/llm/anthropic/utils/message_inputs.ts +55 -9
- package/src/llm/google/utils/common.ts +0 -14
- package/src/scripts/ant_web_search_edge_case.ts +162 -0
- package/src/stream.ts +4 -1
- package/src/tools/ToolNode.ts +12 -1
|
@@ -363,6 +363,32 @@ function _formatContent(message: BaseMessage) {
|
|
|
363
363
|
return content;
|
|
364
364
|
} else {
|
|
365
365
|
const contentBlocks = content.map((contentPart) => {
|
|
366
|
+
/**
|
|
367
|
+
* Skip malformed blocks that have server tool fields mixed with text type.
|
|
368
|
+
* These can occur when server_tool_use blocks get mislabeled during aggregation.
|
|
369
|
+
*/
|
|
370
|
+
if (
|
|
371
|
+
'id' in contentPart &&
|
|
372
|
+
'name' in contentPart &&
|
|
373
|
+
'input' in contentPart &&
|
|
374
|
+
contentPart.type === 'text'
|
|
375
|
+
) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Skip malformed web_search_tool_result blocks marked as text.
|
|
381
|
+
* These have tool_use_id and nested content arrays.
|
|
382
|
+
*/
|
|
383
|
+
if (
|
|
384
|
+
'tool_use_id' in contentPart &&
|
|
385
|
+
'content' in contentPart &&
|
|
386
|
+
Array.isArray(contentPart.content) &&
|
|
387
|
+
contentPart.type === 'text'
|
|
388
|
+
) {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
|
|
366
392
|
if (isDataContentBlock(contentPart)) {
|
|
367
393
|
return convertToProviderContentBlock(
|
|
368
394
|
contentPart,
|
|
@@ -459,6 +485,21 @@ function _formatContent(message: BaseMessage) {
|
|
|
459
485
|
}
|
|
460
486
|
}
|
|
461
487
|
|
|
488
|
+
/**
|
|
489
|
+
* Skip server tool blocks when formatting content for subsequent requests.
|
|
490
|
+
* Server tools (e.g., web_search) are executed by the provider's API, and both
|
|
491
|
+
* the tool use and their results are already included in the response.
|
|
492
|
+
* We should not send these back in subsequent requests as they're not tool_calls
|
|
493
|
+
* that need invocation - they're already completed inline.
|
|
494
|
+
*/
|
|
495
|
+
if (
|
|
496
|
+
contentPartCopy.type === 'server_tool_use' ||
|
|
497
|
+
contentPartCopy.type === 'web_search_tool_result' ||
|
|
498
|
+
contentPartCopy.type === 'web_search_result'
|
|
499
|
+
) {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
|
|
462
503
|
// TODO: Fix when SDK types are fixed
|
|
463
504
|
return {
|
|
464
505
|
...contentPartCopy,
|
|
@@ -487,10 +528,14 @@ function _formatContent(message: BaseMessage) {
|
|
|
487
528
|
input: contentPart.functionCall.args,
|
|
488
529
|
};
|
|
489
530
|
} else {
|
|
531
|
+
console.error(
|
|
532
|
+
'Unsupported content part:',
|
|
533
|
+
JSON.stringify(contentPart, null, 2)
|
|
534
|
+
);
|
|
490
535
|
throw new Error('Unsupported message content format');
|
|
491
536
|
}
|
|
492
537
|
});
|
|
493
|
-
return contentBlocks;
|
|
538
|
+
return contentBlocks.filter((block) => block !== null);
|
|
494
539
|
}
|
|
495
540
|
}
|
|
496
541
|
|
|
@@ -545,14 +590,15 @@ export function _convertMessagesToAnthropicPayload(
|
|
|
545
590
|
}
|
|
546
591
|
} else {
|
|
547
592
|
const { content } = message;
|
|
548
|
-
const hasMismatchedToolCalls = !message.tool_calls.every(
|
|
549
|
-
|
|
550
|
-
(
|
|
551
|
-
(contentPart
|
|
552
|
-
contentPart.type === '
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
593
|
+
const hasMismatchedToolCalls = !message.tool_calls.every(
|
|
594
|
+
(toolCall) =>
|
|
595
|
+
!!content.find(
|
|
596
|
+
(contentPart) =>
|
|
597
|
+
(contentPart.type === 'tool_use' ||
|
|
598
|
+
contentPart.type === 'input_json_delta' ||
|
|
599
|
+
contentPart.type === 'server_tool_use') &&
|
|
600
|
+
contentPart.id === toolCall.id
|
|
601
|
+
)
|
|
556
602
|
);
|
|
557
603
|
if (hasMismatchedToolCalls) {
|
|
558
604
|
console.warn(
|
|
@@ -316,20 +316,6 @@ function _convertLangChainContentToPart(
|
|
|
316
316
|
mimeType,
|
|
317
317
|
},
|
|
318
318
|
};
|
|
319
|
-
} else if (
|
|
320
|
-
content.type === 'document' ||
|
|
321
|
-
content.type === 'audio' ||
|
|
322
|
-
content.type === 'video'
|
|
323
|
-
) {
|
|
324
|
-
if (!isMultimodalModel) {
|
|
325
|
-
throw new Error(`This model does not support ${content.type}s`);
|
|
326
|
-
}
|
|
327
|
-
return {
|
|
328
|
-
inlineData: {
|
|
329
|
-
data: content.data,
|
|
330
|
-
mimeType: content.mimeType,
|
|
331
|
-
},
|
|
332
|
-
};
|
|
333
319
|
} else if (content.type === 'media') {
|
|
334
320
|
return messageContentMedia(content);
|
|
335
321
|
} else if (content.type === 'tool_use') {
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
// src/scripts/cli.ts
|
|
3
|
+
import { config } from 'dotenv';
|
|
4
|
+
config();
|
|
5
|
+
import { HumanMessage, BaseMessage } from '@langchain/core/messages';
|
|
6
|
+
import type * as t from '@/types';
|
|
7
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
8
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
9
|
+
import { Calculator } from '@/tools/Calculator';
|
|
10
|
+
|
|
11
|
+
import { getArgs } from '@/scripts/args';
|
|
12
|
+
import { Run } from '@/run';
|
|
13
|
+
import { GraphEvents, Callback, Providers } from '@/common';
|
|
14
|
+
import { getLLMConfig } from '@/utils/llmConfig';
|
|
15
|
+
|
|
16
|
+
const conversationHistory: BaseMessage[] = [];
|
|
17
|
+
let _contentParts: (t.MessageContentComplex | undefined)[] = [];
|
|
18
|
+
async function testStandardStreaming(): Promise<void> {
|
|
19
|
+
const { userName, location, currentDate } = await getArgs();
|
|
20
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
21
|
+
_contentParts = contentParts;
|
|
22
|
+
const customHandlers = {
|
|
23
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
24
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
25
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
26
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
27
|
+
handle: (
|
|
28
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
29
|
+
data: t.StreamEventData
|
|
30
|
+
): void => {
|
|
31
|
+
console.log('====== ON_RUN_STEP_COMPLETED ======');
|
|
32
|
+
// console.dir(data, { depth: null });
|
|
33
|
+
aggregateContent({
|
|
34
|
+
event,
|
|
35
|
+
data: data as unknown as { result: t.ToolEndEvent },
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
40
|
+
handle: (
|
|
41
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
42
|
+
data: t.StreamEventData
|
|
43
|
+
): void => {
|
|
44
|
+
console.log('====== ON_RUN_STEP ======');
|
|
45
|
+
console.dir(data, { depth: null });
|
|
46
|
+
aggregateContent({ event, data: data as t.RunStep });
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
50
|
+
handle: (
|
|
51
|
+
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
52
|
+
data: t.StreamEventData
|
|
53
|
+
): void => {
|
|
54
|
+
console.log('====== ON_RUN_STEP_DELTA ======');
|
|
55
|
+
console.dir(data, { depth: null });
|
|
56
|
+
aggregateContent({ event, data: data as t.RunStepDeltaEvent });
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
60
|
+
handle: (
|
|
61
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
62
|
+
data: t.StreamEventData
|
|
63
|
+
): void => {
|
|
64
|
+
// console.log('====== ON_MESSAGE_DELTA ======');
|
|
65
|
+
// console.dir(data, { depth: null });
|
|
66
|
+
aggregateContent({ event, data: data as t.MessageDeltaEvent });
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
[GraphEvents.TOOL_START]: {
|
|
70
|
+
handle: (
|
|
71
|
+
_event: string,
|
|
72
|
+
data: t.StreamEventData,
|
|
73
|
+
metadata?: Record<string, unknown>
|
|
74
|
+
): void => {
|
|
75
|
+
console.log('====== TOOL_START ======');
|
|
76
|
+
// console.dir(data, { depth: null });
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const llmConfig = getLLMConfig(
|
|
82
|
+
Providers.ANTHROPIC
|
|
83
|
+
) as t.AnthropicClientOptions & t.SharedLLMConfig;
|
|
84
|
+
llmConfig.model = 'claude-haiku-4-5';
|
|
85
|
+
|
|
86
|
+
const run = await Run.create<t.IState>({
|
|
87
|
+
runId: 'test-run-id',
|
|
88
|
+
graphConfig: {
|
|
89
|
+
type: 'standard',
|
|
90
|
+
llmConfig,
|
|
91
|
+
tools: [
|
|
92
|
+
{
|
|
93
|
+
type: 'web_search_20250305',
|
|
94
|
+
name: 'web_search',
|
|
95
|
+
max_uses: 5,
|
|
96
|
+
},
|
|
97
|
+
new Calculator(),
|
|
98
|
+
],
|
|
99
|
+
instructions: 'You are a friendly AI assistant.',
|
|
100
|
+
// additional_instructions: `Always address the user by their name. The user's name is ${userName} and they are located in ${location}.`,
|
|
101
|
+
},
|
|
102
|
+
returnContent: true,
|
|
103
|
+
customHandlers,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const config = {
|
|
107
|
+
configurable: {
|
|
108
|
+
provider: Providers.ANTHROPIC,
|
|
109
|
+
thread_id: 'conversation-num-1',
|
|
110
|
+
},
|
|
111
|
+
streamMode: 'values',
|
|
112
|
+
version: 'v2' as const,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
console.log('Test 1: Web search + calculator (simultaneous tool test)');
|
|
116
|
+
|
|
117
|
+
// const userMessage = `
|
|
118
|
+
// Make a search for the weather in ${location} today, which is ${currentDate}.
|
|
119
|
+
// Before making the search, please let me know what you're about to do, then immediately start searching without hesitation.
|
|
120
|
+
// Make sure to always refer to me by name, which is ${userName}.
|
|
121
|
+
// After giving me a thorough summary, tell me a joke about the weather forecast we went over.
|
|
122
|
+
// `;
|
|
123
|
+
// const userMessage = 'Are massage guns good?';
|
|
124
|
+
// const userMessage = 'What is functional programming?';
|
|
125
|
+
// const userMessage = "Get me today's trending news.";
|
|
126
|
+
// const userMessage = "search recent italy earthquake volcano activity";
|
|
127
|
+
// const userMessage =
|
|
128
|
+
// "use 'Trump' as the exact search query and tell me what you find.";
|
|
129
|
+
const userMessage =
|
|
130
|
+
'Can you search the web for the current population of Tokyo, and also calculate what 15% of that population would be? Do both at the same time.';
|
|
131
|
+
|
|
132
|
+
conversationHistory.push(new HumanMessage(userMessage));
|
|
133
|
+
|
|
134
|
+
const inputs = {
|
|
135
|
+
messages: conversationHistory,
|
|
136
|
+
};
|
|
137
|
+
const finalContentParts = await run.processStream(inputs, config);
|
|
138
|
+
const finalMessages = run.getRunMessages();
|
|
139
|
+
if (finalMessages) {
|
|
140
|
+
conversationHistory.push(...finalMessages);
|
|
141
|
+
console.dir(conversationHistory, { depth: null });
|
|
142
|
+
}
|
|
143
|
+
// console.dir(finalContentParts, { depth: null });
|
|
144
|
+
console.log('\n\n====================\n\n');
|
|
145
|
+
// console.dir(contentParts, { depth: null });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
149
|
+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
150
|
+
console.log('Content Parts:');
|
|
151
|
+
console.dir(_contentParts, { depth: null });
|
|
152
|
+
process.exit(1);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
testStandardStreaming().catch((err) => {
|
|
156
|
+
console.error(err);
|
|
157
|
+
console.log('Conversation history:');
|
|
158
|
+
console.dir(conversationHistory, { depth: null });
|
|
159
|
+
console.log('Content Parts:');
|
|
160
|
+
console.dir(_contentParts, { depth: null });
|
|
161
|
+
process.exit(1);
|
|
162
|
+
});
|
package/src/stream.ts
CHANGED
|
@@ -155,7 +155,10 @@ export class ChatModelStreamHandler implements t.EventHandler {
|
|
|
155
155
|
chunk.tool_calls.length > 0 &&
|
|
156
156
|
chunk.tool_calls.every(
|
|
157
157
|
(tc) =>
|
|
158
|
-
tc.id != null &&
|
|
158
|
+
tc.id != null &&
|
|
159
|
+
tc.id !== '' &&
|
|
160
|
+
(tc as Partial<ToolCall>).name != null &&
|
|
161
|
+
tc.name !== ''
|
|
159
162
|
)
|
|
160
163
|
) {
|
|
161
164
|
hasToolCalls = true;
|
package/src/tools/ToolNode.ts
CHANGED
|
@@ -201,7 +201,18 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
201
201
|
|
|
202
202
|
outputs = await Promise.all(
|
|
203
203
|
aiMessage.tool_calls
|
|
204
|
-
?.filter((call) =>
|
|
204
|
+
?.filter((call) => {
|
|
205
|
+
/**
|
|
206
|
+
* Filter out:
|
|
207
|
+
* 1. Already processed tool calls (present in toolMessageIds)
|
|
208
|
+
* 2. Server tool calls (e.g., web_search with IDs starting with 'srvtoolu_')
|
|
209
|
+
* which are executed by the provider's API and don't require invocation
|
|
210
|
+
*/
|
|
211
|
+
return (
|
|
212
|
+
(call.id == null || !toolMessageIds.has(call.id)) &&
|
|
213
|
+
!(call.id?.startsWith('srvtoolu_') ?? false)
|
|
214
|
+
);
|
|
215
|
+
})
|
|
205
216
|
.map((call) => this.runTool(call, config)) ?? []
|
|
206
217
|
);
|
|
207
218
|
}
|