0
# Context Management
1
2
Context management functions provide utilities for updating and accessing the currently active trace and observation within the OpenTelemetry context. These functions enable you to modify trace attributes from anywhere within an active observation context.
3
4
## Core Functions
5
6
### updateActiveTrace
7
8
Updates the currently active trace with new attributes from within any observation context.
9
10
```typescript { .api }
11
/**
12
* Updates the currently active trace with new attributes.
13
*
14
* This function finds the currently active OpenTelemetry span and updates
15
* it with trace-level attributes. If no active span is found, a warning is logged.
16
*
17
* @param attributes - Trace attributes to set
18
*/
19
function updateActiveTrace(attributes: LangfuseTraceAttributes): void;
20
21
interface LangfuseTraceAttributes {
22
/** Human-readable name for the trace */
23
name?: string;
24
/** Identifier for the user associated with this trace */
25
userId?: string;
26
/** Session identifier for grouping related traces */
27
sessionId?: string;
28
/** Version identifier for the code/application */
29
version?: string;
30
/** Release identifier for deployment tracking */
31
release?: string;
32
/** Input data that initiated the trace */
33
input?: unknown;
34
/** Final output data from the trace */
35
output?: unknown;
36
/** Additional metadata for the trace */
37
metadata?: unknown;
38
/** Tags for categorizing and filtering traces */
39
tags?: string[];
40
/** Whether this trace should be publicly visible */
41
public?: boolean;
42
/** Environment where the trace was captured */
43
environment?: string;
44
}
45
```
46
47
**Usage:**
48
49
```typescript
50
import { startActiveObservation, updateActiveTrace } from '@langfuse/tracing';
51
52
await startActiveObservation('user-workflow', async (observation) => {
53
// Update trace with user context
54
updateActiveTrace({
55
name: 'checkout-flow',
56
userId: 'user-123',
57
sessionId: 'session-456',
58
tags: ['checkout', 'payment'],
59
metadata: { cartValue: 99.99 }
60
});
61
62
// Perform operations...
63
const result = await processCheckout();
64
65
// Update trace with final output
66
updateActiveTrace({
67
output: {
68
orderId: result.orderId,
69
success: true
70
}
71
});
72
});
73
```
74
75
### updateActiveObservation
76
77
Updates the currently active observation with new attributes from within the observation context.
78
79
```typescript { .api }
80
/**
81
* Updates the currently active observation with new attributes.
82
*
83
* This function finds the currently active OpenTelemetry span in the execution context
84
* and updates it with Langfuse-specific attributes. It supports all observation types
85
* through TypeScript overloads for type safety.
86
*
87
* **Important**: When `asType` is omitted or undefined, the observation type attribute
88
* is NOT updated, preserving the existing observation type. This prevents inadvertently
89
* overwriting the type to "span". Only specify `asType` when you explicitly want to
90
* change the observation type (which is rarely needed).
91
*
92
* @param attributes - Observation-specific attributes to update
93
* @param options - Configuration specifying observation type (defaults to 'span')
94
*/
95
function updateActiveObservation(
96
attributes: LangfuseObservationAttributes,
97
options?: { asType?: LangfuseObservationType }
98
): void;
99
100
// Type-specific overloads for type safety
101
function updateActiveObservation(
102
attributes: LangfuseSpanAttributes,
103
options?: { asType: "span" }
104
): void;
105
106
function updateActiveObservation(
107
attributes: LangfuseGenerationAttributes,
108
options: { asType: "generation" }
109
): void;
110
111
function updateActiveObservation(
112
attributes: LangfuseAgentAttributes,
113
options: { asType: "agent" }
114
): void;
115
116
// ... additional overloads for tool, chain, retriever, evaluator, guardrail, embedding
117
```
118
119
**Usage:**
120
121
```typescript
122
import { startActiveObservation, updateActiveObservation } from '@langfuse/tracing';
123
124
// Update active span (default)
125
await startActiveObservation('data-processing', async () => {
126
const result = await processData(inputData);
127
128
updateActiveObservation({
129
output: { processedRecords: result.count },
130
metadata: { processingTime: result.duration }
131
});
132
});
133
134
// Update active generation with LLM-specific attributes
135
await startActiveObservation('llm-call', async () => {
136
const response = await openai.chat.completions.create({
137
model: 'gpt-4',
138
messages: [{ role: 'user', content: 'Hello' }]
139
});
140
141
updateActiveObservation({
142
output: response.choices[0].message,
143
usageDetails: {
144
promptTokens: response.usage.prompt_tokens,
145
completionTokens: response.usage.completion_tokens,
146
totalTokens: response.usage.total_tokens
147
},
148
costDetails: { totalCost: 0.025, currency: 'USD' }
149
}, { asType: 'generation' });
150
}, { asType: 'generation' });
151
```
152
153
### getActiveTraceId
154
155
Gets the trace ID of the currently active span.
156
157
```typescript { .api }
158
/**
159
* Gets the current active trace ID.
160
*
161
* If there is no span in the current context, returns undefined.
162
*
163
* @returns The trace ID of the currently active span, or undefined if no span is active
164
*/
165
function getActiveTraceId(): string | undefined;
166
```
167
168
**Usage:**
169
170
```typescript
171
import { startActiveObservation, getActiveTraceId } from '@langfuse/tracing';
172
173
await startActiveObservation('operation', async () => {
174
const traceId = getActiveTraceId();
175
176
if (traceId) {
177
console.log('Current trace ID:', traceId);
178
// Use trace ID for correlation with external systems
179
await logToExternalSystem({ traceId, event: 'operation-started' });
180
}
181
});
182
```
183
184
### getActiveSpanId
185
186
Gets the observation ID (span ID) of the currently active span.
187
188
```typescript { .api }
189
/**
190
* Gets the current active observation ID.
191
*
192
* If there is no OTEL span in the current context, returns undefined.
193
*
194
* @returns The ID of the currently active OTEL span, or undefined if no OTEL span is active
195
*/
196
function getActiveSpanId(): string | undefined;
197
```
198
199
**Usage:**
200
201
```typescript
202
import { startActiveObservation, getActiveSpanId } from '@langfuse/tracing';
203
204
await startActiveObservation('parent-operation', async () => {
205
const parentSpanId = getActiveSpanId();
206
207
await startActiveObservation('child-operation', async () => {
208
const childSpanId = getActiveSpanId();
209
210
console.log('Parent span ID:', parentSpanId);
211
console.log('Child span ID:', childSpanId);
212
});
213
});
214
```
215
216
## Use Cases
217
218
### Dynamic User Context
219
220
Update trace with user information discovered during execution.
221
222
```typescript
223
import { startActiveObservation, updateActiveTrace } from '@langfuse/tracing';
224
225
async function handleRequest(request: Request) {
226
return await startActiveObservation('handle-request', async () => {
227
// Initially, we don't know the user
228
const authToken = request.headers.get('Authorization');
229
const user = await authenticateUser(authToken);
230
231
// Update trace with authenticated user
232
updateActiveTrace({
233
userId: user.id,
234
sessionId: user.sessionId,
235
metadata: {
236
userRole: user.role,
237
accountType: user.accountType
238
}
239
});
240
241
// Continue processing with user context
242
return await processRequest(request, user);
243
});
244
}
245
```
246
247
### Progressive Attribute Updates
248
249
Update observations as more information becomes available.
250
251
```typescript
252
await startActiveObservation('multi-step-process', async () => {
253
// Step 1: Initialize
254
updateActiveObservation({
255
input: { phase: 'initialization' },
256
metadata: { startTime: Date.now() }
257
});
258
259
const config = await loadConfiguration();
260
261
// Step 2: Processing
262
updateActiveObservation({
263
metadata: {
264
configLoaded: true,
265
configVersion: config.version
266
}
267
});
268
269
const result = await processWithConfig(config);
270
271
// Step 3: Completion
272
updateActiveObservation({
273
output: result,
274
metadata: {
275
completionTime: Date.now(),
276
recordsProcessed: result.count
277
}
278
});
279
});
280
```
281
282
### Error Context Enhancement
283
284
Add detailed error context to active observations.
285
286
```typescript
287
await startActiveObservation('risky-operation', async () => {
288
try {
289
const result = await performRiskyOperation();
290
291
updateActiveObservation({
292
output: result,
293
level: 'DEFAULT'
294
});
295
296
return result;
297
} catch (error) {
298
// Enhance error with context
299
updateActiveObservation({
300
level: 'ERROR',
301
statusMessage: error.message,
302
output: {
303
error: error.message,
304
errorCode: error.code,
305
stack: error.stack
306
},
307
metadata: {
308
recoveryAttempted: false,
309
criticalFailure: true
310
}
311
});
312
313
throw error;
314
}
315
});
316
```
317
318
### Cross-Cutting Concerns
319
320
Add tracing information from middleware or decorators.
321
322
```typescript
323
// Middleware example
324
async function timingMiddleware(
325
handler: () => Promise<any>
326
) {
327
const startTime = Date.now();
328
329
try {
330
const result = await handler();
331
332
const duration = Date.now() - startTime;
333
updateActiveObservation({
334
metadata: {
335
duration,
336
performance: duration < 1000 ? 'fast' : 'slow'
337
}
338
});
339
340
return result;
341
} catch (error) {
342
const duration = Date.now() - startTime;
343
updateActiveObservation({
344
level: 'ERROR',
345
metadata: {
346
duration,
347
failedAfter: duration
348
}
349
});
350
351
throw error;
352
}
353
}
354
355
// Usage in traced operation
356
await startActiveObservation('operation', async () => {
357
return await timingMiddleware(async () => {
358
return await doWork();
359
});
360
});
361
```
362
363
### External System Correlation
364
365
Use trace and span IDs to correlate with external logging systems.
366
367
```typescript
368
import { startActiveObservation, getActiveTraceId, getActiveSpanId } from '@langfuse/tracing';
369
370
async function callExternalAPI(endpoint: string, data: any) {
371
return await startActiveObservation('external-api-call', async () => {
372
const traceId = getActiveTraceId();
373
const spanId = getActiveSpanId();
374
375
// Include trace context in external API call
376
const response = await fetch(endpoint, {
377
method: 'POST',
378
headers: {
379
'Content-Type': 'application/json',
380
'X-Trace-Id': traceId || 'unknown',
381
'X-Span-Id': spanId || 'unknown'
382
},
383
body: JSON.stringify(data)
384
});
385
386
// Log correlation info
387
console.log(`API call to ${endpoint}`, {
388
traceId,
389
spanId,
390
statusCode: response.status
391
});
392
393
updateActiveObservation({
394
output: {
395
statusCode: response.status,
396
correlationId: response.headers.get('X-Correlation-Id')
397
},
398
metadata: {
399
endpoint,
400
traceId,
401
spanId
402
}
403
});
404
405
return response.json();
406
});
407
}
408
```
409
410
### Nested Context Updates
411
412
Update trace and observation from deeply nested function calls.
413
414
```typescript
415
async function deeplyNestedOperation() {
416
// Can update trace/observation from anywhere in the call stack
417
updateActiveTrace({
418
tags: ['nested-operation'],
419
metadata: { depth: 'level-3' }
420
});
421
422
updateActiveObservation({
423
metadata: { nestedCallCompleted: true }
424
});
425
}
426
427
async function middleOperation() {
428
await deeplyNestedOperation();
429
430
updateActiveObservation({
431
metadata: { middleCompleted: true }
432
});
433
}
434
435
await startActiveObservation('top-level', async () => {
436
await middleOperation();
437
438
updateActiveObservation({
439
output: { allLevelsCompleted: true }
440
});
441
});
442
```
443
444
### Conditional Trace Updates
445
446
Update trace attributes based on runtime conditions.
447
448
```typescript
449
await startActiveObservation('conditional-workflow', async (obs) => {
450
const user = await getCurrentUser();
451
452
// Update trace based on user type
453
if (user.role === 'admin') {
454
updateActiveTrace({
455
tags: ['admin-operation'],
456
metadata: { elevated: true }
457
});
458
} else if (user.role === 'premium') {
459
updateActiveTrace({
460
tags: ['premium-operation'],
461
metadata: { accountTier: 'premium' }
462
});
463
}
464
465
// Perform operation
466
const result = await processForUser(user);
467
468
// Update based on result
469
if (result.requiresReview) {
470
updateActiveTrace({
471
tags: ['requires-review'],
472
public: false // Keep sensitive operations private
473
});
474
}
475
});
476
```
477
478
## Type Safety
479
480
The `updateActiveObservation` function provides type-specific overloads for each observation type.
481
482
```typescript
483
// Span attributes (default)
484
updateActiveObservation({
485
input: { data: 'value' },
486
output: { result: 'success' },
487
metadata: { custom: 'info' }
488
});
489
490
// Generation attributes
491
updateActiveObservation({
492
model: 'gpt-4',
493
modelParameters: { temperature: 0.7 },
494
usageDetails: { totalTokens: 500 },
495
costDetails: { totalCost: 0.025 }
496
}, { asType: 'generation' });
497
498
// Agent attributes
499
updateActiveObservation({
500
output: {
501
toolsUsed: ['search', 'calculator'],
502
iterationsRequired: 3
503
},
504
metadata: { efficiency: 0.85 }
505
}, { asType: 'agent' });
506
507
// Tool attributes
508
updateActiveObservation({
509
output: {
510
result: searchResults,
511
latency: 1200
512
},
513
metadata: { cacheHit: false }
514
}, { asType: 'tool' });
515
```
516
517
## Context Requirements
518
519
All context management functions require an active OpenTelemetry span context:
520
521
### Valid Context
522
523
```typescript
524
// ✓ Valid: Inside startActiveObservation
525
await startActiveObservation('operation', async () => {
526
updateActiveTrace({ userId: 'user-123' }); // Works
527
updateActiveObservation({ output: { result: 'success' } }); // Works
528
const traceId = getActiveTraceId(); // Returns trace ID
529
});
530
531
// ✓ Valid: Inside observe
532
const tracedFn = observe(async () => {
533
updateActiveTrace({ tags: ['processed'] }); // Works
534
updateActiveObservation({ level: 'DEFAULT' }); // Works
535
});
536
537
// ✓ Valid: Inside startObservation with manual context
538
const span = startObservation('operation');
539
context.with(
540
trace.setSpan(context.active(), span.otelSpan),
541
() => {
542
updateActiveObservation({ output: { done: true } }); // Works
543
}
544
);
545
span.end();
546
```
547
548
### Invalid Context
549
550
```typescript
551
// ✗ Invalid: Outside any observation context
552
updateActiveTrace({ userId: 'user-123' }); // Warning logged, no effect
553
554
updateActiveObservation({ output: { result: 'success' } }); // Warning logged, no effect
555
556
const traceId = getActiveTraceId(); // Returns undefined
557
```
558
559
## Best Practices
560
561
### Use for Dynamic Context
562
563
Update traces when context is discovered during execution, not known upfront.
564
565
```typescript
566
// Good: Update when information becomes available
567
await startActiveObservation('api-request', async () => {
568
const token = extractToken(request);
569
const user = await validateToken(token);
570
571
updateActiveTrace({
572
userId: user.id,
573
sessionId: user.sessionId
574
});
575
576
return await processRequest(user);
577
});
578
579
// Less ideal: If you know the context upfront, pass it to the observation
580
await startActiveObservation('api-request', async (obs) => {
581
obs.updateTrace({
582
userId: knownUser.id,
583
sessionId: knownUser.sessionId
584
});
585
586
return await processRequest(knownUser);
587
});
588
```
589
590
### Prefer Observation.update() When Available
591
592
When you have a reference to the observation, use its `update()` method directly.
593
594
```typescript
595
// Preferred: Direct update when you have the reference
596
await startActiveObservation('operation', async (observation) => {
597
observation.update({ output: { result: 'success' } });
598
observation.updateTrace({ tags: ['completed'] });
599
});
600
601
// Use updateActiveObservation when you don't have the reference
602
async function helperFunction() {
603
// Called from within an observation but doesn't have the reference
604
updateActiveObservation({ metadata: { helperCalled: true } });
605
}
606
607
await startActiveObservation('main-operation', async () => {
608
await helperFunction();
609
});
610
```
611
612
### Handle Missing Context Gracefully
613
614
Check for active context before using IDs.
615
616
```typescript
617
function logOperationContext() {
618
const traceId = getActiveTraceId();
619
const spanId = getActiveSpanId();
620
621
if (traceId && spanId) {
622
console.log('Operation context:', { traceId, spanId });
623
} else {
624
console.log('No active tracing context');
625
}
626
}
627
628
// Works in both traced and untraced contexts
629
await startActiveObservation('operation', async () => {
630
logOperationContext(); // Logs trace and span IDs
631
});
632
633
logOperationContext(); // Logs "No active tracing context"
634
```
635
636
### Batch Related Updates
637
638
Group related attribute updates together for clarity.
639
640
```typescript
641
// Good: Batch related updates
642
await startActiveObservation('operation', async () => {
643
const result = await complexOperation();
644
645
updateActiveObservation({
646
output: result,
647
level: 'DEFAULT',
648
metadata: {
649
duration: result.duration,
650
itemsProcessed: result.count,
651
efficiency: result.efficiency
652
}
653
});
654
});
655
656
// Avoid: Multiple separate updates
657
await startActiveObservation('operation', async () => {
658
const result = await complexOperation();
659
660
updateActiveObservation({ output: result });
661
updateActiveObservation({ level: 'DEFAULT' });
662
updateActiveObservation({ metadata: { duration: result.duration } });
663
updateActiveObservation({ metadata: { itemsProcessed: result.count } });
664
});
665
```
666
667
### Consistent Attribute Types
668
669
Maintain consistent attribute types across updates.
670
671
```typescript
672
// Good: Consistent types
673
updateActiveObservation({
674
metadata: {
675
count: 10, // number
676
status: 'success', // string
677
enabled: true // boolean
678
}
679
});
680
681
// Later update maintains types
682
updateActiveObservation({
683
metadata: {
684
count: 20, // still number
685
status: 'complete', // still string
686
enabled: false // still boolean
687
}
688
});
689
```
690