@librechat/agents 3.2.43 → 3.2.45

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 (76) 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/reducer.cjs +15 -2
  17. package/dist/cjs/messages/reducer.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/reducer.mjs +15 -2
  40. package/dist/esm/messages/reducer.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/types/graph.d.ts +6 -0
  54. package/dist/types/utils/misc.d.ts +1 -0
  55. package/package.json +1 -1
  56. package/src/graphs/Graph.ts +20 -13
  57. package/src/instrumentation.ts +33 -29
  58. package/src/langfuse.ts +114 -7
  59. package/src/langfuseConfig.ts +214 -0
  60. package/src/langfuseRuntimeContext.ts +93 -0
  61. package/src/langfuseRuntimeScope.ts +135 -0
  62. package/src/langfuseToolOutputTracing.ts +25 -263
  63. package/src/messages/reducer.spec.ts +32 -0
  64. package/src/messages/reducer.ts +23 -7
  65. package/src/run.ts +44 -34
  66. package/src/specs/deterministic-trace-id.test.ts +4 -11
  67. package/src/specs/langfuse-callbacks.test.ts +278 -22
  68. package/src/specs/langfuse-config.test.ts +10 -3
  69. package/src/specs/langfuse-instrumentation.test.ts +32 -12
  70. package/src/specs/langfuse-routing.integration.test.ts +543 -0
  71. package/src/specs/langfuse-runtime-context.test.ts +92 -0
  72. package/src/specs/langfuse-tool-output-tracing.test.ts +353 -5
  73. package/src/tools/ToolNode.ts +10 -5
  74. package/src/tools/__tests__/ToolNode.langfuse.test.ts +21 -11
  75. package/src/types/graph.ts +9 -0
  76. package/src/utils/misc.ts +18 -0
@@ -0,0 +1,214 @@
1
+ import type { ResolvedLangfuseToolOutputTracingConfig } from '@/langfuseRuntimeContext';
2
+ import type * as t from '@/types';
3
+ import { parseBooleanEnv } from '@/utils/misc';
4
+
5
+ export const LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT = '[tool output redacted]';
6
+
7
+ function isPresent(value: unknown): value is string {
8
+ return typeof value === 'string' && value.trim() !== '';
9
+ }
10
+
11
+ export function normalizeToolName(name: string): string {
12
+ return name.trim().toLowerCase();
13
+ }
14
+
15
+ function normalizeToolNames(names: string[] | undefined): Set<string> {
16
+ const normalized = new Set<string>();
17
+ for (const name of names ?? []) {
18
+ if (isPresent(name)) {
19
+ normalized.add(normalizeToolName(name));
20
+ }
21
+ }
22
+ return normalized;
23
+ }
24
+
25
+ function parseToolNames(value: string | undefined): string[] | undefined {
26
+ if (!isPresent(value)) {
27
+ return undefined;
28
+ }
29
+
30
+ return value
31
+ .split(',')
32
+ .map((name) => name.trim())
33
+ .filter((name) => name !== '');
34
+ }
35
+
36
+ function getEnvToolOutputTracingEnabled(): boolean | undefined {
37
+ const traceToolOutputs = parseBooleanEnv(
38
+ process.env.LANGFUSE_TRACE_TOOL_OUTPUTS
39
+ );
40
+ if (traceToolOutputs != null) {
41
+ return traceToolOutputs;
42
+ }
43
+
44
+ const redactToolOutputs = parseBooleanEnv(
45
+ process.env.LANGFUSE_REDACT_TOOL_OUTPUTS
46
+ );
47
+ if (redactToolOutputs != null) {
48
+ return !redactToolOutputs;
49
+ }
50
+
51
+ return parseBooleanEnv(process.env.LANGFUSE_TOOL_OUTPUT_TRACING_ENABLED);
52
+ }
53
+
54
+ function getEnvRedactedToolNames(): string[] | undefined {
55
+ return (
56
+ parseToolNames(process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAMES) ??
57
+ parseToolNames(process.env.LANGFUSE_REDACT_TOOL_NAMES)
58
+ );
59
+ }
60
+
61
+ function getEnvRedactionText(): string | undefined {
62
+ return isPresent(process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT)
63
+ ? process.env.LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT
64
+ : undefined;
65
+ }
66
+
67
+ function getEnvToolNameMatchMode(): 'exact' | 'partial' | undefined {
68
+ const mode = (
69
+ process.env.LANGFUSE_REDACT_TOOL_OUTPUT_NAME_MATCH_MODE ??
70
+ process.env.LANGFUSE_REDACT_TOOL_NAME_MATCH_MODE
71
+ )
72
+ ?.trim()
73
+ .toLowerCase();
74
+ if (mode === 'exact' || mode === 'partial') {
75
+ return mode;
76
+ }
77
+ return undefined;
78
+ }
79
+
80
+ function hasEnvToolOutputTracingConfig(): boolean {
81
+ return (
82
+ getEnvToolOutputTracingEnabled() != null ||
83
+ getEnvRedactedToolNames() != null ||
84
+ getEnvRedactionText() != null ||
85
+ getEnvToolNameMatchMode() != null
86
+ );
87
+ }
88
+
89
+ function resolveToolOutputTracingEnabled(
90
+ runConfig?: t.LangfuseToolOutputTracingConfig,
91
+ agentConfig?: t.LangfuseToolOutputTracingConfig
92
+ ): boolean {
93
+ return (
94
+ agentConfig?.enabled ??
95
+ runConfig?.enabled ??
96
+ getEnvToolOutputTracingEnabled() ??
97
+ true
98
+ );
99
+ }
100
+
101
+ function resolveRedactedToolNames(
102
+ runConfig?: t.LangfuseToolOutputTracingConfig,
103
+ agentConfig?: t.LangfuseToolOutputTracingConfig
104
+ ): Set<string> {
105
+ return normalizeToolNames([
106
+ ...(getEnvRedactedToolNames() ?? []),
107
+ ...(runConfig?.redactedToolNames ?? []),
108
+ ...(agentConfig?.redactedToolNames ?? []),
109
+ ]);
110
+ }
111
+
112
+ function resolveToolNameMatchMode(
113
+ runConfig?: t.LangfuseToolOutputTracingConfig,
114
+ agentConfig?: t.LangfuseToolOutputTracingConfig
115
+ ): 'exact' | 'partial' {
116
+ const modes = [
117
+ getEnvToolNameMatchMode(),
118
+ runConfig?.redactedToolNameMatchMode,
119
+ agentConfig?.redactedToolNameMatchMode,
120
+ ];
121
+ return modes.includes('partial') ? 'partial' : 'exact';
122
+ }
123
+
124
+ export function hasToolOutputTracingConfig(
125
+ runLangfuse?: t.LangfuseConfig,
126
+ agentLangfuse?: t.LangfuseConfig
127
+ ): boolean {
128
+ return (
129
+ runLangfuse?.toolOutputTracing != null ||
130
+ agentLangfuse?.toolOutputTracing != null ||
131
+ hasEnvToolOutputTracingConfig()
132
+ );
133
+ }
134
+
135
+ export function resolveToolOutputTracingConfig(
136
+ runLangfuse?: t.LangfuseConfig,
137
+ agentLangfuse?: t.LangfuseConfig
138
+ ): ResolvedLangfuseToolOutputTracingConfig {
139
+ const runConfig = runLangfuse?.toolOutputTracing;
140
+ const agentConfig = agentLangfuse?.toolOutputTracing;
141
+
142
+ return {
143
+ enabled: resolveToolOutputTracingEnabled(runConfig, agentConfig),
144
+ redactedToolNames: resolveRedactedToolNames(runConfig, agentConfig),
145
+ redactedToolNameMatchMode: resolveToolNameMatchMode(runConfig, agentConfig),
146
+ redactionText:
147
+ agentConfig?.redactionText ??
148
+ runConfig?.redactionText ??
149
+ getEnvRedactionText() ??
150
+ LANGFUSE_TOOL_OUTPUT_REDACTION_TEXT,
151
+ };
152
+ }
153
+
154
+ export function resolveLangfuseConfig(
155
+ runLangfuse?: t.LangfuseConfig,
156
+ agentLangfuse?: t.LangfuseConfig
157
+ ): t.LangfuseConfig | undefined {
158
+ if (runLangfuse == null) {
159
+ return agentLangfuse;
160
+ }
161
+ if (agentLangfuse == null) {
162
+ return runLangfuse;
163
+ }
164
+
165
+ const toolNodeTracing =
166
+ runLangfuse.toolNodeTracing != null || agentLangfuse.toolNodeTracing != null
167
+ ? {
168
+ ...runLangfuse.toolNodeTracing,
169
+ ...agentLangfuse.toolNodeTracing,
170
+ }
171
+ : undefined;
172
+ const toolOutputTracing =
173
+ runLangfuse.toolOutputTracing != null ||
174
+ agentLangfuse.toolOutputTracing != null
175
+ ? {
176
+ ...runLangfuse.toolOutputTracing,
177
+ ...agentLangfuse.toolOutputTracing,
178
+ }
179
+ : undefined;
180
+ const metadata =
181
+ runLangfuse.metadata != null || agentLangfuse.metadata != null
182
+ ? {
183
+ ...runLangfuse.metadata,
184
+ ...agentLangfuse.metadata,
185
+ }
186
+ : undefined;
187
+ const librechatTraceAttributes =
188
+ runLangfuse.librechatTraceAttributes != null ||
189
+ agentLangfuse.librechatTraceAttributes != null
190
+ ? {
191
+ ...runLangfuse.librechatTraceAttributes,
192
+ ...agentLangfuse.librechatTraceAttributes,
193
+ }
194
+ : undefined;
195
+ const tags =
196
+ runLangfuse.tags != null || agentLangfuse.tags != null
197
+ ? [
198
+ ...new Set([
199
+ ...(runLangfuse.tags ?? []),
200
+ ...(agentLangfuse.tags ?? []),
201
+ ]),
202
+ ]
203
+ : undefined;
204
+
205
+ return {
206
+ ...runLangfuse,
207
+ ...agentLangfuse,
208
+ ...(metadata != null ? { metadata } : {}),
209
+ ...(librechatTraceAttributes != null ? { librechatTraceAttributes } : {}),
210
+ ...(tags != null ? { tags } : {}),
211
+ ...(toolNodeTracing != null ? { toolNodeTracing } : {}),
212
+ ...(toolOutputTracing != null ? { toolOutputTracing } : {}),
213
+ };
214
+ }
@@ -0,0 +1,93 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { AsyncLocalStorage } from 'node:async_hooks';
3
+ import type * as t from '@/types';
4
+
5
+ export type ResolvedLangfuseToolOutputTracingConfig = {
6
+ enabled: boolean;
7
+ redactedToolNames: Set<string>;
8
+ redactedToolNameMatchMode: 'exact' | 'partial';
9
+ redactionText: string;
10
+ };
11
+
12
+ export type LangfuseRuntimeContext = {
13
+ langfuse?: t.LangfuseConfig;
14
+ traceIdSeed?: string;
15
+ toolOutputTracing?: ResolvedLangfuseToolOutputTracingConfig;
16
+ };
17
+
18
+ const langfuseRuntimeContextStore =
19
+ new AsyncLocalStorage<LangfuseRuntimeContext>();
20
+
21
+ function hasText(value: string | undefined): value is string {
22
+ return value != null && value.trim() !== '';
23
+ }
24
+
25
+ export function hasLangfuseRuntimeContextValue(
26
+ context: LangfuseRuntimeContext
27
+ ): boolean {
28
+ return (
29
+ context.langfuse != null ||
30
+ hasText(context.traceIdSeed) ||
31
+ context.toolOutputTracing != null
32
+ );
33
+ }
34
+
35
+ /** sha256(seed) -> first 32 hex chars; matches `@langfuse/tracing` `createTraceId`. */
36
+ export function traceIdFromSeed(seed: string): string {
37
+ return createHash('sha256').update(seed, 'utf8').digest('hex').slice(0, 32);
38
+ }
39
+
40
+ export function getLangfuseRuntimeContext():
41
+ | LangfuseRuntimeContext
42
+ | undefined {
43
+ return langfuseRuntimeContextStore.getStore();
44
+ }
45
+
46
+ export function getLangfuseRuntimeConfig(): t.LangfuseConfig | undefined {
47
+ return getLangfuseRuntimeContext()?.langfuse;
48
+ }
49
+
50
+ export function getTraceIdSeed(): string | undefined {
51
+ return getLangfuseRuntimeContext()?.traceIdSeed;
52
+ }
53
+
54
+ export function getLangfuseRuntimeToolOutputTracingConfig():
55
+ | ResolvedLangfuseToolOutputTracingConfig
56
+ | undefined {
57
+ return getLangfuseRuntimeContext()?.toolOutputTracing;
58
+ }
59
+
60
+ /**
61
+ * Runs `fn` with a merged Langfuse runtime context. Undefined fields inherit
62
+ * from the parent scope; callers intentionally cannot clear parent values by
63
+ * passing `undefined`.
64
+ */
65
+ export function runWithLangfuseRuntimeContext<T>(
66
+ context: LangfuseRuntimeContext,
67
+ fn: () => T
68
+ ): T {
69
+ const current = getLangfuseRuntimeContext();
70
+ const next = {
71
+ ...(current ?? {}),
72
+ ...(context.langfuse !== undefined ? { langfuse: context.langfuse } : {}),
73
+ ...(hasText(context.traceIdSeed)
74
+ ? { traceIdSeed: context.traceIdSeed }
75
+ : {}),
76
+ ...(context.toolOutputTracing !== undefined
77
+ ? { toolOutputTracing: context.toolOutputTracing }
78
+ : {}),
79
+ };
80
+
81
+ return hasLangfuseRuntimeContextValue(next)
82
+ ? langfuseRuntimeContextStore.run(next, fn)
83
+ : fn();
84
+ }
85
+
86
+ export function runWithTraceIdSeed<T>(
87
+ seed: string | undefined,
88
+ fn: () => T
89
+ ): T {
90
+ return hasText(seed)
91
+ ? runWithLangfuseRuntimeContext({ traceIdSeed: seed }, fn)
92
+ : fn();
93
+ }
@@ -0,0 +1,135 @@
1
+ import { context, createContextKey } from '@opentelemetry/api';
2
+ import type { Context } from '@opentelemetry/api';
3
+ import type {
4
+ LangfuseRuntimeContext,
5
+ ResolvedLangfuseToolOutputTracingConfig,
6
+ } from '@/langfuseRuntimeContext';
7
+ import type * as t from '@/types';
8
+ import {
9
+ getLangfuseRuntimeConfig,
10
+ getLangfuseRuntimeToolOutputTracingConfig,
11
+ getTraceIdSeed,
12
+ hasLangfuseRuntimeContextValue,
13
+ runWithLangfuseRuntimeContext,
14
+ } from '@/langfuseRuntimeContext';
15
+ import {
16
+ hasToolOutputTracingConfig,
17
+ resolveLangfuseConfig,
18
+ resolveToolOutputTracingConfig,
19
+ } from '@/langfuseConfig';
20
+
21
+ export type LangfuseRuntimeScope = LangfuseRuntimeContext;
22
+
23
+ export type ResolveLangfuseRuntimeScopeParams = {
24
+ runLangfuse?: t.LangfuseConfig;
25
+ langfuseOverlay?: t.LangfuseConfig;
26
+ traceIdSeed?: string;
27
+ };
28
+
29
+ const langfuseToolOutputTracingConfigKey = createContextKey(
30
+ 'librechat.langfuse.tool-output-tracing'
31
+ );
32
+ const langfuseConfigKey = createContextKey('librechat.langfuse.config');
33
+ const langfuseTraceIdSeedKey = createContextKey(
34
+ 'librechat.langfuse.trace-id-seed'
35
+ );
36
+
37
+ function isRecord(value: unknown): value is Record<string, unknown> {
38
+ return value != null && typeof value === 'object' && !Array.isArray(value);
39
+ }
40
+
41
+ function hasText(value: string | undefined): value is string {
42
+ return value != null && value.trim() !== '';
43
+ }
44
+
45
+ export function getOtelLangfuseConfig(
46
+ activeContext: Context
47
+ ): t.LangfuseConfig | undefined {
48
+ const value = activeContext.getValue(langfuseConfigKey);
49
+ return isRecord(value) ? (value as t.LangfuseConfig) : undefined;
50
+ }
51
+
52
+ export function getOtelTraceIdSeed(activeContext: Context): string | undefined {
53
+ const value = activeContext.getValue(langfuseTraceIdSeedKey);
54
+ return typeof value === 'string' && value.trim() !== '' ? value : undefined;
55
+ }
56
+
57
+ export function getOtelToolOutputTracingConfig(
58
+ activeContext: Context
59
+ ): ResolvedLangfuseToolOutputTracingConfig | undefined {
60
+ const value = activeContext.getValue(langfuseToolOutputTracingConfigKey);
61
+ return isRecord(value)
62
+ ? (value as ResolvedLangfuseToolOutputTracingConfig)
63
+ : undefined;
64
+ }
65
+
66
+ export function resolveLangfuseConfigForSpan(
67
+ activeContext: Context
68
+ ): t.LangfuseConfig | undefined {
69
+ return getLangfuseRuntimeConfig() ?? getOtelLangfuseConfig(activeContext);
70
+ }
71
+
72
+ export function resolveTraceIdSeedForSpan(
73
+ activeContext: Context
74
+ ): string | undefined {
75
+ return getTraceIdSeed() ?? getOtelTraceIdSeed(activeContext);
76
+ }
77
+
78
+ export function resolveToolOutputTracingConfigForSpan(
79
+ activeContext: Context
80
+ ): ResolvedLangfuseToolOutputTracingConfig | undefined {
81
+ return (
82
+ getLangfuseRuntimeToolOutputTracingConfig() ??
83
+ getOtelToolOutputTracingConfig(activeContext)
84
+ );
85
+ }
86
+
87
+ export function withLangfuseRuntimeScope<T>(
88
+ scope: LangfuseRuntimeScope,
89
+ action: () => T
90
+ ): T {
91
+ if (!hasLangfuseRuntimeContextValue(scope)) {
92
+ return action();
93
+ }
94
+
95
+ let activeContext = context.active();
96
+ if (scope.langfuse != null) {
97
+ activeContext = activeContext.setValue(langfuseConfigKey, scope.langfuse);
98
+ }
99
+ if (scope.toolOutputTracing != null) {
100
+ activeContext = activeContext.setValue(
101
+ langfuseToolOutputTracingConfigKey,
102
+ scope.toolOutputTracing
103
+ );
104
+ }
105
+ if (hasText(scope.traceIdSeed)) {
106
+ activeContext = activeContext.setValue(
107
+ langfuseTraceIdSeedKey,
108
+ scope.traceIdSeed
109
+ );
110
+ }
111
+
112
+ // Span processors receive the OTel parent context in `onStart`, while
113
+ // LangChain callback handlers may run outside that context and need ALS.
114
+ // The trace id generator reads the seed from ALS or OTel context so SDK
115
+ // callbacks that preserve only one of those contexts still keep trace/score
116
+ // cohesion.
117
+ return runWithLangfuseRuntimeContext(scope, () =>
118
+ context.with(activeContext, action)
119
+ );
120
+ }
121
+
122
+ export function resolveLangfuseRuntimeScope({
123
+ runLangfuse,
124
+ langfuseOverlay,
125
+ traceIdSeed,
126
+ }: ResolveLangfuseRuntimeScopeParams): LangfuseRuntimeScope {
127
+ const langfuse = resolveLangfuseConfig(runLangfuse, langfuseOverlay);
128
+ const toolOutputTracing = !hasToolOutputTracingConfig(
129
+ runLangfuse,
130
+ langfuseOverlay
131
+ )
132
+ ? undefined
133
+ : resolveToolOutputTracingConfig(runLangfuse, langfuseOverlay);
134
+ return { langfuse, traceIdSeed, toolOutputTracing };
135
+ }