@librechat/agents 3.1.96 → 3.1.98

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 (81) hide show
  1. package/dist/cjs/graphs/Graph.cjs +60 -21
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/instrumentation.cjs +120 -9
  4. package/dist/cjs/instrumentation.cjs.map +1 -1
  5. package/dist/cjs/langfuse.cjs +30 -226
  6. package/dist/cjs/langfuse.cjs.map +1 -1
  7. package/dist/cjs/langfuseToolOutputTracing.cjs +476 -0
  8. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -0
  9. package/dist/cjs/llm/bedrock/index.cjs +10 -0
  10. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  11. package/dist/cjs/llm/bedrock/toolCache.cjs +125 -0
  12. package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -0
  13. package/dist/cjs/messages/cache.cjs +17 -9
  14. package/dist/cjs/messages/cache.cjs.map +1 -1
  15. package/dist/cjs/run.cjs +142 -69
  16. package/dist/cjs/run.cjs.map +1 -1
  17. package/dist/cjs/tools/ToolNode.cjs +26 -9
  18. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  19. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +10 -6
  20. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  21. package/dist/esm/graphs/Graph.mjs +62 -23
  22. package/dist/esm/graphs/Graph.mjs.map +1 -1
  23. package/dist/esm/instrumentation.mjs +118 -9
  24. package/dist/esm/instrumentation.mjs.map +1 -1
  25. package/dist/esm/langfuse.mjs +28 -224
  26. package/dist/esm/langfuse.mjs.map +1 -1
  27. package/dist/esm/langfuseToolOutputTracing.mjs +468 -0
  28. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -0
  29. package/dist/esm/llm/bedrock/index.mjs +10 -0
  30. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  31. package/dist/esm/llm/bedrock/toolCache.mjs +122 -0
  32. package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -0
  33. package/dist/esm/messages/cache.mjs +17 -9
  34. package/dist/esm/messages/cache.mjs.map +1 -1
  35. package/dist/esm/run.mjs +144 -71
  36. package/dist/esm/run.mjs.map +1 -1
  37. package/dist/esm/tools/ToolNode.mjs +26 -9
  38. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  39. package/dist/esm/tools/subagent/SubagentExecutor.mjs +10 -6
  40. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  41. package/dist/types/graphs/Graph.d.ts +5 -1
  42. package/dist/types/instrumentation.d.ts +5 -1
  43. package/dist/types/langfuse.d.ts +6 -28
  44. package/dist/types/langfuseToolOutputTracing.d.ts +20 -0
  45. package/dist/types/llm/bedrock/index.d.ts +16 -0
  46. package/dist/types/llm/bedrock/toolCache.d.ts +4 -0
  47. package/dist/types/messages/cache.d.ts +2 -2
  48. package/dist/types/run.d.ts +5 -1
  49. package/dist/types/tools/ToolNode.d.ts +4 -1
  50. package/dist/types/tools/subagent/SubagentExecutor.d.ts +2 -0
  51. package/dist/types/types/graph.d.ts +30 -0
  52. package/dist/types/types/llm.d.ts +2 -2
  53. package/dist/types/types/run.d.ts +6 -0
  54. package/dist/types/types/tools.d.ts +7 -0
  55. package/package.json +2 -1
  56. package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +332 -0
  57. package/src/agents/__tests__/AgentContext.bedrock.live.test.ts +504 -0
  58. package/src/graphs/Graph.ts +104 -34
  59. package/src/instrumentation.ts +172 -11
  60. package/src/langfuse.ts +59 -324
  61. package/src/langfuseToolOutputTracing.ts +702 -0
  62. package/src/llm/bedrock/index.ts +32 -1
  63. package/src/llm/bedrock/llm.spec.ts +154 -1
  64. package/src/llm/bedrock/toolCache.test.ts +131 -0
  65. package/src/llm/bedrock/toolCache.ts +191 -0
  66. package/src/messages/cache.test.ts +97 -38
  67. package/src/messages/cache.ts +18 -10
  68. package/src/run.ts +190 -87
  69. package/src/specs/langfuse-callbacks.test.ts +178 -1
  70. package/src/specs/langfuse-config.test.ts +112 -76
  71. package/src/specs/langfuse-instrumentation.test.ts +283 -0
  72. package/src/specs/langfuse-metadata.test.ts +54 -1
  73. package/src/specs/langfuse-tool-output-tracing.test.ts +616 -0
  74. package/src/tools/ToolNode.ts +35 -8
  75. package/src/tools/__tests__/SubagentExecutor.test.ts +32 -0
  76. package/src/tools/__tests__/ToolNode.langfuse.test.ts +47 -0
  77. package/src/tools/subagent/SubagentExecutor.ts +11 -6
  78. package/src/types/graph.ts +32 -0
  79. package/src/types/llm.ts +2 -2
  80. package/src/types/run.ts +6 -0
  81. package/src/types/tools.ts +7 -0
package/dist/esm/run.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import './instrumentation.mjs';
1
+ import { initializeLangfuseTracing } from './instrumentation.mjs';
2
2
  import { PromptTemplate } from '@langchain/core/prompts';
3
3
  import { RunnableLambda } from '@langchain/core/runnables';
4
4
  import { ChatOpenAI as ChatOpenAI$1, AzureChatOpenAI as AzureChatOpenAI$1 } from '@langchain/openai';
5
- import { MemorySaver, isInterrupted, INTERRUPT, Command } from '@langchain/langgraph';
5
+ import { MemorySaver, Command, isInterrupted, INTERRUPT } from '@langchain/langgraph';
6
6
  import { HumanMessage } from '@langchain/core/messages';
7
7
  import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
8
8
  import { createCompletionTitleRunnable, createTitleRunnable } from './utils/title.mjs';
@@ -16,7 +16,8 @@ import { executeHooks } from './hooks/executeHooks.mjs';
16
16
  import './hooks/createWorkspacePolicyHook.mjs';
17
17
  import { isOpenAILike } from './utils/llm.mjs';
18
18
  import { appendCallbacks, findCallback } from './utils/callbacks.mjs';
19
- import { hasLangfuseEnvConfig, hasExplicitLangfuseConfig, createLangfuseTraceMetadata, createLegacyLangfuseHandler, getLangfuseTraceName, createLangfuseHandler, isLangfuseCallbackHandler, disposeLangfuseHandler } from './langfuse.mjs';
19
+ import { createLangfuseTraceMetadata, createLangfuseHandler, getLangfuseTraceName, disposeLangfuseHandler, isLangfuseCallbackHandler } from './langfuse.mjs';
20
+ import { resolveLangfuseConfig, withLangfuseToolOutputTracingConfig } from './langfuseToolOutputTracing.mjs';
20
21
 
21
22
  // src/run.ts
22
23
  const defaultOmitOptions = new Set([
@@ -71,6 +72,7 @@ class Run {
71
72
  handlerRegistry;
72
73
  hookRegistry;
73
74
  humanInTheLoop;
75
+ langfuse;
74
76
  toolOutputReferences;
75
77
  eagerEventToolExecution;
76
78
  toolExecution;
@@ -110,6 +112,7 @@ class Run {
110
112
  this.handlerRegistry = handlerRegistry;
111
113
  this.hookRegistry = config.hooks;
112
114
  this.humanInTheLoop = config.humanInTheLoop;
115
+ this.langfuse = config.langfuse;
113
116
  this.toolOutputReferences = config.toolOutputReferences;
114
117
  this.eagerEventToolExecution = config.eagerEventToolExecution;
115
118
  this.toolExecution = config.toolExecution;
@@ -168,6 +171,7 @@ class Run {
168
171
  signal,
169
172
  runId: this.id,
170
173
  agents: [agentConfig],
174
+ langfuse: this.langfuse,
171
175
  tokenCounter: this.tokenCounter,
172
176
  indexTokenCountMap: this.indexTokenCountMap,
173
177
  calibrationRatio: this.calibrationRatio,
@@ -188,6 +192,7 @@ class Run {
188
192
  runId: this.id,
189
193
  agents,
190
194
  edges,
195
+ langfuse: this.langfuse,
191
196
  tokenCounter: this.tokenCounter,
192
197
  indexTokenCountMap: this.indexTokenCountMap,
193
198
  calibrationRatio: this.calibrationRatio,
@@ -421,6 +426,77 @@ class Run {
421
426
  }
422
427
  };
423
428
  }
429
+ shouldClearHookSession(streamThrew) {
430
+ return (this._interrupt == null || this._haltedReason != null || streamThrew);
431
+ }
432
+ isAwaitingResume(streamThrew) {
433
+ return (this._interrupt != null && this._haltedReason == null && !streamThrew);
434
+ }
435
+ getStreamLangfuseConfig(graph) {
436
+ const primaryContext = graph.agentContexts.get(graph.defaultAgentId);
437
+ if (primaryContext != null) {
438
+ return resolveLangfuseConfig(this.langfuse, primaryContext.langfuse);
439
+ }
440
+ for (const context of graph.agentContexts.values()) {
441
+ const langfuse = resolveLangfuseConfig(this.langfuse, context.langfuse);
442
+ if (langfuse != null) {
443
+ return langfuse;
444
+ }
445
+ }
446
+ return this.langfuse;
447
+ }
448
+ getStreamToolOutputTracingLangfuseConfig(graph) {
449
+ const toolOutputTracingConfigs = Array.from(graph.agentContexts.values())
450
+ .map((context) => {
451
+ return resolveLangfuseConfig(this.langfuse, context.langfuse)
452
+ ?.toolOutputTracing;
453
+ })
454
+ .filter((config) => {
455
+ return config != null;
456
+ });
457
+ if (toolOutputTracingConfigs.length === 0) {
458
+ return this.langfuse?.toolOutputTracing != null
459
+ ? { toolOutputTracing: this.langfuse.toolOutputTracing }
460
+ : undefined;
461
+ }
462
+ if (toolOutputTracingConfigs.length === 1) {
463
+ return { toolOutputTracing: toolOutputTracingConfigs[0] };
464
+ }
465
+ let enabled;
466
+ let redactionText;
467
+ let redactedToolNameMatchMode;
468
+ const redactedToolNames = new Set();
469
+ for (const config of toolOutputTracingConfigs) {
470
+ if (config.enabled === false) {
471
+ enabled = false;
472
+ }
473
+ else if (enabled !== false && config.enabled != null) {
474
+ enabled = config.enabled;
475
+ }
476
+ redactionText ??= config.redactionText;
477
+ if (config.redactedToolNameMatchMode === 'partial') {
478
+ redactedToolNameMatchMode = 'partial';
479
+ }
480
+ else {
481
+ redactedToolNameMatchMode ??= config.redactedToolNameMatchMode;
482
+ }
483
+ for (const toolName of config.redactedToolNames ?? []) {
484
+ redactedToolNames.add(toolName);
485
+ }
486
+ }
487
+ return {
488
+ toolOutputTracing: {
489
+ ...(enabled != null ? { enabled } : {}),
490
+ ...(redactedToolNames.size > 0
491
+ ? { redactedToolNames: Array.from(redactedToolNames) }
492
+ : {}),
493
+ ...(redactedToolNameMatchMode != null
494
+ ? { redactedToolNameMatchMode }
495
+ : {}),
496
+ ...(redactionText != null ? { redactionText } : {}),
497
+ },
498
+ };
499
+ }
424
500
  async processStream(inputs, callerConfig, streamOptions) {
425
501
  if (this.graphRunnable == null) {
426
502
  throw new Error('Run not initialized. Make sure to use Run.create() to instantiate the Run.');
@@ -428,6 +504,8 @@ class Run {
428
504
  if (!this.Graph) {
429
505
  throw new Error('Graph not initialized. Make sure to use Run.create() to instantiate the Run.');
430
506
  }
507
+ const graphRunnable = this.graphRunnable;
508
+ const graph = this.Graph;
431
509
  /**
432
510
  * `Command` inputs (currently only `Command({ resume })`) are
433
511
  * resume-mode invocations: LangGraph rebuilds graph state from the
@@ -454,7 +532,7 @@ class Run {
454
532
  * boundary.
455
533
  */
456
534
  if (!isResume) {
457
- this.Graph.resetValues(streamOptions?.keepContent);
535
+ graph.resetValues(streamOptions?.keepContent);
458
536
  }
459
537
  this._interrupt = undefined;
460
538
  this._haltedReason = undefined;
@@ -469,28 +547,31 @@ class Run {
469
547
  });
470
548
  customHandler.awaitHandlers = true;
471
549
  config.callbacks = appendCallbacks(config.callbacks, streamCallbacks ? [streamCallbacks, customHandler] : [customHandler]);
472
- if (hasLangfuseEnvConfig() &&
473
- !hasExplicitLangfuseConfig(this.Graph.agentContexts.values())) {
474
- const userId = typeof config.configurable?.user_id === 'string'
475
- ? config.configurable.user_id
476
- : undefined;
477
- const sessionId = typeof config.configurable?.thread_id === 'string'
478
- ? config.configurable.thread_id
479
- : undefined;
480
- const primaryContext = this.Graph.agentContexts.get(this.Graph.defaultAgentId);
481
- const traceMetadata = createLangfuseTraceMetadata({
482
- messageId: this.id,
483
- parentMessageId: config.configurable?.requestBody?.parentMessageId,
484
- agentName: primaryContext?.name,
485
- });
486
- const handler = createLegacyLangfuseHandler({
487
- userId,
488
- sessionId,
489
- traceMetadata,
490
- tags: ['librechat', 'agent'],
491
- });
550
+ const primaryContext = graph.agentContexts.get(graph.defaultAgentId);
551
+ const userId = typeof config.configurable?.user_id === 'string'
552
+ ? config.configurable.user_id
553
+ : undefined;
554
+ const sessionId = typeof config.configurable?.thread_id === 'string'
555
+ ? config.configurable.thread_id
556
+ : undefined;
557
+ const traceMetadata = createLangfuseTraceMetadata({
558
+ messageId: this.id,
559
+ parentMessageId: config.configurable?.requestBody?.parentMessageId,
560
+ agentId: graph.defaultAgentId,
561
+ agentName: primaryContext?.name,
562
+ });
563
+ const streamLangfuseConfig = this.getStreamLangfuseConfig(graph);
564
+ initializeLangfuseTracing(streamLangfuseConfig);
565
+ const langfuseHandler = createLangfuseHandler({
566
+ langfuse: streamLangfuseConfig,
567
+ userId,
568
+ sessionId,
569
+ traceMetadata,
570
+ tags: ['librechat', 'agent'],
571
+ });
572
+ if (langfuseHandler != null) {
492
573
  config.runName = config.runName ?? getLangfuseTraceName(traceMetadata);
493
- config.callbacks = appendCallbacks(config.callbacks, [handler]);
574
+ config.callbacks = appendCallbacks(config.callbacks, [langfuseHandler]);
494
575
  }
495
576
  if (!this.id) {
496
577
  throw new Error('Run ID not provided');
@@ -506,24 +587,6 @@ class Run {
506
587
  return undefined;
507
588
  }
508
589
  }
509
- /**
510
- * `streamEvents` accepts both state inputs and `Command` (resume) at
511
- * runtime, but our `CompiledStateWorkflow` type narrows the first
512
- * arg to `BaseGraphState`. Cast on the call so the resume path
513
- * type-checks without widening the wrapper for every caller.
514
- */
515
- const stream = this.graphRunnable.streamEvents(inputs, config, {
516
- raiseError: true,
517
- /**
518
- * Prevent EventStreamCallbackHandler from processing custom events.
519
- * Custom events are already handled via our createCustomEventCallback()
520
- * which routes them through the handlerRegistry.
521
- * Without this flag, EventStreamCallbackHandler throws errors when
522
- * custom events are dispatched for run IDs not in its internal map
523
- * (due to timing issues in parallel execution or after run cleanup).
524
- */
525
- ignoreCustomEvent: true,
526
- });
527
590
  /**
528
591
  * Tracks whether the stream loop threw. Used by the `finally`
529
592
  * block to decide whether to honor the interrupt-preservation
@@ -534,7 +597,25 @@ class Run {
534
597
  * preserving session hooks would leak them into the next run.
535
598
  */
536
599
  let streamThrew = false;
537
- try {
600
+ const consumeStream = async () => {
601
+ /**
602
+ * `streamEvents` accepts both state inputs and `Command` (resume) at
603
+ * runtime, but our `CompiledStateWorkflow` type narrows the first
604
+ * arg to `BaseGraphState`. Cast on the call so the resume path
605
+ * type-checks without widening the wrapper for every caller.
606
+ */
607
+ const stream = graphRunnable.streamEvents(inputs, config, {
608
+ raiseError: true,
609
+ /**
610
+ * Prevent EventStreamCallbackHandler from processing custom events.
611
+ * Custom events are already handled via our createCustomEventCallback()
612
+ * which routes them through the handlerRegistry.
613
+ * Without this flag, EventStreamCallbackHandler throws errors when
614
+ * custom events are dispatched for run IDs not in its internal map
615
+ * (due to timing issues in parallel execution or after run cleanup).
616
+ */
617
+ ignoreCustomEvent: true,
618
+ });
538
619
  for await (const event of stream) {
539
620
  const { data, metadata, ...info } = event;
540
621
  const eventName = info.event;
@@ -619,8 +700,8 @@ class Run {
619
700
  hook_event_name: 'Stop',
620
701
  runId: this.id,
621
702
  threadId,
622
- agentId: this.Graph.defaultAgentId,
623
- messages: this.Graph.getRunMessages() ?? stateInputs?.messages ?? [],
703
+ agentId: graph.defaultAgentId,
704
+ messages: graph.getRunMessages() ?? stateInputs?.messages ?? [],
624
705
  stopHookActive: false, // will be true when stop is triggered by a hook (Phase 2)
625
706
  },
626
707
  sessionId: this.id,
@@ -628,6 +709,9 @@ class Run {
628
709
  /* Stop hook errors must not masquerade as stream failures */
629
710
  });
630
711
  }
712
+ };
713
+ try {
714
+ await withLangfuseToolOutputTracingConfig(streamLangfuseConfig, consumeStream, this.getStreamToolOutputTracingLangfuseConfig(graph));
631
715
  }
632
716
  catch (err) {
633
717
  streamThrew = true;
@@ -665,9 +749,7 @@ class Run {
665
749
  * expected, sessions must drop). Every state where no resume
666
750
  * is expected clears.
667
751
  */
668
- if (this._interrupt == null ||
669
- this._haltedReason != null ||
670
- streamThrew) {
752
+ if (this.shouldClearHookSession(streamThrew)) {
671
753
  this.hookRegistry?.clearSession(this.id);
672
754
  }
673
755
  /**
@@ -679,6 +761,7 @@ class Run {
679
761
  * unaffected — their entries live under their own session ids.
680
762
  */
681
763
  this.hookRegistry?.clearHaltSignal(this.id);
764
+ await disposeLangfuseHandler(langfuseHandler);
682
765
  /**
683
766
  * Break the reference chain that keeps heavy data alive via
684
767
  * LangGraph's internal `__pregel_scratchpad.currentTaskInput` →
@@ -726,7 +809,7 @@ class Run {
726
809
  * Run from scratch) is a separate concern; see
727
810
  * `HumanInTheLoopConfig` JSDoc.
728
811
  */
729
- const awaitingResume = this._interrupt != null && this._haltedReason == null && !streamThrew;
812
+ const awaitingResume = this.isAwaitingResume(streamThrew);
730
813
  if (!this.skipCleanup && !awaitingResume) {
731
814
  this.Graph.clearHeavyState();
732
815
  }
@@ -905,25 +988,15 @@ class Run {
905
988
  const sessionId = typeof chainOptions.configurable?.thread_id === 'string'
906
989
  ? chainOptions.configurable.thread_id
907
990
  : undefined;
908
- const hasExplicitLangfuse = this.Graph != null &&
909
- hasExplicitLangfuseConfig(this.Graph.agentContexts.values());
910
- if (titleContext?.langfuse != null) {
911
- titleLangfuseHandler = createLangfuseHandler({
912
- langfuse: titleContext.langfuse,
913
- userId,
914
- sessionId,
915
- traceMetadata,
916
- tags: ['librechat', 'title'],
917
- });
918
- }
919
- else if (hasLangfuseEnvConfig() && !hasExplicitLangfuse) {
920
- titleLangfuseHandler = createLegacyLangfuseHandler({
921
- userId,
922
- sessionId,
923
- traceMetadata,
924
- tags: ['librechat', 'title'],
925
- });
926
- }
991
+ const titleLangfuseConfig = resolveLangfuseConfig(this.langfuse, titleContext?.langfuse);
992
+ initializeLangfuseTracing(titleLangfuseConfig);
993
+ titleLangfuseHandler = createLangfuseHandler({
994
+ langfuse: titleLangfuseConfig,
995
+ userId,
996
+ sessionId,
997
+ traceMetadata,
998
+ tags: ['librechat', 'title'],
999
+ });
927
1000
  if (titleLangfuseHandler != null) {
928
1001
  chainOptions.callbacks = appendCallbacks(chainOptions.callbacks, [
929
1002
  titleLangfuseHandler,
@@ -976,7 +1049,7 @@ class Run {
976
1049
  });
977
1050
  try {
978
1051
  try {
979
- return await fullChain.invoke({ input: inputText, output: response }, invokeConfig);
1052
+ return await withLangfuseToolOutputTracingConfig(this.langfuse, () => fullChain.invoke({ input: inputText, output: response }, invokeConfig), titleContext?.langfuse);
980
1053
  }
981
1054
  catch (_e) {
982
1055
  // Fallback: strip callbacks to avoid EventStream tracer errors in certain environments
@@ -986,7 +1059,7 @@ class Run {
986
1059
  const safeConfig = Object.assign({}, rest, {
987
1060
  callbacks: langfuseHandler ? [langfuseHandler] : [],
988
1061
  });
989
- return await fullChain.invoke({ input: inputText, output: response }, safeConfig);
1062
+ return await withLangfuseToolOutputTracingConfig(this.langfuse, () => fullChain.invoke({ input: inputText, output: response }, safeConfig), titleContext?.langfuse);
990
1063
  }
991
1064
  }
992
1065
  finally {