@librechat/agents 2.4.88 → 2.4.90

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/run.ts CHANGED
@@ -4,13 +4,15 @@ import { zodToJsonSchema } from 'zod-to-json-schema';
4
4
  import { CallbackHandler } from '@langfuse/langchain';
5
5
  import { PromptTemplate } from '@langchain/core/prompts';
6
6
  import { SystemMessage } from '@langchain/core/messages';
7
+ import { RunnableLambda } from '@langchain/core/runnables';
7
8
  import { AzureChatOpenAI, ChatOpenAI } from '@langchain/openai';
8
9
  import type {
9
- BaseMessage,
10
10
  MessageContentComplex,
11
+ BaseMessage,
11
12
  } from '@langchain/core/messages';
12
- import type { ClientCallbacks, SystemCallbacks } from '@/graphs/Graph';
13
+ import type { StringPromptValue } from '@langchain/core/prompt_values';
13
14
  import type { RunnableConfig } from '@langchain/core/runnables';
15
+ import type { ClientCallbacks, SystemCallbacks } from '@/graphs/Graph';
14
16
  import type * as t from '@/types';
15
17
  import { GraphEvents, Providers, Callback, TitleMethod } from '@/common';
16
18
  import { manualToolStreamProviders } from '@/llm/providers';
@@ -291,18 +293,38 @@ export class Run<T extends t.BaseGraphState> {
291
293
  titleMethod = TitleMethod.COMPLETION,
292
294
  titlePromptTemplate,
293
295
  }: t.RunTitleOptions): Promise<{ language?: string; title?: string }> {
296
+ if (
297
+ chainOptions != null &&
298
+ isPresent(process.env.LANGFUSE_SECRET_KEY) &&
299
+ isPresent(process.env.LANGFUSE_PUBLIC_KEY) &&
300
+ isPresent(process.env.LANGFUSE_BASE_URL)
301
+ ) {
302
+ const userId = chainOptions.configurable?.user_id;
303
+ const sessionId = chainOptions.configurable?.thread_id;
304
+ const traceMetadata = {
305
+ messageId: 'title-' + this.id,
306
+ };
307
+ const handler = new CallbackHandler({
308
+ userId,
309
+ sessionId,
310
+ traceMetadata,
311
+ });
312
+ chainOptions.callbacks = (
313
+ (chainOptions.callbacks as t.ProvidedCallbacks) ?? []
314
+ ).concat([handler]);
315
+ }
316
+
294
317
  const convoTemplate = PromptTemplate.fromTemplate(
295
318
  titlePromptTemplate ?? 'User: {input}\nAI: {output}'
296
319
  );
320
+
297
321
  const response = contentParts
298
322
  .map((part) => {
299
323
  if (part?.type === 'text') return part.text;
300
324
  return '';
301
325
  })
302
326
  .join('\n');
303
- const convo = (
304
- await convoTemplate.invoke({ input: inputText, output: response })
305
- ).value;
327
+
306
328
  const model = this.Graph?.getNewModel({
307
329
  provider,
308
330
  omitOptions,
@@ -328,10 +350,32 @@ export class Run<T extends t.BaseGraphState> {
328
350
  model.n = (clientOptions as t.OpenAIClientOptions | undefined)
329
351
  ?.n as number;
330
352
  }
331
- const chain =
353
+
354
+ const convoToTitleInput = new RunnableLambda({
355
+ func: (
356
+ promptValue: StringPromptValue
357
+ ): { convo: string; inputText: string; skipLanguage?: boolean } => ({
358
+ convo: promptValue.value,
359
+ inputText,
360
+ skipLanguage,
361
+ }),
362
+ }).withConfig({ runName: 'ConvoTransform' });
363
+
364
+ const titleChain =
332
365
  titleMethod === TitleMethod.COMPLETION
333
366
  ? await createCompletionTitleRunnable(model, titlePrompt)
334
367
  : await createTitleRunnable(model, titlePrompt);
335
- return await chain.invoke({ convo, inputText, skipLanguage }, chainOptions);
368
+
369
+ /** Pipes `convoTemplate` -> `transformer` -> `titleChain` */
370
+ const fullChain = convoTemplate
371
+ .withConfig({ runName: 'ConvoTemplate' })
372
+ .pipe(convoToTitleInput)
373
+ .pipe(titleChain)
374
+ .withConfig({ runName: 'TitleChain' });
375
+
376
+ return await fullChain.invoke(
377
+ { input: inputText, output: response },
378
+ chainOptions
379
+ );
336
380
  }
337
381
  }
@@ -17,6 +17,7 @@ import {
17
17
  import { GraphEvents, Providers, TitleMethod } from '@/common';
18
18
  import { getLLMConfig } from '@/utils/llmConfig';
19
19
  import { getArgs } from '@/scripts/args';
20
+ import { sleep } from '@/utils/run';
20
21
  import { Run } from '@/run';
21
22
 
22
23
  const conversationHistory: BaseMessage[] = [];
@@ -129,7 +130,7 @@ async function testStandardStreaming(): Promise<void> {
129
130
  }
130
131
 
131
132
  const run = await Run.create<t.IState>({
132
- runId: 'test-run-id',
133
+ runId: 'test-simple-script',
133
134
  graphConfig: {
134
135
  type: 'standard',
135
136
  llmConfig,
@@ -144,7 +145,9 @@ async function testStandardStreaming(): Promise<void> {
144
145
  });
145
146
 
146
147
  const config = {
148
+ runId: 'test-simple-script',
147
149
  configurable: {
150
+ user_id: 'user-123',
148
151
  thread_id: 'conversation-num-1',
149
152
  },
150
153
  streamMode: 'values',
@@ -176,6 +179,10 @@ async function testStandardStreaming(): Promise<void> {
176
179
  contentParts,
177
180
  // titleMethod: TitleMethod.STRUCTURED,
178
181
  chainOptions: {
182
+ configurable: {
183
+ user_id: 'user-123',
184
+ thread_id: 'conversation-num-1',
185
+ },
179
186
  callbacks: [
180
187
  {
181
188
  handleLLMEnd,
@@ -192,6 +199,7 @@ async function testStandardStreaming(): Promise<void> {
192
199
  console.log('Collected usage metadata:', collectedUsage);
193
200
  console.log('Generated Title:', titleResult);
194
201
  console.log('Collected title usage metadata:', collected);
202
+ await sleep(5000);
195
203
  }
196
204
 
197
205
  process.on('unhandledRejection', (reason, promise) => {
@@ -1,7 +1,8 @@
1
1
  import { z } from 'zod';
2
- import { RunnableLambda } from '@langchain/core/runnables';
3
2
  import { ChatPromptTemplate } from '@langchain/core/prompts';
3
+ import { RunnableLambda, RunnableSequence } from '@langchain/core/runnables';
4
4
  import type { Runnable, RunnableConfig } from '@langchain/core/runnables';
5
+ import type { AIMessage } from '@langchain/core/messages';
5
6
  import type * as t from '@/types';
6
7
  import { ContentTypes } from '@/common';
7
8
 
@@ -42,7 +43,55 @@ export const createTitleRunnable = async (
42
43
 
43
44
  const titlePrompt = ChatPromptTemplate.fromTemplate(
44
45
  _titlePrompt ?? defaultTitlePrompt
45
- );
46
+ ).withConfig({ runName: 'TitlePrompt' });
47
+
48
+ const titleOnlyInnerChain = RunnableSequence.from([titlePrompt, titleLLM]);
49
+ const combinedInnerChain = RunnableSequence.from([titlePrompt, combinedLLM]);
50
+
51
+ /** Wrap titleOnlyChain in RunnableLambda to create parent span */
52
+ const titleOnlyChain = new RunnableLambda({
53
+ func: async (
54
+ input: { convo: string },
55
+ config?: Partial<RunnableConfig>
56
+ ): Promise<{ title: string }> => {
57
+ return await titleOnlyInnerChain.invoke(input, config);
58
+ },
59
+ }).withConfig({ runName: 'TitleOnlyChain' });
60
+
61
+ /** Wrap combinedChain in RunnableLambda to create parent span */
62
+ const combinedChain = new RunnableLambda({
63
+ func: async (
64
+ input: { convo: string },
65
+ config?: Partial<RunnableConfig>
66
+ ): Promise<{ language: string; title: string }> => {
67
+ return await combinedInnerChain.invoke(input, config);
68
+ },
69
+ }).withConfig({ runName: 'TitleLanguageChain' });
70
+
71
+ /** Runnable to add default values if needed */
72
+ const addDefaults = new RunnableLambda({
73
+ func: (
74
+ result: { language: string; title: string } | undefined
75
+ ): { language: string; title: string } => ({
76
+ language: result?.language ?? 'English',
77
+ title: result?.title ?? '',
78
+ }),
79
+ }).withConfig({ runName: 'AddDefaults' });
80
+
81
+ const combinedChainInner = RunnableSequence.from([
82
+ combinedChain,
83
+ addDefaults,
84
+ ]);
85
+
86
+ /** Wrap combinedChainWithDefaults in RunnableLambda to create parent span */
87
+ const combinedChainWithDefaults = new RunnableLambda({
88
+ func: async (
89
+ input: { convo: string },
90
+ config?: Partial<RunnableConfig>
91
+ ): Promise<{ language: string; title: string }> => {
92
+ return await combinedChainInner.invoke(input, config);
93
+ },
94
+ }).withConfig({ runName: 'CombinedChainWithDefaults' });
46
95
 
47
96
  return new RunnableLambda({
48
97
  func: async (
@@ -53,28 +102,17 @@ export const createTitleRunnable = async (
53
102
  },
54
103
  config?: Partial<RunnableConfig>
55
104
  ): Promise<{ language: string; title: string } | { title: string }> => {
105
+ const invokeInput = { convo: input.convo };
106
+
56
107
  if (input.skipLanguage) {
57
- return (await titlePrompt.pipe(titleLLM).invoke(
58
- {
59
- convo: input.convo,
60
- },
61
- config
62
- )) as { title: string };
108
+ return (await titleOnlyChain.invoke(invokeInput, config)) as {
109
+ title: string;
110
+ };
63
111
  }
64
112
 
65
- const result = (await titlePrompt.pipe(combinedLLM).invoke(
66
- {
67
- convo: input.convo,
68
- },
69
- config
70
- )) as { language: string; title: string } | undefined;
71
-
72
- return {
73
- language: result?.language ?? 'English',
74
- title: result?.title ?? '',
75
- };
113
+ return await combinedChainWithDefaults.invoke(invokeInput, config);
76
114
  },
77
- });
115
+ }).withConfig({ runName: 'TitleGenerator' });
78
116
  };
79
117
 
80
118
  const defaultCompletionPrompt = `Provide a concise, 5-word-or-less title for the conversation, using title case conventions. Only return the title itself.
@@ -88,22 +126,11 @@ export const createCompletionTitleRunnable = async (
88
126
  ): Promise<Runnable> => {
89
127
  const completionPrompt = ChatPromptTemplate.fromTemplate(
90
128
  titlePrompt ?? defaultCompletionPrompt
91
- );
129
+ ).withConfig({ runName: 'CompletionTitlePrompt' });
92
130
 
93
- return new RunnableLambda({
94
- func: async (
95
- input: {
96
- convo: string;
97
- inputText: string;
98
- skipLanguage: boolean;
99
- },
100
- config?: Partial<RunnableConfig>
101
- ): Promise<{ title: string }> => {
102
- const promptOutput = await completionPrompt.invoke({
103
- convo: input.convo,
104
- });
105
-
106
- const response = await model.invoke(promptOutput, config);
131
+ /** Runnable to extract content from model response */
132
+ const extractContent = new RunnableLambda({
133
+ func: (response: AIMessage): { title: string } => {
107
134
  let content = '';
108
135
  if (typeof response.content === 'string') {
109
136
  content = response.content;
@@ -116,10 +143,23 @@ export const createCompletionTitleRunnable = async (
116
143
  .map((part) => part.text)
117
144
  .join('');
118
145
  }
119
- const title = content.trim();
120
- return {
121
- title,
122
- };
146
+ return { title: content.trim() };
147
+ },
148
+ }).withConfig({ runName: 'ExtractTitle' });
149
+
150
+ const innerChain = RunnableSequence.from([
151
+ completionPrompt,
152
+ model,
153
+ extractContent,
154
+ ]);
155
+
156
+ /** Wrap in RunnableLambda to create a parent span for LangFuse */
157
+ return new RunnableLambda({
158
+ func: async (
159
+ input: { convo: string },
160
+ config?: Partial<RunnableConfig>
161
+ ): Promise<{ title: string }> => {
162
+ return await innerChain.invoke(input, config);
123
163
  },
124
- });
164
+ }).withConfig({ runName: 'CompletionTitleChain' });
125
165
  };