@librechat/agents 3.0.27 → 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.
@@ -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 && tc.id !== '' && tc.name != null && tc.name !== ''
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;
@@ -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) => call.id == null || !toolMessageIds.has(call.id))
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
  }