@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.
- package/dist/cjs/graphs/Graph.cjs +17 -8
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +20 -21
- package/dist/cjs/instrumentation.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +65 -13
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseConfig.cjs +106 -0
- package/dist/cjs/langfuseConfig.cjs.map +1 -0
- package/dist/cjs/langfuseRuntimeContext.cjs +48 -0
- package/dist/cjs/langfuseRuntimeContext.cjs.map +1 -0
- package/dist/cjs/langfuseRuntimeScope.cjs +57 -0
- package/dist/cjs/langfuseRuntimeScope.cjs.map +1 -0
- package/dist/cjs/langfuseToolOutputTracing.cjs +14 -119
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/main.cjs +1 -0
- package/dist/cjs/messages/format.cjs +1 -1
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/run.cjs +25 -12
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +5 -2
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/utils/misc.cjs +17 -0
- package/dist/cjs/utils/misc.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +16 -7
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +22 -22
- package/dist/esm/instrumentation.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +65 -14
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseConfig.mjs +102 -0
- package/dist/esm/langfuseConfig.mjs.map +1 -0
- package/dist/esm/langfuseRuntimeContext.mjs +44 -0
- package/dist/esm/langfuseRuntimeContext.mjs.map +1 -0
- package/dist/esm/langfuseRuntimeScope.mjs +53 -0
- package/dist/esm/langfuseRuntimeScope.mjs.map +1 -0
- package/dist/esm/langfuseToolOutputTracing.mjs +13 -115
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -2
- package/dist/esm/messages/format.mjs +1 -1
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/run.mjs +21 -8
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +5 -2
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/utils/misc.mjs +17 -1
- package/dist/esm/utils/misc.mjs.map +1 -1
- package/dist/types/instrumentation.d.ts +0 -1
- package/dist/types/langfuse.d.ts +5 -1
- package/dist/types/langfuseConfig.d.ts +7 -0
- package/dist/types/langfuseRuntimeContext.d.ts +26 -0
- package/dist/types/langfuseRuntimeScope.d.ts +17 -0
- package/dist/types/langfuseToolOutputTracing.d.ts +4 -11
- package/dist/types/messages/format.d.ts +4 -0
- package/dist/types/types/graph.d.ts +6 -0
- package/dist/types/utils/misc.d.ts +1 -0
- package/package.json +1 -1
- package/src/graphs/Graph.ts +20 -13
- package/src/instrumentation.ts +33 -29
- package/src/langfuse.ts +114 -7
- package/src/langfuseConfig.ts +214 -0
- package/src/langfuseRuntimeContext.ts +93 -0
- package/src/langfuseRuntimeScope.ts +135 -0
- package/src/langfuseToolOutputTracing.ts +25 -263
- package/src/messages/format.ts +6 -1
- package/src/messages/formatAgentMessages.test.ts +76 -0
- package/src/run.ts +44 -34
- package/src/specs/deterministic-trace-id.test.ts +4 -11
- package/src/specs/langfuse-callbacks.test.ts +278 -22
- package/src/specs/langfuse-config.test.ts +10 -3
- package/src/specs/langfuse-instrumentation.test.ts +32 -12
- package/src/specs/langfuse-routing.integration.test.ts +543 -0
- package/src/specs/langfuse-runtime-context.test.ts +92 -0
- package/src/specs/langfuse-tool-output-tracing.test.ts +353 -5
- package/src/tools/ToolNode.ts +10 -5
- package/src/tools/__tests__/ToolNode.langfuse.test.ts +21 -11
- package/src/types/graph.ts +9 -0
- package/src/utils/misc.ts +18 -0
|
@@ -1,50 +1,109 @@
|
|
|
1
1
|
import { HumanMessage } from '@langchain/core/messages';
|
|
2
2
|
import { CallbackManager } from '@langchain/core/callbacks/manager';
|
|
3
|
+
import { context as otelContext, trace as otelTrace } from '@opentelemetry/api';
|
|
3
4
|
import type * as t from '@/types';
|
|
5
|
+
import { traceIdFromSeed } from '@/langfuseRuntimeContext';
|
|
4
6
|
import { Providers } from '@/common';
|
|
5
7
|
import { Run } from '@/run';
|
|
6
8
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
const mockProcessorStarts: Array<{
|
|
10
|
+
params: unknown;
|
|
11
|
+
traceId: string;
|
|
12
|
+
}> = [];
|
|
13
|
+
const mockSpanAttributeSets: Array<Record<string, unknown>> = [];
|
|
14
|
+
let mockProviderInput:
|
|
15
|
+
| {
|
|
16
|
+
spanProcessors?: Array<{
|
|
17
|
+
onStart?: (span: unknown, parentContext: unknown) => void;
|
|
18
|
+
onEnd?: (span: unknown) => void;
|
|
19
|
+
}>;
|
|
20
|
+
idGenerator?: {
|
|
21
|
+
generateTraceId: () => string;
|
|
22
|
+
generateSpanId: () => string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
| undefined;
|
|
26
|
+
|
|
27
|
+
const createMockSpan = (traceIdOverride?: string) => {
|
|
28
|
+
const traceId =
|
|
29
|
+
traceIdOverride ??
|
|
30
|
+
mockProviderInput?.idGenerator?.generateTraceId() ??
|
|
31
|
+
'trace-id';
|
|
32
|
+
const spanId = mockProviderInput?.idGenerator?.generateSpanId() ?? 'span-id';
|
|
33
|
+
const span = {
|
|
34
|
+
end: jest.fn(() => {
|
|
35
|
+
for (const processor of mockProviderInput?.spanProcessors ?? []) {
|
|
36
|
+
processor.onEnd?.(span);
|
|
37
|
+
}
|
|
38
|
+
}),
|
|
39
|
+
spanContext: jest.fn(() => ({
|
|
40
|
+
traceId,
|
|
41
|
+
spanId,
|
|
42
|
+
traceFlags: 1,
|
|
43
|
+
})),
|
|
44
|
+
setAttributes: jest.fn((attributes: Record<string, unknown>) => {
|
|
45
|
+
mockSpanAttributeSets.push(attributes);
|
|
46
|
+
}),
|
|
47
|
+
setStatus: jest.fn(),
|
|
48
|
+
attributes: {},
|
|
49
|
+
};
|
|
50
|
+
for (const processor of mockProviderInput?.spanProcessors ?? []) {
|
|
51
|
+
processor.onStart?.(span, otelContext.active());
|
|
52
|
+
}
|
|
53
|
+
return span;
|
|
16
54
|
};
|
|
17
|
-
|
|
55
|
+
|
|
56
|
+
const mockStartSpan = jest.fn(() => createMockSpan());
|
|
18
57
|
const mockStartActiveSpan = jest.fn(
|
|
19
58
|
(
|
|
20
59
|
_name: string,
|
|
21
60
|
_options: unknown,
|
|
22
|
-
|
|
23
|
-
callback: (span: typeof
|
|
24
|
-
) =>
|
|
61
|
+
activeContext: Parameters<typeof otelTrace.getSpanContext>[0],
|
|
62
|
+
callback: (span: ReturnType<typeof createMockSpan>) => unknown
|
|
63
|
+
) =>
|
|
64
|
+
callback(createMockSpan(otelTrace.getSpanContext(activeContext)?.traceId))
|
|
25
65
|
);
|
|
26
66
|
const mockForceFlush = jest.fn();
|
|
27
67
|
const mockShutdown = jest.fn();
|
|
28
68
|
|
|
29
69
|
jest.mock('@langfuse/otel', () => ({
|
|
30
|
-
LangfuseSpanProcessor: jest.fn().mockImplementation(() => ({
|
|
70
|
+
LangfuseSpanProcessor: jest.fn().mockImplementation((params) => ({
|
|
71
|
+
forceFlush: jest.fn(),
|
|
72
|
+
onEnd: jest.fn(),
|
|
73
|
+
onStart: jest.fn((span) => {
|
|
74
|
+
mockProcessorStarts.push({
|
|
75
|
+
params,
|
|
76
|
+
traceId: span.spanContext().traceId,
|
|
77
|
+
});
|
|
78
|
+
}),
|
|
79
|
+
shutdown: jest.fn(),
|
|
80
|
+
})),
|
|
31
81
|
isDefaultExportSpan: jest.fn(() => false),
|
|
32
82
|
}));
|
|
33
83
|
|
|
34
84
|
jest.mock('@opentelemetry/sdk-trace-base', () => ({
|
|
35
|
-
BasicTracerProvider: jest.fn().mockImplementation(() =>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
85
|
+
BasicTracerProvider: jest.fn().mockImplementation((input) => {
|
|
86
|
+
mockProviderInput = input;
|
|
87
|
+
return {
|
|
88
|
+
forceFlush: mockForceFlush,
|
|
89
|
+
getTracer: jest.fn(() => ({
|
|
90
|
+
startActiveSpan: mockStartActiveSpan,
|
|
91
|
+
startSpan: mockStartSpan,
|
|
92
|
+
})),
|
|
93
|
+
shutdown: mockShutdown,
|
|
94
|
+
};
|
|
95
|
+
}),
|
|
43
96
|
}));
|
|
44
97
|
|
|
45
98
|
describe('Langfuse callback composition', () => {
|
|
46
99
|
beforeEach(() => {
|
|
47
100
|
jest.clearAllMocks();
|
|
101
|
+
mockProcessorStarts.length = 0;
|
|
102
|
+
mockSpanAttributeSets.length = 0;
|
|
103
|
+
delete process.env.LANGFUSE_PUBLIC_KEY;
|
|
104
|
+
delete process.env.LANGFUSE_SECRET_KEY;
|
|
105
|
+
delete process.env.LANGFUSE_BASE_URL;
|
|
106
|
+
delete process.env.LANGFUSE_BASEURL;
|
|
48
107
|
delete process.env.LANGFUSE_FORCE_FLUSH_ON_DISPOSE;
|
|
49
108
|
});
|
|
50
109
|
|
|
@@ -183,6 +242,203 @@ describe('Langfuse callback composition', () => {
|
|
|
183
242
|
);
|
|
184
243
|
});
|
|
185
244
|
|
|
245
|
+
it('binds handler callback spans to their own Langfuse config and trace seed', async () => {
|
|
246
|
+
const { createLangfuseHandler } = await import('@/langfuse');
|
|
247
|
+
const { initializeLangfuseTracing } = await import('@/instrumentation');
|
|
248
|
+
const tenantA = {
|
|
249
|
+
publicKey: 'pk-tenant-a',
|
|
250
|
+
secretKey: 'sk-tenant-a',
|
|
251
|
+
baseUrl: 'https://langfuse.proxy',
|
|
252
|
+
deterministicTraceId: true,
|
|
253
|
+
};
|
|
254
|
+
const tenantB = {
|
|
255
|
+
publicKey: 'pk-tenant-b',
|
|
256
|
+
secretKey: 'sk-tenant-b',
|
|
257
|
+
baseUrl: 'https://langfuse.proxy',
|
|
258
|
+
deterministicTraceId: true,
|
|
259
|
+
};
|
|
260
|
+
initializeLangfuseTracing(tenantA);
|
|
261
|
+
initializeLangfuseTracing(tenantB);
|
|
262
|
+
|
|
263
|
+
const handlerA = createLangfuseHandler({
|
|
264
|
+
langfuse: tenantA,
|
|
265
|
+
traceIdSeed: 'run-tenant-a',
|
|
266
|
+
});
|
|
267
|
+
const handlerB = createLangfuseHandler({
|
|
268
|
+
langfuse: tenantB,
|
|
269
|
+
traceIdSeed: 'run-tenant-b',
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
await Promise.all([
|
|
273
|
+
handlerA?.handleChainStart(
|
|
274
|
+
{ lc: 1, type: 'not_implemented', id: ['TenantAChain'] },
|
|
275
|
+
{ input: 'tenant a' },
|
|
276
|
+
'lc-run-a'
|
|
277
|
+
),
|
|
278
|
+
handlerB?.handleChainStart(
|
|
279
|
+
{ lc: 1, type: 'not_implemented', id: ['TenantBChain'] },
|
|
280
|
+
{ input: 'tenant b' },
|
|
281
|
+
'lc-run-b'
|
|
282
|
+
),
|
|
283
|
+
]);
|
|
284
|
+
|
|
285
|
+
expect(mockProcessorStarts).toEqual(
|
|
286
|
+
expect.arrayContaining([
|
|
287
|
+
expect.objectContaining({
|
|
288
|
+
params: expect.objectContaining({
|
|
289
|
+
publicKey: 'pk-tenant-a',
|
|
290
|
+
secretKey: 'sk-tenant-a',
|
|
291
|
+
baseUrl: 'https://langfuse.proxy',
|
|
292
|
+
}),
|
|
293
|
+
traceId: traceIdFromSeed('run-tenant-a'),
|
|
294
|
+
}),
|
|
295
|
+
expect.objectContaining({
|
|
296
|
+
params: expect.objectContaining({
|
|
297
|
+
publicKey: 'pk-tenant-b',
|
|
298
|
+
secretKey: 'sk-tenant-b',
|
|
299
|
+
baseUrl: 'https://langfuse.proxy',
|
|
300
|
+
}),
|
|
301
|
+
traceId: traceIdFromSeed('run-tenant-b'),
|
|
302
|
+
}),
|
|
303
|
+
])
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('preserves an active agent Langfuse runtime scope for callback-created spans', async () => {
|
|
308
|
+
const { createLangfuseHandler } = await import('@/langfuse');
|
|
309
|
+
const { initializeLangfuseTracing } = await import('@/instrumentation');
|
|
310
|
+
const { withLangfuseRuntimeScope } = await import('@/langfuseRuntimeScope');
|
|
311
|
+
const runLangfuse = {
|
|
312
|
+
publicKey: 'pk-run',
|
|
313
|
+
secretKey: 'sk-run',
|
|
314
|
+
baseUrl: 'https://langfuse.run',
|
|
315
|
+
deterministicTraceId: true,
|
|
316
|
+
};
|
|
317
|
+
const agentLangfuse = {
|
|
318
|
+
publicKey: 'pk-agent',
|
|
319
|
+
secretKey: 'sk-agent',
|
|
320
|
+
baseUrl: 'https://langfuse.agent',
|
|
321
|
+
deterministicTraceId: true,
|
|
322
|
+
};
|
|
323
|
+
initializeLangfuseTracing(runLangfuse);
|
|
324
|
+
initializeLangfuseTracing(agentLangfuse);
|
|
325
|
+
const streamHandler = createLangfuseHandler({
|
|
326
|
+
langfuse: runLangfuse,
|
|
327
|
+
traceIdSeed: 'run-seed',
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
await withLangfuseRuntimeScope(
|
|
331
|
+
{ langfuse: agentLangfuse, traceIdSeed: 'agent-seed' },
|
|
332
|
+
() =>
|
|
333
|
+
streamHandler?.handleChainStart(
|
|
334
|
+
{ lc: 1, type: 'not_implemented', id: ['AgentScopedChain'] },
|
|
335
|
+
{ input: 'agent scoped' },
|
|
336
|
+
'lc-agent-run'
|
|
337
|
+
)
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
expect(mockProcessorStarts).toContainEqual(
|
|
341
|
+
expect.objectContaining({
|
|
342
|
+
params: expect.objectContaining({
|
|
343
|
+
publicKey: 'pk-agent',
|
|
344
|
+
secretKey: 'sk-agent',
|
|
345
|
+
baseUrl: 'https://langfuse.agent',
|
|
346
|
+
}),
|
|
347
|
+
traceId: traceIdFromSeed('agent-seed'),
|
|
348
|
+
})
|
|
349
|
+
);
|
|
350
|
+
expect(mockProcessorStarts).not.toContainEqual(
|
|
351
|
+
expect.objectContaining({
|
|
352
|
+
params: expect.objectContaining({
|
|
353
|
+
publicKey: 'pk-run',
|
|
354
|
+
secretKey: 'sk-run',
|
|
355
|
+
baseUrl: 'https://langfuse.run',
|
|
356
|
+
}),
|
|
357
|
+
traceId: traceIdFromSeed('run-seed'),
|
|
358
|
+
})
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('attaches configured trace attributes to Langfuse callback spans', async () => {
|
|
363
|
+
const { createLangfuseHandler } = await import('@/langfuse');
|
|
364
|
+
const { initializeLangfuseTracing } = await import('@/instrumentation');
|
|
365
|
+
const langfuse = {
|
|
366
|
+
publicKey: 'pk-tenant-a',
|
|
367
|
+
secretKey: 'sk-tenant-a',
|
|
368
|
+
baseUrl: 'https://langfuse.proxy',
|
|
369
|
+
librechatTraceAttributes: {
|
|
370
|
+
'librechat.langfuse.tenant_export.enabled': 'true',
|
|
371
|
+
'librechat.langfuse.destination': 'eu',
|
|
372
|
+
ignored: '',
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
initializeLangfuseTracing(langfuse);
|
|
376
|
+
const handler = createLangfuseHandler({ langfuse });
|
|
377
|
+
|
|
378
|
+
await handler?.handleChainStart(
|
|
379
|
+
{ lc: 1, type: 'not_implemented', id: ['TenantAChain'] },
|
|
380
|
+
{ input: 'tenant a' },
|
|
381
|
+
'lc-run-a'
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
expect(mockSpanAttributeSets).toContainEqual({
|
|
385
|
+
'librechat.langfuse.tenant_export.enabled': 'true',
|
|
386
|
+
'librechat.langfuse.destination': 'eu',
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('uses deterministic trace ids when tracing is configured from env only', async () => {
|
|
391
|
+
process.env.LANGFUSE_PUBLIC_KEY = 'pk-env';
|
|
392
|
+
process.env.LANGFUSE_SECRET_KEY = 'sk-env';
|
|
393
|
+
process.env.LANGFUSE_BASE_URL = 'https://langfuse.env';
|
|
394
|
+
|
|
395
|
+
const runId = 'test-langfuse-env-deterministic-run';
|
|
396
|
+
const run = await Run.create<t.IState>({
|
|
397
|
+
runId,
|
|
398
|
+
graphConfig: {
|
|
399
|
+
type: 'standard',
|
|
400
|
+
agents: [
|
|
401
|
+
{
|
|
402
|
+
agentId: 'agent_abc123',
|
|
403
|
+
name: 'DWAINE',
|
|
404
|
+
provider: Providers.OPENAI,
|
|
405
|
+
clientOptions: { model: 'gpt-4' },
|
|
406
|
+
tools: [],
|
|
407
|
+
},
|
|
408
|
+
],
|
|
409
|
+
},
|
|
410
|
+
langfuse: {
|
|
411
|
+
deterministicTraceId: true,
|
|
412
|
+
metadata: { 'librechat.tenant.id': 'tenant-env' },
|
|
413
|
+
tags: ['tenant:tenant-env'],
|
|
414
|
+
},
|
|
415
|
+
skipCleanup: true,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
run.Graph?.overrideTestModel(['hello']);
|
|
419
|
+
|
|
420
|
+
await run.processStream(
|
|
421
|
+
{ messages: [new HumanMessage('hello')] },
|
|
422
|
+
{
|
|
423
|
+
configurable: { thread_id: 'thread-1', user_id: 'user-1' },
|
|
424
|
+
version: 'v2' as const,
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
expect(mockProcessorStarts).toEqual(
|
|
429
|
+
expect.arrayContaining([
|
|
430
|
+
expect.objectContaining({
|
|
431
|
+
params: expect.objectContaining({
|
|
432
|
+
publicKey: 'pk-env',
|
|
433
|
+
secretKey: 'sk-env',
|
|
434
|
+
baseUrl: 'https://langfuse.env',
|
|
435
|
+
}),
|
|
436
|
+
traceId: traceIdFromSeed(runId),
|
|
437
|
+
}),
|
|
438
|
+
])
|
|
439
|
+
);
|
|
440
|
+
});
|
|
441
|
+
|
|
186
442
|
it('adds current agent metadata when a stream Langfuse callback already exists', async () => {
|
|
187
443
|
const metadataSpy = jest.fn();
|
|
188
444
|
const { createLangfuseHandler } = await import('@/langfuse');
|
|
@@ -9,9 +9,16 @@ import {
|
|
|
9
9
|
|
|
10
10
|
const mockForceFlush = jest.fn();
|
|
11
11
|
|
|
12
|
-
jest.mock('@langfuse/langchain', () =>
|
|
13
|
-
CallbackHandler
|
|
14
|
-
}
|
|
12
|
+
jest.mock('@langfuse/langchain', () => {
|
|
13
|
+
const CallbackHandler = jest.fn(function (
|
|
14
|
+
this: { params?: unknown; name?: string },
|
|
15
|
+
params: unknown
|
|
16
|
+
) {
|
|
17
|
+
this.params = params;
|
|
18
|
+
this.name = 'LangfuseCallbackHandler';
|
|
19
|
+
});
|
|
20
|
+
return { CallbackHandler };
|
|
21
|
+
});
|
|
15
22
|
|
|
16
23
|
jest.mock('@langfuse/tracing', () => ({
|
|
17
24
|
getLangfuseTracerProvider: jest.fn(() => ({
|
|
@@ -33,16 +33,17 @@ type BasicTracerProviderInput = {
|
|
|
33
33
|
shutdown?: unknown;
|
|
34
34
|
}>;
|
|
35
35
|
};
|
|
36
|
-
type RoutingSpanProcessorForTest =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
type RoutingSpanProcessorForTest =
|
|
37
|
+
BasicTracerProviderInput['spanProcessors'][0] & {
|
|
38
|
+
processors: Map<
|
|
39
|
+
string,
|
|
40
|
+
{
|
|
41
|
+
fallbackConfig?: {
|
|
42
|
+
enabled?: boolean;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
>;
|
|
46
|
+
};
|
|
46
47
|
const mockBasicTracerProvider = jest.fn(
|
|
47
48
|
(_input?: BasicTracerProviderInput) => mockTracerProvider
|
|
48
49
|
);
|
|
@@ -217,6 +218,25 @@ describe('Langfuse instrumentation', () => {
|
|
|
217
218
|
expect(mockSetLangfuseTracerProvider).toHaveBeenCalledTimes(1);
|
|
218
219
|
});
|
|
219
220
|
|
|
221
|
+
it('does not store raw secret keys in processor cache keys', async () => {
|
|
222
|
+
const { initializeLangfuseTracing } = await import('@/instrumentation');
|
|
223
|
+
initializeLangfuseTracing({
|
|
224
|
+
publicKey: 'pk-cache',
|
|
225
|
+
secretKey: 'sk-cache-secret',
|
|
226
|
+
baseUrl: 'https://langfuse.cache',
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const providerInput = mockBasicTracerProvider.mock
|
|
230
|
+
.calls[0][0] as BasicTracerProviderInput;
|
|
231
|
+
const routingProcessor = providerInput
|
|
232
|
+
.spanProcessors[0] as RoutingSpanProcessorForTest;
|
|
233
|
+
const processorKeys = Array.from(routingProcessor.processors.keys());
|
|
234
|
+
|
|
235
|
+
expect(processorKeys).toHaveLength(1);
|
|
236
|
+
expect(processorKeys[0]).toContain('secretKeyHash');
|
|
237
|
+
expect(processorKeys[0]).not.toContain('sk-cache-secret');
|
|
238
|
+
});
|
|
239
|
+
|
|
220
240
|
it('passes explicit redaction config into the redacting processor fallback', async () => {
|
|
221
241
|
const { initializeLangfuseTracing } = await import('@/instrumentation');
|
|
222
242
|
initializeLangfuseTracing({
|
|
@@ -228,8 +248,8 @@ describe('Langfuse instrumentation', () => {
|
|
|
228
248
|
|
|
229
249
|
const providerInput = mockBasicTracerProvider.mock
|
|
230
250
|
.calls[0][0] as BasicTracerProviderInput;
|
|
231
|
-
const routingProcessor =
|
|
232
|
-
|
|
251
|
+
const routingProcessor = providerInput
|
|
252
|
+
.spanProcessors[0] as RoutingSpanProcessorForTest;
|
|
233
253
|
const childProcessors = Array.from(routingProcessor.processors.values());
|
|
234
254
|
expect(childProcessors[0]?.fallbackConfig).toMatchObject({
|
|
235
255
|
enabled: false,
|