@librechat/agents 3.6.7 → 3.6.8

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 (88) 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/subagent/SubagentExecutor.cjs +2 -2
  31. package/dist/esm/common/constants.mjs +8 -1
  32. package/dist/esm/common/constants.mjs.map +1 -1
  33. package/dist/esm/graphs/Graph.mjs +18 -6
  34. package/dist/esm/graphs/Graph.mjs.map +1 -1
  35. package/dist/esm/graphs/MultiAgentGraph.mjs +6 -1
  36. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  37. package/dist/esm/instrumentation.mjs +4 -2
  38. package/dist/esm/instrumentation.mjs.map +1 -1
  39. package/dist/esm/langfuse.mjs +47 -16
  40. package/dist/esm/langfuse.mjs.map +1 -1
  41. package/dist/esm/langfuseOperation.mjs +6 -0
  42. package/dist/esm/langfuseOperation.mjs.map +1 -0
  43. package/dist/esm/langfuseRuntimeContext.mjs +3 -2
  44. package/dist/esm/langfuseRuntimeContext.mjs.map +1 -1
  45. package/dist/esm/langfuseRuntimeScope.mjs +12 -3
  46. package/dist/esm/langfuseRuntimeScope.mjs.map +1 -1
  47. package/dist/esm/langfuseSpanRegistry.mjs +34 -1
  48. package/dist/esm/langfuseSpanRegistry.mjs.map +1 -1
  49. package/dist/esm/langfuseToolOutputTracing.mjs +1 -6
  50. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  51. package/dist/esm/langfuseTraceShaping.mjs +58 -10
  52. package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
  53. package/dist/esm/llm/openai/index.mjs +8 -0
  54. package/dist/esm/llm/openai/index.mjs.map +1 -1
  55. package/dist/esm/main.mjs +3 -3
  56. package/dist/esm/run.mjs +59 -25
  57. package/dist/esm/run.mjs.map +1 -1
  58. package/dist/esm/stream.mjs +1 -1
  59. package/dist/esm/tools/ToolNode.mjs +2 -2
  60. package/dist/esm/tools/subagent/SubagentExecutor.mjs +2 -2
  61. package/dist/types/common/constants.d.ts +8 -0
  62. package/dist/types/graphs/Graph.d.ts +8 -3
  63. package/dist/types/graphs/MultiAgentGraph.d.ts +1 -0
  64. package/dist/types/langfuse.d.ts +10 -1
  65. package/dist/types/langfuseOperation.d.ts +2 -0
  66. package/dist/types/langfuseRuntimeContext.d.ts +2 -0
  67. package/dist/types/langfuseRuntimeScope.d.ts +3 -1
  68. package/dist/types/langfuseSpanRegistry.d.ts +11 -1
  69. package/dist/types/langfuseTraceShaping.d.ts +8 -6
  70. package/dist/types/run.d.ts +4 -3
  71. package/dist/types/types/activityLabel.d.ts +4 -3
  72. package/dist/types/types/reasoningLabel.d.ts +1 -1
  73. package/package.json +5 -5
  74. package/src/common/constants.ts +9 -0
  75. package/src/graphs/Graph.ts +125 -103
  76. package/src/graphs/MultiAgentGraph.ts +6 -2
  77. package/src/instrumentation.ts +12 -0
  78. package/src/langfuse.ts +83 -17
  79. package/src/langfuseOperation.ts +2 -0
  80. package/src/langfuseRuntimeContext.ts +6 -0
  81. package/src/langfuseRuntimeScope.ts +31 -1
  82. package/src/langfuseSpanRegistry.ts +78 -2
  83. package/src/langfuseToolOutputTracing.ts +0 -2
  84. package/src/langfuseTraceShaping.ts +120 -26
  85. package/src/llm/openai/index.ts +34 -3
  86. package/src/run.ts +118 -49
  87. package/src/types/activityLabel.ts +4 -3
  88. package/src/types/reasoningLabel.ts +1 -1
@@ -1,6 +1,6 @@
1
- import { composeAbortSignals } from "./utils/misc.mjs";
2
1
  import { CODE_EXECUTION_TOOLS, LOCAL_CODING_BUNDLE_NAMES } from "./common/enum.mjs";
3
2
  import "./common/index.mjs";
3
+ import { composeAbortSignals } from "./utils/misc.mjs";
4
4
  import { calculateMaxToolResultChars, truncateToolResultContent } from "./utils/truncation.mjs";
5
5
  import { serializeStructuredValueBounded, serializeToolContentBounded } from "./utils/toolContent.mjs";
6
6
  import { getMessageId } from "./messages/ids.mjs";
@@ -1,8 +1,8 @@
1
+ import { CODE_EXECUTION_TOOLS } from "../common/enum.mjs";
2
+ import "../common/index.mjs";
1
3
  import { attachRunStepResumeState } from "./runStepResume.mjs";
2
4
  import { SUBAGENT_PARENT_BATCH_CONFIG_KEY, SUBAGENT_REPLAY_CONTROLLER, attachSubagentResumeManifest } from "./subagent/SubagentReplay.mjs";
3
5
  import { composeAbortSignals } from "../utils/misc.mjs";
4
- import { CODE_EXECUTION_TOOLS } from "../common/enum.mjs";
5
- import "../common/index.mjs";
6
6
  import { resolveLangfuseRuntimeScope, withLangfuseRuntimeScope } from "../langfuseRuntimeScope.mjs";
7
7
  import { calculateMaxToolResultChars, truncateToolResultContent } from "../utils/truncation.mjs";
8
8
  import { cloneToolMessageWithContent, compactToolContent, hasComputerCallOutputMarker, isComputerCallOutputContent, isComputerCallOutputMessage, serializeStructuredValueBounded, serializeToolContentBounded } from "../utils/toolContent.mjs";
@@ -1,8 +1,8 @@
1
+ import "../../common/enum.mjs";
2
+ import "../../common/index.mjs";
1
3
  import { stripRunStepResumeState } from "../runStepResume.mjs";
2
4
  import { SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY, SUBAGENT_RESUME_MANIFEST_CONFIG_KEY, attachSubagentResumeManifest, getSubagentResumeManifest } from "./SubagentReplay.mjs";
3
5
  import { composeAbortSignals } from "../../utils/misc.mjs";
4
- import "../../common/enum.mjs";
5
- import "../../common/index.mjs";
6
6
  import { seedAgentInitialSessions } from "../../utils/toolSessions.mjs";
7
7
  import { convertInjectedMessages } from "../../messages/injected.mjs";
8
8
  import { StreamLimitExceededError } from "../../llm/streamLimits.mjs";
@@ -29,3 +29,11 @@ export declare const PREEMPT_BOUNDARY_HOOK_TIMEOUT_MS = 120000;
29
29
  * the stream config; a preemption-enabled run adds its seal budget on top.
30
30
  */
31
31
  export declare const DEFAULT_RECURSION_LIMIT = 50;
32
+ /** Stable runtime names used for agent workflow observations. */
33
+ export declare const STANDARD_GRAPH_RUN_NAME = "AgentGraph";
34
+ export declare const MULTI_AGENT_GRAPH_RUN_NAME = "MultiAgentGraph";
35
+ export declare const AGENT_MODEL_CALL_RUN_NAME = "AgentModelCall";
36
+ export declare const ACTIVITY_LABEL_RUN_NAME = "StepLabel";
37
+ export declare const REASONING_LABEL_RUN_NAME = "ReasoningLabel";
38
+ export declare const ACTIVITY_PHASE_RUN_NAME = "MultiStepLabel";
39
+ export declare const ACTIVITY_PHASE_LABEL_RUN_NAME = "MultiStepLabelGeneration";
@@ -234,6 +234,7 @@ export declare abstract class Graph<T extends t.BaseGraphState = t.BaseGraphStat
234
234
  getOrCreateFileCheckpointer(): t.LocalFileCheckpointer | undefined;
235
235
  }
236
236
  export declare class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
237
+ readonly runName: string;
237
238
  overrideModel?: t.ChatModel;
238
239
  private subagentModelOverride?;
239
240
  private readonly graphFactory;
@@ -260,10 +261,12 @@ export declare class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode>
260
261
  * component: public run ids are unrestricted and may repeat across
261
262
  * concurrently executing runs (retries, duplicate submissions,
262
263
  * tenant-local message ids), and equal stamps would let those runs adopt
263
- * each other's scopes. One graph instance = one execution's stamp, shared
264
- * by the stream handler and every graph-level scope of that execution.
264
+ * each other's scopes. Fresh stream executions rotate this stamp; resumes
265
+ * retain it so the continuation stays in the interrupted execution.
265
266
  */
266
- readonly langfuseScopeRunId: string;
267
+ langfuseScopeRunId: string;
268
+ /** Opaque key for parenting post-processing observations to this run. */
269
+ langfuseTraceAnchor: object;
267
270
  /**
268
271
  * Boundary between historical messages (loaded from conversation state)
269
272
  * and messages produced during the current run. Set once in the state
@@ -388,6 +391,8 @@ export declare class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode>
388
391
  */
389
392
  pendingPreemptReturn: Set<string>;
390
393
  constructor({ runId, signal, agents, langfuse, tokenCounter, indexTokenCountMap, calibrationRatio, subagentUsageSink, subagentTasks, subagentScope, subagentExecutionContext, preemption, streamLimits, }: t.StandardGraphInput, dependencies?: GraphFactoryDependencies);
394
+ /** Rotates the Langfuse identities that must never cross fresh executions. */
395
+ startFreshLangfuseExecution(): void;
391
396
  resetValues(keepContent?: boolean, checkpointScope?: string): void;
392
397
  /** Seeds the sidecar message view that checkpoint restoration bypasses. */
393
398
  restoreCheckpointMessages(messages: BaseMessage[], pendingMessages?: BaseMessage[]): void;
@@ -16,6 +16,7 @@ import { StandardGraph } from './Graph';
16
16
  * OR continues its workflow (direct edges), but not both simultaneously.
17
17
  */
18
18
  export declare class MultiAgentGraph extends StandardGraph {
19
+ readonly runName = "MultiAgentGraph";
19
20
  private edges;
20
21
  private startingNodes;
21
22
  private directEdges;
@@ -1,4 +1,5 @@
1
1
  import { CallbackHandler } from '@langfuse/langchain';
2
+ import type { SpanContext } from '@opentelemetry/api';
2
3
  import type { ResolvedLangfuseToolOutputTracingConfig } from '@/langfuseRuntimeContext';
3
4
  import type * as t from '@/types';
4
5
  import { hasLangfuseConfigCredentials, hasLangfuseEnvCredentials, hasLangfuseEnvConfig } from '@/langfuseConfig';
@@ -12,6 +13,14 @@ type LangfuseHandlerParams = {
12
13
  traceMetadata?: LangfuseTraceMetadata;
13
14
  tags?: string[];
14
15
  traceIdSeed?: string;
16
+ /** Opaque key used by the span processor to capture this run's parents. */
17
+ traceAnchor?: object;
18
+ /** Agent lane this handler owns when ambient callback scope is unavailable. */
19
+ agentId?: string;
20
+ /** Exported observation that auxiliary work should be nested beneath. */
21
+ parentSpanContext?: SpanContext;
22
+ /** Keep an existing parent trace's user, session, name, and metadata. */
23
+ inheritTraceIdentity?: boolean;
15
24
  /** Identity of the run this handler traces; ambient runtime scopes are
16
25
  * only adopted when stamped with the same run (see
17
26
  * `LangfuseRuntimeContext.runId`). */
@@ -42,7 +51,7 @@ export declare function createLangfuseTraceMetadata({ messageId, parentMessageId
42
51
  export declare function getLangfuseTraceName(traceMetadata?: LangfuseTraceMetadata, fallback?: string): string;
43
52
  export declare function shouldCreateLangfuseHandler(langfuse?: t.LangfuseConfig): boolean;
44
53
  export declare function createLegacyLangfuseHandler(params: LangfuseHandlerParams): CallbackHandler;
45
- export declare function createLangfuseHandler({ langfuse, userId, sessionId, traceMetadata, tags, traceIdSeed, runId, toolOutputTracing, traceName, }: AgentLangfuseHandlerParams): CallbackHandler | undefined;
54
+ export declare function createLangfuseHandler({ langfuse, userId, sessionId, traceMetadata, tags, traceIdSeed, traceAnchor, agentId, parentSpanContext, inheritTraceIdentity, runId, toolOutputTracing, traceName, }: AgentLangfuseHandlerParams): CallbackHandler | undefined;
46
55
  export declare function withLangfuseAttributes<T>(params: LangfuseAttributeParams, action: () => T): T;
47
56
  export declare function hasExplicitLangfuseConfig(contexts: Iterable<{
48
57
  langfuse?: t.LangfuseConfig;
@@ -0,0 +1,2 @@
1
+ /** Internal observation metadata key used to distinguish SDK operations from public tags. */
2
+ export declare const LANGFUSE_OPERATION_METADATA_KEY = "librechat.sdk.operation";
@@ -8,6 +8,8 @@ export type ResolvedLangfuseToolOutputTracingConfig = {
8
8
  export type LangfuseRuntimeContext = {
9
9
  langfuse?: t.LangfuseConfig;
10
10
  traceIdSeed?: string;
11
+ /** Opaque run-owned key used to recover the exported trace parent. */
12
+ traceAnchor?: object;
11
13
  toolOutputTracing?: ResolvedLangfuseToolOutputTracingConfig;
12
14
  /**
13
15
  * Identity of the run this scope belongs to. LangChain executes
@@ -6,6 +6,7 @@ export type ResolveLangfuseRuntimeScopeParams = {
6
6
  runLangfuse?: t.LangfuseConfig;
7
7
  langfuseOverlay?: t.LangfuseConfig;
8
8
  traceIdSeed?: string;
9
+ traceAnchor?: object;
9
10
  runId?: string;
10
11
  agentId?: string;
11
12
  };
@@ -18,6 +19,7 @@ export type LangfuseRuntimeScopeOptions = {
18
19
  };
19
20
  export declare function getOtelLangfuseConfig(activeContext: Context): t.LangfuseConfig | undefined;
20
21
  export declare function getOtelTraceIdSeed(activeContext: Context): string | undefined;
22
+ export declare function resolveLangfuseTraceAnchor(activeContext: Context): object | undefined;
21
23
  export declare function getOtelToolOutputTracingConfig(activeContext: Context): ResolvedLangfuseToolOutputTracingConfig | undefined;
22
24
  export declare function resolveLangfuseConfigForSpan(activeContext: Context): t.LangfuseConfig | undefined;
23
25
  export declare function resolveTraceIdSeedForSpan(activeContext: Context): string | undefined;
@@ -29,4 +31,4 @@ export declare function resolveLangfuseScopeRunId(activeContext: Context): strin
29
31
  export declare function resolveLangfuseScopeAgentId(activeContext: Context): string | undefined;
30
32
  export declare function resolveToolOutputTracingConfigForSpan(activeContext: Context): ResolvedLangfuseToolOutputTracingConfig | undefined;
31
33
  export declare function withLangfuseRuntimeScope<T>(scope: LangfuseRuntimeScope, action: () => T, options?: LangfuseRuntimeScopeOptions): T;
32
- export declare function resolveLangfuseRuntimeScope({ runLangfuse, langfuseOverlay, traceIdSeed, runId, agentId, }: ResolveLangfuseRuntimeScopeParams): LangfuseRuntimeScope;
34
+ export declare function resolveLangfuseRuntimeScope({ runLangfuse, langfuseOverlay, traceIdSeed, traceAnchor, runId, agentId, }: ResolveLangfuseRuntimeScopeParams): LangfuseRuntimeScope;
@@ -1,8 +1,18 @@
1
1
  import type { LangfuseSpanProcessorParams } from '@langfuse/otel';
2
- import type { Span } from '@opentelemetry/api';
2
+ import type { Span, SpanContext } from '@opentelemetry/api';
3
3
  import type * as t from '@/types';
4
4
  export declare function registerLangfuseManagedSpan(span: Span, destinationKey: string): void;
5
5
  export declare function getLangfuseManagedSpanDestination(span: Span): string | undefined;
6
+ /** Captures the first exported observation for a run and for each agent lane. */
7
+ export declare function registerLangfuseTraceAnchorSpan(anchor: object, span: Span, destinationKey: string, agentId?: string): void;
8
+ /**
9
+ * Resolves the most specific safe parent available for auxiliary work. An
10
+ * active observation wins so labels emitted inside a tool or reasoning step
11
+ * stay beside their source; otherwise the run root anchors the label in the
12
+ * same trace. An agent lane is the fallback when its overlay exports to a
13
+ * different destination. Cross-destination parents are never returned.
14
+ */
15
+ export declare function resolveLangfuseTraceAnchorParent(anchor: object | undefined, destinationKey: string | undefined, agentId?: string): SpanContext | undefined;
6
16
  export declare function getLangfuseSpanProcessorParams(langfuse?: t.LangfuseConfig): LangfuseSpanProcessorParams | undefined;
7
17
  /**
8
18
  * Identity of an export destination (project credentials + endpoint +
@@ -13,16 +13,18 @@ export declare function shouldDropLangfuseSpan(spanName: string): boolean;
13
13
  * (`provider__model`) — strip it so switching models doesn't break
14
14
  * name-based logic (item 1). The outer workflow node is named with the
15
15
  * bare agent id; ephemeral ids reduce to their stable sender name.
16
+ * - Graph and prompt-to-model framework names become stable SDK operations:
17
+ * `AgentGraph` / `MultiAgentGraph` and `AgentModelCall`.
16
18
  * - LLM generation spans keep the provider client class name (`ChatOpenAI`,
17
- * `AzureChatOpenAI`, …); rename them to a provider-agnostic `llm` so the
18
- * name reflects the operation, not the model (the model stays on the
19
- * generation's model attribute).
19
+ * `AzureChatOpenAI`, …); rename ordinary calls to a provider-agnostic `llm`
20
+ * and label calls by their activity/reasoning role. The model stays on the
21
+ * generation's model attribute.
20
22
  * - Agent nodes become `agent` observations, while tool-dispatch nodes become
21
23
  * stable `chain` observations whose input is scoped to the pending calls.
22
24
  * Individual child calls remain `tool` observations (items 3 & 4).
23
25
  * - Agent trace roots become `agent` observations, while title and activity
24
- * summary roots become `chain` observations. Root and trace input/output are
25
- * reduced to the user question and assistant response when chat messages are
26
- * available (item 2).
26
+ * summary operations become `chain` observations. Root-observation
27
+ * input/output are reduced to the user question and assistant response when
28
+ * chat messages are available (item 2).
27
29
  */
28
30
  export declare function shapeLangfuseSpan(span: ReadableSpan): void;
@@ -264,9 +264,10 @@ export declare class Run<_T extends t.BaseGraphState> {
264
264
  generateReasoningLabel({ provider, clientOptions, visibleReasoning, reasoningStepId, revision, status, previousLabel, agentId, prompt, charLimit, chainOptions, traceSeed, sourceRunId, sourceTraceId, responseId, }: t.RunReasoningLabelOptions): Promise<t.ReasoningLabelResult>;
265
265
  /**
266
266
  * Generates one parent summary for two or more logical activities. The
267
- * summary model is traced as a dedicated activity-phase chain root in the
268
- * conversation session, with the model callback recorded as its generation
269
- * child. No session id means no phase trace, avoiding orphan observations.
267
+ * summary model is traced as an activity-phase chain beneath the source
268
+ * agent run, with the model callback recorded as its generation child. It
269
+ * falls back to a dedicated trace when no source observation is available.
270
+ * No session id means no phase trace, avoiding orphan observations.
270
271
  */
271
272
  generateActivityPhaseLabel({ provider, clientOptions, activities, totalActivityCount, assistantContext, closingTextPhase, prompt, charLimit, chainOptions, traceSeed, sourceRunId, sourceTraceId, responseId, phaseIndex, status, agentIds, }: t.RunActivityPhaseLabelOptions): Promise<{
272
273
  label?: string;
@@ -60,8 +60,9 @@ export type RunActivityLabelOptions = {
60
60
  };
61
61
  /**
62
62
  * Seed for deterministic Langfuse trace ids (e.g. `${runId}-${slotIndex}`)
63
- * so each batch's label gets a distinct, reproducible trace. When omitted,
64
- * a per-run sequence keeps batches from collapsing into one trace.
63
+ * for a distinct, reproducible fallback trace when the source agent
64
+ * observation is unavailable. Normally the label is parented into the
65
+ * source agent trace instead.
65
66
  */
66
67
  traceSeed?: string;
67
68
  };
@@ -108,7 +109,7 @@ export type RunActivityPhaseLabelOptions = {
108
109
  traceSeed?: string;
109
110
  /** Stable source run identifier recorded on the phase observation. */
110
111
  sourceRunId?: string;
111
- /** Source Langfuse trace id for linking this detached summary trace. */
112
+ /** Source Langfuse trace id retained as correlation metadata. */
112
113
  sourceTraceId?: string;
113
114
  /** Host response/message identifier recorded on the phase observation. */
114
115
  responseId?: string;
@@ -49,7 +49,7 @@ export type RunReasoningLabelOptions = {
49
49
  traceSeed?: string;
50
50
  /** Stable source run identifier recorded on the observation. */
51
51
  sourceRunId?: string;
52
- /** Source Langfuse trace id for linking this detached label trace. */
52
+ /** Source Langfuse trace id retained as correlation metadata. */
53
53
  sourceTraceId?: string;
54
54
  /** Host response/message identifier recorded on the observation. */
55
55
  responseId?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.6.7",
3
+ "version": "3.6.8",
4
4
  "reova": {
5
5
  "enabled": true,
6
6
  "endpoint": "https://telemetry.reo.dev/data"
@@ -245,10 +245,10 @@
245
245
  "@langchain/openai": "1.5.8",
246
246
  "@langchain/textsplitters": "^1.0.1",
247
247
  "@langchain/xai": "^1.4.3",
248
- "@langfuse/core": "^5.4.1",
249
- "@langfuse/langchain": "^5.4.1",
250
- "@langfuse/otel": "^5.4.1",
251
- "@langfuse/tracing": "^5.4.1",
248
+ "@langfuse/core": "^5.10.1",
249
+ "@langfuse/langchain": "^5.10.1",
250
+ "@langfuse/otel": "^5.10.1",
251
+ "@langfuse/tracing": "^5.10.1",
252
252
  "@opentelemetry/context-async-hooks": "^2.9.0",
253
253
  "@opentelemetry/sdk-node": "^0.220.0",
254
254
  "@types/diff": "^7.0.2",
@@ -33,3 +33,12 @@ export const PREEMPT_BOUNDARY_HOOK_TIMEOUT_MS = 120_000;
33
33
  * the stream config; a preemption-enabled run adds its seal budget on top.
34
34
  */
35
35
  export const DEFAULT_RECURSION_LIMIT = 50;
36
+
37
+ /** Stable runtime names used for agent workflow observations. */
38
+ export const STANDARD_GRAPH_RUN_NAME = 'AgentGraph';
39
+ export const MULTI_AGENT_GRAPH_RUN_NAME = 'MultiAgentGraph';
40
+ export const AGENT_MODEL_CALL_RUN_NAME = 'AgentModelCall';
41
+ export const ACTIVITY_LABEL_RUN_NAME = 'StepLabel';
42
+ export const REASONING_LABEL_RUN_NAME = 'ReasoningLabel';
43
+ export const ACTIVITY_PHASE_RUN_NAME = 'MultiStepLabel';
44
+ export const ACTIVITY_PHASE_LABEL_RUN_NAME = 'MultiStepLabelGeneration';
@@ -74,6 +74,17 @@ import {
74
74
  appendPredecessorHandoffCue,
75
75
  removePredecessorHandoffCue,
76
76
  } from '@/messages';
77
+ import {
78
+ Constants,
79
+ GraphNodeKeys,
80
+ ContentTypes,
81
+ GraphEvents,
82
+ Providers,
83
+ StepTypes,
84
+ STANDARD_GRAPH_RUN_NAME,
85
+ AGENT_MODEL_CALL_RUN_NAME,
86
+ PREEMPT_BOUNDARY_HOOK_TIMEOUT_MS,
87
+ } from '@/common';
77
88
  import {
78
89
  resetIfNotEmpty,
79
90
  isAnthropicLike,
@@ -106,15 +117,6 @@ import {
106
117
  isGraphSubagentConfig,
107
118
  normalizeSubagentConfigEntries,
108
119
  } from '@/tools/subagent';
109
- import {
110
- Constants,
111
- GraphNodeKeys,
112
- ContentTypes,
113
- GraphEvents,
114
- Providers,
115
- StepTypes,
116
- PREEMPT_BOUNDARY_HOOK_TIMEOUT_MS,
117
- } from '@/common';
118
120
  import {
119
121
  createLangfuseHandler,
120
122
  createLangfuseTraceMetadata,
@@ -1260,6 +1262,7 @@ export abstract class Graph<
1260
1262
  }
1261
1263
 
1262
1264
  export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1265
+ readonly runName: string = STANDARD_GRAPH_RUN_NAME;
1263
1266
  overrideModel?: t.ChatModel;
1264
1267
  private subagentModelOverride?: t.ChatModel;
1265
1268
  private readonly graphFactory: GraphFactory;
@@ -1286,10 +1289,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1286
1289
  * component: public run ids are unrestricted and may repeat across
1287
1290
  * concurrently executing runs (retries, duplicate submissions,
1288
1291
  * tenant-local message ids), and equal stamps would let those runs adopt
1289
- * each other's scopes. One graph instance = one execution's stamp, shared
1290
- * by the stream handler and every graph-level scope of that execution.
1292
+ * each other's scopes. Fresh stream executions rotate this stamp; resumes
1293
+ * retain it so the continuation stays in the interrupted execution.
1291
1294
  */
1292
- readonly langfuseScopeRunId: string;
1295
+ langfuseScopeRunId: string;
1296
+ /** Opaque key for parenting post-processing observations to this run. */
1297
+ langfuseTraceAnchor: object = {};
1293
1298
  /**
1294
1299
  * Boundary between historical messages (loaded from conversation state)
1295
1300
  * and messages produced during the current run. Set once in the state
@@ -1498,6 +1503,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1498
1503
  this.defaultAgentId = agents[0].agentId;
1499
1504
  }
1500
1505
 
1506
+ /** Rotates the Langfuse identities that must never cross fresh executions. */
1507
+ startFreshLangfuseExecution(): void {
1508
+ this.langfuseTraceAnchor = {};
1509
+ this.langfuseScopeRunId = `${this.runId ?? 'graph'}:${nanoid()}`;
1510
+ }
1511
+
1501
1512
  /* Init */
1502
1513
 
1503
1514
  resetValues(keepContent?: boolean, checkpointScope?: string): void {
@@ -2913,7 +2924,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2913
2924
  });
2914
2925
 
2915
2926
  if (agentContext.systemRunnable) {
2916
- model = agentContext.systemRunnable.pipe(model as Runnable);
2927
+ model = agentContext.systemRunnable
2928
+ .pipe(model as Runnable)
2929
+ .withConfig({ runName: AGENT_MODEL_CALL_RUN_NAME });
2917
2930
  }
2918
2931
 
2919
2932
  if (agentContext.tokenCalculationPromise) {
@@ -4010,6 +4023,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4010
4023
  tags: ['librechat', 'agent'],
4011
4024
  traceIdSeed:
4012
4025
  langfuse?.deterministicTraceId === true ? this.runId : undefined,
4026
+ traceAnchor: this.langfuseTraceAnchor,
4027
+ agentId,
4013
4028
  runId: this.langfuseScopeRunId,
4014
4029
  toolOutputTracing: hasToolOutputTracingConfig(
4015
4030
  this.langfuse,
@@ -4052,6 +4067,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4052
4067
  resolveLangfuseRuntimeScope({
4053
4068
  runLangfuse: this.langfuse,
4054
4069
  langfuseOverlay: agentContext.langfuse,
4070
+ traceAnchor: this.langfuseTraceAnchor,
4055
4071
  runId: this.langfuseScopeRunId,
4056
4072
  agentId,
4057
4073
  }),
@@ -4845,94 +4861,98 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4845
4861
  createChildGraphByKind: createConfiguredChildGraph,
4846
4862
  createDetachedChildGraphFactory: (
4847
4863
  parentHandlerRegistry
4848
- ): GraphFactory =>
4849
- snapshotChildGraphFactory(parentHandlerRegistry),
4864
+ ): GraphFactory => snapshotChildGraphFactory(parentHandlerRegistry),
4850
4865
  });
4851
4866
  this.registerSubagentExecutor(executor);
4852
4867
 
4853
- const subagentTool = tool(async (rawInput, config) => {
4854
- const input = rawInput as {
4855
- description?: string;
4856
- subagent_type?: string;
4857
- subagent_thread_id?: string;
4858
- run_in_background?: boolean;
4859
- };
4860
- const description =
4861
- typeof input.description === 'string' &&
4862
- input.description.trim().length > 0
4863
- ? input.description
4864
- : DEFAULT_SUBAGENT_DESCRIPTION;
4865
- const subagentType =
4866
- typeof input.subagent_type === 'string' ? input.subagent_type : '';
4867
- const subagentThreadId =
4868
- typeof input.subagent_thread_id === 'string' &&
4869
- input.subagent_thread_id.trim() !== ''
4870
- ? input.subagent_thread_id.trim()
4871
- : undefined;
4872
- const threadId = config.configurable?.thread_id as string | undefined;
4873
- /** Surface the parent call id so child checkpoints, interrupts, and
4874
- * update events remain correlated across replay and resume. */
4875
- const toolRuntime = config as {
4876
- toolCallId?: string;
4877
- toolCall?: { id?: string };
4878
- };
4879
- const toolCall = toolRuntime.toolCall;
4880
- let parentToolCallId: string | undefined;
4881
- if (
4882
- typeof toolRuntime.toolCallId === 'string' &&
4883
- toolRuntime.toolCallId !== ''
4884
- ) {
4885
- parentToolCallId = toolRuntime.toolCallId;
4886
- } else if (typeof toolCall?.id === 'string' && toolCall.id !== '') {
4887
- parentToolCallId = toolCall.id;
4888
- }
4889
- /** The parent tool batch's entry-captured scope (stamped by
4890
- * ToolNode before PreToolUse hooks) — binds this child to the
4891
- * run that dispatched it, not to whatever controller a reset
4892
- * installed while the hooks were awaited. */
4893
- const batchScope = config.configurable?.[
4894
- RUN_BREAKER_SCOPE_CONFIG_KEY
4895
- ] as RunBreakerScope | undefined;
4896
- const executeParams = {
4897
- description,
4898
- subagentType,
4899
- threadId,
4900
- signal: config.signal,
4901
- parentToolCallId,
4902
- breaker: batchScope?.controller,
4903
- /**
4904
- * Forward the parent's `configurable` so host-set fields
4905
- * (`requestBody`, `user`, etc.) propagate into the child
4906
- * workflow. The executor scrubs run-identity fields before
4907
- * forwarding — see `SubagentExecuteParams.parentConfigurable`.
4908
- */
4909
- parentConfigurable: config.configurable as
4910
- | Record<string, unknown>
4911
- | undefined,
4912
- };
4913
- if (input.run_in_background === true) {
4914
- return executor.executeInBackground({
4915
- ...executeParams,
4916
- ...(subagentThreadId == null
4917
- ? {}
4918
- : { subagentThreadId }),
4919
- });
4920
- }
4921
- if (subagentThreadId != null) {
4922
- return JSON.stringify({
4923
- status: 'rejected',
4924
- tool: Constants.SUBAGENT,
4925
- message:
4926
- 'Child-thread continuation requires run_in_background.',
4927
- });
4928
- }
4929
- const result = await executor.execute(executeParams);
4930
- return result.content;
4931
- }, buildSubagentToolParams(executableConfigs, {
4932
- background: this.subagentTasks != null,
4933
- threadContinuation:
4934
- this.subagentTasks?.store.supportsThreadContinuation === true,
4935
- }));
4868
+ const subagentTool = tool(
4869
+ async (rawInput, config) => {
4870
+ const input = rawInput as {
4871
+ description?: string;
4872
+ subagent_type?: string;
4873
+ subagent_thread_id?: string;
4874
+ run_in_background?: boolean;
4875
+ };
4876
+ const description =
4877
+ typeof input.description === 'string' &&
4878
+ input.description.trim().length > 0
4879
+ ? input.description
4880
+ : DEFAULT_SUBAGENT_DESCRIPTION;
4881
+ const subagentType =
4882
+ typeof input.subagent_type === 'string'
4883
+ ? input.subagent_type
4884
+ : '';
4885
+ const subagentThreadId =
4886
+ typeof input.subagent_thread_id === 'string' &&
4887
+ input.subagent_thread_id.trim() !== ''
4888
+ ? input.subagent_thread_id.trim()
4889
+ : undefined;
4890
+ const threadId = config.configurable?.thread_id as
4891
+ | string
4892
+ | undefined;
4893
+ /** Surface the parent call id so child checkpoints, interrupts, and
4894
+ * update events remain correlated across replay and resume. */
4895
+ const toolRuntime = config as {
4896
+ toolCallId?: string;
4897
+ toolCall?: { id?: string };
4898
+ };
4899
+ const toolCall = toolRuntime.toolCall;
4900
+ let parentToolCallId: string | undefined;
4901
+ if (
4902
+ typeof toolRuntime.toolCallId === 'string' &&
4903
+ toolRuntime.toolCallId !== ''
4904
+ ) {
4905
+ parentToolCallId = toolRuntime.toolCallId;
4906
+ } else if (typeof toolCall?.id === 'string' && toolCall.id !== '') {
4907
+ parentToolCallId = toolCall.id;
4908
+ }
4909
+ /** The parent tool batch's entry-captured scope (stamped by
4910
+ * ToolNode before PreToolUse hooks) — binds this child to the
4911
+ * run that dispatched it, not to whatever controller a reset
4912
+ * installed while the hooks were awaited. */
4913
+ const batchScope = config.configurable?.[
4914
+ RUN_BREAKER_SCOPE_CONFIG_KEY
4915
+ ] as RunBreakerScope | undefined;
4916
+ const executeParams = {
4917
+ description,
4918
+ subagentType,
4919
+ threadId,
4920
+ signal: config.signal,
4921
+ parentToolCallId,
4922
+ breaker: batchScope?.controller,
4923
+ /**
4924
+ * Forward the parent's `configurable` so host-set fields
4925
+ * (`requestBody`, `user`, etc.) propagate into the child
4926
+ * workflow. The executor scrubs run-identity fields before
4927
+ * forwarding — see `SubagentExecuteParams.parentConfigurable`.
4928
+ */
4929
+ parentConfigurable: config.configurable as
4930
+ | Record<string, unknown>
4931
+ | undefined,
4932
+ };
4933
+ if (input.run_in_background === true) {
4934
+ return executor.executeInBackground({
4935
+ ...executeParams,
4936
+ ...(subagentThreadId == null ? {} : { subagentThreadId }),
4937
+ });
4938
+ }
4939
+ if (subagentThreadId != null) {
4940
+ return JSON.stringify({
4941
+ status: 'rejected',
4942
+ tool: Constants.SUBAGENT,
4943
+ message:
4944
+ 'Child-thread continuation requires run_in_background.',
4945
+ });
4946
+ }
4947
+ const result = await executor.execute(executeParams);
4948
+ return result.content;
4949
+ },
4950
+ buildSubagentToolParams(executableConfigs, {
4951
+ background: this.subagentTasks != null,
4952
+ threadContinuation:
4953
+ this.subagentTasks?.store.supportsThreadContinuation === true,
4954
+ })
4955
+ );
4936
4956
  const replayableSubagentTool = subagentTool as typeof subagentTool &
4937
4957
  ReplayableSubagentTool;
4938
4958
  replayableSubagentTool[SUBAGENT_REPLAY_CONTROLLER] = {
@@ -5218,7 +5238,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
5218
5238
  .addEdge(summarizeNode, agentNode)
5219
5239
  .addEdge(toolNode, agentContext.toolEnd ? END : agentNode);
5220
5240
 
5221
- return workflow.compile();
5241
+ return workflow.compile({ name: STANDARD_GRAPH_RUN_NAME });
5222
5242
  }
5223
5243
 
5224
5244
  createWorkflow(): t.CompiledStateWorkflow {
@@ -5247,11 +5267,13 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
5247
5267
  >,
5248
5268
  { ends: [END] }
5249
5269
  )
5250
- .addEdge(START, this.defaultAgentId)
5251
- // LangGraph compile() types are overly strict for opt-in options
5252
- .compile(this.compileOptions as unknown as never);
5270
+ .addEdge(START, this.defaultAgentId);
5253
5271
 
5254
- return workflow;
5272
+ // LangGraph compile() types are overly strict for opt-in options
5273
+ return workflow.compile({
5274
+ ...this.compileOptions,
5275
+ name: STANDARD_GRAPH_RUN_NAME,
5276
+ } as unknown as never);
5255
5277
  }
5256
5278
 
5257
5279
  /**
@@ -20,9 +20,9 @@ import type { ToolRuntime } from '@langchain/core/tools';
20
20
  import type { GraphFactoryDependencies } from '@/graphs/graphFactory';
21
21
  import type * as t from '@/types';
22
22
  import { serializeToolContentBounded } from '@/utils/toolContent';
23
+ import { Constants, MULTI_AGENT_GRAPH_RUN_NAME } from '@/common';
23
24
  import { HARD_MAX_TOOL_RESULT_CHARS } from '@/utils/truncation';
24
25
  import { StandardGraph } from './Graph';
25
- import { Constants } from '@/common';
26
26
 
27
27
  /** Pattern to extract instructions from transfer ToolMessage content */
28
28
  const HANDOFF_INSTRUCTIONS_PATTERN = /(?:Instructions?|Context):\s*(.+)/is;
@@ -177,6 +177,7 @@ function withHandoffGroupMetadata(
177
177
  * OR continues its workflow (direct edges), but not both simultaneously.
178
178
  */
179
179
  export class MultiAgentGraph extends StandardGraph {
180
+ override readonly runName = MULTI_AGENT_GRAPH_RUN_NAME;
180
181
  private edges: t.GraphEdge[];
181
182
  private startingNodes: Set<string> = new Set();
182
183
  private directEdges: t.GraphEdge[] = [];
@@ -1438,6 +1439,9 @@ export class MultiAgentGraph extends StandardGraph {
1438
1439
  }
1439
1440
  }
1440
1441
 
1441
- return builder.compile(this.compileOptions as unknown as never);
1442
+ return builder.compile({
1443
+ ...this.compileOptions,
1444
+ name: MULTI_AGENT_GRAPH_RUN_NAME,
1445
+ } as unknown as never);
1442
1446
  }
1443
1447
  }