@librechat/agents 3.0.0-rc1 → 3.0.0-rc11

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.
Files changed (187) hide show
  1. package/dist/cjs/common/enum.cjs +1 -0
  2. package/dist/cjs/common/enum.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +7 -2
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/graphs/MultiAgentGraph.cjs +229 -44
  6. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  7. package/dist/cjs/llm/anthropic/index.cjs +21 -2
  8. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  9. package/dist/cjs/llm/google/index.cjs +3 -0
  10. package/dist/cjs/llm/google/index.cjs.map +1 -1
  11. package/dist/cjs/llm/google/utils/common.cjs +13 -0
  12. package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
  13. package/dist/cjs/llm/ollama/index.cjs +3 -0
  14. package/dist/cjs/llm/ollama/index.cjs.map +1 -1
  15. package/dist/cjs/llm/openai/index.cjs +53 -1
  16. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  17. package/dist/cjs/llm/openai/utils/index.cjs +6 -1
  18. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  19. package/dist/cjs/llm/openrouter/index.cjs +5 -1
  20. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  21. package/dist/cjs/llm/vertexai/index.cjs +1 -1
  22. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  23. package/dist/cjs/main.cjs +3 -1
  24. package/dist/cjs/main.cjs.map +1 -1
  25. package/dist/cjs/messages/core.cjs +5 -1
  26. package/dist/cjs/messages/core.cjs.map +1 -1
  27. package/dist/cjs/messages/format.cjs +52 -34
  28. package/dist/cjs/messages/format.cjs.map +1 -1
  29. package/dist/cjs/messages/prune.cjs +28 -0
  30. package/dist/cjs/messages/prune.cjs.map +1 -1
  31. package/dist/cjs/run.cjs +28 -15
  32. package/dist/cjs/run.cjs.map +1 -1
  33. package/dist/cjs/stream.cjs +1 -1
  34. package/dist/cjs/stream.cjs.map +1 -1
  35. package/dist/cjs/tools/ToolNode.cjs +2 -0
  36. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  37. package/dist/cjs/tools/search/firecrawl.cjs +3 -1
  38. package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
  39. package/dist/cjs/tools/search/rerankers.cjs +8 -6
  40. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  41. package/dist/cjs/tools/search/search.cjs +5 -5
  42. package/dist/cjs/tools/search/search.cjs.map +1 -1
  43. package/dist/cjs/tools/search/serper-scraper.cjs +132 -0
  44. package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -0
  45. package/dist/cjs/tools/search/tool.cjs +46 -9
  46. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  47. package/dist/cjs/utils/handlers.cjs +70 -0
  48. package/dist/cjs/utils/handlers.cjs.map +1 -0
  49. package/dist/esm/common/enum.mjs +1 -0
  50. package/dist/esm/common/enum.mjs.map +1 -1
  51. package/dist/esm/graphs/Graph.mjs +7 -2
  52. package/dist/esm/graphs/Graph.mjs.map +1 -1
  53. package/dist/esm/graphs/MultiAgentGraph.mjs +230 -45
  54. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  55. package/dist/esm/llm/anthropic/index.mjs +21 -2
  56. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  57. package/dist/esm/llm/google/index.mjs +3 -0
  58. package/dist/esm/llm/google/index.mjs.map +1 -1
  59. package/dist/esm/llm/google/utils/common.mjs +13 -0
  60. package/dist/esm/llm/google/utils/common.mjs.map +1 -1
  61. package/dist/esm/llm/ollama/index.mjs +3 -0
  62. package/dist/esm/llm/ollama/index.mjs.map +1 -1
  63. package/dist/esm/llm/openai/index.mjs +53 -1
  64. package/dist/esm/llm/openai/index.mjs.map +1 -1
  65. package/dist/esm/llm/openai/utils/index.mjs +6 -1
  66. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  67. package/dist/esm/llm/openrouter/index.mjs +5 -1
  68. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  69. package/dist/esm/llm/vertexai/index.mjs +1 -1
  70. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  71. package/dist/esm/main.mjs +2 -1
  72. package/dist/esm/main.mjs.map +1 -1
  73. package/dist/esm/messages/core.mjs +5 -1
  74. package/dist/esm/messages/core.mjs.map +1 -1
  75. package/dist/esm/messages/format.mjs +52 -34
  76. package/dist/esm/messages/format.mjs.map +1 -1
  77. package/dist/esm/messages/prune.mjs +28 -0
  78. package/dist/esm/messages/prune.mjs.map +1 -1
  79. package/dist/esm/run.mjs +28 -15
  80. package/dist/esm/run.mjs.map +1 -1
  81. package/dist/esm/stream.mjs +1 -1
  82. package/dist/esm/stream.mjs.map +1 -1
  83. package/dist/esm/tools/ToolNode.mjs +2 -0
  84. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  85. package/dist/esm/tools/search/firecrawl.mjs +3 -1
  86. package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
  87. package/dist/esm/tools/search/rerankers.mjs +8 -6
  88. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  89. package/dist/esm/tools/search/search.mjs +5 -5
  90. package/dist/esm/tools/search/search.mjs.map +1 -1
  91. package/dist/esm/tools/search/serper-scraper.mjs +129 -0
  92. package/dist/esm/tools/search/serper-scraper.mjs.map +1 -0
  93. package/dist/esm/tools/search/tool.mjs +46 -9
  94. package/dist/esm/tools/search/tool.mjs.map +1 -1
  95. package/dist/esm/utils/handlers.mjs +68 -0
  96. package/dist/esm/utils/handlers.mjs.map +1 -0
  97. package/dist/types/common/enum.d.ts +2 -1
  98. package/dist/types/graphs/MultiAgentGraph.d.ts +12 -2
  99. package/dist/types/llm/anthropic/index.d.ts +3 -0
  100. package/dist/types/llm/google/index.d.ts +1 -0
  101. package/dist/types/llm/ollama/index.d.ts +1 -0
  102. package/dist/types/llm/openai/index.d.ts +14 -0
  103. package/dist/types/llm/openrouter/index.d.ts +4 -2
  104. package/dist/types/llm/vertexai/index.d.ts +1 -1
  105. package/dist/types/messages/format.d.ts +23 -20
  106. package/dist/types/run.d.ts +1 -1
  107. package/dist/types/tools/search/firecrawl.d.ts +2 -1
  108. package/dist/types/tools/search/rerankers.d.ts +4 -1
  109. package/dist/types/tools/search/search.d.ts +1 -2
  110. package/dist/types/tools/search/serper-scraper.d.ts +59 -0
  111. package/dist/types/tools/search/tool.d.ts +25 -4
  112. package/dist/types/tools/search/types.d.ts +31 -1
  113. package/dist/types/types/graph.d.ts +38 -4
  114. package/dist/types/types/llm.d.ts +1 -0
  115. package/dist/types/types/run.d.ts +5 -1
  116. package/dist/types/utils/handlers.d.ts +34 -0
  117. package/dist/types/utils/index.d.ts +1 -0
  118. package/package.json +11 -3
  119. package/src/common/enum.ts +1 -0
  120. package/src/graphs/Graph.ts +8 -2
  121. package/src/graphs/MultiAgentGraph.ts +267 -50
  122. package/src/llm/anthropic/index.ts +23 -2
  123. package/src/llm/google/index.ts +4 -0
  124. package/src/llm/google/utils/common.ts +14 -0
  125. package/src/llm/ollama/index.ts +3 -0
  126. package/src/llm/openai/index.ts +60 -1
  127. package/src/llm/openai/utils/index.ts +7 -1
  128. package/src/llm/openrouter/index.ts +15 -6
  129. package/src/llm/vertexai/index.ts +2 -2
  130. package/src/messages/core.ts +5 -2
  131. package/src/messages/format.ts +67 -39
  132. package/src/messages/formatMessage.test.ts +418 -2
  133. package/src/messages/prune.ts +51 -0
  134. package/src/run.ts +38 -27
  135. package/src/scripts/multi-agent-chain.ts +278 -0
  136. package/src/scripts/multi-agent-document-review-chain.ts +197 -0
  137. package/src/scripts/multi-agent-hybrid-flow.ts +310 -0
  138. package/src/scripts/multi-agent-parallel.ts +27 -23
  139. package/src/scripts/multi-agent-supervisor.ts +362 -0
  140. package/src/scripts/search.ts +5 -1
  141. package/src/scripts/test-custom-prompt-key.ts +145 -0
  142. package/src/scripts/test-handoff-input.ts +170 -0
  143. package/src/scripts/test-multi-agent-list-handoff.ts +261 -0
  144. package/src/scripts/test-tools-before-handoff.ts +233 -0
  145. package/src/scripts/tools.ts +4 -1
  146. package/src/stream.ts +4 -1
  147. package/src/tools/search/firecrawl.ts +5 -2
  148. package/src/tools/search/jina-reranker.test.ts +126 -0
  149. package/src/tools/search/rerankers.ts +11 -5
  150. package/src/tools/search/search.ts +6 -8
  151. package/src/tools/search/serper-scraper.ts +155 -0
  152. package/src/tools/search/tool.ts +49 -8
  153. package/src/tools/search/types.ts +46 -0
  154. package/src/types/graph.ts +51 -5
  155. package/src/types/llm.ts +1 -0
  156. package/src/types/run.ts +6 -1
  157. package/src/utils/handlers.ts +107 -0
  158. package/src/utils/index.ts +2 -1
  159. package/src/utils/llmConfig.ts +35 -1
  160. package/dist/types/scripts/abort.d.ts +0 -1
  161. package/dist/types/scripts/ant_web_search.d.ts +0 -1
  162. package/dist/types/scripts/args.d.ts +0 -7
  163. package/dist/types/scripts/caching.d.ts +0 -1
  164. package/dist/types/scripts/cli.d.ts +0 -1
  165. package/dist/types/scripts/cli2.d.ts +0 -1
  166. package/dist/types/scripts/cli3.d.ts +0 -1
  167. package/dist/types/scripts/cli4.d.ts +0 -1
  168. package/dist/types/scripts/cli5.d.ts +0 -1
  169. package/dist/types/scripts/code_exec.d.ts +0 -1
  170. package/dist/types/scripts/code_exec_files.d.ts +0 -1
  171. package/dist/types/scripts/code_exec_simple.d.ts +0 -1
  172. package/dist/types/scripts/content.d.ts +0 -1
  173. package/dist/types/scripts/empty_input.d.ts +0 -1
  174. package/dist/types/scripts/handoff-test.d.ts +0 -1
  175. package/dist/types/scripts/image.d.ts +0 -1
  176. package/dist/types/scripts/memory.d.ts +0 -1
  177. package/dist/types/scripts/multi-agent-conditional.d.ts +0 -1
  178. package/dist/types/scripts/multi-agent-parallel.d.ts +0 -1
  179. package/dist/types/scripts/multi-agent-sequence.d.ts +0 -1
  180. package/dist/types/scripts/multi-agent-test.d.ts +0 -1
  181. package/dist/types/scripts/search.d.ts +0 -1
  182. package/dist/types/scripts/simple.d.ts +0 -1
  183. package/dist/types/scripts/stream.d.ts +0 -1
  184. package/dist/types/scripts/thinking.d.ts +0 -1
  185. package/dist/types/scripts/tools.d.ts +0 -1
  186. package/dist/types/specs/spec.utils.d.ts +0 -1
  187. package/src/scripts/multi-agent-example-output.md +0 -110
@@ -1,6 +1,11 @@
1
1
  import { z } from 'zod';
2
2
  import { tool } from '@langchain/core/tools';
3
- import { ToolMessage, HumanMessage } from '@langchain/core/messages';
3
+ import { PromptTemplate } from '@langchain/core/prompts';
4
+ import {
5
+ ToolMessage,
6
+ HumanMessage,
7
+ getBufferString,
8
+ } from '@langchain/core/messages';
4
9
  import {
5
10
  END,
6
11
  START,
@@ -14,10 +19,21 @@ import type { ToolRunnableConfig } from '@langchain/core/tools';
14
19
  import type { BaseMessage } from '@langchain/core/messages';
15
20
  import type * as t from '@/types';
16
21
  import { StandardGraph } from './Graph';
22
+ import { Constants } from '@/common';
17
23
 
18
24
  /**
19
25
  * MultiAgentGraph extends StandardGraph to support dynamic multi-agent workflows
20
- * with handoffs, fan-in/fan-out, and other composable patterns
26
+ * with handoffs, fan-in/fan-out, and other composable patterns.
27
+ *
28
+ * Key behavior:
29
+ * - Agents with ONLY handoff edges: Can dynamically route to any handoff destination
30
+ * - Agents with ONLY direct edges: Always follow their direct edges
31
+ * - Agents with BOTH: Use Command for exclusive routing (handoff OR direct, not both)
32
+ * - If handoff occurs: Only the handoff destination executes
33
+ * - If no handoff: Direct edges execute (potentially in parallel)
34
+ *
35
+ * This enables the common pattern where an agent either delegates (handoff)
36
+ * OR continues its workflow (direct edges), but not both simultaneously.
21
37
  */
22
38
  export class MultiAgentGraph extends StandardGraph {
23
39
  private edges: t.GraphEdge[];
@@ -119,14 +135,6 @@ export class MultiAgentGraph extends StandardGraph {
119
135
  agentContext.tools = [];
120
136
  }
121
137
  agentContext.tools.push(...handoffTools);
122
-
123
- // Update tool map
124
- for (const tool of handoffTools) {
125
- if (!agentContext.toolMap) {
126
- agentContext.toolMap = new Map();
127
- }
128
- agentContext.toolMap.set(tool.name, tool);
129
- }
130
138
  }
131
139
  }
132
140
 
@@ -137,37 +145,52 @@ export class MultiAgentGraph extends StandardGraph {
137
145
  const tools: t.GenericTool[] = [];
138
146
  const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
139
147
 
140
- // If there's a condition, create a single conditional handoff tool
148
+ /** If there's a condition, create a single conditional handoff tool */
141
149
  if (edge.condition != null) {
142
150
  const toolName = 'conditional_transfer';
143
151
  const toolDescription =
144
152
  edge.description ?? 'Conditionally transfer control based on state';
145
153
 
154
+ /** Check if we have a prompt for handoff input */
155
+ const hasHandoffInput =
156
+ edge.prompt != null && typeof edge.prompt === 'string';
157
+ const handoffInputDescription = hasHandoffInput ? edge.prompt : undefined;
158
+ const promptKey = edge.promptKey ?? 'instructions';
159
+
146
160
  tools.push(
147
161
  tool(
148
- async (_, config) => {
162
+ async (input: Record<string, unknown>, config) => {
149
163
  const state = getCurrentTaskInput() as t.BaseGraphState;
150
164
  const toolCallId =
151
165
  (config as ToolRunnableConfig | undefined)?.toolCall?.id ??
152
166
  'unknown';
153
167
 
154
- // Evaluate condition
168
+ /** Evaluated condition */
155
169
  const result = edge.condition!(state);
156
170
  let destination: string;
157
171
 
158
172
  if (typeof result === 'boolean') {
159
- // If true, use first destination; if false, don't transfer
173
+ /** If true, use first destination; if false, don't transfer */
160
174
  if (!result) return null;
161
175
  destination = destinations[0];
162
176
  } else if (typeof result === 'string') {
163
177
  destination = result;
164
178
  } else {
165
- // Array of destinations - for now, use the first
179
+ /** Array of destinations - for now, use the first */
166
180
  destination = Array.isArray(result) ? result[0] : destinations[0];
167
181
  }
168
182
 
183
+ let content = `Conditionally transferred to ${destination}`;
184
+ if (
185
+ hasHandoffInput &&
186
+ promptKey in input &&
187
+ input[promptKey] != null
188
+ ) {
189
+ content += `\n\n${promptKey.charAt(0).toUpperCase() + promptKey.slice(1)}: ${input[promptKey]}`;
190
+ }
191
+
169
192
  const toolMessage = new ToolMessage({
170
- content: `Conditionally transferred to ${destination}`,
193
+ content,
171
194
  name: toolName,
172
195
  tool_call_id: toolCallId,
173
196
  });
@@ -180,26 +203,51 @@ export class MultiAgentGraph extends StandardGraph {
180
203
  },
181
204
  {
182
205
  name: toolName,
183
- schema: z.object({}),
206
+ schema: hasHandoffInput
207
+ ? z.object({
208
+ [promptKey]: z
209
+ .string()
210
+ .optional()
211
+ .describe(handoffInputDescription as string),
212
+ })
213
+ : z.object({}),
184
214
  description: toolDescription,
185
215
  }
186
216
  )
187
217
  );
188
218
  } else {
189
- // Create individual tools for each destination
219
+ /** Create individual tools for each destination */
190
220
  for (const destination of destinations) {
191
- const toolName = `transfer_to_${destination}`;
221
+ const toolName = `${Constants.LC_TRANSFER_TO_}${destination}`;
192
222
  const toolDescription =
193
223
  edge.description ?? `Transfer control to agent '${destination}'`;
194
224
 
225
+ /** Check if we have a prompt for handoff input */
226
+ const hasHandoffInput =
227
+ edge.prompt != null && typeof edge.prompt === 'string';
228
+ const handoffInputDescription = hasHandoffInput
229
+ ? edge.prompt
230
+ : undefined;
231
+ const promptKey = edge.promptKey ?? 'instructions';
232
+
195
233
  tools.push(
196
234
  tool(
197
- async (_, config) => {
235
+ async (input: Record<string, unknown>, config) => {
198
236
  const toolCallId =
199
237
  (config as ToolRunnableConfig | undefined)?.toolCall?.id ??
200
238
  'unknown';
239
+
240
+ let content = `Successfully transferred to ${destination}`;
241
+ if (
242
+ hasHandoffInput &&
243
+ promptKey in input &&
244
+ input[promptKey] != null
245
+ ) {
246
+ content += `\n\n${promptKey.charAt(0).toUpperCase() + promptKey.slice(1)}: ${input[promptKey]}`;
247
+ }
248
+
201
249
  const toolMessage = new ToolMessage({
202
- content: `Successfully transferred to ${destination}`,
250
+ content,
203
251
  name: toolName,
204
252
  tool_call_id: toolCallId,
205
253
  });
@@ -214,7 +262,14 @@ export class MultiAgentGraph extends StandardGraph {
214
262
  },
215
263
  {
216
264
  name: toolName,
217
- schema: z.object({}),
265
+ schema: hasHandoffInput
266
+ ? z.object({
267
+ [promptKey]: z
268
+ .string()
269
+ .optional()
270
+ .describe(handoffInputDescription as string),
271
+ })
272
+ : z.object({}),
218
273
  description: toolDescription,
219
274
  }
220
275
  )
@@ -229,14 +284,14 @@ export class MultiAgentGraph extends StandardGraph {
229
284
  * Create a complete agent subgraph (similar to createReactAgent)
230
285
  */
231
286
  private createAgentSubgraph(agentId: string): t.CompiledAgentWorfklow {
232
- // This is essentially the same as createAgentNode from StandardGraph
287
+ /** This is essentially the same as `createAgentNode` from `StandardGraph` */
233
288
  return this.createAgentNode(agentId);
234
289
  }
235
290
 
236
291
  /**
237
292
  * Create the multi-agent workflow with dynamic handoffs
238
293
  */
239
- override createWorkflow(): t.CompiledStateWorkflow {
294
+ override createWorkflow(): t.CompiledMultiAgentWorkflow {
240
295
  const StateAnnotation = Annotation.Root({
241
296
  messages: Annotation<BaseMessage[]>({
242
297
  reducer: (a, b) => {
@@ -249,6 +304,12 @@ export class MultiAgentGraph extends StandardGraph {
249
304
  },
250
305
  default: () => [],
251
306
  }),
307
+ /** Channel for passing filtered messages to agents when excludeResults is true */
308
+ agentMessages: Annotation<BaseMessage[]>({
309
+ /** Replaces state entirely */
310
+ reducer: (a, b) => b,
311
+ default: () => [],
312
+ }),
252
313
  });
253
314
 
254
315
  const builder = new StateGraph(StateAnnotation);
@@ -277,19 +338,123 @@ export class MultiAgentGraph extends StandardGraph {
277
338
  }
278
339
  }
279
340
 
280
- // If agent has handoff destinations, add END to possible ends
281
- // If agent only has direct destinations, it naturally ends without explicit END
282
- const destinations = new Set([...handoffDestinations]);
341
+ /** Check if this agent has BOTH handoff and direct edges */
342
+ const hasHandoffEdges = handoffDestinations.size > 0;
343
+ const hasDirectEdges = directDestinations.size > 0;
344
+ const needsCommandRouting = hasHandoffEdges && hasDirectEdges;
345
+
346
+ /** Collect all possible destinations for this agent */
347
+ const allDestinations = new Set([
348
+ ...handoffDestinations,
349
+ ...directDestinations,
350
+ ]);
283
351
  if (handoffDestinations.size > 0 || directDestinations.size === 0) {
284
- destinations.add(END);
352
+ allDestinations.add(END);
285
353
  }
286
354
 
287
- // Create the agent subgraph (includes agent + tools)
355
+ /** Agent subgraph (includes agent + tools) */
288
356
  const agentSubgraph = this.createAgentSubgraph(agentId);
289
357
 
290
- // Add the agent as a node with its possible destinations
291
- builder.addNode(agentId, agentSubgraph, {
292
- ends: Array.from(destinations),
358
+ /** Wrapper function that handles agentMessages channel and conditional routing */
359
+ const agentWrapper = async (
360
+ state: t.MultiAgentGraphState
361
+ ): Promise<t.MultiAgentGraphState | Command> => {
362
+ let result: t.MultiAgentGraphState;
363
+
364
+ if (state.agentMessages != null && state.agentMessages.length > 0) {
365
+ /**
366
+ * When using agentMessages (excludeResults=true), we need to update
367
+ * the token map to account for the new prompt message
368
+ */
369
+ const agentContext = this.agentContexts.get(agentId);
370
+ if (agentContext && agentContext.tokenCounter) {
371
+ // The agentMessages contains:
372
+ // 1. Filtered messages (0 to startIndex) - already have token counts
373
+ // 2. New prompt message - needs token counting
374
+
375
+ const freshTokenMap: Record<string, number> = {};
376
+
377
+ // Copy existing token counts for filtered messages (0 to startIndex)
378
+ for (let i = 0; i < this.startIndex; i++) {
379
+ const tokenCount = agentContext.indexTokenCountMap[i];
380
+ if (tokenCount !== undefined) {
381
+ freshTokenMap[i] = tokenCount;
382
+ }
383
+ }
384
+
385
+ // Calculate tokens only for the new prompt message (last message)
386
+ const promptMessageIndex = state.agentMessages.length - 1;
387
+ if (promptMessageIndex >= this.startIndex) {
388
+ const promptMessage = state.agentMessages[promptMessageIndex];
389
+ freshTokenMap[promptMessageIndex] =
390
+ agentContext.tokenCounter(promptMessage);
391
+ }
392
+
393
+ // Update the agent's token map with instructions added
394
+ agentContext.updateTokenMapWithInstructions(freshTokenMap);
395
+ }
396
+
397
+ /** Temporary state with messages replaced by `agentMessages` */
398
+ const transformedState: t.MultiAgentGraphState = {
399
+ ...state,
400
+ messages: state.agentMessages,
401
+ };
402
+ result = await agentSubgraph.invoke(transformedState);
403
+ result = {
404
+ ...result,
405
+ /** Clear agentMessages for next agent */
406
+ agentMessages: [],
407
+ };
408
+ } else {
409
+ result = await agentSubgraph.invoke(state);
410
+ }
411
+
412
+ /** If agent has both handoff and direct edges, use Command for exclusive routing */
413
+ if (needsCommandRouting) {
414
+ /** Check if a handoff occurred */
415
+ const lastMessage = result.messages[
416
+ result.messages.length - 1
417
+ ] as BaseMessage | null;
418
+ if (
419
+ lastMessage != null &&
420
+ lastMessage.getType() === 'tool' &&
421
+ typeof lastMessage.name === 'string' &&
422
+ lastMessage.name.startsWith(Constants.LC_TRANSFER_TO_)
423
+ ) {
424
+ /** Handoff occurred - extract destination and navigate there exclusively */
425
+ const handoffDest = lastMessage.name.replace(
426
+ Constants.LC_TRANSFER_TO_,
427
+ ''
428
+ );
429
+ return new Command({
430
+ update: result,
431
+ goto: handoffDest,
432
+ });
433
+ } else {
434
+ /** No handoff - proceed with direct edges */
435
+ const directDests = Array.from(directDestinations);
436
+ if (directDests.length === 1) {
437
+ return new Command({
438
+ update: result,
439
+ goto: directDests[0],
440
+ });
441
+ } else if (directDests.length > 1) {
442
+ /** Multiple direct destinations - they'll run in parallel */
443
+ return new Command({
444
+ update: result,
445
+ goto: directDests,
446
+ });
447
+ }
448
+ }
449
+ }
450
+
451
+ /** No special routing needed - return state normally */
452
+ return result;
453
+ };
454
+
455
+ /** Wrapped agent as a node with its possible destinations */
456
+ builder.addNode(agentId, agentWrapper, {
457
+ ends: Array.from(allDestinations),
293
458
  });
294
459
  }
295
460
 
@@ -300,7 +465,8 @@ export class MultiAgentGraph extends StandardGraph {
300
465
  builder.addEdge(START, startNode);
301
466
  }
302
467
 
303
- /** Add direct edges for automatic transitions
468
+ /**
469
+ * Add direct edges for automatic transitions
304
470
  * Group edges by destination to handle fan-in scenarios
305
471
  */
306
472
  const edgesByDestination = new Map<string, t.GraphEdge[]>();
@@ -318,38 +484,74 @@ export class MultiAgentGraph extends StandardGraph {
318
484
  for (const [destination, edges] of edgesByDestination) {
319
485
  /** Checks if this is a fan-in scenario with prompt instructions */
320
486
  const edgesWithPrompt = edges.filter(
321
- (edge) =>
322
- edge.promptInstructions != null && edge.promptInstructions !== ''
487
+ (edge) => edge.prompt != null && edge.prompt !== ''
323
488
  );
324
489
 
325
490
  if (edgesWithPrompt.length > 0) {
326
- // Fan-in with prompt: create a single wrapper node for this destination
491
+ /**
492
+ * Single wrapper node for destination (Fan-in with prompt)
493
+ */
327
494
  const wrapperNodeId = `fan_in_${destination}_prompt`;
328
-
329
- // Use the first edge's prompt instructions (they should all be the same for fan-in)
330
- const promptInstructions = edgesWithPrompt[0].promptInstructions;
495
+ /**
496
+ * First edge's `prompt`
497
+ * (they should all be the same for fan-in)
498
+ */
499
+ const prompt = edgesWithPrompt[0].prompt;
500
+ /**
501
+ * First edge's `excludeResults` flag
502
+ * (they should all be the same for fan-in)
503
+ */
504
+ const excludeResults = edgesWithPrompt[0].excludeResults;
331
505
 
332
506
  builder.addNode(wrapperNodeId, async (state: t.BaseGraphState) => {
333
507
  let promptText: string | undefined;
334
-
335
- if (typeof promptInstructions === 'function') {
336
- promptText = promptInstructions(state.messages);
337
- } else {
338
- promptText = promptInstructions;
508
+ let effectiveExcludeResults = excludeResults;
509
+
510
+ if (typeof prompt === 'function') {
511
+ promptText = await prompt(state.messages, this.startIndex);
512
+ } else if (prompt != null) {
513
+ if (prompt.includes('{results}')) {
514
+ const resultsMessages = state.messages.slice(this.startIndex);
515
+ const resultsString = getBufferString(resultsMessages);
516
+ const promptTemplate = PromptTemplate.fromTemplate(prompt);
517
+ const result = await promptTemplate.invoke({
518
+ results: resultsString,
519
+ });
520
+ promptText = result.value;
521
+ effectiveExcludeResults =
522
+ excludeResults !== false && promptText !== '';
523
+ } else {
524
+ promptText = prompt;
525
+ }
339
526
  }
340
527
 
341
528
  if (promptText != null && promptText !== '') {
342
- // Return state with the prompt message added
529
+ if (
530
+ effectiveExcludeResults == null ||
531
+ effectiveExcludeResults === false
532
+ ) {
533
+ return {
534
+ messages: [new HumanMessage(promptText)],
535
+ };
536
+ }
537
+
538
+ /** When `excludeResults` is true, use agentMessages channel
539
+ * to pass filtered messages + prompt to the destination agent
540
+ */
541
+ const filteredMessages = state.messages.slice(0, this.startIndex);
343
542
  return {
344
- messages: [...state.messages, new HumanMessage(promptText)],
543
+ messages: [new HumanMessage(promptText)],
544
+ agentMessages: messagesStateReducer(filteredMessages, [
545
+ new HumanMessage(promptText),
546
+ ]),
345
547
  };
346
548
  }
347
549
 
348
- // No prompt needed, return empty update
550
+ /** No prompt needed, return empty update */
349
551
  return {};
350
552
  });
351
553
 
352
- // Add edges from all sources to the wrapper, then wrapper to destination
554
+ /** Add edges from all sources to the wrapper, then wrapper to destination */
353
555
  for (const edge of edges) {
354
556
  const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
355
557
  for (const source of sources) {
@@ -359,15 +561,30 @@ export class MultiAgentGraph extends StandardGraph {
359
561
  }
360
562
  }
361
563
 
362
- // Single edge from wrapper to destination
564
+ /** Single edge from wrapper to destination */
363
565
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
364
566
  /** @ts-ignore */
365
567
  builder.addEdge(wrapperNodeId, destination);
366
568
  } else {
367
- // No prompt instructions, add direct edges
569
+ /** No prompt instructions, add direct edges (skip if source uses Command routing) */
368
570
  for (const edge of edges) {
369
571
  const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
370
572
  for (const source of sources) {
573
+ /** Check if this source node has both handoff and direct edges */
574
+ const sourceHandoffEdges = this.handoffEdges.filter((e) => {
575
+ const eSources = Array.isArray(e.from) ? e.from : [e.from];
576
+ return eSources.includes(source);
577
+ });
578
+ const sourceDirectEdges = this.directEdges.filter((e) => {
579
+ const eSources = Array.isArray(e.from) ? e.from : [e.from];
580
+ return eSources.includes(source);
581
+ });
582
+
583
+ /** Skip adding edge if source uses Command routing (has both types) */
584
+ if (sourceHandoffEdges.length > 0 && sourceDirectEdges.length > 0) {
585
+ continue;
586
+ }
587
+
371
588
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
372
589
  /** @ts-ignore */
373
590
  builder.addEdge(source, destination);
@@ -135,12 +135,18 @@ export class CustomAnthropic extends ChatAnthropicMessages {
135
135
  private message_delta: AnthropicMessageDeltaEvent | undefined;
136
136
  private tools_in_params?: boolean;
137
137
  private emitted_usage?: boolean;
138
+ top_k: number | undefined;
138
139
  constructor(fields?: CustomAnthropicInput) {
139
140
  super(fields);
140
141
  this.resetTokenEvents();
142
+ this.setDirectFields(fields);
141
143
  this._lc_stream_delay = fields?._lc_stream_delay ?? 25;
142
144
  }
143
145
 
146
+ static lc_name(): 'LibreChatAnthropic' {
147
+ return 'LibreChatAnthropic';
148
+ }
149
+
144
150
  /**
145
151
  * Get the parameters used to invoke the model
146
152
  */
@@ -158,7 +164,7 @@ export class CustomAnthropic extends ChatAnthropicMessages {
158
164
  | undefined = handleToolChoice(options?.tool_choice);
159
165
 
160
166
  if (this.thinking.type === 'enabled') {
161
- if (this.topK !== -1 && (this.topK as number | undefined) != null) {
167
+ if (this.top_k !== -1 && (this.top_k as number | undefined) != null) {
162
168
  throw new Error('topK is not supported when thinking is enabled');
163
169
  }
164
170
  if (this.topP !== -1 && (this.topP as number | undefined) != null) {
@@ -187,7 +193,7 @@ export class CustomAnthropic extends ChatAnthropicMessages {
187
193
  return {
188
194
  model: this.model,
189
195
  temperature: this.temperature,
190
- top_k: this.topK,
196
+ top_k: this.top_k,
191
197
  top_p: this.topP,
192
198
  stop_sequences: options?.stop ?? this.stopSequences,
193
199
  stream: this.streaming,
@@ -244,6 +250,21 @@ export class CustomAnthropic extends ChatAnthropicMessages {
244
250
  this.tools_in_params = undefined;
245
251
  }
246
252
 
253
+ setDirectFields(fields?: CustomAnthropicInput): void {
254
+ this.temperature = fields?.temperature ?? undefined;
255
+ this.topP = fields?.topP ?? undefined;
256
+ this.top_k = fields?.topK;
257
+ if (this.temperature === -1 || this.temperature === 1) {
258
+ this.temperature = undefined;
259
+ }
260
+ if (this.topP === -1) {
261
+ this.topP = undefined;
262
+ }
263
+ if (this.top_k === -1) {
264
+ this.top_k = undefined;
265
+ }
266
+ }
267
+
247
268
  private createGenerationChunk({
248
269
  token,
249
270
  chunk,
@@ -107,6 +107,10 @@ export class CustomChatGoogleGenerativeAI extends ChatGoogleGenerativeAI {
107
107
  this.streamUsage = fields.streamUsage ?? this.streamUsage;
108
108
  }
109
109
 
110
+ static lc_name(): 'LibreChatGoogleGenerativeAI' {
111
+ return 'LibreChatGoogleGenerativeAI';
112
+ }
113
+
110
114
  invocationParams(
111
115
  options?: this['ParsedCallOptions']
112
116
  ): Omit<GenerateContentRequest, 'contents'> {
@@ -301,6 +301,20 @@ function _convertLangChainContentToPart(
301
301
  mimeType,
302
302
  },
303
303
  };
304
+ } else if (
305
+ content.type === 'document' ||
306
+ content.type === 'audio' ||
307
+ content.type === 'video'
308
+ ) {
309
+ if (!isMultimodalModel) {
310
+ throw new Error(`This model does not support ${content.type}s`);
311
+ }
312
+ return {
313
+ inlineData: {
314
+ data: content.data,
315
+ mimeType: content.mimeType,
316
+ },
317
+ };
304
318
  } else if (content.type === 'media') {
305
319
  return messageContentMedia(content);
306
320
  } else if (content.type === 'tool_use') {
@@ -13,6 +13,9 @@ import {
13
13
  } from './utils';
14
14
 
15
15
  export class ChatOllama extends BaseChatOllama {
16
+ static lc_name(): 'LibreChatOllama' {
17
+ return 'LibreChatOllama';
18
+ }
16
19
  async *_streamResponseChunks(
17
20
  messages: BaseMessage[],
18
21
  options: this['ParsedCallOptions'],