@librechat/agents 3.6.7 → 3.6.9

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 (103) hide show
  1. package/dist/cjs/common/constants.cjs +14 -0
  2. package/dist/cjs/common/constants.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +18 -6
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/graphs/MultiAgentGraph.cjs +6 -1
  6. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  7. package/dist/cjs/instrumentation.cjs +2 -0
  8. package/dist/cjs/instrumentation.cjs.map +1 -1
  9. package/dist/cjs/langfuse.cjs +46 -15
  10. package/dist/cjs/langfuse.cjs.map +1 -1
  11. package/dist/cjs/langfuseOperation.cjs +6 -0
  12. package/dist/cjs/langfuseOperation.cjs.map +1 -0
  13. package/dist/cjs/langfuseRuntimeContext.cjs +3 -1
  14. package/dist/cjs/langfuseRuntimeContext.cjs.map +1 -1
  15. package/dist/cjs/langfuseRuntimeScope.cjs +11 -1
  16. package/dist/cjs/langfuseRuntimeScope.cjs.map +1 -1
  17. package/dist/cjs/langfuseSpanRegistry.cjs +35 -0
  18. package/dist/cjs/langfuseSpanRegistry.cjs.map +1 -1
  19. package/dist/cjs/langfuseToolOutputTracing.cjs +1 -6
  20. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  21. package/dist/cjs/langfuseTraceShaping.cjs +58 -10
  22. package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
  23. package/dist/cjs/llm/openai/index.cjs +8 -0
  24. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  25. package/dist/cjs/main.cjs +8 -1
  26. package/dist/cjs/run.cjs +59 -25
  27. package/dist/cjs/run.cjs.map +1 -1
  28. package/dist/cjs/stream.cjs +1 -1
  29. package/dist/cjs/tools/ToolNode.cjs +2 -2
  30. package/dist/cjs/tools/search/search.cjs +19 -9
  31. package/dist/cjs/tools/search/search.cjs.map +1 -1
  32. package/dist/cjs/tools/search/tool.cjs +2 -1
  33. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  34. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +3 -3
  35. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  36. package/dist/esm/common/constants.mjs +8 -1
  37. package/dist/esm/common/constants.mjs.map +1 -1
  38. package/dist/esm/graphs/Graph.mjs +18 -6
  39. package/dist/esm/graphs/Graph.mjs.map +1 -1
  40. package/dist/esm/graphs/MultiAgentGraph.mjs +6 -1
  41. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  42. package/dist/esm/instrumentation.mjs +4 -2
  43. package/dist/esm/instrumentation.mjs.map +1 -1
  44. package/dist/esm/langfuse.mjs +47 -16
  45. package/dist/esm/langfuse.mjs.map +1 -1
  46. package/dist/esm/langfuseOperation.mjs +6 -0
  47. package/dist/esm/langfuseOperation.mjs.map +1 -0
  48. package/dist/esm/langfuseRuntimeContext.mjs +3 -2
  49. package/dist/esm/langfuseRuntimeContext.mjs.map +1 -1
  50. package/dist/esm/langfuseRuntimeScope.mjs +12 -3
  51. package/dist/esm/langfuseRuntimeScope.mjs.map +1 -1
  52. package/dist/esm/langfuseSpanRegistry.mjs +34 -1
  53. package/dist/esm/langfuseSpanRegistry.mjs.map +1 -1
  54. package/dist/esm/langfuseToolOutputTracing.mjs +1 -6
  55. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  56. package/dist/esm/langfuseTraceShaping.mjs +58 -10
  57. package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
  58. package/dist/esm/llm/openai/index.mjs +8 -0
  59. package/dist/esm/llm/openai/index.mjs.map +1 -1
  60. package/dist/esm/main.mjs +3 -3
  61. package/dist/esm/run.mjs +59 -25
  62. package/dist/esm/run.mjs.map +1 -1
  63. package/dist/esm/stream.mjs +1 -1
  64. package/dist/esm/tools/ToolNode.mjs +2 -2
  65. package/dist/esm/tools/search/search.mjs +19 -9
  66. package/dist/esm/tools/search/search.mjs.map +1 -1
  67. package/dist/esm/tools/search/tool.mjs +2 -1
  68. package/dist/esm/tools/search/tool.mjs.map +1 -1
  69. package/dist/esm/tools/subagent/SubagentExecutor.mjs +3 -3
  70. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  71. package/dist/types/common/constants.d.ts +8 -0
  72. package/dist/types/graphs/Graph.d.ts +8 -3
  73. package/dist/types/graphs/MultiAgentGraph.d.ts +1 -0
  74. package/dist/types/langfuse.d.ts +10 -1
  75. package/dist/types/langfuseOperation.d.ts +2 -0
  76. package/dist/types/langfuseRuntimeContext.d.ts +2 -0
  77. package/dist/types/langfuseRuntimeScope.d.ts +3 -1
  78. package/dist/types/langfuseSpanRegistry.d.ts +11 -1
  79. package/dist/types/langfuseTraceShaping.d.ts +8 -6
  80. package/dist/types/run.d.ts +4 -3
  81. package/dist/types/tools/search/types.d.ts +15 -0
  82. package/dist/types/types/activityLabel.d.ts +4 -3
  83. package/dist/types/types/reasoningLabel.d.ts +1 -1
  84. package/package.json +5 -5
  85. package/src/common/constants.ts +9 -0
  86. package/src/graphs/Graph.ts +125 -103
  87. package/src/graphs/MultiAgentGraph.ts +6 -2
  88. package/src/instrumentation.ts +12 -0
  89. package/src/langfuse.ts +83 -17
  90. package/src/langfuseOperation.ts +2 -0
  91. package/src/langfuseRuntimeContext.ts +6 -0
  92. package/src/langfuseRuntimeScope.ts +31 -1
  93. package/src/langfuseSpanRegistry.ts +78 -2
  94. package/src/langfuseToolOutputTracing.ts +0 -2
  95. package/src/langfuseTraceShaping.ts +120 -26
  96. package/src/llm/openai/index.ts +34 -3
  97. package/src/run.ts +118 -49
  98. package/src/tools/search/search.ts +25 -8
  99. package/src/tools/search/tool.ts +2 -0
  100. package/src/tools/search/types.ts +16 -0
  101. package/src/tools/subagent/SubagentExecutor.ts +8 -4
  102. package/src/types/activityLabel.ts +4 -3
  103. package/src/types/reasoningLabel.ts +1 -1
package/src/run.ts CHANGED
@@ -26,6 +26,16 @@ import type { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
26
26
  import type { StandardGraph } from '@/graphs/Graph';
27
27
  import type { HookRegistry } from '@/hooks';
28
28
  import type * as t from '@/types';
29
+ import {
30
+ Callback,
31
+ GraphEvents,
32
+ TitleMethod,
33
+ ACTIVITY_LABEL_RUN_NAME,
34
+ REASONING_LABEL_RUN_NAME,
35
+ ACTIVITY_PHASE_RUN_NAME,
36
+ ACTIVITY_PHASE_LABEL_RUN_NAME,
37
+ DEFAULT_RECURSION_LIMIT,
38
+ } from '@/common';
29
39
  import {
30
40
  requireValidSubagentResumeManifest,
31
41
  stripSubagentResumeManifest,
@@ -58,6 +68,10 @@ import {
58
68
  resolveLangfuseConfig,
59
69
  resolveToolOutputTracingConfig,
60
70
  } from '@/langfuseConfig';
71
+ import {
72
+ resolveLangfuseDestinationKey,
73
+ resolveLangfuseTraceAnchorParent,
74
+ } from '@/langfuseSpanRegistry';
61
75
  import {
62
76
  appendCallbacks,
63
77
  filterCallbacks,
@@ -72,17 +86,12 @@ import {
72
86
  getRunStepResumeState,
73
87
  stripRunStepResumeState,
74
88
  } from '@/tools/runStepResume';
75
- import {
76
- Callback,
77
- GraphEvents,
78
- TitleMethod,
79
- DEFAULT_RECURSION_LIMIT,
80
- } from '@/common';
81
89
  import {
82
90
  createCompletionTitleRunnable,
83
91
  createTitleRunnable,
84
92
  } from '@/utils/title';
85
93
  import { applyGraphRuntimeConfig } from '@/graphs/applyGraphRuntimeConfig';
94
+ import { LANGFUSE_OPERATION_METADATA_KEY } from '@/langfuseOperation';
86
95
  import { createTokenCounter, encodingForModel } from '@/utils/tokens';
87
96
  import { initializeLangfuseTracing } from './instrumentation';
88
97
  import { seedRunInitialSessions } from '@/utils/toolSessions';
@@ -986,6 +995,9 @@ export class Run<_T extends t.BaseGraphState> {
986
995
  * instead of `inputs.messages`.
987
996
  */
988
997
  const isResume = inputs instanceof Command;
998
+ if (!isResume) {
999
+ graph.startFreshLangfuseExecution();
1000
+ }
989
1001
  const stateInputs = isResume ? undefined : (inputs as t.IState);
990
1002
  if (stateInputs != null) {
991
1003
  this.activityPhaseTraceInput = findActivityPhaseTraceInput(
@@ -1116,6 +1128,7 @@ export class Run<_T extends t.BaseGraphState> {
1116
1128
  streamLangfuseConfig?.deterministicTraceId === true
1117
1129
  ? this.id
1118
1130
  : undefined,
1131
+ traceAnchor: graph.langfuseTraceAnchor,
1119
1132
  // The graph's per-execution stamp, NOT the public run id: public ids
1120
1133
  // may repeat across concurrent executions (retries, tenant-local
1121
1134
  // message ids), and equal stamps defeat foreign-scope rejection.
@@ -1131,6 +1144,7 @@ export class Run<_T extends t.BaseGraphState> {
1131
1144
  streamLangfuseConfig?.deterministicTraceId === true
1132
1145
  ? this.id
1133
1146
  : undefined,
1147
+ traceAnchor: graph.langfuseTraceAnchor,
1134
1148
  runId: graph.langfuseScopeRunId,
1135
1149
  // The aggregate multi-agent policy from the runtime scope — the
1136
1150
  // handler must restore THIS (not the primary agent's config-derived
@@ -1139,7 +1153,6 @@ export class Run<_T extends t.BaseGraphState> {
1139
1153
  traceName,
1140
1154
  });
1141
1155
  if (langfuseHandler != null) {
1142
- config.runName = traceName;
1143
1156
  config.callbacks = appendCallbacks(config.callbacks, [langfuseHandler]);
1144
1157
  }
1145
1158
 
@@ -1201,18 +1214,22 @@ export class Run<_T extends t.BaseGraphState> {
1201
1214
  * arg to `BaseGraphState`. Cast on the call so the resume path
1202
1215
  * type-checks without widening the wrapper for every caller.
1203
1216
  */
1204
- const stream = graphRunnable.streamEvents(inputs as t.IState, config, {
1205
- raiseError: true,
1206
- /**
1207
- * Prevent EventStreamCallbackHandler from processing custom events.
1208
- * Custom events are already handled via our createCustomEventCallback()
1209
- * which routes them through the handlerRegistry.
1210
- * Without this flag, EventStreamCallbackHandler throws errors when
1211
- * custom events are dispatched for run IDs not in its internal map
1212
- * (due to timing issues in parallel execution or after run cleanup).
1213
- */
1214
- ignoreCustomEvent: true,
1215
- });
1217
+ const stream = graphRunnable.streamEvents(
1218
+ inputs as t.IState,
1219
+ { ...config, runName: graph.runName },
1220
+ {
1221
+ raiseError: true,
1222
+ /**
1223
+ * Prevent EventStreamCallbackHandler from processing custom events.
1224
+ * Custom events are already handled via our createCustomEventCallback()
1225
+ * which routes them through the handlerRegistry.
1226
+ * Without this flag, EventStreamCallbackHandler throws errors when
1227
+ * custom events are dispatched for run IDs not in its internal map
1228
+ * (due to timing issues in parallel execution or after run cleanup).
1229
+ */
1230
+ ignoreCustomEvent: true,
1231
+ }
1232
+ );
1216
1233
 
1217
1234
  for await (const event of stream) {
1218
1235
  const { data, metadata, ...info } = event;
@@ -2123,6 +2140,7 @@ export class Run<_T extends t.BaseGraphState> {
2123
2140
  const labelAgentName =
2124
2141
  labelAgentId == null ? undefined : labelContext?.name;
2125
2142
  const labelMetadata: Record<string, unknown> = {
2143
+ [LANGFUSE_OPERATION_METADATA_KEY]: ACTIVITY_LABEL_RUN_NAME,
2126
2144
  sourceRunId: this.id,
2127
2145
  responseId: this.id,
2128
2146
  activityIndex: labelIndex,
@@ -2132,7 +2150,7 @@ export class Run<_T extends t.BaseGraphState> {
2132
2150
  ...(labelAgentId == null ? {} : { agentId: labelAgentId }),
2133
2151
  ...(labelAgentName == null ? {} : { agentName: labelAgentName }),
2134
2152
  };
2135
- const traceMetadata = {
2153
+ const traceMetadata: Record<string, string> = {
2136
2154
  ...createLangfuseTraceMetadata({
2137
2155
  messageId: 'activity-label-' + this.id,
2138
2156
  parentMessageId: labelParentMessageId,
@@ -2150,16 +2168,9 @@ export class Run<_T extends t.BaseGraphState> {
2150
2168
  labelContext?.langfuse
2151
2169
  );
2152
2170
  initializeLangfuseTracing(labelLangfuseConfig);
2153
- /** Seed policy, threading two constraints:
2154
- * 1. `runWithLangfuseRuntimeContext` SPREADS the surrounding context, so
2155
- * an absent seed INHERITS the parent run's and collapses every label
2156
- * into that trace. When a parent seed is active we must override it
2157
- * with a per-label one.
2158
- * 2. Without deterministic tracing there is no parent seed, and forcing
2159
- * one here would make label trace ids deterministic when neither
2160
- * `processStream` nor `generateTitle` are — so leave it unset.
2161
- * Seeded when determinism is opted into OR a parent seed is live;
2162
- * otherwise unseeded, matching the other generation paths. */
2171
+ /** A captured source observation parents the label in the agent trace.
2172
+ * The distinct seed remains the standalone fallback when labeling runs
2173
+ * before tracing starts or against another Langfuse destination. */
2163
2174
  const inheritedTraceSeed = getTraceIdSeed();
2164
2175
  const labelTraceSeed =
2165
2176
  labelLangfuseConfig?.deterministicTraceId === true ||
@@ -2174,6 +2185,15 @@ export class Run<_T extends t.BaseGraphState> {
2174
2185
  traceIdSeed: labelTraceSeed,
2175
2186
  runId: labelScopeRunId,
2176
2187
  });
2188
+ const labelParentSpanContext = resolveLangfuseTraceAnchorParent(
2189
+ this.Graph?.langfuseTraceAnchor,
2190
+ resolveLangfuseDestinationKey(labelLangfuseConfig),
2191
+ labelAgentId
2192
+ );
2193
+ if (labelParentSpanContext != null) {
2194
+ labelMetadata.sourceTraceId = labelParentSpanContext.traceId;
2195
+ traceMetadata.sourceTraceId = labelParentSpanContext.traceId;
2196
+ }
2177
2197
  /** Handler only when a session id resolved from
2178
2198
  * `chainOptions.configurable.thread_id`: without it the label call has
2179
2199
  * no conversation identity, and tracing it would create an orphan
@@ -2187,15 +2207,18 @@ export class Run<_T extends t.BaseGraphState> {
2187
2207
  langfuse: labelLangfuseConfig,
2188
2208
  userId: labelUserId,
2189
2209
  sessionId: labelSessionId,
2190
- traceMetadata,
2210
+ traceMetadata:
2211
+ labelParentSpanContext == null ? traceMetadata : undefined,
2191
2212
  tags: labelTags,
2192
2213
  traceIdSeed:
2193
2214
  labelLangfuseConfig?.deterministicTraceId === true
2194
2215
  ? labelTraceSeed
2195
2216
  : undefined,
2217
+ parentSpanContext: labelParentSpanContext,
2218
+ inheritTraceIdentity: labelParentSpanContext != null,
2196
2219
  runId: labelScopeRunId,
2197
2220
  toolOutputTracing: labelRuntimeScope.toolOutputTracing,
2198
- traceName: labelRunName,
2221
+ traceName: labelParentSpanContext == null ? labelRunName : undefined,
2199
2222
  });
2200
2223
  }
2201
2224
  if (labelLangfuseHandler != null) {
@@ -2297,9 +2320,11 @@ export class Run<_T extends t.BaseGraphState> {
2297
2320
  langfuse: labelLangfuseConfig,
2298
2321
  userId: labelUserId,
2299
2322
  sessionId: labelSessionId,
2300
- traceName: labelRunName,
2301
- traceMetadata,
2323
+ traceName: labelParentSpanContext == null ? labelRunName : undefined,
2324
+ traceMetadata:
2325
+ labelParentSpanContext == null ? traceMetadata : undefined,
2302
2326
  tags: labelTags,
2327
+ inheritTraceIdentity: labelParentSpanContext != null,
2303
2328
  },
2304
2329
  () =>
2305
2330
  model.invoke(
@@ -2457,6 +2482,7 @@ export class Run<_T extends t.BaseGraphState> {
2457
2482
  const resolvedSourceRunId = sourceRunId ?? this.id;
2458
2483
  const reasoningResponseId = responseId ?? this.id;
2459
2484
  const reasoningMetadata: Record<string, unknown> = {
2485
+ [LANGFUSE_OPERATION_METADATA_KEY]: REASONING_LABEL_RUN_NAME,
2460
2486
  sourceRunId: resolvedSourceRunId,
2461
2487
  ...(sourceTraceId == null ? {} : { sourceTraceId }),
2462
2488
  responseId: reasoningResponseId,
@@ -2470,7 +2496,7 @@ export class Run<_T extends t.BaseGraphState> {
2470
2496
  ...(reasoningAgentId == null ? {} : { agentId: reasoningAgentId }),
2471
2497
  ...(reasoningAgentName == null ? {} : { agentName: reasoningAgentName }),
2472
2498
  };
2473
- const traceMetadata = {
2499
+ const traceMetadata: Record<string, string> = {
2474
2500
  ...createLangfuseTraceMetadata({
2475
2501
  messageId: `reasoning-label-${reasoningResponseId}`,
2476
2502
  parentMessageId: reasoningParentMessageId,
@@ -2512,21 +2538,40 @@ export class Run<_T extends t.BaseGraphState> {
2512
2538
  traceIdSeed: reasoningTraceSeed,
2513
2539
  runId: reasoningScopeRunId,
2514
2540
  });
2541
+ const candidateReasoningParent = resolveLangfuseTraceAnchorParent(
2542
+ this.Graph?.langfuseTraceAnchor,
2543
+ resolveLangfuseDestinationKey(reasoningLangfuseConfig),
2544
+ reasoningAgentId
2545
+ );
2546
+ const reasoningParentSpanContext =
2547
+ resolvedSourceRunId === this.id &&
2548
+ (sourceTraceId == null ||
2549
+ sourceTraceId === candidateReasoningParent?.traceId)
2550
+ ? candidateReasoningParent
2551
+ : undefined;
2552
+ if (reasoningParentSpanContext != null) {
2553
+ reasoningMetadata.sourceTraceId = reasoningParentSpanContext.traceId;
2554
+ traceMetadata.sourceTraceId = reasoningParentSpanContext.traceId;
2555
+ }
2515
2556
  let reasoningLangfuseHandler: CallbackEntry | undefined;
2516
2557
  if (reasoningSessionId != null) {
2517
2558
  reasoningLangfuseHandler = createLangfuseHandler({
2518
2559
  langfuse: reasoningLangfuseConfig,
2519
2560
  userId: reasoningUserId,
2520
2561
  sessionId: reasoningSessionId,
2521
- traceMetadata,
2562
+ traceMetadata:
2563
+ reasoningParentSpanContext == null ? traceMetadata : undefined,
2522
2564
  tags: reasoningTags,
2523
2565
  traceIdSeed:
2524
2566
  reasoningLangfuseConfig?.deterministicTraceId === true
2525
2567
  ? reasoningTraceSeed
2526
2568
  : undefined,
2569
+ parentSpanContext: reasoningParentSpanContext,
2570
+ inheritTraceIdentity: reasoningParentSpanContext != null,
2527
2571
  runId: reasoningScopeRunId,
2528
2572
  toolOutputTracing: reasoningRuntimeScope.toolOutputTracing,
2529
- traceName: reasoningRunName,
2573
+ traceName:
2574
+ reasoningParentSpanContext == null ? reasoningRunName : undefined,
2530
2575
  });
2531
2576
  }
2532
2577
  if (reasoningLangfuseHandler != null) {
@@ -2585,9 +2630,12 @@ export class Run<_T extends t.BaseGraphState> {
2585
2630
  langfuse: reasoningLangfuseConfig,
2586
2631
  userId: reasoningUserId,
2587
2632
  sessionId: reasoningSessionId,
2588
- traceName: reasoningRunName,
2589
- traceMetadata,
2633
+ traceName:
2634
+ reasoningParentSpanContext == null ? reasoningRunName : undefined,
2635
+ traceMetadata:
2636
+ reasoningParentSpanContext == null ? traceMetadata : undefined,
2590
2637
  tags: reasoningTags,
2638
+ inheritTraceIdentity: reasoningParentSpanContext != null,
2591
2639
  },
2592
2640
  () =>
2593
2641
  model.invoke(
@@ -2638,9 +2686,10 @@ export class Run<_T extends t.BaseGraphState> {
2638
2686
 
2639
2687
  /**
2640
2688
  * Generates one parent summary for two or more logical activities. The
2641
- * summary model is traced as a dedicated activity-phase chain root in the
2642
- * conversation session, with the model callback recorded as its generation
2643
- * child. No session id means no phase trace, avoiding orphan observations.
2689
+ * summary model is traced as an activity-phase chain beneath the source
2690
+ * agent run, with the model callback recorded as its generation child. It
2691
+ * falls back to a dedicated trace when no source observation is available.
2692
+ * No session id means no phase trace, avoiding orphan observations.
2644
2693
  */
2645
2694
  async generateActivityPhaseLabel({
2646
2695
  provider,
@@ -2771,6 +2820,7 @@ export class Run<_T extends t.BaseGraphState> {
2771
2820
  const phaseParentMessageId =
2772
2821
  phaseChainOptions.configurable?.requestBody?.parentMessageId;
2773
2822
  const phaseMetadata: Record<string, unknown> = {
2823
+ [LANGFUSE_OPERATION_METADATA_KEY]: ACTIVITY_PHASE_LABEL_RUN_NAME,
2774
2824
  sourceRunId: sourceRunId ?? this.id,
2775
2825
  ...(sourceTraceId == null ? {} : { sourceTraceId }),
2776
2826
  responseId: phaseMessageId,
@@ -2785,7 +2835,7 @@ export class Run<_T extends t.BaseGraphState> {
2785
2835
  ...(phaseAgentName == null ? {} : { agentName: phaseAgentName }),
2786
2836
  ...(closingTextPhase == null ? {} : { closingTextPhase }),
2787
2837
  };
2788
- const traceMetadata = {
2838
+ const traceMetadata: Record<string, string> = {
2789
2839
  ...createLangfuseTraceMetadata({
2790
2840
  messageId: phaseMessageId,
2791
2841
  parentMessageId: phaseParentMessageId,
@@ -2826,6 +2876,19 @@ export class Run<_T extends t.BaseGraphState> {
2826
2876
  traceIdSeed: phaseTraceSeed,
2827
2877
  runId: phaseScopeRunId,
2828
2878
  });
2879
+ const candidatePhaseParent = resolveLangfuseTraceAnchorParent(
2880
+ this.Graph?.langfuseTraceAnchor,
2881
+ resolveLangfuseDestinationKey(phaseLangfuseConfig)
2882
+ );
2883
+ const phaseParentSpanContext =
2884
+ (sourceRunId == null || sourceRunId === this.id) &&
2885
+ (sourceTraceId == null || sourceTraceId === candidatePhaseParent?.traceId)
2886
+ ? candidatePhaseParent
2887
+ : undefined;
2888
+ if (phaseParentSpanContext != null) {
2889
+ phaseMetadata.sourceTraceId = phaseParentSpanContext.traceId;
2890
+ traceMetadata.sourceTraceId = phaseParentSpanContext.traceId;
2891
+ }
2829
2892
  let phaseLangfuseHandler: CallbackEntry | undefined;
2830
2893
  const sourceUserText =
2831
2894
  this.activityPhaseTraceInput ??
@@ -2835,15 +2898,18 @@ export class Run<_T extends t.BaseGraphState> {
2835
2898
  langfuse: phaseLangfuseConfig,
2836
2899
  userId: phaseUserId,
2837
2900
  sessionId: phaseSessionId,
2838
- traceMetadata,
2901
+ traceMetadata:
2902
+ phaseParentSpanContext == null ? traceMetadata : undefined,
2839
2903
  tags: phaseTags,
2840
2904
  traceIdSeed:
2841
2905
  phaseLangfuseConfig?.deterministicTraceId === true
2842
2906
  ? phaseTraceSeed
2843
2907
  : undefined,
2908
+ parentSpanContext: phaseParentSpanContext,
2909
+ inheritTraceIdentity: phaseParentSpanContext != null,
2844
2910
  runId: phaseScopeRunId,
2845
2911
  toolOutputTracing: phaseRuntimeScope.toolOutputTracing,
2846
- traceName: phaseTraceName,
2912
+ traceName: phaseParentSpanContext == null ? phaseTraceName : undefined,
2847
2913
  });
2848
2914
  }
2849
2915
  if (phaseLangfuseHandler != null) {
@@ -2864,7 +2930,7 @@ export class Run<_T extends t.BaseGraphState> {
2864
2930
  const invokeConfig = Object.assign({}, phaseChainOptions, {
2865
2931
  run_id: phaseRunId,
2866
2932
  runId: phaseRunId,
2867
- runName: 'summarize-activity-phase',
2933
+ runName: ACTIVITY_PHASE_RUN_NAME,
2868
2934
  tags: [...new Set([...(phaseChainOptions.tags ?? []), ...phaseTags])],
2869
2935
  metadata: {
2870
2936
  ...(phaseChainOptions.metadata ?? {}),
@@ -2943,7 +3009,7 @@ export class Run<_T extends t.BaseGraphState> {
2943
3009
  ? { label, messages: [new AIMessage(label)] }
2944
3010
  : { messages: [] };
2945
3011
  },
2946
- }).withConfig({ runName: 'summarize-activity-phase' });
3012
+ }).withConfig({ runName: ACTIVITY_PHASE_RUN_NAME });
2947
3013
 
2948
3014
  try {
2949
3015
  const result = await withLangfuseRuntimeScope(phaseRuntimeScope, () =>
@@ -2952,9 +3018,12 @@ export class Run<_T extends t.BaseGraphState> {
2952
3018
  langfuse: phaseLangfuseConfig,
2953
3019
  userId: phaseUserId,
2954
3020
  sessionId: phaseSessionId,
2955
- traceName: phaseTraceName,
2956
- traceMetadata,
3021
+ traceName:
3022
+ phaseParentSpanContext == null ? phaseTraceName : undefined,
3023
+ traceMetadata:
3024
+ phaseParentSpanContext == null ? traceMetadata : undefined,
2957
3025
  tags: phaseTags,
3026
+ inheritTraceIdentity: phaseParentSpanContext != null,
2958
3027
  },
2959
3028
  () =>
2960
3029
  phaseRunnable.invoke(
@@ -7,6 +7,9 @@ import { createTavilyAPI } from './tavily-search';
7
7
  import { createCrwAPI } from './crw-search';
8
8
  import { BaseReranker } from './rerankers';
9
9
 
10
+ /** Engines queried when `searxngSearchOptions.engines` is not configured. */
11
+ const DEFAULT_SEARXNG_ENGINES = 'google,bing,duckduckgo';
12
+
10
13
  const chunker = {
11
14
  cleanText: (text: string): string => {
12
15
  if (!text) return '';
@@ -284,15 +287,20 @@ const createSerperAPI = (
284
287
  const createSearXNGAPI = (
285
288
  instanceUrl?: string,
286
289
  apiKey?: string,
287
- agents?: t.HttpAgentConfig
290
+ options?: t.SearxNGSearchOptions
288
291
  ): {
289
292
  getSources: (params: t.GetSourcesParams) => Promise<t.SearchResult>;
290
293
  } => {
294
+ const engines = options?.engines?.trim();
295
+ const language = options?.language?.trim();
291
296
  const config = {
292
297
  instanceUrl: instanceUrl ?? process.env.SEARXNG_INSTANCE_URL,
293
298
  apiKey: apiKey ?? process.env.SEARXNG_API_KEY,
294
- defaultLocation: 'all',
295
- timeout: 10000,
299
+ engines:
300
+ engines != null && engines !== '' ? engines : DEFAULT_SEARXNG_ENGINES,
301
+ language: language != null && language !== '' ? language : 'all',
302
+ timeRange: options?.timeRange,
303
+ timeout: options?.timeout ?? 10000,
296
304
  };
297
305
 
298
306
  if (config.instanceUrl == null || config.instanceUrl === '') {
@@ -336,11 +344,15 @@ const createSearXNGAPI = (
336
344
  format: 'json',
337
345
  pageno: 1,
338
346
  categories: category,
339
- language: 'all',
347
+ language: config.language,
340
348
  safesearch: safeSearch,
341
- engines: 'google,bing,duckduckgo',
349
+ engines: config.engines,
342
350
  };
343
351
 
352
+ if (config.timeRange != null) {
353
+ params.time_range = config.timeRange;
354
+ }
355
+
344
356
  const headers: Record<string, string> = {
345
357
  'Content-Type': 'application/json',
346
358
  };
@@ -353,8 +365,8 @@ const createSearXNGAPI = (
353
365
  headers,
354
366
  params,
355
367
  timeout: config.timeout,
356
- httpAgent: agents?.httpAgent,
357
- httpsAgent: agents?.httpsAgent,
368
+ httpAgent: options?.httpAgent,
369
+ httpsAgent: options?.httpsAgent,
358
370
  });
359
371
 
360
372
  const data = response.data;
@@ -490,6 +502,7 @@ export const createSearchAPI = (
490
502
  serperApiKey,
491
503
  searxngInstanceUrl,
492
504
  searxngApiKey,
505
+ searxngSearchOptions,
493
506
  tavilyApiKey,
494
507
  tavilySearchUrl,
495
508
  tavilySearchOptions,
@@ -508,7 +521,11 @@ export const createSearchAPI = (
508
521
  if (searchProvider.toLowerCase() === 'serper') {
509
522
  return createSerperAPI(serperApiKey, agents);
510
523
  } else if (searchProvider.toLowerCase() === 'searxng') {
511
- return createSearXNGAPI(searxngInstanceUrl, searxngApiKey, agents);
524
+ return createSearXNGAPI(searxngInstanceUrl, searxngApiKey, {
525
+ ...searxngSearchOptions,
526
+ httpAgent: httpAgent ?? searxngSearchOptions?.httpAgent,
527
+ httpsAgent: httpsAgent ?? searxngSearchOptions?.httpsAgent,
528
+ });
512
529
  } else if (searchProvider.toLowerCase() === 'tavily') {
513
530
  return createTavilyAPI(tavilyApiKey, tavilySearchUrl, {
514
531
  ...tavilySearchOptions,
@@ -417,6 +417,7 @@ export const createSearchTool = (
417
417
  serperApiKey,
418
418
  searxngInstanceUrl,
419
419
  searxngApiKey,
420
+ searxngSearchOptions,
420
421
  tavilyApiKey,
421
422
  tavilySearchUrl,
422
423
  tavilyExtractUrl,
@@ -494,6 +495,7 @@ export const createSearchTool = (
494
495
  serperApiKey,
495
496
  searxngInstanceUrl,
496
497
  searxngApiKey,
498
+ searxngSearchOptions,
497
499
  tavilyApiKey,
498
500
  tavilySearchUrl,
499
501
  tavilySearchOptions: effectiveTavilySearchOptions,
@@ -181,11 +181,27 @@ export interface CrwSearchResponse {
181
181
  error_code?: string;
182
182
  }
183
183
 
184
+ export interface SearxNGSearchOptions extends HttpAgentConfig {
185
+ /**
186
+ * Comma-separated list of engines to query, e.g. "google,bing,startpage".
187
+ * Defaults to "google,bing,duckduckgo". Engines not enabled on the target
188
+ * instance are ignored by SearXNG.
189
+ */
190
+ engines?: string;
191
+ /** Code of the language for search results. Defaults to "all". */
192
+ language?: string;
193
+ /** Time range filter, sent to SearXNG as `time_range`. Omitted when unset. */
194
+ timeRange?: SearxNGSearchPayload['time_range'];
195
+ /** HTTP request timeout in ms. Defaults to 10000. */
196
+ timeout?: number;
197
+ }
198
+
184
199
  export interface SearchConfig extends HttpAgentConfig {
185
200
  searchProvider?: SearchProvider;
186
201
  serperApiKey?: string;
187
202
  searxngInstanceUrl?: string;
188
203
  searxngApiKey?: string;
204
+ searxngSearchOptions?: SearxNGSearchOptions;
189
205
  tavilyApiKey?: string;
190
206
  tavilySearchUrl?: string;
191
207
  tavilyExtractUrl?: string;
@@ -2745,10 +2745,14 @@ export class SubagentExecutor {
2745
2745
 
2746
2746
  for (;;) {
2747
2747
  let childResult: MultiAgentGraphState;
2748
- if (this.humanInTheLoop?.enabled === true) {
2749
- /** Execute as an independently checkpointed root instead of inheriting
2750
- * the parent's Pregel namespace. Parent decisions are routed explicitly
2751
- * by interrupt id, so concurrent children keep isolated resume state. */
2748
+ if (
2749
+ this.humanInTheLoop?.enabled === true ||
2750
+ params.taskRuntime != null
2751
+ ) {
2752
+ /** Execute as an independent LangGraph root. HITL children need an
2753
+ * isolated Pregel namespace, while detached children must replace the
2754
+ * parent ToolNode's ambient runnable config so its AbortSignal cannot
2755
+ * cancel task-owned work when the parent stream settles. */
2752
2756
  childResult =
2753
2757
  await AsyncLocalStorageProviderSingleton.runWithConfig(
2754
2758
  childInvokeConfig,
@@ -62,8 +62,9 @@ export type RunActivityLabelOptions = {
62
62
  };
63
63
  /**
64
64
  * Seed for deterministic Langfuse trace ids (e.g. `${runId}-${slotIndex}`)
65
- * so each batch's label gets a distinct, reproducible trace. When omitted,
66
- * a per-run sequence keeps batches from collapsing into one trace.
65
+ * for a distinct, reproducible fallback trace when the source agent
66
+ * observation is unavailable. Normally the label is parented into the
67
+ * source agent trace instead.
67
68
  */
68
69
  traceSeed?: string;
69
70
  };
@@ -112,7 +113,7 @@ export type RunActivityPhaseLabelOptions = {
112
113
  traceSeed?: string;
113
114
  /** Stable source run identifier recorded on the phase observation. */
114
115
  sourceRunId?: string;
115
- /** Source Langfuse trace id for linking this detached summary trace. */
116
+ /** Source Langfuse trace id retained as correlation metadata. */
116
117
  sourceTraceId?: string;
117
118
  /** Host response/message identifier recorded on the phase observation. */
118
119
  responseId?: string;
@@ -52,7 +52,7 @@ export type RunReasoningLabelOptions = {
52
52
  traceSeed?: string;
53
53
  /** Stable source run identifier recorded on the observation. */
54
54
  sourceRunId?: string;
55
- /** Source Langfuse trace id for linking this detached label trace. */
55
+ /** Source Langfuse trace id retained as correlation metadata. */
56
56
  sourceTraceId?: string;
57
57
  /** Host response/message identifier recorded on the observation. */
58
58
  responseId?: string;