@librechat/agents 1.4.8 → 1.5.0

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
@@ -1,28 +1,27 @@
1
1
  // src/run.ts
2
- import type { BaseCallbackHandler, CallbackHandlerMethods } from '@langchain/core/callbacks/base';
2
+ import { PromptTemplate } from '@langchain/core/prompts';
3
+
3
4
  import type { BaseMessage, MessageContentComplex } from '@langchain/core/messages';
4
5
  import type { RunnableConfig } from '@langchain/core/runnables';
5
6
  import type { ClientCallbacks, SystemCallbacks } from '@/graphs/Graph';
6
7
  import type * as t from '@/types';
7
8
  import { GraphEvents, Providers, Callback } from '@/common';
9
+ import { createTitleRunnable } from '@/utils/title';
8
10
  import { StandardGraph } from '@/graphs/Graph';
9
11
  import { HandlerRegistry } from '@/events';
10
- import { sleep } from '@/utils';
11
12
 
12
13
  export class Run<T extends t.BaseGraphState> {
13
14
  graphRunnable?: t.CompiledWorkflow<T, Partial<T>, string>;
14
15
  // private collab!: CollabGraph;
15
16
  // private taskManager!: TaskManager;
16
17
  private handlerRegistry: HandlerRegistry;
17
- private Graph: StandardGraph | undefined;
18
+ Graph: StandardGraph | undefined;
18
19
  provider: Providers | undefined;
19
20
  run_id: string | undefined;
20
21
  returnContent: boolean = false;
21
- streamRate: number | undefined;
22
22
 
23
23
  private constructor(config: t.RunConfig) {
24
24
  const handlerRegistry = new HandlerRegistry();
25
- this.streamRate = config.streamRate;
26
25
 
27
26
  if (config.customHandlers) {
28
27
  for (const [eventType, handler] of Object.entries(config.customHandlers)) {
@@ -44,7 +43,7 @@ export class Run<T extends t.BaseGraphState> {
44
43
  }
45
44
 
46
45
  private createStandardGraph(config: t.StandardGraphConfig): t.CompiledWorkflow<t.IState, Partial<t.IState>, string> {
47
- const { runId, llmConfig, instructions, additional_instructions, tools = [] } = config;
46
+ const { runId, llmConfig, instructions, additional_instructions, streamRate, streamBuffer, tools = [] } = config;
48
47
  const { provider, ...clientOptions } = llmConfig;
49
48
 
50
49
  const standardGraph = new StandardGraph({
@@ -54,6 +53,8 @@ export class Run<T extends t.BaseGraphState> {
54
53
  instructions,
55
54
  clientOptions,
56
55
  additional_instructions,
56
+ streamBuffer,
57
+ streamRate,
57
58
  });
58
59
  this.Graph = standardGraph;
59
60
  return standardGraph.createWorkflow();
@@ -87,7 +88,7 @@ export class Run<T extends t.BaseGraphState> {
87
88
  const hasTools = this.Graph.tools ? this.Graph.tools.length > 0 : false;
88
89
  if (clientCallbacks) {
89
90
  /* TODO: conflicts with callback manager */
90
- const callbacks = config.callbacks as (BaseCallbackHandler | CallbackHandlerMethods)[] || [];
91
+ const callbacks = config.callbacks as t.ProvidedCallbacks ?? [];
91
92
  config.callbacks = callbacks.concat(this.getCallbacks(clientCallbacks));
92
93
  }
93
94
  const stream = this.graphRunnable.streamEvents(inputs, config);
@@ -112,9 +113,6 @@ export class Run<T extends t.BaseGraphState> {
112
113
  if (handler) {
113
114
  handler.handle(eventName, data, metadata, this.Graph);
114
115
  }
115
- if (this.streamRate != null) {
116
- await sleep(this.streamRate);
117
- }
118
116
  }
119
117
 
120
118
  if (this.returnContent) {
@@ -141,4 +139,36 @@ export class Run<T extends t.BaseGraphState> {
141
139
  [Callback.TOOL_END]: this.createSystemCallback(clientCallbacks, Callback.TOOL_END),
142
140
  };
143
141
  }
142
+
143
+ async generateTitle({
144
+ inputText,
145
+ contentParts,
146
+ titlePrompt,
147
+ clientOptions,
148
+ chainOptions,
149
+ skipLanguage,
150
+ } : {
151
+ inputText: string;
152
+ contentParts: (t.MessageContentComplex | undefined)[];
153
+ titlePrompt?: string;
154
+ skipLanguage?: boolean;
155
+ clientOptions?: t.ClientOptions;
156
+ chainOptions?: Partial<RunnableConfig> | undefined;
157
+ }): Promise<{ language: string; title: string }> {
158
+ const convoTemplate = PromptTemplate.fromTemplate('User: {input}\nAI: {output}');
159
+ const response = contentParts.map((part) => {
160
+ if (part?.type === 'text') return part.text;
161
+ return '';
162
+ }).join('\n');
163
+ const convo = (await convoTemplate.invoke({ input: inputText, output: response })).value;
164
+ const model = this.Graph?.getNewModel({
165
+ clientOptions,
166
+ omitOriginalOptions: ['streaming'],
167
+ });
168
+ if (!model) {
169
+ return { language: '', title: '' };
170
+ }
171
+ const chain = await createTitleRunnable(model, titlePrompt);
172
+ return await chain.invoke({ convo, inputText, skipLanguage }, chainOptions) as { language: string; title: string };
173
+ }
144
174
  }
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-console */
2
1
  // src/scripts/cli.ts
3
2
  import { config } from 'dotenv';
4
3
  config();
@@ -6,15 +5,14 @@ import { HumanMessage, BaseMessage } from '@langchain/core/messages';
6
5
  import { TavilySearchResults } from '@langchain/community/tools/tavily_search';
7
6
  import type * as t from '@/types';
8
7
  import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
9
- import { ToolEndHandler } from '@/events';
10
-
11
-
8
+ import { ToolEndHandler, createMetadataAggregator } from '@/events';
9
+ import { getLLMConfig } from '@/utils/llmConfig';
12
10
  import { getArgs } from '@/scripts/args';
11
+ import { GraphEvents } from '@/common';
13
12
  import { Run } from '@/run';
14
- import { GraphEvents, Callback } from '@/common';
15
- import { getLLMConfig } from '@/utils/llmConfig';
16
13
 
17
14
  const conversationHistory: BaseMessage[] = [];
15
+
18
16
  async function testStandardStreaming(): Promise<void> {
19
17
  const { userName, location, provider, currentDate } = await getArgs();
20
18
  const { contentParts, aggregateContent } = createContentAggregator();
@@ -98,7 +96,19 @@ async function testStandardStreaming(): Promise<void> {
98
96
  }
99
97
  // console.dir(finalContentParts, { depth: null });
100
98
  console.log('\n\n====================\n\n');
101
- // console.dir(contentParts, { depth: null });
99
+ console.dir(contentParts, { depth: null });
100
+ const { handleLLMEnd, collected } = createMetadataAggregator();
101
+ const titleResult = await run.generateTitle({
102
+ inputText: userMessage,
103
+ contentParts,
104
+ chainOptions: {
105
+ callbacks: [{
106
+ handleLLMEnd,
107
+ }],
108
+ },
109
+ });
110
+ console.log('Generated Title:', titleResult);
111
+ console.log('Collected metadata:', collected);
102
112
  }
103
113
 
104
114
  process.on('unhandledRejection', (reason, promise) => {
@@ -0,0 +1,122 @@
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
+ streamBuffer: 3000,
71
+ streamRate: 30,
72
+ },
73
+ returnContent: true,
74
+ customHandlers,
75
+ });
76
+
77
+ const config = {
78
+ configurable: {
79
+ provider,
80
+ thread_id: 'conversation-num-1',
81
+ },
82
+ streamMode: 'values',
83
+ version: 'v2' as const,
84
+ };
85
+
86
+ console.log('Test 1: Weather query (content parts test)');
87
+
88
+ const userMessage = `
89
+ Make a search for the weather in ${location} today, which is ${currentDate}.
90
+ Before making the search, please let me know what you're about to do, then immediately start searching without hesitation.
91
+ Make sure to always refer to me by name, which is ${userName}.
92
+ After giving me a thorough summary, tell me a joke about the weather forecast we went over.
93
+ `;
94
+
95
+ conversationHistory.push(new HumanMessage(userMessage));
96
+
97
+ const inputs = {
98
+ messages: conversationHistory,
99
+ };
100
+ const finalContentParts = await run.processStream(inputs, config);
101
+ const finalMessages = run.getRunMessages();
102
+ if (finalMessages) {
103
+ conversationHistory.push(...finalMessages);
104
+ console.dir(conversationHistory, { depth: null });
105
+ }
106
+ // console.dir(finalContentParts, { depth: null });
107
+ console.log('\n\n====================\n\n');
108
+ // console.dir(contentParts, { depth: null });
109
+ }
110
+
111
+ process.on('unhandledRejection', (reason, promise) => {
112
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
113
+ console.log('Conversation history:');
114
+ process.exit(1);
115
+ });
116
+
117
+ testStandardStreaming().catch((err) => {
118
+ console.error(err);
119
+ console.log('Conversation history:');
120
+ console.dir(conversationHistory, { depth: null });
121
+ process.exit(1);
122
+ });
package/src/types/run.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  import type * as z from 'zod';
3
3
  import type { BaseMessage } from '@langchain/core/messages';
4
4
  import type { StructuredTool } from '@langchain/core/tools';
5
+ import type { BaseCallbackHandler, CallbackHandlerMethods } from '@langchain/core/callbacks/base';
5
6
  import type * as e from '@/common/enum';
6
7
  import type * as g from '@/types/graph';
7
8
  import type * as t from '@/types/tools';
@@ -20,6 +21,7 @@ export type StandardGraphConfig = {
20
21
  toolMap?: t.ToolMap;
21
22
  additional_instructions?: string;
22
23
  streamBuffer?: number;
24
+ streamRate?: number;
23
25
  clientOptions?: Record<string, unknown>;
24
26
  };
25
27
 
@@ -54,7 +56,7 @@ export type RunConfig = {
54
56
  graphConfig: StandardGraphConfig | CollaborativeGraphConfig | TaskManagerGraphConfig;
55
57
  customHandlers?: Record<string, g.EventHandler>;
56
58
  returnContent?: boolean;
57
- /** The rate at which to process stream events */
58
- streamRate?: number;
59
59
  runId?: string;
60
60
  };
61
+
62
+ export type ProvidedCallbacks = (BaseCallbackHandler | CallbackHandlerMethods)[] | undefined;
@@ -1,8 +1,21 @@
1
1
  // src/types/stream.ts
2
- import type { MessageContentImageUrl, MessageContentText, ToolMessage } from '@langchain/core/messages';
2
+ import type { MessageContentImageUrl, MessageContentText, ToolMessage, BaseMessage } from '@langchain/core/messages';
3
3
  import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
4
+ import type { LLMResult, Generation } from '@langchain/core/outputs';
4
5
  import { StepTypes, ContentTypes } from '@/common/enum';
5
6
 
7
+ export type HandleLLMEnd = (output: LLMResult, runId: string, parentRunId?: string, tags?: string[]) => void;
8
+
9
+ export type MetadataAggregatorResult = {
10
+ handleLLMEnd: HandleLLMEnd;
11
+ collected: Record<string, unknown>[];
12
+ };
13
+
14
+ export type StreamGeneration = Generation & {
15
+ text?: string;
16
+ message?: BaseMessage
17
+ };
18
+
6
19
  /** Event names are of the format: on_[runnable_type]_(start|stream|end).
7
20
 
8
21
  Runnable types are one of:
@@ -0,0 +1,51 @@
1
+ import { z } from 'zod';
2
+ import { ChatPromptTemplate } from '@langchain/core/prompts';
3
+ import { RunnableLambda } from '@langchain/core/runnables';
4
+ import type { Runnable } from '@langchain/core/runnables';
5
+ import * as t from '@/types';
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}';
11
+
12
+ const languagePrompt = ChatPromptTemplate.fromTemplate(languageInstructions);
13
+
14
+ const languageSchema = z.object({
15
+ language: z.string().describe('The detected language of the conversation')
16
+ });
17
+
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'),
20
+ });
21
+
22
+ export const createTitleRunnable = async (model: t.ChatModelInstance, _titlePrompt?: string): Promise<Runnable> => {
23
+ // Disabled since this works fine
24
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
25
+ /* @ts-ignore */
26
+ const languageLLM = model.withStructuredOutput(languageSchema);
27
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
28
+ /* @ts-ignore */
29
+ const titleLLM = model.withStructuredOutput(titleSchema);
30
+
31
+ const languageChain = languagePrompt.pipe(languageLLM);
32
+
33
+ const titlePrompt = ChatPromptTemplate.fromTemplate(_titlePrompt ?? defaultTitlePrompt);
34
+
35
+ return new RunnableLambda({
36
+ func: async (input: { convo: string, inputText: string, skipLanguage: boolean }): Promise<{ language: string; title: string } | { title: string }> => {
37
+ if (input.skipLanguage) {
38
+ return await titlePrompt.pipe(titleLLM).invoke({
39
+ convo: input.convo
40
+ }) as { title: string };
41
+ }
42
+ const languageResult = await languageChain.invoke({ text: input.inputText }) as { language: string };
43
+ const language = languageResult.language;
44
+ const titleResult = await titlePrompt.pipe(titleLLM).invoke({
45
+ language,
46
+ convo: input.convo
47
+ }) as { title: string };
48
+ return { language, title: titleResult.title };
49
+ },
50
+ });
51
+ };