@librechat/agents 3.2.44 → 3.2.46

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 (77) hide show
  1. package/dist/cjs/graphs/Graph.cjs +17 -8
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/instrumentation.cjs +20 -21
  4. package/dist/cjs/instrumentation.cjs.map +1 -1
  5. package/dist/cjs/langfuse.cjs +65 -13
  6. package/dist/cjs/langfuse.cjs.map +1 -1
  7. package/dist/cjs/langfuseConfig.cjs +106 -0
  8. package/dist/cjs/langfuseConfig.cjs.map +1 -0
  9. package/dist/cjs/langfuseRuntimeContext.cjs +48 -0
  10. package/dist/cjs/langfuseRuntimeContext.cjs.map +1 -0
  11. package/dist/cjs/langfuseRuntimeScope.cjs +57 -0
  12. package/dist/cjs/langfuseRuntimeScope.cjs.map +1 -0
  13. package/dist/cjs/langfuseToolOutputTracing.cjs +14 -119
  14. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  15. package/dist/cjs/main.cjs +1 -0
  16. package/dist/cjs/messages/format.cjs +1 -1
  17. package/dist/cjs/messages/format.cjs.map +1 -1
  18. package/dist/cjs/run.cjs +25 -12
  19. package/dist/cjs/run.cjs.map +1 -1
  20. package/dist/cjs/tools/ToolNode.cjs +5 -2
  21. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  22. package/dist/cjs/utils/misc.cjs +17 -0
  23. package/dist/cjs/utils/misc.cjs.map +1 -1
  24. package/dist/esm/graphs/Graph.mjs +16 -7
  25. package/dist/esm/graphs/Graph.mjs.map +1 -1
  26. package/dist/esm/instrumentation.mjs +22 -22
  27. package/dist/esm/instrumentation.mjs.map +1 -1
  28. package/dist/esm/langfuse.mjs +65 -14
  29. package/dist/esm/langfuse.mjs.map +1 -1
  30. package/dist/esm/langfuseConfig.mjs +102 -0
  31. package/dist/esm/langfuseConfig.mjs.map +1 -0
  32. package/dist/esm/langfuseRuntimeContext.mjs +44 -0
  33. package/dist/esm/langfuseRuntimeContext.mjs.map +1 -0
  34. package/dist/esm/langfuseRuntimeScope.mjs +53 -0
  35. package/dist/esm/langfuseRuntimeScope.mjs.map +1 -0
  36. package/dist/esm/langfuseToolOutputTracing.mjs +13 -115
  37. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  38. package/dist/esm/main.mjs +2 -2
  39. package/dist/esm/messages/format.mjs +1 -1
  40. package/dist/esm/messages/format.mjs.map +1 -1
  41. package/dist/esm/run.mjs +21 -8
  42. package/dist/esm/run.mjs.map +1 -1
  43. package/dist/esm/tools/ToolNode.mjs +5 -2
  44. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  45. package/dist/esm/utils/misc.mjs +17 -1
  46. package/dist/esm/utils/misc.mjs.map +1 -1
  47. package/dist/types/instrumentation.d.ts +0 -1
  48. package/dist/types/langfuse.d.ts +5 -1
  49. package/dist/types/langfuseConfig.d.ts +7 -0
  50. package/dist/types/langfuseRuntimeContext.d.ts +26 -0
  51. package/dist/types/langfuseRuntimeScope.d.ts +17 -0
  52. package/dist/types/langfuseToolOutputTracing.d.ts +4 -11
  53. package/dist/types/messages/format.d.ts +4 -0
  54. package/dist/types/types/graph.d.ts +6 -0
  55. package/dist/types/utils/misc.d.ts +1 -0
  56. package/package.json +1 -1
  57. package/src/graphs/Graph.ts +20 -13
  58. package/src/instrumentation.ts +33 -29
  59. package/src/langfuse.ts +114 -7
  60. package/src/langfuseConfig.ts +214 -0
  61. package/src/langfuseRuntimeContext.ts +93 -0
  62. package/src/langfuseRuntimeScope.ts +135 -0
  63. package/src/langfuseToolOutputTracing.ts +25 -263
  64. package/src/messages/format.ts +6 -1
  65. package/src/messages/formatAgentMessages.test.ts +76 -0
  66. package/src/run.ts +44 -34
  67. package/src/specs/deterministic-trace-id.test.ts +4 -11
  68. package/src/specs/langfuse-callbacks.test.ts +278 -22
  69. package/src/specs/langfuse-config.test.ts +10 -3
  70. package/src/specs/langfuse-instrumentation.test.ts +32 -12
  71. package/src/specs/langfuse-routing.integration.test.ts +543 -0
  72. package/src/specs/langfuse-runtime-context.test.ts +92 -0
  73. package/src/specs/langfuse-tool-output-tracing.test.ts +353 -5
  74. package/src/tools/ToolNode.ts +10 -5
  75. package/src/tools/__tests__/ToolNode.langfuse.test.ts +21 -11
  76. package/src/types/graph.ts +9 -0
  77. package/src/utils/misc.ts +18 -0
@@ -1,6 +1,4 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
1
  import { LangfuseSpanProcessor } from '@langfuse/otel';
3
- import { context, createContextKey } from '@opentelemetry/api';
4
2
  import { LangfuseOtelSpanAttributes } from '@langfuse/tracing';
5
3
  import type {
6
4
  ReadableSpan,
@@ -9,17 +7,19 @@ import type {
9
7
  } from '@opentelemetry/sdk-trace-base';
10
8
  import type { LangfuseSpanProcessorParams } from '@langfuse/otel';
11
9
  import type { Context } from '@opentelemetry/api';
10
+ import type { ResolvedLangfuseToolOutputTracingConfig } from '@/langfuseRuntimeContext';
12
11
  import type * as t from '@/types';
12
+ import {
13
+ LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT,
14
+ hasToolOutputTracingConfig,
15
+ normalizeToolName,
16
+ resolveLangfuseConfig,
17
+ resolveToolOutputTracingConfig,
18
+ } from '@/langfuseConfig';
19
+ import { resolveToolOutputTracingConfigForSpan } from '@/langfuseRuntimeScope';
13
20
 
14
- export const LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT = '[tool output redacted]';
21
+ export { LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT, resolveLangfuseConfig };
15
22
 
16
- const langfuseToolOutputTracingConfigKey = createContextKey(
17
- 'librechat.langfuse.tool-output-tracing'
18
- );
19
- const langfuseConfigKey = createContextKey('librechat.langfuse.config');
20
- const toolOutputTracingStorage =
21
- new AsyncLocalStorage<ResolvedLangfuseToolOutputTracingConfig>();
22
- const langfuseConfigStorage = new AsyncLocalStorage<t.LangfuseConfig>();
23
23
  const LANGGRAPH_TOOL_NODE_PREFIX = 'tools=';
24
24
 
25
25
  const CHAT_ROLES = new Set([
@@ -30,13 +30,6 @@ const CHAT_ROLES = new Set([
30
30
  'user',
31
31
  ]);
32
32
 
33
- export type ResolvedLangfuseToolOutputTracingConfig = {
34
- enabled: boolean;
35
- redactedToolNames: Set<string>;
36
- redactedToolNameMatchMode: 'exact' | 'partial';
37
- redactionText: string;
38
- };
39
-
40
33
  type SpanWithAttributes = ReadableSpan & {
41
34
  attributes: Record<string, unknown>;
42
35
  };
@@ -60,122 +53,6 @@ function isPresent(value: unknown): value is string {
60
53
  return typeof value === 'string' && value.trim() !== '';
61
54
  }
62
55
 
63
- function parseBoolean(value: string | undefined): boolean | undefined {
64
- if (value == null) {
65
- return undefined;
66
- }
67
-
68
- const normalized = value.trim().toLowerCase();
69
- if (['1', 'true', 'yes', 'on'].includes(normalized)) {
70
- return true;
71
- }
72
- if (['0', 'false', 'no', 'off'].includes(normalized)) {
73
- return false;
74
- }
75
-
76
- return undefined;
77
- }
78
-
79
- function normalizeToolName(name: string): string {
80
- return name.trim().toLowerCase();
81
- }
82
-
83
- function normalizeToolNames(names: string[] | undefined): Set<string> {
84
- const normalized = new Set<string>();
85
- for (const name of names ?? []) {
86
- if (isPresent(name)) {
87
- normalized.add(normalizeToolName(name));
88
- }
89
- }
90
- return normalized;
91
- }
92
-
93
- function parseToolNames(value: string | undefined): string[] | undefined {
94
- if (!isPresent(value)) {
95
- return undefined;
96
- }
97
-
98
- return value
99
- .split(',')
100
- .map((name) => name.trim())
101
- .filter((name) => name !== '');
102
- }
103
-
104
- function getEnvToolOutputTracingEnabled(): boolean | undefined {
105
- const traceToolOutputs = parseBoolean(
106
- process.env.LANGFUSE_TRACE_TOOL_OUTPUTS
107
- );
108
- if (traceToolOutputs != null) {
109
- return traceToolOutputs;
110
- }
111
-
112
- const redactToolOutputs = parseBoolean(
113
- process.env.LANGFUSE_REDACT_TOOL_OUTPUTS
114
- );
115
- if (redactToolOutputs != null) {
116
- return !redactToolOutputs;
117
- }
118
-
119
- return parseBoolean(process.env.LANGFUSE_TOOL_OUTPUT_TRACING_ENABLED);
120
- }
121
-
122
- function getEnvRedactedToolNames(): string[] | undefined {
123
- return (
124
- parseToolNames(process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAMES) ??
125
- parseToolNames(process.env.LANGFUSE_REDACT_TOOL_NAMES)
126
- );
127
- }
128
-
129
- function getEnvRedactionText(): string | undefined {
130
- return isPresent(process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT)
131
- ? process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT
132
- : undefined;
133
- }
134
-
135
- function getEnvToolNameMatchMode(): 'exact' | 'partial' | undefined {
136
- const mode = (
137
- process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAME_MATCH_MODE ??
138
- process.env.LANGFUSE_REDACT_TOOL_NAME_MATCH_MODE
139
- )
140
- ?.trim()
141
- .toLowerCase();
142
- if (mode === 'exact' || mode === 'partial') {
143
- return mode;
144
- }
145
- return undefined;
146
- }
147
-
148
- function resolveToolOutputTracingConfig(
149
- runLangfuse?: t.LangfuseConfig,
150
- agentLangfuse?: t.LangfuseConfig
151
- ): ResolvedLangfuseToolOutputTracingConfig {
152
- const runConfig = runLangfuse?.toolOutputTracing;
153
- const agentConfig = agentLangfuse?.toolOutputTracing;
154
-
155
- return {
156
- enabled:
157
- agentConfig?.enabled ??
158
- runConfig?.enabled ??
159
- getEnvToolOutputTracingEnabled() ??
160
- true,
161
- redactedToolNames: normalizeToolNames(
162
- agentConfig?.redactedToolNames ??
163
- runConfig?.redactedToolNames ??
164
- getEnvRedactedToolNames()
165
- ),
166
- redactedToolNameMatchMode:
167
- agentConfig?.redactedToolNameMatchMode ??
168
- runConfig?.redactedToolNameMatchMode ??
169
- getEnvToolNameMatchMode() ??
170
- 'exact',
171
- redactionText:
172
- agentConfig?.redactionText ??
173
- runConfig?.redactionText ??
174
- getEnvRedactionText() ??
175
- LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT,
176
- };
177
- }
178
-
179
56
  function shouldApplyToolOutputRedaction(
180
57
  config: ResolvedLangfuseToolOutputTracingConfig
181
58
  ): boolean {
@@ -467,6 +344,10 @@ function classifyLangGraphToolNodeSpan(
467
344
  }
468
345
  }
469
346
 
347
+ export function classifyLangfuseToolNodeSpan(span: ReadableSpan): void {
348
+ classifyLangGraphToolNodeSpan((span as SpanWithAttributes).attributes);
349
+ }
350
+
470
351
  function redactToolObservationOutput(
471
352
  span: ReadableSpan,
472
353
  attributes: Record<string, unknown>,
@@ -491,7 +372,7 @@ export function redactLangfuseSpanToolOutputs(
491
372
  config: ResolvedLangfuseToolOutputTracingConfig
492
373
  ): void {
493
374
  const attributes = (span as SpanWithAttributes).attributes;
494
- classifyLangGraphToolNodeSpan(attributes);
375
+ classifyLangfuseToolNodeSpan(span);
495
376
 
496
377
  if (!shouldApplyToolOutputRedaction(config)) {
497
378
  return;
@@ -509,32 +390,6 @@ export function redactLangfuseSpanToolOutputs(
509
390
  }
510
391
  }
511
392
 
512
- function getContextToolOutputTracingConfig(
513
- activeContext: Context
514
- ): ResolvedLangfuseToolOutputTracingConfig | undefined {
515
- const asyncConfig = toolOutputTracingStorage.getStore();
516
- if (asyncConfig != null) {
517
- return asyncConfig;
518
- }
519
-
520
- const value = activeContext.getValue(langfuseToolOutputTracingConfigKey);
521
- return isRecord(value)
522
- ? (value as ResolvedLangfuseToolOutputTracingConfig)
523
- : undefined;
524
- }
525
-
526
- export function getContextLangfuseConfig(
527
- activeContext: Context
528
- ): t.LangfuseConfig | undefined {
529
- const asyncConfig = langfuseConfigStorage.getStore();
530
- if (asyncConfig != null) {
531
- return asyncConfig;
532
- }
533
-
534
- const value = activeContext.getValue(langfuseConfigKey);
535
- return isRecord(value) ? (value as t.LangfuseConfig) : undefined;
536
- }
537
-
538
393
  class ToolOutputRedactingLangfuseSpanProcessor implements SpanProcessor {
539
394
  private readonly processor: LangfuseSpanProcessor;
540
395
  private readonly fallbackConfig?: ResolvedLangfuseToolOutputTracingConfig;
@@ -553,7 +408,8 @@ class ToolOutputRedactingLangfuseSpanProcessor implements SpanProcessor {
553
408
 
554
409
  onStart(span: Span, parentContext: Context): void {
555
410
  const config =
556
- getContextToolOutputTracingConfig(parentContext) ?? this.fallbackConfig;
411
+ resolveToolOutputTracingConfigForSpan(parentContext) ??
412
+ this.fallbackConfig;
557
413
  if (config != null) {
558
414
  this.spanConfigs.set(span, config);
559
415
  }
@@ -561,12 +417,11 @@ class ToolOutputRedactingLangfuseSpanProcessor implements SpanProcessor {
561
417
  }
562
418
 
563
419
  onEnd(span: ReadableSpan): void {
564
- const config =
565
- this.spanConfigs.get(span) ??
566
- toolOutputTracingStorage.getStore() ??
567
- this.fallbackConfig ??
568
- resolveToolOutputTracingConfig();
569
- redactLangfuseSpanToolOutputs(span, config);
420
+ classifyLangfuseToolNodeSpan(span);
421
+ const config = this.spanConfigs.get(span) ?? this.fallbackConfig;
422
+ if (config != null) {
423
+ redactLangfuseSpanToolOutputs(span, config);
424
+ }
570
425
  this.processor.onEnd(span);
571
426
  }
572
427
 
@@ -584,52 +439,12 @@ export function createLangfuseSpanProcessor(
584
439
  runLangfuse?: t.LangfuseConfig,
585
440
  agentLangfuse?: t.LangfuseConfig
586
441
  ): SpanProcessor {
587
- const fallbackConfig =
588
- runLangfuse != null || agentLangfuse != null
589
- ? resolveToolOutputTracingConfig(runLangfuse, agentLangfuse)
590
- : undefined;
442
+ const fallbackConfig = hasToolOutputTracingConfig(runLangfuse, agentLangfuse)
443
+ ? resolveToolOutputTracingConfig(runLangfuse, agentLangfuse)
444
+ : undefined;
591
445
  return new ToolOutputRedactingLangfuseSpanProcessor(params, fallbackConfig);
592
446
  }
593
447
 
594
- export function withLangfuseToolOutputTracingConfig<T>(
595
- runLangfuse: t.LangfuseConfig | undefined,
596
- action: () => T,
597
- agentLangfuse?: t.LangfuseConfig
598
- ): T {
599
- const langfuse = resolveLangfuseConfig(runLangfuse, agentLangfuse);
600
- const hasNoToolOutputConfig =
601
- runLangfuse?.toolOutputTracing == null &&
602
- agentLangfuse?.toolOutputTracing == null;
603
-
604
- if (langfuse == null && hasNoToolOutputConfig) {
605
- return action();
606
- }
607
-
608
- const config = hasNoToolOutputConfig
609
- ? undefined
610
- : resolveToolOutputTracingConfig(runLangfuse, agentLangfuse);
611
- let activeContext = context.active();
612
- if (langfuse != null) {
613
- activeContext = activeContext.setValue(langfuseConfigKey, langfuse);
614
- }
615
- if (config != null) {
616
- activeContext = activeContext.setValue(
617
- langfuseToolOutputTracingConfigKey,
618
- config
619
- );
620
- }
621
-
622
- const runWithContext = (): T => context.with(activeContext, action);
623
- const runWithToolOutputConfig = (): T =>
624
- config != null
625
- ? toolOutputTracingStorage.run(config, runWithContext)
626
- : runWithContext();
627
-
628
- return langfuse != null
629
- ? langfuseConfigStorage.run(langfuse, runWithToolOutputConfig)
630
- : runWithToolOutputConfig();
631
- }
632
-
633
448
  function hasLangfuseEnvKeys(): boolean {
634
449
  return (
635
450
  isPresent(process.env.LANGFUSE_SECRET_KEY) &&
@@ -665,56 +480,3 @@ export function shouldTraceToolNodeForLangfuse({
665
480
 
666
481
  return hasLangfuseConfigKeys(langfuse) || hasLangfuseEnvKeys();
667
482
  }
668
-
669
- export function resolveLangfuseConfig(
670
- runLangfuse?: t.LangfuseConfig,
671
- agentLangfuse?: t.LangfuseConfig
672
- ): t.LangfuseConfig | undefined {
673
- if (runLangfuse == null) {
674
- return agentLangfuse;
675
- }
676
- if (agentLangfuse == null) {
677
- return runLangfuse;
678
- }
679
-
680
- const toolNodeTracing =
681
- runLangfuse.toolNodeTracing != null || agentLangfuse.toolNodeTracing != null
682
- ? {
683
- ...runLangfuse.toolNodeTracing,
684
- ...agentLangfuse.toolNodeTracing,
685
- }
686
- : undefined;
687
- const toolOutputTracing =
688
- runLangfuse.toolOutputTracing != null ||
689
- agentLangfuse.toolOutputTracing != null
690
- ? {
691
- ...runLangfuse.toolOutputTracing,
692
- ...agentLangfuse.toolOutputTracing,
693
- }
694
- : undefined;
695
- const metadata =
696
- runLangfuse.metadata != null || agentLangfuse.metadata != null
697
- ? {
698
- ...runLangfuse.metadata,
699
- ...agentLangfuse.metadata,
700
- }
701
- : undefined;
702
- const tags =
703
- runLangfuse.tags != null || agentLangfuse.tags != null
704
- ? [
705
- ...new Set([
706
- ...(runLangfuse.tags ?? []),
707
- ...(agentLangfuse.tags ?? []),
708
- ]),
709
- ]
710
- : undefined;
711
-
712
- return {
713
- ...runLangfuse,
714
- ...agentLangfuse,
715
- ...(metadata != null ? { metadata } : {}),
716
- ...(tags != null ? { tags } : {}),
717
- ...(toolNodeTracing != null ? { toolNodeTracing } : {}),
718
- ...(toolOutputTracing != null ? { toolOutputTracing } : {}),
719
- };
720
- }
@@ -334,6 +334,10 @@ interface FormatAssistantMessageOptions {
334
334
 
335
335
  interface FormatAgentMessagesOptions {
336
336
  provider?: Providers;
337
+ /** Reconstruct hidden `reasoning_content` from `THINK` parts onto prior
338
+ * tool-call messages. Explicit opt-in for OpenAI-compatible endpoints that
339
+ * replay reasoning across turns; defaults to on for DeepSeek thinking-mode. */
340
+ preserveReasoningContent?: boolean;
337
341
  /** Skill names already primed fresh this turn (manual/always-apply). Their
338
342
  * historical `skill` tool_calls are not reconstructed into a HumanMessage,
339
343
  * so the same SKILL.md body is not injected twice in one request. */
@@ -1435,7 +1439,8 @@ export const formatAgentMessages = (
1435
1439
 
1436
1440
  const formattedMessages = formatAssistantMessage(processedMessage, {
1437
1441
  preserveUnpairedServerToolUses: i === payload.length - 1,
1438
- preserveReasoningContent: options?.provider === Providers.DEEPSEEK,
1442
+ preserveReasoningContent:
1443
+ options?.preserveReasoningContent ?? options?.provider === Providers.DEEPSEEK,
1439
1444
  provider: options?.provider,
1440
1445
  });
1441
1446
  if (sourceMessageId != null && sourceMessageId !== '') {
@@ -2189,6 +2189,82 @@ describe('formatAgentMessages', () => {
2189
2189
  );
2190
2190
  });
2191
2191
 
2192
+ it('should preserve hidden reasoning_content via explicit preserveReasoningContent without a provider', () => {
2193
+ const payload: TPayload = [
2194
+ {
2195
+ role: 'assistant',
2196
+ content: [
2197
+ {
2198
+ type: ContentTypes.THINK,
2199
+ [ContentTypes.THINK]: 'Need calculator.',
2200
+ },
2201
+ {
2202
+ type: ContentTypes.TEXT,
2203
+ [ContentTypes.TEXT]: 'Using calculator.',
2204
+ tool_call_ids: ['call_1'],
2205
+ },
2206
+ {
2207
+ type: ContentTypes.TOOL_CALL,
2208
+ tool_call: {
2209
+ id: 'call_1',
2210
+ name: 'calculator',
2211
+ args: '{"input":"127 * 453"}',
2212
+ output: '57531',
2213
+ },
2214
+ },
2215
+ ],
2216
+ },
2217
+ ];
2218
+
2219
+ const result = formatAgentMessages(payload, undefined, undefined, undefined, {
2220
+ preserveReasoningContent: true,
2221
+ });
2222
+
2223
+ const toolCallMessage = result.messages[0] as AIMessage;
2224
+ expect(toolCallMessage.tool_calls).toHaveLength(1);
2225
+ expect(toolCallMessage.additional_kwargs.reasoning_content).toBe(
2226
+ 'Need calculator.'
2227
+ );
2228
+ });
2229
+
2230
+ it('should not reconstruct reasoning_content when preserveReasoningContent is explicitly false for DeepSeek', () => {
2231
+ const payload: TPayload = [
2232
+ {
2233
+ role: 'assistant',
2234
+ content: [
2235
+ {
2236
+ type: ContentTypes.THINK,
2237
+ [ContentTypes.THINK]: 'Need calculator.',
2238
+ },
2239
+ {
2240
+ type: ContentTypes.TEXT,
2241
+ [ContentTypes.TEXT]: 'Using calculator.',
2242
+ tool_call_ids: ['call_1'],
2243
+ },
2244
+ {
2245
+ type: ContentTypes.TOOL_CALL,
2246
+ tool_call: {
2247
+ id: 'call_1',
2248
+ name: 'calculator',
2249
+ args: '{"input":"127 * 453"}',
2250
+ output: '57531',
2251
+ },
2252
+ },
2253
+ ],
2254
+ },
2255
+ ];
2256
+
2257
+ const result = formatAgentMessages(payload, undefined, undefined, undefined, {
2258
+ provider: Providers.DEEPSEEK,
2259
+ preserveReasoningContent: false,
2260
+ });
2261
+
2262
+ const toolCallMessage = result.messages[0] as AIMessage;
2263
+ expect(
2264
+ toolCallMessage.additional_kwargs.reasoning_content
2265
+ ).toBeUndefined();
2266
+ });
2267
+
2192
2268
  it('should preserve DeepSeek reasoning from supported hidden content blocks', () => {
2193
2269
  const payload: TPayload = [
2194
2270
  {
package/src/run.ts CHANGED
@@ -27,9 +27,9 @@ import {
27
27
  withLangfuseAttributes,
28
28
  } from '@/langfuse';
29
29
  import {
30
- resolveLangfuseConfig,
31
- withLangfuseToolOutputTracingConfig,
32
- } from '@/langfuseToolOutputTracing';
30
+ resolveLangfuseRuntimeScope,
31
+ withLangfuseRuntimeScope,
32
+ } from '@/langfuseRuntimeScope';
33
33
  import {
34
34
  appendCallbacks,
35
35
  findCallback,
@@ -39,13 +39,11 @@ import {
39
39
  createCompletionTitleRunnable,
40
40
  createTitleRunnable,
41
41
  } from '@/utils/title';
42
- import {
43
- initializeLangfuseTracing,
44
- runWithTraceIdSeed,
45
- } from './instrumentation';
46
42
  import { createTokenCounter, encodingForModel } from '@/utils/tokens';
43
+ import { initializeLangfuseTracing } from './instrumentation';
47
44
  import { GraphEvents, Callback, TitleMethod } from '@/common';
48
45
  import { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
46
+ import { resolveLangfuseConfig } from '@/langfuseConfig';
49
47
  import { StandardGraph } from '@/graphs/Graph';
50
48
  import { initializeModel } from '@/llm/init';
51
49
  import { HandlerRegistry } from '@/events';
@@ -746,6 +744,10 @@ export class Run<_T extends t.BaseGraphState> {
746
744
  sessionId,
747
745
  traceMetadata,
748
746
  tags: ['librechat', 'agent'],
747
+ traceIdSeed:
748
+ streamLangfuseConfig?.deterministicTraceId === true
749
+ ? this.id
750
+ : undefined,
749
751
  });
750
752
  if (langfuseHandler != null) {
751
753
  config.runName = traceName;
@@ -915,26 +917,26 @@ export class Run<_T extends t.BaseGraphState> {
915
917
  // When opted in, seed the root trace id from this run's id so feedback /
916
918
  // other external signals can be attached to the trace later without a
917
919
  // lookup (see SeededTraceIdGenerator in ./instrumentation).
918
- await runWithTraceIdSeed(
919
- streamLangfuseConfig?.deterministicTraceId === true
920
- ? this.id
921
- : undefined,
920
+ await withLangfuseRuntimeScope(
921
+ resolveLangfuseRuntimeScope({
922
+ runLangfuse: streamLangfuseConfig,
923
+ langfuseOverlay: this.getStreamToolOutputTracingLangfuseConfig(graph),
924
+ traceIdSeed:
925
+ streamLangfuseConfig?.deterministicTraceId === true
926
+ ? this.id
927
+ : undefined,
928
+ }),
922
929
  () =>
923
- withLangfuseToolOutputTracingConfig(
924
- streamLangfuseConfig,
925
- () =>
926
- withLangfuseAttributes(
927
- {
928
- langfuse: streamLangfuseConfig,
929
- userId,
930
- sessionId,
931
- traceName,
932
- traceMetadata,
933
- tags: ['librechat', 'agent'],
934
- },
935
- consumeStream
936
- ),
937
- this.getStreamToolOutputTracingLangfuseConfig(graph)
930
+ withLangfuseAttributes(
931
+ {
932
+ langfuse: streamLangfuseConfig,
933
+ userId,
934
+ sessionId,
935
+ traceName,
936
+ traceMetadata,
937
+ tags: ['librechat', 'agent'],
938
+ },
939
+ consumeStream
938
940
  )
939
941
  );
940
942
  } catch (err) {
@@ -1312,6 +1314,10 @@ export class Run<_T extends t.BaseGraphState> {
1312
1314
  sessionId: titleSessionId,
1313
1315
  traceMetadata,
1314
1316
  tags: ['librechat', 'title'],
1317
+ traceIdSeed:
1318
+ titleLangfuseConfig?.deterministicTraceId === true
1319
+ ? 'title-' + this.id
1320
+ : undefined,
1315
1321
  });
1316
1322
 
1317
1323
  if (titleLangfuseHandler != null) {
@@ -1404,10 +1410,12 @@ export class Run<_T extends t.BaseGraphState> {
1404
1410
 
1405
1411
  try {
1406
1412
  try {
1407
- return await withLangfuseToolOutputTracingConfig(
1408
- this.langfuse,
1409
- () => invokeTitleChain(invokeConfig),
1410
- titleContext?.langfuse
1413
+ return await withLangfuseRuntimeScope(
1414
+ resolveLangfuseRuntimeScope({
1415
+ runLangfuse: this.langfuse,
1416
+ langfuseOverlay: titleContext?.langfuse,
1417
+ }),
1418
+ () => invokeTitleChain(invokeConfig)
1411
1419
  );
1412
1420
  } catch (_e) {
1413
1421
  // Fallback: strip callbacks to avoid EventStream tracer errors in certain environments
@@ -1420,10 +1428,12 @@ export class Run<_T extends t.BaseGraphState> {
1420
1428
  const safeConfig = Object.assign({}, rest, {
1421
1429
  callbacks: langfuseHandler ? [langfuseHandler] : [],
1422
1430
  });
1423
- return await withLangfuseToolOutputTracingConfig(
1424
- this.langfuse,
1425
- () => invokeTitleChain(safeConfig as Partial<RunnableConfig>),
1426
- titleContext?.langfuse
1431
+ return await withLangfuseRuntimeScope(
1432
+ resolveLangfuseRuntimeScope({
1433
+ runLangfuse: this.langfuse,
1434
+ langfuseOverlay: titleContext?.langfuse,
1435
+ }),
1436
+ () => invokeTitleChain(safeConfig as Partial<RunnableConfig>)
1427
1437
  );
1428
1438
  }
1429
1439
  } finally {
@@ -1,8 +1,5 @@
1
- import { createHash } from 'node:crypto';
2
- import {
3
- initializeLangfuseTracing,
4
- runWithTraceIdSeed,
5
- } from '@/instrumentation';
1
+ import { runWithTraceIdSeed, traceIdFromSeed } from '@/langfuseRuntimeContext';
2
+ import { initializeLangfuseTracing } from '@/instrumentation';
6
3
 
7
4
  const langfuse = {
8
5
  publicKey: 'pk-lf-test',
@@ -10,10 +7,6 @@ const langfuse = {
10
7
  baseUrl: 'http://localhost:3999',
11
8
  };
12
9
 
13
- /** sha256(seed) → first 32 hex chars; what `@langfuse/tracing` `createTraceId` produces. */
14
- const expectedTraceId = (seed: string): string =>
15
- createHash('sha256').update(seed, 'utf8').digest('hex').slice(0, 32);
16
-
17
10
  describe('deterministic Langfuse trace ids', () => {
18
11
  it('derives the root trace id from the seed when run inside runWithTraceIdSeed', () => {
19
12
  const provider = initializeLangfuseTracing(langfuse);
@@ -28,7 +21,7 @@ describe('deterministic Langfuse trace ids', () => {
28
21
  span.end();
29
22
  });
30
23
 
31
- expect(traceId).toBe(expectedTraceId(seed));
24
+ expect(traceId).toBe(traceIdFromSeed(seed));
32
25
  });
33
26
 
34
27
  it('falls back to a random trace id when no seed is active', () => {
@@ -40,7 +33,7 @@ describe('deterministic Langfuse trace ids', () => {
40
33
  span.end();
41
34
 
42
35
  expect(traceId).toMatch(/^[0-9a-f]{32}$/);
43
- expect(traceId).not.toBe(expectedTraceId('response-message-id-1234'));
36
+ expect(traceId).not.toBe(traceIdFromSeed('response-message-id-1234'));
44
37
  });
45
38
 
46
39
  it('runWithTraceIdSeed is a passthrough when the seed is undefined', () => {