@librechat/agents 1.4.4 → 1.4.6

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/src/messages.ts CHANGED
@@ -9,7 +9,7 @@ export function getConverseOverrideMessage({
9
9
  lastMessageY
10
10
  }: {
11
11
  userMessage: string[];
12
- lastMessageX: AIMessageChunk;
12
+ lastMessageX: AIMessageChunk | null;
13
13
  lastMessageY: ToolMessage;
14
14
  }): HumanMessage {
15
15
  const content = `
@@ -19,7 +19,7 @@ User: ${userMessage[1]}
19
19
  # YOU HAVE ALREADY RESPONDED TO THE LATEST USER MESSAGE:
20
20
 
21
21
  # Observations:
22
- - ${lastMessageX.content}
22
+ - ${lastMessageX?.content}
23
23
 
24
24
  # Tool Calls:
25
25
  - ${lastMessageX?.tool_calls?.join('\n- ')}
@@ -149,8 +149,8 @@ export function formatAnthropicMessage(message: AIMessageChunk): AIMessage {
149
149
  export function convertMessagesToContent(messages: BaseMessage[]): t.MessageContentComplex[] {
150
150
  const processedContent: t.MessageContentComplex[] = [];
151
151
 
152
- const addContentPart = (message: BaseMessage): void => {
153
- const content = message?.content;
152
+ const addContentPart = (message: BaseMessage | null): void => {
153
+ const content = message?.lc_kwargs.content != null ? message.lc_kwargs.content : message?.content;
154
154
  if (content === undefined) {
155
155
  return;
156
156
  }
@@ -169,7 +169,7 @@ export function convertMessagesToContent(messages: BaseMessage[]): t.MessageCont
169
169
  const toolCallMap = new Map<string, t.CustomToolCall>();
170
170
 
171
171
  for (let i = 0; i < messages.length; i++) {
172
- const message = messages[i];
172
+ const message = messages[i] as BaseMessage | null;
173
173
  const messageType = message?._getType();
174
174
 
175
175
  if (messageType === 'ai' && (message as AIMessage).tool_calls?.length) {
package/src/run.ts CHANGED
@@ -16,6 +16,7 @@ export class Run<T extends t.BaseGraphState> {
16
16
  private Graph: StandardGraph | undefined;
17
17
  provider: Providers | undefined;
18
18
  run_id: string | undefined;
19
+ returnContent: boolean = false;
19
20
 
20
21
  private constructor(config: t.RunConfig) {
21
22
  const handlerRegistry = new HandlerRegistry();
@@ -35,6 +36,8 @@ export class Run<T extends t.BaseGraphState> {
35
36
  this.Graph.handlerRegistry = handlerRegistry;
36
37
  }
37
38
  }
39
+
40
+ this.returnContent = config.returnContent ?? false;
38
41
  }
39
42
 
40
43
  private createStandardGraph(config: t.StandardGraphConfig): t.CompiledWorkflow<t.IState, Partial<t.IState>, string> {
@@ -108,7 +111,9 @@ export class Run<T extends t.BaseGraphState> {
108
111
  }
109
112
  }
110
113
 
111
- return this.Graph.getContentParts();
114
+ if (this.returnContent) {
115
+ return this.Graph.getContentParts();
116
+ }
112
117
  }
113
118
 
114
119
  private createSystemCallback<K extends keyof ClientCallbacks>(
@@ -68,6 +68,7 @@ async function testStandardStreaming(): Promise<void> {
68
68
  instructions: 'You are a friendly AI assistant. Always address the user by their name.',
69
69
  additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
70
70
  },
71
+ returnContent: true,
71
72
  customHandlers,
72
73
  });
73
74
 
@@ -99,9 +100,9 @@ async function testStandardStreaming(): Promise<void> {
99
100
  conversationHistory.push(...finalMessages);
100
101
  console.dir(conversationHistory, { depth: null });
101
102
  }
102
- console.dir(finalContentParts, { depth: null });
103
+ // console.dir(finalContentParts, { depth: null });
103
104
  console.log('\n\n====================\n\n');
104
- console.dir(contentParts, { depth: null });
105
+ // console.dir(contentParts, { depth: null });
105
106
  }
106
107
 
107
108
  process.on('unhandledRejection', (reason, promise) => {
@@ -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 (contentPart.type !== 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] = {
package/src/types/run.ts CHANGED
@@ -52,5 +52,6 @@ export type TaskManagerGraphConfig = {
52
52
  export type RunConfig = {
53
53
  graphConfig: StandardGraphConfig | CollaborativeGraphConfig | TaskManagerGraphConfig;
54
54
  customHandlers?: Record<string, g.EventHandler>;
55
+ returnContent?: boolean;
55
56
  runId?: string;
56
57
  };
@@ -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