@mastra/langfuse 0.0.0-vector-query-tool-provider-options-20250828222356 → 0.0.0-vnext-20251104230439
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/CHANGELOG.md +390 -3
- package/README.md +3 -7
- package/dist/ai-tracing.d.ts +41 -8
- package/dist/ai-tracing.d.ts.map +1 -1
- package/dist/index.cjs +218 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +218 -35
- package/dist/index.js.map +1 -1
- package/package.json +21 -7
- package/eslint.config.js +0 -6
- package/src/ai-tracing.test.ts +0 -663
- package/src/ai-tracing.ts +0 -198
- package/src/index.ts +0 -9
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -5
- package/tsup.config.ts +0 -17
- package/vitest.config.ts +0 -11
package/src/ai-tracing.test.ts
DELETED
|
@@ -1,663 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Langfuse Exporter Tests
|
|
3
|
-
*
|
|
4
|
-
* These tests focus on Langfuse-specific functionality:
|
|
5
|
-
* - Langfuse client interactions
|
|
6
|
-
* - Mapping logic (spans -> traces/generations/spans)
|
|
7
|
-
* - Type-specific metadata extraction
|
|
8
|
-
* - Langfuse-specific error handling
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { AITracingEvent, AnyAISpan, LLMGenerationAttributes, ToolCallAttributes } from '@mastra/core/ai-tracing';
|
|
12
|
-
import { AISpanType, AITracingEventType } from '@mastra/core/ai-tracing';
|
|
13
|
-
import { Langfuse } from 'langfuse';
|
|
14
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
15
|
-
import { LangfuseExporter } from './ai-tracing';
|
|
16
|
-
import type { LangfuseExporterConfig } from './ai-tracing';
|
|
17
|
-
|
|
18
|
-
// Mock Langfuse constructor (must be at the top level)
|
|
19
|
-
vi.mock('langfuse');
|
|
20
|
-
|
|
21
|
-
describe('LangfuseExporter', () => {
|
|
22
|
-
// Mock objects
|
|
23
|
-
let mockGeneration: any;
|
|
24
|
-
let mockSpan: any;
|
|
25
|
-
let mockTrace: any;
|
|
26
|
-
let mockLangfuseClient: any;
|
|
27
|
-
let LangfuseMock: any;
|
|
28
|
-
|
|
29
|
-
let exporter: LangfuseExporter;
|
|
30
|
-
let config: LangfuseExporterConfig;
|
|
31
|
-
|
|
32
|
-
beforeEach(() => {
|
|
33
|
-
vi.clearAllMocks();
|
|
34
|
-
|
|
35
|
-
// Set up mocks
|
|
36
|
-
mockGeneration = {
|
|
37
|
-
update: vi.fn(),
|
|
38
|
-
end: vi.fn(),
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
mockSpan = {
|
|
42
|
-
update: vi.fn(),
|
|
43
|
-
end: vi.fn(),
|
|
44
|
-
generation: vi.fn().mockReturnValue(mockGeneration),
|
|
45
|
-
span: vi.fn(),
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
mockTrace = {
|
|
49
|
-
generation: vi.fn().mockReturnValue(mockGeneration),
|
|
50
|
-
span: vi.fn().mockReturnValue(mockSpan),
|
|
51
|
-
update: vi.fn(),
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// Set up circular reference
|
|
55
|
-
mockSpan.span.mockReturnValue(mockSpan);
|
|
56
|
-
|
|
57
|
-
mockLangfuseClient = {
|
|
58
|
-
trace: vi.fn().mockReturnValue(mockTrace),
|
|
59
|
-
shutdownAsync: vi.fn().mockResolvedValue(undefined),
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// Get the mocked Langfuse constructor and configure it
|
|
63
|
-
LangfuseMock = vi.mocked(Langfuse);
|
|
64
|
-
LangfuseMock.mockImplementation(() => mockLangfuseClient);
|
|
65
|
-
|
|
66
|
-
config = {
|
|
67
|
-
publicKey: 'test-public-key',
|
|
68
|
-
secretKey: 'test-secret-key',
|
|
69
|
-
baseUrl: 'https://test-langfuse.com',
|
|
70
|
-
options: {
|
|
71
|
-
debug: false,
|
|
72
|
-
flushAt: 1,
|
|
73
|
-
flushInterval: 1000,
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
exporter = new LangfuseExporter(config);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
describe('Initialization', () => {
|
|
81
|
-
it('should initialize with correct configuration', () => {
|
|
82
|
-
expect(exporter.name).toBe('langfuse');
|
|
83
|
-
// Verify Langfuse client was created with correct config
|
|
84
|
-
expect(LangfuseMock).toHaveBeenCalledWith({
|
|
85
|
-
publicKey: 'test-public-key',
|
|
86
|
-
secretKey: 'test-secret-key',
|
|
87
|
-
baseUrl: 'https://test-langfuse.com',
|
|
88
|
-
debug: false,
|
|
89
|
-
flushAt: 1,
|
|
90
|
-
flushInterval: 1000,
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('Trace Creation', () => {
|
|
96
|
-
it('should create Langfuse trace for root spans', async () => {
|
|
97
|
-
const rootSpan = createMockSpan({
|
|
98
|
-
id: 'root-span-id',
|
|
99
|
-
name: 'root-agent',
|
|
100
|
-
type: AISpanType.AGENT_RUN,
|
|
101
|
-
isRoot: true,
|
|
102
|
-
attributes: {
|
|
103
|
-
agentId: 'agent-123',
|
|
104
|
-
instructions: 'Test agent',
|
|
105
|
-
spanType: 'agent_run',
|
|
106
|
-
},
|
|
107
|
-
metadata: { userId: 'user-456', sessionId: 'session-789' },
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const event: AITracingEvent = {
|
|
111
|
-
type: AITracingEventType.SPAN_STARTED,
|
|
112
|
-
span: rootSpan,
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
await exporter.exportEvent(event);
|
|
116
|
-
|
|
117
|
-
// Should create Langfuse trace with correct parameters
|
|
118
|
-
expect(mockLangfuseClient.trace).toHaveBeenCalledWith({
|
|
119
|
-
id: 'root-span-id', // Uses span.trace.id
|
|
120
|
-
name: 'root-agent',
|
|
121
|
-
userId: 'user-456',
|
|
122
|
-
sessionId: 'session-789',
|
|
123
|
-
metadata: {
|
|
124
|
-
agentId: 'agent-123',
|
|
125
|
-
instructions: 'Test agent',
|
|
126
|
-
spanType: 'agent_run',
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('should not create trace for child spans', async () => {
|
|
132
|
-
const childSpan = createMockSpan({
|
|
133
|
-
id: 'child-span-id',
|
|
134
|
-
name: 'child-tool',
|
|
135
|
-
type: AISpanType.TOOL_CALL,
|
|
136
|
-
isRoot: false,
|
|
137
|
-
attributes: { toolId: 'calculator' },
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
const event: AITracingEvent = {
|
|
141
|
-
type: AITracingEventType.SPAN_STARTED,
|
|
142
|
-
span: childSpan,
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
await exporter.exportEvent(event);
|
|
146
|
-
|
|
147
|
-
// Should not create trace for child spans
|
|
148
|
-
expect(mockLangfuseClient.trace).not.toHaveBeenCalled();
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
describe('LLM Generation Mapping', () => {
|
|
153
|
-
it('should create Langfuse generation for LLM_GENERATION spans', async () => {
|
|
154
|
-
const llmSpan = createMockSpan({
|
|
155
|
-
id: 'llm-span-id',
|
|
156
|
-
name: 'gpt-4-call',
|
|
157
|
-
type: AISpanType.LLM_GENERATION,
|
|
158
|
-
isRoot: true,
|
|
159
|
-
input: { messages: [{ role: 'user', content: 'Hello' }] },
|
|
160
|
-
output: { content: 'Hi there!' },
|
|
161
|
-
attributes: {
|
|
162
|
-
model: 'gpt-4',
|
|
163
|
-
provider: 'openai',
|
|
164
|
-
usage: {
|
|
165
|
-
promptTokens: 10,
|
|
166
|
-
completionTokens: 5,
|
|
167
|
-
totalTokens: 15,
|
|
168
|
-
},
|
|
169
|
-
parameters: {
|
|
170
|
-
temperature: 0.7,
|
|
171
|
-
maxTokens: 100,
|
|
172
|
-
topP: 0.9,
|
|
173
|
-
},
|
|
174
|
-
streaming: false,
|
|
175
|
-
resultType: 'response_generation',
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
const event: AITracingEvent = {
|
|
180
|
-
type: AITracingEventType.SPAN_STARTED,
|
|
181
|
-
span: llmSpan,
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
await exporter.exportEvent(event);
|
|
185
|
-
|
|
186
|
-
// Should create Langfuse generation with LLM-specific fields
|
|
187
|
-
expect(mockTrace.generation).toHaveBeenCalledWith({
|
|
188
|
-
id: 'llm-span-id',
|
|
189
|
-
name: 'gpt-4-call',
|
|
190
|
-
startTime: llmSpan.startTime,
|
|
191
|
-
model: 'gpt-4',
|
|
192
|
-
modelParameters: {
|
|
193
|
-
temperature: 0.7,
|
|
194
|
-
maxTokens: 100,
|
|
195
|
-
topP: 0.9,
|
|
196
|
-
},
|
|
197
|
-
input: { messages: [{ role: 'user', content: 'Hello' }] },
|
|
198
|
-
output: { content: 'Hi there!' },
|
|
199
|
-
usage: {
|
|
200
|
-
promptTokens: 10,
|
|
201
|
-
completionTokens: 5,
|
|
202
|
-
totalTokens: 15,
|
|
203
|
-
},
|
|
204
|
-
metadata: {
|
|
205
|
-
provider: 'openai',
|
|
206
|
-
resultType: 'response_generation',
|
|
207
|
-
spanType: 'llm_generation',
|
|
208
|
-
streaming: false,
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('should handle LLM spans without optional fields', async () => {
|
|
214
|
-
const minimalLlmSpan = createMockSpan({
|
|
215
|
-
id: 'minimal-llm',
|
|
216
|
-
name: 'simple-llm',
|
|
217
|
-
type: AISpanType.LLM_GENERATION,
|
|
218
|
-
isRoot: true,
|
|
219
|
-
attributes: {
|
|
220
|
-
model: 'gpt-3.5-turbo',
|
|
221
|
-
// No usage, parameters, input, output, etc.
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
const event: AITracingEvent = {
|
|
226
|
-
type: AITracingEventType.SPAN_STARTED,
|
|
227
|
-
span: minimalLlmSpan,
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
await exporter.exportEvent(event);
|
|
231
|
-
|
|
232
|
-
expect(mockTrace.generation).toHaveBeenCalledWith({
|
|
233
|
-
id: 'minimal-llm',
|
|
234
|
-
name: 'simple-llm',
|
|
235
|
-
startTime: minimalLlmSpan.startTime,
|
|
236
|
-
model: 'gpt-3.5-turbo',
|
|
237
|
-
metadata: {
|
|
238
|
-
spanType: 'llm_generation',
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
describe('Regular Span Mapping', () => {
|
|
245
|
-
it('should create Langfuse span for non-LLM span types', async () => {
|
|
246
|
-
const toolSpan = createMockSpan({
|
|
247
|
-
id: 'tool-span-id',
|
|
248
|
-
name: 'calculator-tool',
|
|
249
|
-
type: AISpanType.TOOL_CALL,
|
|
250
|
-
isRoot: true,
|
|
251
|
-
input: { operation: 'add', a: 2, b: 3 },
|
|
252
|
-
output: { result: 5 },
|
|
253
|
-
attributes: {
|
|
254
|
-
toolId: 'calculator',
|
|
255
|
-
success: true,
|
|
256
|
-
},
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
const event: AITracingEvent = {
|
|
260
|
-
type: AITracingEventType.SPAN_STARTED,
|
|
261
|
-
span: toolSpan,
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
await exporter.exportEvent(event);
|
|
265
|
-
|
|
266
|
-
expect(mockTrace.span).toHaveBeenCalledWith({
|
|
267
|
-
id: 'tool-span-id',
|
|
268
|
-
name: 'calculator-tool',
|
|
269
|
-
startTime: toolSpan.startTime,
|
|
270
|
-
input: { operation: 'add', a: 2, b: 3 },
|
|
271
|
-
output: { result: 5 },
|
|
272
|
-
metadata: {
|
|
273
|
-
spanType: 'tool_call',
|
|
274
|
-
toolId: 'calculator',
|
|
275
|
-
success: true,
|
|
276
|
-
},
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
describe('Type-Specific Metadata Extraction', () => {
|
|
282
|
-
it('should extract agent-specific metadata', async () => {
|
|
283
|
-
const agentSpan = createMockSpan({
|
|
284
|
-
id: 'agent-span',
|
|
285
|
-
name: 'customer-agent',
|
|
286
|
-
type: AISpanType.AGENT_RUN,
|
|
287
|
-
isRoot: true,
|
|
288
|
-
attributes: {
|
|
289
|
-
agentId: 'agent-456',
|
|
290
|
-
availableTools: ['search', 'calculator'],
|
|
291
|
-
maxSteps: 10,
|
|
292
|
-
currentStep: 3,
|
|
293
|
-
instructions: 'Help customers',
|
|
294
|
-
},
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
const event: AITracingEvent = {
|
|
298
|
-
type: AITracingEventType.SPAN_STARTED,
|
|
299
|
-
span: agentSpan,
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
await exporter.exportEvent(event);
|
|
303
|
-
|
|
304
|
-
expect(mockTrace.span).toHaveBeenCalledWith(
|
|
305
|
-
expect.objectContaining({
|
|
306
|
-
metadata: expect.objectContaining({
|
|
307
|
-
spanType: 'agent_run',
|
|
308
|
-
agentId: 'agent-456',
|
|
309
|
-
availableTools: ['search', 'calculator'],
|
|
310
|
-
maxSteps: 10,
|
|
311
|
-
currentStep: 3,
|
|
312
|
-
}),
|
|
313
|
-
}),
|
|
314
|
-
);
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it('should extract MCP tool-specific metadata', async () => {
|
|
318
|
-
const mcpSpan = createMockSpan({
|
|
319
|
-
id: 'mcp-span',
|
|
320
|
-
name: 'mcp-tool-call',
|
|
321
|
-
type: AISpanType.MCP_TOOL_CALL,
|
|
322
|
-
isRoot: true,
|
|
323
|
-
attributes: {
|
|
324
|
-
toolId: 'file-reader',
|
|
325
|
-
mcpServer: 'filesystem-mcp',
|
|
326
|
-
serverVersion: '1.0.0',
|
|
327
|
-
success: true,
|
|
328
|
-
},
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
const event: AITracingEvent = {
|
|
332
|
-
type: AITracingEventType.SPAN_STARTED,
|
|
333
|
-
span: mcpSpan,
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
await exporter.exportEvent(event);
|
|
337
|
-
|
|
338
|
-
expect(mockTrace.span).toHaveBeenCalledWith(
|
|
339
|
-
expect.objectContaining({
|
|
340
|
-
metadata: expect.objectContaining({
|
|
341
|
-
spanType: 'mcp_tool_call',
|
|
342
|
-
toolId: 'file-reader',
|
|
343
|
-
mcpServer: 'filesystem-mcp',
|
|
344
|
-
serverVersion: '1.0.0',
|
|
345
|
-
success: true,
|
|
346
|
-
}),
|
|
347
|
-
}),
|
|
348
|
-
);
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it('should extract workflow-specific metadata', async () => {
|
|
352
|
-
const workflowSpan = createMockSpan({
|
|
353
|
-
id: 'workflow-span',
|
|
354
|
-
name: 'data-processing-workflow',
|
|
355
|
-
type: AISpanType.WORKFLOW_RUN,
|
|
356
|
-
isRoot: true,
|
|
357
|
-
attributes: {
|
|
358
|
-
workflowId: 'wf-123',
|
|
359
|
-
status: 'running',
|
|
360
|
-
},
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
const event: AITracingEvent = {
|
|
364
|
-
type: AITracingEventType.SPAN_STARTED,
|
|
365
|
-
span: workflowSpan,
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
await exporter.exportEvent(event);
|
|
369
|
-
|
|
370
|
-
expect(mockTrace.span).toHaveBeenCalledWith(
|
|
371
|
-
expect.objectContaining({
|
|
372
|
-
metadata: expect.objectContaining({
|
|
373
|
-
spanType: 'workflow_run',
|
|
374
|
-
workflowId: 'wf-123',
|
|
375
|
-
status: 'running',
|
|
376
|
-
}),
|
|
377
|
-
}),
|
|
378
|
-
);
|
|
379
|
-
});
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
describe('Span Updates', () => {
|
|
383
|
-
it('should update LLM generation with new data', async () => {
|
|
384
|
-
// First, start a span
|
|
385
|
-
const llmSpan = createMockSpan({
|
|
386
|
-
id: 'llm-span',
|
|
387
|
-
name: 'gpt-4-call',
|
|
388
|
-
type: AISpanType.LLM_GENERATION,
|
|
389
|
-
isRoot: true,
|
|
390
|
-
attributes: { model: 'gpt-4' },
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
await exporter.exportEvent({
|
|
394
|
-
type: AITracingEventType.SPAN_STARTED,
|
|
395
|
-
span: llmSpan,
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
// Then update it
|
|
399
|
-
llmSpan.attributes = {
|
|
400
|
-
...llmSpan.attributes,
|
|
401
|
-
usage: { totalTokens: 150 },
|
|
402
|
-
} as LLMGenerationAttributes;
|
|
403
|
-
llmSpan.output = { content: 'Updated response' };
|
|
404
|
-
|
|
405
|
-
await exporter.exportEvent({
|
|
406
|
-
type: AITracingEventType.SPAN_UPDATED,
|
|
407
|
-
span: llmSpan,
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
expect(mockGeneration.update).toHaveBeenCalledWith({
|
|
411
|
-
metadata: expect.objectContaining({
|
|
412
|
-
spanType: 'llm_generation',
|
|
413
|
-
}),
|
|
414
|
-
model: 'gpt-4',
|
|
415
|
-
output: { content: 'Updated response' },
|
|
416
|
-
usage: {
|
|
417
|
-
totalTokens: 150,
|
|
418
|
-
},
|
|
419
|
-
});
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
it('should update regular spans', async () => {
|
|
423
|
-
const toolSpan = createMockSpan({
|
|
424
|
-
id: 'tool-span',
|
|
425
|
-
name: 'calculator',
|
|
426
|
-
type: AISpanType.TOOL_CALL,
|
|
427
|
-
isRoot: true,
|
|
428
|
-
attributes: { toolId: 'calc', success: false },
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
await exporter.exportEvent({
|
|
432
|
-
type: AITracingEventType.SPAN_STARTED,
|
|
433
|
-
span: toolSpan,
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
// Update with success
|
|
437
|
-
toolSpan.attributes = {
|
|
438
|
-
...toolSpan.attributes,
|
|
439
|
-
success: true,
|
|
440
|
-
} as ToolCallAttributes;
|
|
441
|
-
toolSpan.output = { result: 42 };
|
|
442
|
-
|
|
443
|
-
await exporter.exportEvent({
|
|
444
|
-
type: AITracingEventType.SPAN_UPDATED,
|
|
445
|
-
span: toolSpan,
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
expect(mockSpan.update).toHaveBeenCalledWith({
|
|
449
|
-
metadata: expect.objectContaining({
|
|
450
|
-
spanType: 'tool_call',
|
|
451
|
-
success: true,
|
|
452
|
-
}),
|
|
453
|
-
output: { result: 42 },
|
|
454
|
-
});
|
|
455
|
-
});
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
describe('Span Ending', () => {
|
|
459
|
-
it('should end span with success status', async () => {
|
|
460
|
-
const span = createMockSpan({
|
|
461
|
-
id: 'test-span',
|
|
462
|
-
name: 'test',
|
|
463
|
-
type: AISpanType.GENERIC,
|
|
464
|
-
isRoot: true,
|
|
465
|
-
attributes: {},
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
await exporter.exportEvent({
|
|
469
|
-
type: AITracingEventType.SPAN_STARTED,
|
|
470
|
-
span,
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
span.endTime = new Date();
|
|
474
|
-
|
|
475
|
-
await exporter.exportEvent({
|
|
476
|
-
type: AITracingEventType.SPAN_ENDED,
|
|
477
|
-
span,
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
console.log('BOOP');
|
|
481
|
-
console.log(span.metadata);
|
|
482
|
-
|
|
483
|
-
expect(mockSpan.end).toHaveBeenCalledWith({
|
|
484
|
-
endTime: span.endTime,
|
|
485
|
-
metadata: expect.objectContaining({
|
|
486
|
-
spanType: 'generic',
|
|
487
|
-
}),
|
|
488
|
-
});
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
it('should end span with error status', async () => {
|
|
492
|
-
const errorSpan = createMockSpan({
|
|
493
|
-
id: 'error-span',
|
|
494
|
-
name: 'failing-operation',
|
|
495
|
-
type: AISpanType.TOOL_CALL,
|
|
496
|
-
isRoot: true,
|
|
497
|
-
attributes: {
|
|
498
|
-
toolId: 'failing-tool',
|
|
499
|
-
},
|
|
500
|
-
errorInfo: {
|
|
501
|
-
message: 'Tool execution failed',
|
|
502
|
-
id: 'TOOL_ERROR',
|
|
503
|
-
category: 'EXECUTION',
|
|
504
|
-
},
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
errorSpan.endTime = new Date();
|
|
508
|
-
|
|
509
|
-
await exporter.exportEvent({
|
|
510
|
-
type: AITracingEventType.SPAN_STARTED,
|
|
511
|
-
span: errorSpan,
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
await exporter.exportEvent({
|
|
515
|
-
type: AITracingEventType.SPAN_ENDED,
|
|
516
|
-
span: errorSpan,
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
expect(mockSpan.end).toHaveBeenCalledWith({
|
|
520
|
-
endTime: errorSpan.endTime,
|
|
521
|
-
metadata: expect.objectContaining({
|
|
522
|
-
spanType: 'tool_call',
|
|
523
|
-
toolId: 'failing-tool',
|
|
524
|
-
}),
|
|
525
|
-
level: 'ERROR',
|
|
526
|
-
statusMessage: 'Tool execution failed',
|
|
527
|
-
});
|
|
528
|
-
});
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
describe('Error Handling', () => {
|
|
532
|
-
it('should handle missing traces gracefully', async () => {
|
|
533
|
-
const orphanSpan = createMockSpan({
|
|
534
|
-
id: 'orphan-span',
|
|
535
|
-
name: 'orphan',
|
|
536
|
-
type: AISpanType.TOOL_CALL,
|
|
537
|
-
isRoot: false, // Child span without parent trace
|
|
538
|
-
attributes: { toolId: 'orphan-tool' },
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
// Should not throw when trying to create child span without trace
|
|
542
|
-
await expect(
|
|
543
|
-
exporter.exportEvent({
|
|
544
|
-
type: AITracingEventType.SPAN_STARTED,
|
|
545
|
-
span: orphanSpan,
|
|
546
|
-
}),
|
|
547
|
-
).resolves.not.toThrow();
|
|
548
|
-
|
|
549
|
-
// Should not create Langfuse span
|
|
550
|
-
expect(mockTrace.span).not.toHaveBeenCalled();
|
|
551
|
-
expect(mockTrace.generation).not.toHaveBeenCalled();
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
it('should handle missing Langfuse objects gracefully', async () => {
|
|
555
|
-
const span = createMockSpan({
|
|
556
|
-
id: 'missing-span',
|
|
557
|
-
name: 'missing',
|
|
558
|
-
type: AISpanType.GENERIC,
|
|
559
|
-
isRoot: true,
|
|
560
|
-
attributes: {},
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
// Try to update non-existent span
|
|
564
|
-
await expect(
|
|
565
|
-
exporter.exportEvent({
|
|
566
|
-
type: AITracingEventType.SPAN_UPDATED,
|
|
567
|
-
span,
|
|
568
|
-
}),
|
|
569
|
-
).resolves.not.toThrow();
|
|
570
|
-
|
|
571
|
-
// Try to end non-existent span
|
|
572
|
-
await expect(
|
|
573
|
-
exporter.exportEvent({
|
|
574
|
-
type: AITracingEventType.SPAN_ENDED,
|
|
575
|
-
span,
|
|
576
|
-
}),
|
|
577
|
-
).resolves.not.toThrow();
|
|
578
|
-
});
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
describe('Shutdown', () => {
|
|
582
|
-
it('should shutdown Langfuse client and clear maps', async () => {
|
|
583
|
-
// Add some data to internal maps
|
|
584
|
-
const span = createMockSpan({
|
|
585
|
-
id: 'test-span',
|
|
586
|
-
name: 'test',
|
|
587
|
-
type: AISpanType.GENERIC,
|
|
588
|
-
isRoot: true,
|
|
589
|
-
attributes: {},
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
await exporter.exportEvent({
|
|
593
|
-
type: AITracingEventType.SPAN_STARTED,
|
|
594
|
-
span,
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
// Verify maps have data
|
|
598
|
-
expect((exporter as any).traceMap.size).toBeGreaterThan(0);
|
|
599
|
-
expect((exporter as any).traceMap.get('test-span').spans.size).toBeGreaterThan(0);
|
|
600
|
-
|
|
601
|
-
// Shutdown
|
|
602
|
-
await exporter.shutdown();
|
|
603
|
-
|
|
604
|
-
// Verify Langfuse client shutdown was called
|
|
605
|
-
expect(mockLangfuseClient.shutdownAsync).toHaveBeenCalled();
|
|
606
|
-
|
|
607
|
-
// Verify maps were cleared
|
|
608
|
-
expect((exporter as any).traceMap.size).toBe(0);
|
|
609
|
-
});
|
|
610
|
-
});
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
// Helper function to create mock spans
|
|
614
|
-
function createMockSpan({
|
|
615
|
-
id,
|
|
616
|
-
name,
|
|
617
|
-
type,
|
|
618
|
-
isRoot,
|
|
619
|
-
attributes,
|
|
620
|
-
metadata,
|
|
621
|
-
input,
|
|
622
|
-
output,
|
|
623
|
-
errorInfo,
|
|
624
|
-
}: {
|
|
625
|
-
id: string;
|
|
626
|
-
name: string;
|
|
627
|
-
type: AISpanType;
|
|
628
|
-
isRoot: boolean;
|
|
629
|
-
attributes: any;
|
|
630
|
-
metadata?: Record<string, any>;
|
|
631
|
-
input?: any;
|
|
632
|
-
output?: any;
|
|
633
|
-
errorInfo?: any;
|
|
634
|
-
}): AnyAISpan {
|
|
635
|
-
const mockSpan = {
|
|
636
|
-
id,
|
|
637
|
-
name,
|
|
638
|
-
type,
|
|
639
|
-
attributes,
|
|
640
|
-
metadata,
|
|
641
|
-
input,
|
|
642
|
-
output,
|
|
643
|
-
errorInfo,
|
|
644
|
-
startTime: new Date(),
|
|
645
|
-
endTime: undefined,
|
|
646
|
-
traceId: isRoot ? id : 'parent-trace-id',
|
|
647
|
-
get isRootSpan() {
|
|
648
|
-
return isRoot;
|
|
649
|
-
},
|
|
650
|
-
trace: {
|
|
651
|
-
id: isRoot ? id : 'parent-trace-id',
|
|
652
|
-
traceId: isRoot ? id : 'parent-trace-id',
|
|
653
|
-
} as AnyAISpan,
|
|
654
|
-
parent: isRoot ? undefined : { id: 'parent-id' },
|
|
655
|
-
aiTracing: {} as any,
|
|
656
|
-
end: vi.fn(),
|
|
657
|
-
error: vi.fn(),
|
|
658
|
-
update: vi.fn(),
|
|
659
|
-
createChildSpan: vi.fn(),
|
|
660
|
-
} as AnyAISpan;
|
|
661
|
-
|
|
662
|
-
return mockSpan;
|
|
663
|
-
}
|