@librechat/agents 3.1.95 → 3.1.97

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 (67) hide show
  1. package/dist/cjs/graphs/Graph.cjs +54 -21
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/instrumentation.cjs +120 -9
  4. package/dist/cjs/instrumentation.cjs.map +1 -1
  5. package/dist/cjs/langfuse.cjs +30 -226
  6. package/dist/cjs/langfuse.cjs.map +1 -1
  7. package/dist/cjs/langfuseToolOutputTracing.cjs +465 -0
  8. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -0
  9. package/dist/cjs/main.cjs +1 -0
  10. package/dist/cjs/main.cjs.map +1 -1
  11. package/dist/cjs/run.cjs +142 -69
  12. package/dist/cjs/run.cjs.map +1 -1
  13. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +29 -2
  14. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  15. package/dist/cjs/tools/ToolNode.cjs +20 -8
  16. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  17. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +10 -6
  18. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  19. package/dist/esm/graphs/Graph.mjs +56 -23
  20. package/dist/esm/graphs/Graph.mjs.map +1 -1
  21. package/dist/esm/instrumentation.mjs +118 -9
  22. package/dist/esm/instrumentation.mjs.map +1 -1
  23. package/dist/esm/langfuse.mjs +28 -224
  24. package/dist/esm/langfuse.mjs.map +1 -1
  25. package/dist/esm/langfuseToolOutputTracing.mjs +457 -0
  26. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -0
  27. package/dist/esm/main.mjs +1 -1
  28. package/dist/esm/run.mjs +144 -71
  29. package/dist/esm/run.mjs.map +1 -1
  30. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +29 -3
  31. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  32. package/dist/esm/tools/ToolNode.mjs +20 -8
  33. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  34. package/dist/esm/tools/subagent/SubagentExecutor.mjs +10 -6
  35. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  36. package/dist/types/graphs/Graph.d.ts +5 -1
  37. package/dist/types/instrumentation.d.ts +5 -1
  38. package/dist/types/langfuse.d.ts +6 -28
  39. package/dist/types/langfuseToolOutputTracing.d.ts +20 -0
  40. package/dist/types/run.d.ts +5 -1
  41. package/dist/types/tools/BashProgrammaticToolCalling.d.ts +1 -0
  42. package/dist/types/tools/ToolNode.d.ts +4 -1
  43. package/dist/types/tools/subagent/SubagentExecutor.d.ts +2 -0
  44. package/dist/types/types/graph.d.ts +30 -0
  45. package/dist/types/types/run.d.ts +6 -0
  46. package/dist/types/types/tools.d.ts +7 -0
  47. package/package.json +2 -1
  48. package/src/graphs/Graph.ts +90 -34
  49. package/src/instrumentation.ts +172 -11
  50. package/src/langfuse.ts +59 -324
  51. package/src/langfuseToolOutputTracing.ts +683 -0
  52. package/src/run.ts +190 -87
  53. package/src/specs/langfuse-callbacks.test.ts +178 -1
  54. package/src/specs/langfuse-config.test.ts +112 -76
  55. package/src/specs/langfuse-instrumentation.test.ts +283 -0
  56. package/src/specs/langfuse-metadata.test.ts +54 -1
  57. package/src/specs/langfuse-tool-output-tracing.test.ts +588 -0
  58. package/src/tools/BashProgrammaticToolCalling.ts +39 -5
  59. package/src/tools/ToolNode.ts +28 -7
  60. package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +54 -0
  61. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +72 -4
  62. package/src/tools/__tests__/SubagentExecutor.test.ts +32 -0
  63. package/src/tools/__tests__/ToolNode.langfuse.test.ts +41 -0
  64. package/src/tools/subagent/SubagentExecutor.ts +11 -6
  65. package/src/types/graph.ts +32 -0
  66. package/src/types/run.ts +6 -0
  67. package/src/types/tools.ts +7 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.1.95",
3
+ "version": "3.1.97",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -230,6 +230,7 @@
230
230
  "@langfuse/langchain": "^5.3.0",
231
231
  "@langfuse/otel": "^5.3.0",
232
232
  "@langfuse/tracing": "^5.3.0",
233
+ "@opentelemetry/context-async-hooks": "2.7.1",
233
234
  "@opentelemetry/sdk-node": "^0.218.0",
234
235
  "@scarf/scarf": "^1.4.0",
235
236
  "@types/diff": "^7.0.2",
@@ -52,7 +52,11 @@ import { attemptInvoke, tryFallbackProviders } from '@/llm/invoke';
52
52
  import { shouldTriggerSummarization } from '@/summarization';
53
53
  import { createSummarizeNode } from '@/summarization/node';
54
54
  import { messagesStateReducer } from '@/messages/reducer';
55
- import { appendCallbacks } from '@/utils/callbacks';
55
+ import {
56
+ appendCallbacks,
57
+ findCallback,
58
+ type CallbackEntry,
59
+ } from '@/utils/callbacks';
56
60
  import { createSchemaOnlyTools } from '@/tools/schema';
57
61
  import { AgentContext } from '@/agents/AgentContext';
58
62
  import { createFakeStreamingLLM } from '@/llm/fake';
@@ -64,9 +68,16 @@ import { isThinkingEnabled } from '@/llm/request';
64
68
  import { initializeModel } from '@/llm/init';
65
69
  import {
66
70
  createLangfuseHandler,
67
- disposeLangfuseHandler,
68
71
  createLangfuseTraceMetadata,
72
+ disposeLangfuseHandler,
73
+ isLangfuseCallbackHandler,
69
74
  } from '@/langfuse';
75
+ import { initializeLangfuseTracing } from '@/instrumentation';
76
+ import {
77
+ resolveLangfuseConfig,
78
+ shouldTraceToolNodeForLangfuse,
79
+ withLangfuseToolOutputTracingConfig,
80
+ } from '@/langfuseToolOutputTracing';
70
81
  import { HandlerRegistry } from '@/events';
71
82
  import { ChatOpenAI } from '@/llm/openai';
72
83
  import { partitionAndMarkOpenRouterToolCache } from '@/llm/openrouter/toolCache';
@@ -174,6 +185,10 @@ export abstract class Graph<
174
185
  * graph compiles.
175
186
  */
176
187
  toolOutputReferences: t.ToolOutputReferencesConfig | undefined;
188
+ /**
189
+ * Run-scoped Langfuse defaults. Per-agent config wins when present.
190
+ */
191
+ langfuse: t.LangfuseConfig | undefined;
177
192
  /**
178
193
  * Run-scoped opt-in for eager event-driven tool execution. The stream
179
194
  * handler may prestart eligible event-driven tools; ToolNode later
@@ -427,6 +442,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
427
442
  runId,
428
443
  signal,
429
444
  agents,
445
+ langfuse,
430
446
  tokenCounter,
431
447
  indexTokenCountMap,
432
448
  calibrationRatio,
@@ -434,6 +450,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
434
450
  super();
435
451
  this.runId = runId;
436
452
  this.signal = signal;
453
+ this.langfuse = langfuse;
437
454
 
438
455
  if (agents.length === 0) {
439
456
  throw new Error('At least one agent configuration is required');
@@ -743,6 +760,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
743
760
  const toolDefinitions = agentContext?.toolDefinitions;
744
761
  const eventDrivenMode =
745
762
  toolDefinitions != null && toolDefinitions.length > 0;
763
+ const traceToolNode = shouldTraceToolNodeForLangfuse({
764
+ runLangfuse: this.langfuse,
765
+ agentLangfuse: agentContext?.langfuse,
766
+ });
746
767
 
747
768
  if (eventDrivenMode) {
748
769
  const schemaTools = createSchemaOnlyTools(toolDefinitions);
@@ -770,6 +791,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
770
791
  const node = new CustomToolNode<t.BaseGraphState>({
771
792
  tools: allTools,
772
793
  toolMap: allToolMap,
794
+ trace: traceToolNode,
795
+ runLangfuse: this.langfuse,
796
+ agentLangfuse: agentContext?.langfuse,
773
797
  eventDrivenMode: true,
774
798
  sessions: this.sessions,
775
799
  toolDefinitions: toolDefMap,
@@ -815,6 +839,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
815
839
  const node = new CustomToolNode<t.BaseGraphState>({
816
840
  tools: allTraditionalTools,
817
841
  toolMap: traditionalToolMap,
842
+ trace: traceToolNode,
843
+ runLangfuse: this.langfuse,
844
+ agentLangfuse: agentContext?.langfuse,
818
845
  toolCallStepIds: this.toolCallStepIds,
819
846
  errorHandler: (data, metadata): Promise<void> =>
820
847
  StandardGraph.handleToolCallErrorStatic(this, data, metadata),
@@ -1338,44 +1365,72 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1338
1365
  { force: true }
1339
1366
  );
1340
1367
 
1341
- const langfuseHandler = createLangfuseHandler({
1342
- langfuse: agentContext.langfuse,
1343
- userId: config.configurable?.user_id as string | undefined,
1344
- sessionId: config.configurable?.thread_id as string | undefined,
1345
- traceMetadata: createLangfuseTraceMetadata({
1346
- messageId: this.runId,
1347
- parentMessageId: config.configurable?.requestBody?.parentMessageId,
1348
- agentId,
1349
- agentName: agentContext.name,
1350
- }),
1351
- tags: ['librechat', 'agent'],
1368
+ const langfuse = resolveLangfuseConfig(
1369
+ this.langfuse,
1370
+ agentContext.langfuse
1371
+ );
1372
+ const traceMetadata = createLangfuseTraceMetadata({
1373
+ messageId: this.runId,
1374
+ parentMessageId: config.configurable?.requestBody?.parentMessageId,
1375
+ agentId,
1376
+ agentName: agentContext.name,
1352
1377
  });
1353
- const invokeConfig = langfuseHandler
1354
- ? {
1355
- ...config,
1356
- callbacks: appendCallbacks(config.callbacks, [langfuseHandler]),
1378
+ let langfuseHandler: CallbackEntry | undefined;
1379
+ let invokeConfig = {
1380
+ ...config,
1381
+ metadata: {
1382
+ ...(config.metadata ?? {}),
1383
+ ...traceMetadata,
1384
+ },
1385
+ };
1386
+ initializeLangfuseTracing(langfuse);
1387
+ if (findCallback(config.callbacks, isLangfuseCallbackHandler) == null) {
1388
+ langfuseHandler = createLangfuseHandler({
1389
+ langfuse,
1390
+ userId: config.configurable?.user_id as string | undefined,
1391
+ sessionId: config.configurable?.thread_id as string | undefined,
1392
+ traceMetadata,
1393
+ tags: ['librechat', 'agent'],
1394
+ });
1395
+ if (langfuseHandler != null) {
1396
+ invokeConfig = {
1397
+ ...invokeConfig,
1398
+ callbacks: appendCallbacks(invokeConfig.callbacks, [
1399
+ langfuseHandler,
1400
+ ]),
1401
+ };
1357
1402
  }
1358
- : config;
1403
+ }
1359
1404
 
1360
1405
  try {
1361
- result = await attemptInvoke(
1362
- {
1363
- model: (this.overrideModel ?? model) as t.ChatModel,
1364
- messages: finalMessages,
1365
- provider: agentContext.provider,
1366
- context: this,
1367
- },
1368
- invokeConfig
1406
+ result = await withLangfuseToolOutputTracingConfig(
1407
+ this.langfuse,
1408
+ () =>
1409
+ attemptInvoke(
1410
+ {
1411
+ model: (this.overrideModel ?? model) as t.ChatModel,
1412
+ messages: finalMessages,
1413
+ provider: agentContext.provider,
1414
+ context: this,
1415
+ },
1416
+ invokeConfig
1417
+ ),
1418
+ agentContext.langfuse
1369
1419
  );
1370
1420
  } catch (primaryError) {
1371
- result = await tryFallbackProviders({
1372
- fallbacks,
1373
- tools: agentContext.tools,
1374
- messages: finalMessages,
1375
- config: invokeConfig,
1376
- primaryError,
1377
- context: this,
1378
- });
1421
+ result = await withLangfuseToolOutputTracingConfig(
1422
+ this.langfuse,
1423
+ () =>
1424
+ tryFallbackProviders({
1425
+ fallbacks,
1426
+ tools: agentContext.tools,
1427
+ messages: finalMessages,
1428
+ config: invokeConfig,
1429
+ primaryError,
1430
+ context: this,
1431
+ }),
1432
+ agentContext.langfuse
1433
+ );
1379
1434
  } finally {
1380
1435
  await disposeLangfuseHandler(langfuseHandler);
1381
1436
  }
@@ -1599,6 +1654,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1599
1654
  parentHandlerRegistry: getParentHandlerRegistry,
1600
1655
  parentRunId: this.runId ?? '',
1601
1656
  parentAgentId: agentContext.agentId,
1657
+ langfuse: this.langfuse,
1602
1658
  tokenCounter: agentContext.tokenCounter,
1603
1659
  maxDepth: effectiveSubagentDepth,
1604
1660
  createChildGraph: (input): StandardGraph => {
@@ -1,17 +1,178 @@
1
- import { NodeSDK } from '@opentelemetry/sdk-node';
2
- import { LangfuseSpanProcessor } from '@langfuse/otel';
1
+ import { setLangfuseTracerProvider } from '@langfuse/tracing';
2
+ import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
3
+ import { context, ROOT_CONTEXT, createContextKey } from '@opentelemetry/api';
4
+ import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
5
+ import {
6
+ hasLangfuseConfigCredentials,
7
+ hasLangfuseEnvCredentials,
8
+ hasLangfuseEnvConfig,
9
+ } from '@/langfuse';
10
+ import {
11
+ createLangfuseSpanProcessor,
12
+ getContextLangfuseConfig,
13
+ } from '@/langfuseToolOutputTracing';
3
14
  import { isPresent } from '@/utils/misc';
15
+ import type {
16
+ ReadableSpan,
17
+ Span,
18
+ SpanProcessor,
19
+ } from '@opentelemetry/sdk-trace-base';
20
+ import type { LangfuseSpanProcessorParams } from '@langfuse/otel';
21
+ import type { Context } from '@opentelemetry/api';
22
+ import type * as t from '@/types';
4
23
 
5
- if (
6
- isPresent(process.env.LANGFUSE_SECRET_KEY) &&
7
- isPresent(process.env.LANGFUSE_PUBLIC_KEY) &&
8
- isPresent(process.env.LANGFUSE_BASE_URL ?? process.env.LANGFUSE_BASEURL)
9
- ) {
10
- const langfuseSpanProcessor = new LangfuseSpanProcessor();
24
+ let langfuseTracerProvider: BasicTracerProvider | undefined;
25
+ let langfuseRoutingSpanProcessor: RoutingLangfuseSpanProcessor | undefined;
26
+ const contextManagerProbeKey = createContextKey(
27
+ 'langfuse-context-manager-probe'
28
+ );
11
29
 
12
- const sdk = new NodeSDK({
13
- spanProcessors: [langfuseSpanProcessor],
30
+ function hasActiveContextManager(): boolean {
31
+ return context.with(
32
+ ROOT_CONTEXT.setValue(contextManagerProbeKey, true),
33
+ () => context.active().getValue(contextManagerProbeKey) === true
34
+ );
35
+ }
36
+
37
+ export function ensureOpenTelemetryContextManager(): void {
38
+ if (hasActiveContextManager()) {
39
+ return;
40
+ }
41
+
42
+ const contextManager = new AsyncLocalStorageContextManager();
43
+ contextManager.enable();
44
+ if (!context.setGlobalContextManager(contextManager)) {
45
+ contextManager.disable();
46
+ }
47
+ }
48
+
49
+ function getLangfuseSpanProcessorParams(
50
+ langfuse?: t.LangfuseConfig
51
+ ): LangfuseSpanProcessorParams | undefined {
52
+ if (langfuse?.enabled === false) {
53
+ return undefined;
54
+ }
55
+ if (hasLangfuseConfigCredentials(langfuse)) {
56
+ return {
57
+ publicKey: langfuse.publicKey,
58
+ secretKey: langfuse.secretKey,
59
+ ...(isPresent(langfuse.baseUrl) ? { baseUrl: langfuse.baseUrl } : {}),
60
+ };
61
+ }
62
+ if (hasLangfuseEnvConfig()) {
63
+ const baseUrl =
64
+ langfuse?.baseUrl ??
65
+ process.env.LANGFUSE_BASE_URL ??
66
+ process.env.LANGFUSE_BASEURL;
67
+ return {
68
+ publicKey: process.env.LANGFUSE_PUBLIC_KEY as string,
69
+ secretKey: process.env.LANGFUSE_SECRET_KEY as string,
70
+ ...(isPresent(baseUrl) ? { baseUrl } : {}),
71
+ };
72
+ }
73
+ if (isPresent(langfuse?.baseUrl) && hasLangfuseEnvCredentials()) {
74
+ return {
75
+ publicKey: process.env.LANGFUSE_PUBLIC_KEY as string,
76
+ secretKey: process.env.LANGFUSE_SECRET_KEY as string,
77
+ baseUrl: langfuse.baseUrl,
78
+ };
79
+ }
80
+ return undefined;
81
+ }
82
+
83
+ function getLangfuseTracerProviderKey(
84
+ params: LangfuseSpanProcessorParams,
85
+ langfuse?: t.LangfuseConfig
86
+ ): string {
87
+ return JSON.stringify({
88
+ publicKey: params.publicKey,
89
+ secretKey: params.secretKey,
90
+ baseUrl: params.baseUrl,
91
+ environment: params.environment,
92
+ toolOutputTracing: langfuse?.toolOutputTracing,
14
93
  });
94
+ }
95
+
96
+ class RoutingLangfuseSpanProcessor implements SpanProcessor {
97
+ private readonly processors = new Map<string, SpanProcessor>();
98
+ private readonly spanProcessors = new WeakMap<object, SpanProcessor>();
99
+
100
+ ensureProcessor(langfuse?: t.LangfuseConfig): SpanProcessor | undefined {
101
+ const params = getLangfuseSpanProcessorParams(langfuse);
102
+ if (params == null) {
103
+ return undefined;
104
+ }
105
+
106
+ const processorKey = getLangfuseTracerProviderKey(params, langfuse);
107
+ const existing = this.processors.get(processorKey);
108
+ if (existing != null) {
109
+ return existing;
110
+ }
111
+
112
+ const processor = createLangfuseSpanProcessor(params, langfuse);
113
+ this.processors.set(processorKey, processor);
114
+ return processor;
115
+ }
116
+
117
+ onStart(span: Span, parentContext: Context): void {
118
+ const processor = this.ensureProcessor(
119
+ getContextLangfuseConfig(parentContext)
120
+ );
121
+ if (processor == null) {
122
+ return;
123
+ }
15
124
 
16
- sdk.start();
125
+ this.spanProcessors.set(span, processor);
126
+ processor.onStart(span, parentContext);
127
+ }
128
+
129
+ onEnd(span: ReadableSpan): void {
130
+ this.spanProcessors.get(span)?.onEnd(span);
131
+ }
132
+
133
+ async forceFlush(): Promise<void> {
134
+ await Promise.all(
135
+ Array.from(this.processors.values(), (processor) =>
136
+ processor.forceFlush()
137
+ )
138
+ );
139
+ }
140
+
141
+ async shutdown(): Promise<void> {
142
+ await Promise.all(
143
+ Array.from(this.processors.values(), (processor) => processor.shutdown())
144
+ );
145
+ }
146
+ }
147
+
148
+ export function initializeLangfuseTracing(
149
+ langfuse?: t.LangfuseConfig
150
+ ): BasicTracerProvider | undefined {
151
+ const params = getLangfuseSpanProcessorParams(langfuse);
152
+ if (params == null) {
153
+ return undefined;
154
+ }
155
+
156
+ if (langfuseTracerProvider != null) {
157
+ langfuseRoutingSpanProcessor?.ensureProcessor(langfuse);
158
+ return langfuseTracerProvider;
159
+ }
160
+
161
+ ensureOpenTelemetryContextManager();
162
+ langfuseRoutingSpanProcessor = new RoutingLangfuseSpanProcessor();
163
+ langfuseRoutingSpanProcessor.ensureProcessor(langfuse);
164
+ langfuseTracerProvider = new BasicTracerProvider({
165
+ spanProcessors: [langfuseRoutingSpanProcessor],
166
+ });
167
+
168
+ setLangfuseTracerProvider(langfuseTracerProvider);
169
+ return langfuseTracerProvider;
17
170
  }
171
+
172
+ export function initializeLangfuseTracingFromEnv():
173
+ | BasicTracerProvider
174
+ | undefined {
175
+ return initializeLangfuseTracing();
176
+ }
177
+
178
+ initializeLangfuseTracingFromEnv();