@librechat/agents 3.1.96 → 3.1.98

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 (81) hide show
  1. package/dist/cjs/graphs/Graph.cjs +60 -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 +476 -0
  8. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -0
  9. package/dist/cjs/llm/bedrock/index.cjs +10 -0
  10. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  11. package/dist/cjs/llm/bedrock/toolCache.cjs +125 -0
  12. package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -0
  13. package/dist/cjs/messages/cache.cjs +17 -9
  14. package/dist/cjs/messages/cache.cjs.map +1 -1
  15. package/dist/cjs/run.cjs +142 -69
  16. package/dist/cjs/run.cjs.map +1 -1
  17. package/dist/cjs/tools/ToolNode.cjs +26 -9
  18. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  19. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +10 -6
  20. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  21. package/dist/esm/graphs/Graph.mjs +62 -23
  22. package/dist/esm/graphs/Graph.mjs.map +1 -1
  23. package/dist/esm/instrumentation.mjs +118 -9
  24. package/dist/esm/instrumentation.mjs.map +1 -1
  25. package/dist/esm/langfuse.mjs +28 -224
  26. package/dist/esm/langfuse.mjs.map +1 -1
  27. package/dist/esm/langfuseToolOutputTracing.mjs +468 -0
  28. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -0
  29. package/dist/esm/llm/bedrock/index.mjs +10 -0
  30. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  31. package/dist/esm/llm/bedrock/toolCache.mjs +122 -0
  32. package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -0
  33. package/dist/esm/messages/cache.mjs +17 -9
  34. package/dist/esm/messages/cache.mjs.map +1 -1
  35. package/dist/esm/run.mjs +144 -71
  36. package/dist/esm/run.mjs.map +1 -1
  37. package/dist/esm/tools/ToolNode.mjs +26 -9
  38. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  39. package/dist/esm/tools/subagent/SubagentExecutor.mjs +10 -6
  40. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  41. package/dist/types/graphs/Graph.d.ts +5 -1
  42. package/dist/types/instrumentation.d.ts +5 -1
  43. package/dist/types/langfuse.d.ts +6 -28
  44. package/dist/types/langfuseToolOutputTracing.d.ts +20 -0
  45. package/dist/types/llm/bedrock/index.d.ts +16 -0
  46. package/dist/types/llm/bedrock/toolCache.d.ts +4 -0
  47. package/dist/types/messages/cache.d.ts +2 -2
  48. package/dist/types/run.d.ts +5 -1
  49. package/dist/types/tools/ToolNode.d.ts +4 -1
  50. package/dist/types/tools/subagent/SubagentExecutor.d.ts +2 -0
  51. package/dist/types/types/graph.d.ts +30 -0
  52. package/dist/types/types/llm.d.ts +2 -2
  53. package/dist/types/types/run.d.ts +6 -0
  54. package/dist/types/types/tools.d.ts +7 -0
  55. package/package.json +2 -1
  56. package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +332 -0
  57. package/src/agents/__tests__/AgentContext.bedrock.live.test.ts +504 -0
  58. package/src/graphs/Graph.ts +104 -34
  59. package/src/instrumentation.ts +172 -11
  60. package/src/langfuse.ts +59 -324
  61. package/src/langfuseToolOutputTracing.ts +702 -0
  62. package/src/llm/bedrock/index.ts +32 -1
  63. package/src/llm/bedrock/llm.spec.ts +154 -1
  64. package/src/llm/bedrock/toolCache.test.ts +131 -0
  65. package/src/llm/bedrock/toolCache.ts +191 -0
  66. package/src/messages/cache.test.ts +97 -38
  67. package/src/messages/cache.ts +18 -10
  68. package/src/run.ts +190 -87
  69. package/src/specs/langfuse-callbacks.test.ts +178 -1
  70. package/src/specs/langfuse-config.test.ts +112 -76
  71. package/src/specs/langfuse-instrumentation.test.ts +283 -0
  72. package/src/specs/langfuse-metadata.test.ts +54 -1
  73. package/src/specs/langfuse-tool-output-tracing.test.ts +616 -0
  74. package/src/tools/ToolNode.ts +35 -8
  75. package/src/tools/__tests__/SubagentExecutor.test.ts +32 -0
  76. package/src/tools/__tests__/ToolNode.langfuse.test.ts +47 -0
  77. package/src/tools/subagent/SubagentExecutor.ts +11 -6
  78. package/src/types/graph.ts +32 -0
  79. package/src/types/llm.ts +2 -2
  80. package/src/types/run.ts +6 -0
  81. package/src/types/tools.ts +7 -0
@@ -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,12 +68,20 @@ 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';
84
+ import { partitionAndMarkBedrockToolCache } from '@/llm/bedrock/toolCache';
73
85
  import type { HookRegistry } from '@/hooks';
74
86
 
75
87
  const { AGENT, TOOLS, SUMMARIZE } = GraphNodeKeys;
@@ -174,6 +186,10 @@ export abstract class Graph<
174
186
  * graph compiles.
175
187
  */
176
188
  toolOutputReferences: t.ToolOutputReferencesConfig | undefined;
189
+ /**
190
+ * Run-scoped Langfuse defaults. Per-agent config wins when present.
191
+ */
192
+ langfuse: t.LangfuseConfig | undefined;
177
193
  /**
178
194
  * Run-scoped opt-in for eager event-driven tool execution. The stream
179
195
  * handler may prestart eligible event-driven tools; ToolNode later
@@ -427,6 +443,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
427
443
  runId,
428
444
  signal,
429
445
  agents,
446
+ langfuse,
430
447
  tokenCounter,
431
448
  indexTokenCountMap,
432
449
  calibrationRatio,
@@ -434,6 +451,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
434
451
  super();
435
452
  this.runId = runId;
436
453
  this.signal = signal;
454
+ this.langfuse = langfuse;
437
455
 
438
456
  if (agents.length === 0) {
439
457
  throw new Error('At least one agent configuration is required');
@@ -743,6 +761,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
743
761
  const toolDefinitions = agentContext?.toolDefinitions;
744
762
  const eventDrivenMode =
745
763
  toolDefinitions != null && toolDefinitions.length > 0;
764
+ const traceToolNode = shouldTraceToolNodeForLangfuse({
765
+ runLangfuse: this.langfuse,
766
+ agentLangfuse: agentContext?.langfuse,
767
+ });
746
768
 
747
769
  if (eventDrivenMode) {
748
770
  const schemaTools = createSchemaOnlyTools(toolDefinitions);
@@ -770,6 +792,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
770
792
  const node = new CustomToolNode<t.BaseGraphState>({
771
793
  tools: allTools,
772
794
  toolMap: allToolMap,
795
+ trace: traceToolNode,
796
+ runLangfuse: this.langfuse,
797
+ agentLangfuse: agentContext?.langfuse,
773
798
  eventDrivenMode: true,
774
799
  sessions: this.sessions,
775
800
  toolDefinitions: toolDefMap,
@@ -815,6 +840,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
815
840
  const node = new CustomToolNode<t.BaseGraphState>({
816
841
  tools: allTraditionalTools,
817
842
  toolMap: traditionalToolMap,
843
+ trace: traceToolNode,
844
+ runLangfuse: this.langfuse,
845
+ agentLangfuse: agentContext?.langfuse,
818
846
  toolCallStepIds: this.toolCallStepIds,
819
847
  errorHandler: (data, metadata): Promise<void> =>
820
848
  StandardGraph.handleToolCallErrorStatic(this, data, metadata),
@@ -935,6 +963,19 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
935
963
  rawToolsForBinding,
936
964
  makeIsDeferred(agentContext.toolDefinitions)
937
965
  ) ?? rawToolsForBinding;
966
+ } else if (
967
+ agentContext.provider === Providers.BEDROCK &&
968
+ (
969
+ agentContext.clientOptions as
970
+ | t.BedrockAnthropicClientOptions
971
+ | undefined
972
+ )?.promptCache === true
973
+ ) {
974
+ toolsForBinding =
975
+ partitionAndMarkBedrockToolCache(
976
+ rawToolsForBinding,
977
+ makeIsDeferred(agentContext.toolDefinitions)
978
+ ) ?? rawToolsForBinding;
938
979
  }
939
980
 
940
981
  let model =
@@ -1338,44 +1379,72 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1338
1379
  { force: true }
1339
1380
  );
1340
1381
 
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'],
1382
+ const langfuse = resolveLangfuseConfig(
1383
+ this.langfuse,
1384
+ agentContext.langfuse
1385
+ );
1386
+ const traceMetadata = createLangfuseTraceMetadata({
1387
+ messageId: this.runId,
1388
+ parentMessageId: config.configurable?.requestBody?.parentMessageId,
1389
+ agentId,
1390
+ agentName: agentContext.name,
1352
1391
  });
1353
- const invokeConfig = langfuseHandler
1354
- ? {
1355
- ...config,
1356
- callbacks: appendCallbacks(config.callbacks, [langfuseHandler]),
1392
+ let langfuseHandler: CallbackEntry | undefined;
1393
+ let invokeConfig = {
1394
+ ...config,
1395
+ metadata: {
1396
+ ...(config.metadata ?? {}),
1397
+ ...traceMetadata,
1398
+ },
1399
+ };
1400
+ initializeLangfuseTracing(langfuse);
1401
+ if (findCallback(config.callbacks, isLangfuseCallbackHandler) == null) {
1402
+ langfuseHandler = createLangfuseHandler({
1403
+ langfuse,
1404
+ userId: config.configurable?.user_id as string | undefined,
1405
+ sessionId: config.configurable?.thread_id as string | undefined,
1406
+ traceMetadata,
1407
+ tags: ['librechat', 'agent'],
1408
+ });
1409
+ if (langfuseHandler != null) {
1410
+ invokeConfig = {
1411
+ ...invokeConfig,
1412
+ callbacks: appendCallbacks(invokeConfig.callbacks, [
1413
+ langfuseHandler,
1414
+ ]),
1415
+ };
1357
1416
  }
1358
- : config;
1417
+ }
1359
1418
 
1360
1419
  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
1420
+ result = await withLangfuseToolOutputTracingConfig(
1421
+ this.langfuse,
1422
+ () =>
1423
+ attemptInvoke(
1424
+ {
1425
+ model: (this.overrideModel ?? model) as t.ChatModel,
1426
+ messages: finalMessages,
1427
+ provider: agentContext.provider,
1428
+ context: this,
1429
+ },
1430
+ invokeConfig
1431
+ ),
1432
+ agentContext.langfuse
1369
1433
  );
1370
1434
  } catch (primaryError) {
1371
- result = await tryFallbackProviders({
1372
- fallbacks,
1373
- tools: agentContext.tools,
1374
- messages: finalMessages,
1375
- config: invokeConfig,
1376
- primaryError,
1377
- context: this,
1378
- });
1435
+ result = await withLangfuseToolOutputTracingConfig(
1436
+ this.langfuse,
1437
+ () =>
1438
+ tryFallbackProviders({
1439
+ fallbacks,
1440
+ tools: agentContext.tools,
1441
+ messages: finalMessages,
1442
+ config: invokeConfig,
1443
+ primaryError,
1444
+ context: this,
1445
+ }),
1446
+ agentContext.langfuse
1447
+ );
1379
1448
  } finally {
1380
1449
  await disposeLangfuseHandler(langfuseHandler);
1381
1450
  }
@@ -1599,6 +1668,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1599
1668
  parentHandlerRegistry: getParentHandlerRegistry,
1600
1669
  parentRunId: this.runId ?? '',
1601
1670
  parentAgentId: agentContext.agentId,
1671
+ langfuse: this.langfuse,
1602
1672
  tokenCounter: agentContext.tokenCounter,
1603
1673
  maxDepth: effectiveSubagentDepth,
1604
1674
  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();