@librechat/agents 2.4.41 → 2.4.42

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.
@@ -4,10 +4,18 @@
4
4
  import { config } from 'dotenv';
5
5
  config();
6
6
  import { Calculator } from '@langchain/community/tools/calculator';
7
- import { HumanMessage, BaseMessage, UsageMetadata } from '@langchain/core/messages';
7
+ import {
8
+ HumanMessage,
9
+ BaseMessage,
10
+ UsageMetadata,
11
+ } from '@langchain/core/messages';
8
12
  import type { StandardGraph } from '@/graphs';
9
13
  import type * as t from '@/types';
10
- import { ToolEndHandler, ModelEndHandler, createMetadataAggregator } from '@/events';
14
+ import {
15
+ ToolEndHandler,
16
+ ModelEndHandler,
17
+ createMetadataAggregator,
18
+ } from '@/events';
11
19
  import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
12
20
  import { ContentTypes, GraphEvents, Providers } from '@/common';
13
21
  import { capitalizeFirstLetter } from './spec.utils';
@@ -36,7 +44,8 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
36
44
  beforeEach(async () => {
37
45
  conversationHistory = [];
38
46
  collectedUsage = [];
39
- const { contentParts: cp, aggregateContent: ac } = createContentAggregator();
47
+ const { contentParts: cp, aggregateContent: ac } =
48
+ createContentAggregator();
40
49
  contentParts = cp as t.MessageContentComplex[];
41
50
  aggregateContent = ac;
42
51
  });
@@ -49,36 +58,62 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
49
58
  onRunStepSpy.mockReset();
50
59
  });
51
60
 
52
- const setupCustomHandlers = (): Record<string | GraphEvents, t.EventHandler> => ({
61
+ const setupCustomHandlers = (): Record<
62
+ string | GraphEvents,
63
+ t.EventHandler
64
+ > => ({
53
65
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
54
66
  [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
55
67
  [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
56
68
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
57
- handle: (event: GraphEvents.ON_RUN_STEP_COMPLETED, data: t.StreamEventData): void => {
58
- aggregateContent({ event, data: data as unknown as { result: t.ToolEndEvent; } });
59
- }
69
+ handle: (
70
+ event: GraphEvents.ON_RUN_STEP_COMPLETED,
71
+ data: t.StreamEventData
72
+ ): void => {
73
+ aggregateContent({
74
+ event,
75
+ data: data as unknown as { result: t.ToolEndEvent },
76
+ });
77
+ },
60
78
  },
61
79
  [GraphEvents.ON_RUN_STEP]: {
62
- handle: (event: GraphEvents.ON_RUN_STEP, data: t.StreamEventData, metadata, graph): void => {
80
+ handle: (
81
+ event: GraphEvents.ON_RUN_STEP,
82
+ data: t.StreamEventData,
83
+ metadata,
84
+ graph
85
+ ): void => {
63
86
  onRunStepSpy(event, data, metadata, graph);
64
87
  aggregateContent({ event, data: data as t.RunStep });
65
- }
88
+ },
66
89
  },
67
90
  [GraphEvents.ON_RUN_STEP_DELTA]: {
68
- handle: (event: GraphEvents.ON_RUN_STEP_DELTA, data: t.StreamEventData): void => {
91
+ handle: (
92
+ event: GraphEvents.ON_RUN_STEP_DELTA,
93
+ data: t.StreamEventData
94
+ ): void => {
69
95
  aggregateContent({ event, data: data as t.RunStepDeltaEvent });
70
- }
96
+ },
71
97
  },
72
98
  [GraphEvents.ON_MESSAGE_DELTA]: {
73
- handle: (event: GraphEvents.ON_MESSAGE_DELTA, data: t.StreamEventData, metadata, graph): void => {
99
+ handle: (
100
+ event: GraphEvents.ON_MESSAGE_DELTA,
101
+ data: t.StreamEventData,
102
+ metadata,
103
+ graph
104
+ ): void => {
74
105
  onMessageDeltaSpy(event, data, metadata, graph);
75
106
  aggregateContent({ event, data: data as t.MessageDeltaEvent });
76
- }
107
+ },
77
108
  },
78
109
  [GraphEvents.TOOL_START]: {
79
- handle: (_event: string, _data: t.StreamEventData, _metadata?: Record<string, unknown>): void => {
110
+ handle: (
111
+ _event: string,
112
+ _data: t.StreamEventData,
113
+ _metadata?: Record<string, unknown>
114
+ ): void => {
80
115
  // Handle tool start
81
- }
116
+ },
82
117
  },
83
118
  });
84
119
 
@@ -93,7 +128,8 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
93
128
  type: 'standard',
94
129
  llmConfig,
95
130
  tools: [new Calculator()],
96
- instructions: 'You are a friendly AI assistant. Always address the user by their name.',
131
+ instructions:
132
+ 'You are a friendly AI assistant. Always address the user by their name.',
97
133
  additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
98
134
  },
99
135
  returnContent: true,
@@ -109,7 +145,9 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
109
145
 
110
146
  const finalContentParts = await run.processStream(inputs, config);
111
147
  expect(finalContentParts).toBeDefined();
112
- const allTextParts = finalContentParts?.every((part) => part.type === ContentTypes.TEXT);
148
+ const allTextParts = finalContentParts?.every(
149
+ (part) => part.type === ContentTypes.TEXT
150
+ );
113
151
  expect(allTextParts).toBe(true);
114
152
  expect(collectedUsage.length).toBeGreaterThan(0);
115
153
  expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
@@ -117,26 +155,33 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
117
155
 
118
156
  const finalMessages = run.getRunMessages();
119
157
  expect(finalMessages).toBeDefined();
120
- conversationHistory.push(...finalMessages ?? []);
158
+ conversationHistory.push(...(finalMessages ?? []));
121
159
  expect(conversationHistory.length).toBeGreaterThan(1);
122
160
  runningHistory = conversationHistory.slice();
123
161
 
124
162
  expect(onMessageDeltaSpy).toHaveBeenCalled();
125
163
  expect(onMessageDeltaSpy.mock.calls.length).toBeGreaterThan(1);
126
- expect((onMessageDeltaSpy.mock.calls[0][3] as StandardGraph).provider).toBeDefined();
164
+ expect(
165
+ (onMessageDeltaSpy.mock.calls[0][3] as StandardGraph).provider
166
+ ).toBeDefined();
127
167
 
128
168
  expect(onRunStepSpy).toHaveBeenCalled();
129
169
  expect(onRunStepSpy.mock.calls.length).toBeGreaterThan(0);
130
- expect((onRunStepSpy.mock.calls[0][3] as StandardGraph).provider).toBeDefined();
170
+ expect(
171
+ (onRunStepSpy.mock.calls[0][3] as StandardGraph).provider
172
+ ).toBeDefined();
131
173
 
132
174
  const { handleLLMEnd, collected } = createMetadataAggregator();
133
175
  const titleResult = await run.generateTitle({
176
+ provider,
134
177
  inputText: userMessage,
135
178
  contentParts,
136
179
  chainOptions: {
137
- callbacks: [{
138
- handleLLMEnd,
139
- }],
180
+ callbacks: [
181
+ {
182
+ handleLLMEnd,
183
+ },
184
+ ],
140
185
  },
141
186
  });
142
187
 
@@ -148,7 +193,10 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
148
193
 
149
194
  test(`${capitalizeFirstLetter(provider)}: should follow-up`, async () => {
150
195
  console.log('Previous conversation length:', runningHistory.length);
151
- console.log('Last message:', runningHistory[runningHistory.length - 1].content);
196
+ console.log(
197
+ 'Last message:',
198
+ runningHistory[runningHistory.length - 1].content
199
+ );
152
200
  const { userName, location } = await getArgs();
153
201
  const llmConfig = getLLMConfig(provider);
154
202
  const customHandlers = setupCustomHandlers();
@@ -159,7 +207,8 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
159
207
  type: 'standard',
160
208
  llmConfig,
161
209
  tools: [new Calculator()],
162
- instructions: 'You are a friendly AI assistant. Always address the user by their name.',
210
+ instructions:
211
+ 'You are a friendly AI assistant. Always address the user by their name.',
163
212
  additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
164
213
  },
165
214
  returnContent: true,
@@ -175,7 +224,9 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
175
224
 
176
225
  const finalContentParts = await run.processStream(inputs, config);
177
226
  expect(finalContentParts).toBeDefined();
178
- const allTextParts = finalContentParts?.every((part) => part.type === ContentTypes.TEXT);
227
+ const allTextParts = finalContentParts?.every(
228
+ (part) => part.type === ContentTypes.TEXT
229
+ );
179
230
  expect(allTextParts).toBe(true);
180
231
  expect(collectedUsage.length).toBeGreaterThan(0);
181
232
  expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
@@ -184,7 +235,10 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
184
235
  const finalMessages = run.getRunMessages();
185
236
  expect(finalMessages).toBeDefined();
186
237
  expect(finalMessages?.length).toBeGreaterThan(0);
187
- console.log(`${capitalizeFirstLetter(provider)} follow-up message:`, finalMessages?.[finalMessages.length - 1]?.content);
238
+ console.log(
239
+ `${capitalizeFirstLetter(provider)} follow-up message:`,
240
+ finalMessages?.[finalMessages.length - 1]?.content
241
+ );
188
242
 
189
243
  expect(onMessageDeltaSpy).toHaveBeenCalled();
190
244
  expect(onMessageDeltaSpy.mock.calls.length).toBeGreaterThan(1);
@@ -196,9 +250,12 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
196
250
  test('should handle errors appropriately', async () => {
197
251
  // Test error scenarios
198
252
  await expect(async () => {
199
- await run.processStream({
200
- messages: [],
201
- }, {} as any);
253
+ await run.processStream(
254
+ {
255
+ messages: [],
256
+ },
257
+ {} as any
258
+ );
202
259
  }).rejects.toThrow();
203
260
  });
204
- });
261
+ });
package/src/types/run.ts CHANGED
@@ -26,6 +26,7 @@ export type StandardGraphConfig = BaseGraphConfig &
26
26
 
27
27
  export type RunTitleOptions = {
28
28
  inputText: string;
29
+ provider: e.Providers;
29
30
  contentParts: (s.MessageContentComplex | undefined)[];
30
31
  titlePrompt?: string;
31
32
  skipLanguage?: boolean;
@@ -4,48 +4,65 @@ import { RunnableLambda } from '@langchain/core/runnables';
4
4
  import type { Runnable } from '@langchain/core/runnables';
5
5
  import * as t from '@/types';
6
6
 
7
- const defaultTitlePrompt = `Write a concise title for this conversation in the detected language. Title in 5 Words or Less. No Punctuation or Quotation.
8
- {convo}`;
9
-
10
- const languageInstructions = 'Detect the language used in the following text. Note: words may be misspelled or cut off; use context clues to identify the language:\n{text}';
7
+ const defaultTitlePrompt = `Analyze this conversation and provide:
8
+ 1. The detected language of the conversation
9
+ 2. A concise title in the detected language (5 words or less, no punctuation or quotation)
11
10
 
12
- const languagePrompt = ChatPromptTemplate.fromTemplate(languageInstructions);
11
+ {convo}`;
13
12
 
14
- const languageSchema = z.object({
15
- language: z.string().describe('The detected language of the conversation')
13
+ const titleSchema = z.object({
14
+ title: z
15
+ .string()
16
+ .describe(
17
+ 'A concise title for the conversation in 5 words or less, without punctuation or quotation'
18
+ ),
16
19
  });
17
20
 
18
- const titleSchema = z.object({
19
- title: z.string().describe('A concise title for the conversation in 5 words or less, without punctuation or quotation'),
21
+ const combinedSchema = z.object({
22
+ language: z.string().describe('The detected language of the conversation'),
23
+ title: z
24
+ .string()
25
+ .describe(
26
+ 'A concise title for the conversation in 5 words or less, without punctuation or quotation'
27
+ ),
20
28
  });
21
29
 
22
- export const createTitleRunnable = async (model: t.ChatModelInstance, _titlePrompt?: string): Promise<Runnable> => {
30
+ export const createTitleRunnable = async (
31
+ model: t.ChatModelInstance,
32
+ _titlePrompt?: string
33
+ ): Promise<Runnable> => {
23
34
  // Disabled since this works fine
24
35
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
25
36
  /* @ts-ignore */
26
- const languageLLM = model.withStructuredOutput(languageSchema);
37
+ const titleLLM = model.withStructuredOutput(titleSchema);
27
38
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
28
39
  /* @ts-ignore */
29
- const titleLLM = model.withStructuredOutput(titleSchema);
30
-
31
- const languageChain = languagePrompt.pipe(languageLLM);
40
+ const combinedLLM = model.withStructuredOutput(combinedSchema);
32
41
 
33
- const titlePrompt = ChatPromptTemplate.fromTemplate(_titlePrompt ?? defaultTitlePrompt);
42
+ const titlePrompt = ChatPromptTemplate.fromTemplate(
43
+ _titlePrompt ?? defaultTitlePrompt
44
+ );
34
45
 
35
46
  return new RunnableLambda({
36
- func: async (input: { convo: string, inputText: string, skipLanguage: boolean }): Promise<{ language: string; title: string } | { title: string }> => {
47
+ func: async (input: {
48
+ convo: string;
49
+ inputText: string;
50
+ skipLanguage: boolean;
51
+ }): Promise<{ language: string; title: string } | { title: string }> => {
37
52
  if (input.skipLanguage) {
38
- return await titlePrompt.pipe(titleLLM).invoke({
39
- convo: input.convo
40
- }) as { title: string };
53
+ return (await titlePrompt.pipe(titleLLM).invoke({
54
+ convo: input.convo,
55
+ })) as { title: string };
41
56
  }
42
- const languageResult = await languageChain.invoke({ text: input.inputText }) as { language: string } | undefined;
43
- const language = languageResult?.language ?? 'English';
44
- const titleResult = await titlePrompt.pipe(titleLLM).invoke({
45
- language,
46
- convo: input.convo
47
- }) as { title: string } | undefined;
48
- return { language, title: titleResult?.title ?? '' };
57
+
58
+ const result = (await titlePrompt.pipe(combinedLLM).invoke({
59
+ convo: input.convo,
60
+ })) as { language: string; title: string } | undefined;
61
+
62
+ return {
63
+ language: result?.language ?? 'English',
64
+ title: result?.title ?? '',
65
+ };
49
66
  },
50
67
  });
51
- };
68
+ };