@librechat/agents 1.4.5 → 1.4.7

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,120 @@
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 { TavilySearchResults } from '@langchain/community/tools/tavily_search';
7
+ import type * as t from '@/types';
8
+ import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
9
+ import { ToolEndHandler } from '@/events';
10
+
11
+
12
+ import { getArgs } from '@/scripts/args';
13
+ import { Run } from '@/run';
14
+ import { GraphEvents, Callback } from '@/common';
15
+ import { getLLMConfig } from '@/utils/llmConfig';
16
+
17
+ const conversationHistory: BaseMessage[] = [];
18
+ async function testStandardStreaming(): Promise<void> {
19
+ const { userName, location, provider, currentDate } = await getArgs();
20
+ const { contentParts, aggregateContent } = createContentAggregator();
21
+ const customHandlers = {
22
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
23
+ // [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
24
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
25
+ [GraphEvents.ON_RUN_STEP_COMPLETED]: {
26
+ handle: (event: GraphEvents.ON_RUN_STEP_COMPLETED, data: t.StreamEventData): void => {
27
+ console.log('====== ON_RUN_STEP_COMPLETED ======');
28
+ // console.dir(data, { depth: null });
29
+ aggregateContent({ event, data: data as unknown as { result: t.ToolEndEvent } });
30
+ }
31
+ },
32
+ [GraphEvents.ON_RUN_STEP]: {
33
+ handle: (event: GraphEvents.ON_RUN_STEP, data: t.StreamEventData): void => {
34
+ console.log('====== ON_RUN_STEP ======');
35
+ console.dir(data, { depth: null });
36
+ aggregateContent({ event, data: data as t.RunStep });
37
+ }
38
+ },
39
+ [GraphEvents.ON_RUN_STEP_DELTA]: {
40
+ handle: (event: GraphEvents.ON_RUN_STEP_DELTA, data: t.StreamEventData): void => {
41
+ console.log('====== ON_RUN_STEP_DELTA ======');
42
+ console.dir(data, { depth: null });
43
+ aggregateContent({ event, data: data as t.RunStepDeltaEvent });
44
+ }
45
+ },
46
+ [GraphEvents.ON_MESSAGE_DELTA]: {
47
+ handle: (event: GraphEvents.ON_MESSAGE_DELTA, data: t.StreamEventData): void => {
48
+ console.log('====== ON_MESSAGE_DELTA ======');
49
+ console.dir(data, { depth: null });
50
+ aggregateContent({ event, data: data as t.MessageDeltaEvent });
51
+ }
52
+ },
53
+ [GraphEvents.TOOL_START]: {
54
+ handle: (_event: string, data: t.StreamEventData, metadata?: Record<string, unknown>): void => {
55
+ console.log('====== TOOL_START ======');
56
+ // console.dir(data, { depth: null });
57
+ }
58
+ },
59
+ };
60
+
61
+ const llmConfig = getLLMConfig(provider);
62
+
63
+ const run = await Run.create<t.IState>({
64
+ graphConfig: {
65
+ type: 'standard',
66
+ llmConfig,
67
+ tools: [new TavilySearchResults()],
68
+ instructions: 'You are a friendly AI assistant. Always address the user by their name.',
69
+ additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
70
+ },
71
+ returnContent: true,
72
+ customHandlers,
73
+ });
74
+
75
+ const config = {
76
+ configurable: {
77
+ provider,
78
+ thread_id: 'conversation-num-1',
79
+ },
80
+ streamMode: 'values',
81
+ version: 'v2' as const,
82
+ };
83
+
84
+ console.log('Test 1: Weather query (content parts test)');
85
+
86
+ const userMessage = `
87
+ Make a search for the weather in ${location} today, which is ${currentDate}.
88
+ Before making the search, please let me know what you're about to do, then immediately start searching without hesitation.
89
+ Make sure to always refer to me by name, which is ${userName}.
90
+ After giving me a thorough summary, tell me a joke about the weather forecast we went over.
91
+ `;
92
+
93
+ conversationHistory.push(new HumanMessage(userMessage));
94
+
95
+ const inputs = {
96
+ messages: conversationHistory,
97
+ };
98
+ const finalContentParts = await run.processStream(inputs, config);
99
+ const finalMessages = run.getRunMessages();
100
+ if (finalMessages) {
101
+ conversationHistory.push(...finalMessages);
102
+ console.dir(conversationHistory, { depth: null });
103
+ }
104
+ // console.dir(finalContentParts, { depth: null });
105
+ console.log('\n\n====================\n\n');
106
+ // console.dir(contentParts, { depth: null });
107
+ }
108
+
109
+ process.on('unhandledRejection', (reason, promise) => {
110
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
111
+ console.log('Conversation history:');
112
+ process.exit(1);
113
+ });
114
+
115
+ testStandardStreaming().catch((err) => {
116
+ console.error(err);
117
+ console.log('Conversation history:');
118
+ console.dir(conversationHistory, { depth: null });
119
+ process.exit(1);
120
+ });
package/src/stream.ts CHANGED
@@ -56,15 +56,51 @@ export class ChatModelStreamHandler implements t.EventHandler {
56
56
 
57
57
  if (hasToolCalls && chunk.tool_calls?.every((tc) => tc.id)) {
58
58
  const tool_calls: ToolCall[] = [];
59
+ const tool_call_ids: string[] = [];
59
60
  for (const tool_call of chunk.tool_calls) {
60
- if (!tool_call.id || graph.toolCallStepIds.has(tool_call.id)) {
61
+ const toolCallId = tool_call.id ?? '';
62
+ if (!toolCallId || graph.toolCallStepIds.has(toolCallId)) {
61
63
  continue;
62
64
  }
63
65
 
64
66
  tool_calls.push(tool_call);
67
+ tool_call_ids.push(toolCallId);
65
68
  }
66
69
 
67
70
  const stepKey = graph.getStepKey(metadata);
71
+
72
+ let prevStepId = '';
73
+ let prevRunStep: t.RunStep | undefined;
74
+ try {
75
+ prevStepId = graph.getStepIdByKey(stepKey, graph.contentData.length - 1);
76
+ prevRunStep = graph.getRunStep(prevStepId);
77
+ } catch (e) {
78
+ // no previous step
79
+ }
80
+
81
+ const dispatchToolCallIds = (lastMessageStepId: string): void => {
82
+ graph.dispatchMessageDelta(lastMessageStepId, {
83
+ content: [{
84
+ type: 'text',
85
+ text: '',
86
+ tool_call_ids,
87
+ }],
88
+ });
89
+ };
90
+ /* If the previous step exists and is a message creation */
91
+ if (prevStepId && prevRunStep && prevRunStep.type === StepTypes.MESSAGE_CREATION) {
92
+ dispatchToolCallIds(prevStepId);
93
+ /* If the previous step doesn't exist or is not a message creation */
94
+ } else if (!prevRunStep || prevRunStep.type !== StepTypes.MESSAGE_CREATION) {
95
+ const messageId = getMessageId(stepKey, graph) ?? '';
96
+ const stepId = graph.dispatchRunStep(stepKey, {
97
+ type: StepTypes.MESSAGE_CREATION,
98
+ message_creation: {
99
+ message_id: messageId,
100
+ },
101
+ });
102
+ dispatchToolCallIds(stepId);
103
+ }
68
104
  graph.dispatchRunStep(stepKey, {
69
105
  type: StepTypes.TOOL_CALLS,
70
106
  tool_calls,
@@ -90,7 +126,9 @@ export class ChatModelStreamHandler implements t.EventHandler {
90
126
  const stepKey = graph.getStepKey(metadata);
91
127
 
92
128
  if (hasToolCallChunks && chunk.tool_call_chunks?.length && typeof chunk.tool_call_chunks[0]?.index === 'number') {
93
- const stepId = graph.getStepIdByKey(stepKey, chunk.tool_call_chunks[0].index);
129
+ const prevStepId = graph.getStepIdByKey(stepKey, graph.contentData.length - 1);
130
+ const prevRunStep = graph.getRunStep(prevStepId);
131
+ const stepId = graph.getStepIdByKey(stepKey, prevRunStep?.index);
94
132
  graph.dispatchRunStepDelta(stepId, {
95
133
  type: StepTypes.TOOL_CALLS,
96
134
  tool_calls: chunk.tool_call_chunks,
@@ -183,16 +221,27 @@ export function createContentAggregator(): ContentAggregatorResult {
183
221
  contentParts[index] = { type: partType };
184
222
  }
185
223
 
224
+ if (!partType.startsWith(contentParts[index]?.type ?? '')) {
225
+ console.warn('Content type mismatch');
226
+ return;
227
+ }
228
+
186
229
  if (
187
230
  partType.startsWith(ContentTypes.TEXT) &&
188
231
  ContentTypes.TEXT in contentPart &&
189
232
  typeof contentPart.text === 'string'
190
233
  ) {
191
- const currentContent = contentParts[index] as { type: ContentTypes.TEXT; text: string };
192
- contentParts[index] = {
234
+ // TODO: update this!!
235
+ const currentContent = contentParts[index] as t.MessageDeltaUpdate;
236
+ const update: t.MessageDeltaUpdate = {
193
237
  type: ContentTypes.TEXT,
194
238
  text: (currentContent.text || '') + contentPart.text,
195
239
  };
240
+
241
+ if (contentPart.tool_call_ids) {
242
+ update.tool_call_ids = contentPart.tool_call_ids;
243
+ }
244
+ contentParts[index] = update;
196
245
  } else if (partType === ContentTypes.IMAGE_URL && 'image_url' in contentPart) {
197
246
  const currentContent = contentParts[index] as { type: 'image_url'; image_url: string };
198
247
  contentParts[index] = {
@@ -1,7 +1,7 @@
1
1
  // src/types/stream.ts
2
2
  import type { MessageContentImageUrl, MessageContentText, ToolMessage } from '@langchain/core/messages';
3
3
  import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
4
- import { StepTypes } from '@/common/enum';
4
+ import { StepTypes, ContentTypes } from '@/common/enum';
5
5
 
6
6
  /** Event names are of the format: on_[runnable_type]_(start|stream|end).
7
7
 
@@ -158,8 +158,14 @@ export interface MessageDelta {
158
158
  * The content of the message in array of text and/or images.
159
159
  */
160
160
  content?: MessageContentComplex[];
161
+ /**
162
+ * The tool call ids associated with the message.
163
+ */
164
+ tool_call_ids?: string[];
161
165
  }
162
166
 
167
+ export type MessageDeltaUpdate = { type: ContentTypes.TEXT; text: string; tool_call_ids?: string[] };
168
+
163
169
  export type ContentType = 'text' | 'image_url' | 'tool_call' | string;
164
170
 
165
171
  // eslint-disable-next-line @typescript-eslint/no-explicit-any