0
# Manual Observations
1
2
Manual observation creation with `startObservation()` provides explicit control over the lifecycle of observations in your Langfuse traces. This approach gives you fine-grained control over when observations start and end, making it ideal for complex workflows where automatic lifecycle management isn't suitable.
3
4
## Core Function
5
6
### startObservation
7
8
Creates and starts a new Langfuse observation with automatic TypeScript type inference based on observation type.
9
10
```typescript { .api }
11
/**
12
* Creates and starts a new Langfuse observation with automatic TypeScript type inference.
13
*
14
* @param name - Descriptive name for the observation (e.g., 'openai-gpt-4', 'vector-search')
15
* @param attributes - Type-specific attributes (input, output, metadata, etc.)
16
* @param options - Configuration options including observation type and timing
17
* @returns Strongly-typed observation object based on `asType` parameter
18
*/
19
function startObservation(
20
name: string,
21
attributes?: LangfuseObservationAttributes,
22
options?: StartObservationOpts
23
): LangfuseObservation;
24
25
// Type-specific overloads for automatic type inference
26
function startObservation(
27
name: string,
28
attributes: LangfuseGenerationAttributes,
29
options: { asType: "generation" }
30
): LangfuseGeneration;
31
32
function startObservation(
33
name: string,
34
attributes: LangfuseEventAttributes,
35
options: { asType: "event" }
36
): LangfuseEvent;
37
38
function startObservation(
39
name: string,
40
attributes: LangfuseAgentAttributes,
41
options: { asType: "agent" }
42
): LangfuseAgent;
43
44
// ... additional overloads for tool, chain, retriever, evaluator, guardrail, embedding
45
46
// Options interface
47
interface StartObservationOpts {
48
/** Type of observation to create. Defaults to 'span' */
49
asType?: LangfuseObservationType;
50
/** Custom start time for the observation */
51
startTime?: Date;
52
/** Parent span context to attach this observation to */
53
parentSpanContext?: SpanContext;
54
}
55
56
type LangfuseObservationType =
57
| "span"
58
| "generation"
59
| "event"
60
| "embedding"
61
| "agent"
62
| "tool"
63
| "chain"
64
| "retriever"
65
| "evaluator"
66
| "guardrail";
67
```
68
69
## Observation Types
70
71
### Span (Default)
72
73
General-purpose observation for tracking operations, functions, and workflows.
74
75
```typescript
76
import { startObservation } from '@langfuse/tracing';
77
78
// Default span creation
79
const span = startObservation('user-authentication', {
80
input: { username: 'john_doe', method: 'oauth' },
81
metadata: { provider: 'google' }
82
});
83
84
try {
85
const user = await authenticateUser(credentials);
86
span.update({
87
output: { userId: user.id, success: true }
88
});
89
} catch (error) {
90
span.update({
91
level: 'ERROR',
92
statusMessage: error.message,
93
output: { success: false, error: error.message }
94
});
95
} finally {
96
span.end();
97
}
98
```
99
100
### Generation
101
102
Specialized observation for LLM calls and AI model interactions.
103
104
```typescript
105
const generation = startObservation('openai-gpt-4', {
106
input: [
107
{ role: 'system', content: 'You are a helpful assistant.' },
108
{ role: 'user', content: 'Explain quantum computing' }
109
],
110
model: 'gpt-4-turbo',
111
modelParameters: {
112
temperature: 0.7,
113
maxTokens: 500
114
}
115
}, { asType: 'generation' });
116
117
const response = await openai.chat.completions.create({
118
model: 'gpt-4-turbo',
119
messages: generation.attributes.input,
120
temperature: 0.7,
121
max_tokens: 500
122
});
123
124
generation.update({
125
output: response.choices[0].message,
126
usageDetails: {
127
promptTokens: response.usage.prompt_tokens,
128
completionTokens: response.usage.completion_tokens,
129
totalTokens: response.usage.total_tokens
130
},
131
costDetails: {
132
totalCost: 0.025,
133
currency: 'USD'
134
}
135
});
136
137
generation.end();
138
```
139
140
### Agent
141
142
Observation for AI agent workflows with tool usage and autonomous operations.
143
144
```typescript
145
const agent = startObservation('research-agent', {
146
input: {
147
task: 'Research renewable energy trends',
148
tools: ['web-search', 'summarizer'],
149
maxIterations: 3
150
},
151
metadata: {
152
model: 'gpt-4',
153
strategy: 'react'
154
}
155
}, { asType: 'agent' });
156
157
// Agent performs multiple operations
158
const searchResults = await performWebSearch('renewable energy 2024');
159
const summary = await summarizeResults(searchResults);
160
161
agent.update({
162
output: {
163
completed: true,
164
toolsUsed: ['web-search', 'summarizer'],
165
iterationsRequired: 2,
166
finalResult: summary
167
},
168
metadata: {
169
efficiency: 0.85,
170
qualityScore: 0.92
171
}
172
});
173
174
agent.end();
175
```
176
177
### Tool
178
179
Observation for individual tool calls and external API interactions.
180
181
```typescript
182
const tool = startObservation('web-search', {
183
input: {
184
query: 'latest AI developments',
185
maxResults: 10
186
},
187
metadata: {
188
provider: 'google-api',
189
timeout: 5000
190
}
191
}, { asType: 'tool' });
192
193
try {
194
const results = await webSearch('latest AI developments');
195
196
tool.update({
197
output: {
198
results: results,
199
count: results.length,
200
relevanceScore: 0.89
201
},
202
metadata: {
203
latency: 1200,
204
cacheHit: false
205
}
206
});
207
} catch (error) {
208
tool.update({
209
level: 'ERROR',
210
statusMessage: 'Search failed',
211
output: { error: error.message }
212
});
213
} finally {
214
tool.end();
215
}
216
```
217
218
### Chain
219
220
Observation for structured multi-step workflows and process chains.
221
222
```typescript
223
const chain = startObservation('rag-pipeline', {
224
input: {
225
query: 'What is renewable energy?',
226
steps: ['retrieval', 'generation']
227
},
228
metadata: {
229
vectorDb: 'pinecone',
230
model: 'gpt-4'
231
}
232
}, { asType: 'chain' });
233
234
// Execute pipeline steps
235
const docs = await retrieveDocuments('renewable energy');
236
const response = await generateResponse(query, docs);
237
238
chain.update({
239
output: {
240
finalResponse: response,
241
stepsCompleted: 2,
242
documentsUsed: docs.length,
243
pipelineEfficiency: 0.87
244
}
245
});
246
247
chain.end();
248
```
249
250
### Retriever
251
252
Observation for document retrieval and search operations.
253
254
```typescript
255
const retriever = startObservation('vector-search', {
256
input: {
257
query: 'machine learning applications',
258
topK: 10,
259
similarityThreshold: 0.7
260
},
261
metadata: {
262
vectorDB: 'pinecone',
263
embeddingModel: 'text-embedding-ada-002',
264
similarity: 'cosine'
265
}
266
}, { asType: 'retriever' });
267
268
const results = await vectorDB.search({
269
query: 'machine learning applications',
270
topK: 10
271
});
272
273
retriever.update({
274
output: {
275
documents: results,
276
count: results.length,
277
avgSimilarity: 0.89
278
},
279
metadata: {
280
searchLatency: 150,
281
cacheHit: false
282
}
283
});
284
285
retriever.end();
286
```
287
288
### Evaluator
289
290
Observation for quality assessment and evaluation operations.
291
292
```typescript
293
const evaluator = startObservation('response-quality-eval', {
294
input: {
295
response: 'Machine learning is a subset of artificial intelligence...',
296
reference: 'Expected high-quality explanation',
297
criteria: ['accuracy', 'completeness', 'clarity']
298
},
299
metadata: {
300
evaluator: 'custom-bert-scorer',
301
threshold: 0.8
302
}
303
}, { asType: 'evaluator' });
304
305
const evaluation = await evaluateResponse({
306
response: inputText,
307
criteria: ['accuracy', 'completeness', 'clarity']
308
});
309
310
evaluator.update({
311
output: {
312
overallScore: 0.87,
313
criteriaScores: {
314
accuracy: 0.92,
315
completeness: 0.85,
316
clarity: 0.90
317
},
318
passed: true,
319
grade: 'excellent'
320
}
321
});
322
323
evaluator.end();
324
```
325
326
### Guardrail
327
328
Observation for safety checks and compliance enforcement.
329
330
```typescript
331
const guardrail = startObservation('content-safety-check', {
332
input: {
333
content: userMessage,
334
policies: ['no-toxicity', 'no-hate-speech', 'no-pii'],
335
strictMode: true
336
},
337
metadata: {
338
guardrailVersion: 'v2.1',
339
confidence: 0.95
340
}
341
}, { asType: 'guardrail' });
342
343
const safetyCheck = await checkContentSafety({
344
text: userMessage,
345
policies: ['no-toxicity', 'no-hate-speech']
346
});
347
348
guardrail.update({
349
output: {
350
safe: safetyCheck.safe,
351
riskScore: 0.15,
352
violations: [],
353
action: 'allow'
354
}
355
});
356
357
guardrail.end();
358
```
359
360
### Embedding
361
362
Observation for text embedding and vector generation operations.
363
364
```typescript
365
const embedding = startObservation('text-embedder', {
366
input: {
367
texts: [
368
'Machine learning is a subset of AI',
369
'Deep learning uses neural networks'
370
],
371
batchSize: 2
372
},
373
model: 'text-embedding-ada-002',
374
metadata: {
375
dimensions: 1536,
376
normalization: 'l2'
377
}
378
}, { asType: 'embedding' });
379
380
const embedResult = await generateEmbeddings({
381
texts: embedding.attributes.input.texts,
382
model: 'text-embedding-ada-002'
383
});
384
385
embedding.update({
386
output: {
387
embeddings: embedResult.vectors,
388
count: embedResult.vectors.length,
389
dimensions: 1536
390
},
391
usageDetails: {
392
totalTokens: embedResult.tokenCount
393
},
394
metadata: {
395
processingTime: 340
396
}
397
});
398
399
embedding.end();
400
```
401
402
### Event
403
404
Observation for point-in-time occurrences or log entries (automatically ended).
405
406
```typescript
407
// Events are automatically ended at creation
408
const event = startObservation('user-login', {
409
input: {
410
userId: '123',
411
method: 'oauth',
412
timestamp: new Date().toISOString()
413
},
414
level: 'DEFAULT',
415
metadata: {
416
ip: '192.168.1.1',
417
userAgent: 'Chrome/120.0',
418
sessionId: 'sess_456'
419
}
420
}, { asType: 'event' });
421
422
// No need to call event.end() - events are automatically ended
423
```
424
425
## Nested Observations
426
427
All observation types support creating child observations to build hierarchical traces.
428
429
```typescript
430
// Parent observation
431
const workflow = startObservation('ai-pipeline', {
432
input: { query: 'Explain quantum computing' }
433
});
434
435
// Create child retriever
436
const retrieval = workflow.startObservation('document-search', {
437
input: { query: 'quantum computing', topK: 5 }
438
}, { asType: 'retriever' });
439
440
const docs = await searchDocuments('quantum computing', 5);
441
retrieval.update({ output: { documents: docs, count: docs.length } });
442
retrieval.end();
443
444
// Create child generation
445
const generation = workflow.startObservation('response-generation', {
446
input: { query: 'Explain quantum computing', context: docs },
447
model: 'gpt-4'
448
}, { asType: 'generation' });
449
450
const response = await llm.generate({ prompt, context: docs });
451
generation.update({
452
output: response,
453
usageDetails: { totalTokens: 500 }
454
});
455
generation.end();
456
457
// Update parent with final results
458
workflow.update({
459
output: {
460
finalResponse: response,
461
childOperations: 2,
462
success: true
463
}
464
});
465
workflow.end();
466
```
467
468
## Observation Class Methods
469
470
All observation classes share common methods for lifecycle management and updates.
471
472
### update()
473
474
Updates the observation with new attributes. Returns the observation for method chaining.
475
476
```typescript { .api }
477
// For span observations
478
update(attributes: LangfuseSpanAttributes): LangfuseSpan;
479
480
// For generation observations
481
update(attributes: LangfuseGenerationAttributes): LangfuseGeneration;
482
483
// Each observation type has its corresponding update signature
484
```
485
486
**Example:**
487
488
```typescript
489
span.update({
490
output: { result: 'success' },
491
level: 'DEFAULT',
492
metadata: { duration: 150 }
493
});
494
```
495
496
### end()
497
498
Marks the observation as complete with optional end timestamp.
499
500
```typescript { .api }
501
/**
502
* Ends the observation, marking it as complete.
503
*
504
* @param endTime - Optional end time, defaults to current time
505
*/
506
end(endTime?: Date | number): void;
507
```
508
509
**Example:**
510
511
```typescript
512
const span = startObservation('operation');
513
// ... perform operation
514
span.end(); // End with current timestamp
515
516
// Or specify custom end time
517
const customEndTime = new Date();
518
span.end(customEndTime);
519
```
520
521
### updateTrace()
522
523
Updates the parent trace with trace-level attributes from within an observation.
524
525
```typescript { .api }
526
/**
527
* Updates the parent trace with new attributes.
528
*
529
* @param attributes - Trace attributes to set
530
* @returns This observation for method chaining
531
*/
532
updateTrace(attributes: LangfuseTraceAttributes): LangfuseObservation;
533
534
interface LangfuseTraceAttributes {
535
/** Human-readable name for the trace */
536
name?: string;
537
/** Identifier for the user associated with this trace */
538
userId?: string;
539
/** Session identifier for grouping related traces */
540
sessionId?: string;
541
/** Version identifier for the code/application */
542
version?: string;
543
/** Release identifier for deployment tracking */
544
release?: string;
545
/** Input data that initiated the trace */
546
input?: unknown;
547
/** Final output data from the trace */
548
output?: unknown;
549
/** Additional metadata for the trace */
550
metadata?: unknown;
551
/** Tags for categorizing and filtering traces */
552
tags?: string[];
553
/** Whether this trace should be publicly visible */
554
public?: boolean;
555
/** Environment where the trace was captured */
556
environment?: string;
557
}
558
```
559
560
**Example:**
561
562
```typescript
563
const observation = startObservation('api-request');
564
565
observation.updateTrace({
566
name: 'user-checkout',
567
userId: 'user-123',
568
sessionId: 'session-456',
569
tags: ['checkout', 'payment'],
570
metadata: { version: '2.1.0' }
571
});
572
573
observation.end();
574
```
575
576
### startObservation()
577
578
Creates a new child observation within this observation's context.
579
580
```typescript { .api }
581
/**
582
* Creates a new child observation within this observation's context.
583
*
584
* @param name - Descriptive name for the child observation
585
* @param attributes - Type-specific attributes
586
* @param options - Configuration including observation type
587
* @returns Strongly-typed observation instance based on `asType`
588
*/
589
startObservation(
590
name: string,
591
attributes?: LangfuseObservationAttributes,
592
options?: { asType?: LangfuseObservationType }
593
): LangfuseObservation;
594
```
595
596
**Example:**
597
598
```typescript
599
const parent = startObservation('workflow');
600
601
// Create child with default type (span)
602
const child1 = parent.startObservation('step-1', {
603
input: { data: 'value' }
604
});
605
606
// Create child with specific type
607
const child2 = parent.startObservation('llm-call', {
608
model: 'gpt-4',
609
input: 'prompt'
610
}, { asType: 'generation' });
611
612
child1.end();
613
child2.end();
614
parent.end();
615
```
616
617
## Common Observation Attributes
618
619
All observation types support these base attributes:
620
621
```typescript { .api }
622
interface LangfuseSpanAttributes {
623
/** Input data for the operation being tracked */
624
input?: unknown;
625
/** Output data from the operation */
626
output?: unknown;
627
/** Additional metadata as key-value pairs */
628
metadata?: Record<string, unknown>;
629
/** Severity level of the observation */
630
level?: "DEBUG" | "DEFAULT" | "WARNING" | "ERROR";
631
/** Human-readable status message */
632
statusMessage?: string;
633
/** Version identifier for the code/model being tracked */
634
version?: string;
635
/** Environment where the operation is running */
636
environment?: string;
637
}
638
```
639
640
## Generation-Specific Attributes
641
642
Generation and embedding observations support additional LLM-specific attributes:
643
644
```typescript { .api }
645
interface LangfuseGenerationAttributes extends LangfuseSpanAttributes {
646
/** Timestamp when the model started generating completion */
647
completionStartTime?: Date;
648
/** Name of the language model used */
649
model?: string;
650
/** Parameters passed to the model */
651
modelParameters?: {
652
[key: string]: string | number;
653
};
654
/** Token usage and other model-specific usage metrics */
655
usageDetails?: {
656
[key: string]: number;
657
} | OpenAiUsage;
658
/** Cost breakdown for the generation */
659
costDetails?: {
660
[key: string]: number;
661
};
662
/** Information about the prompt used from Langfuse prompt management */
663
prompt?: {
664
name: string;
665
version: number;
666
isFallback: boolean;
667
};
668
}
669
670
interface OpenAiUsage {
671
promptTokens?: number;
672
completionTokens?: number;
673
totalTokens?: number;
674
}
675
```
676
677
## Options Configuration
678
679
### Start Time
680
681
Specify a custom start time for backdating observations:
682
683
```typescript
684
const observation = startObservation('operation', {
685
input: { data: 'value' }
686
}, {
687
startTime: new Date('2024-01-01T10:00:00Z')
688
});
689
```
690
691
### Parent Context
692
693
Manually specify parent span context for custom hierarchies:
694
695
```typescript
696
import { startObservation } from '@langfuse/tracing';
697
698
const parent = startObservation('parent');
699
const parentContext = parent.otelSpan.spanContext();
700
701
// Create sibling observation by using same parent context
702
const child = startObservation('child', {
703
input: { step: 'processing' }
704
}, {
705
parentSpanContext: parentContext
706
});
707
708
child.end();
709
parent.end();
710
```
711
712
## Best Practices
713
714
### Always End Observations
715
716
Manual observations require explicit `end()` calls. Use try-finally blocks to ensure proper cleanup:
717
718
```typescript
719
const observation = startObservation('operation');
720
721
try {
722
// Perform operation
723
const result = await performWork();
724
observation.update({ output: result });
725
} catch (error) {
726
observation.update({
727
level: 'ERROR',
728
statusMessage: error.message
729
});
730
throw error;
731
} finally {
732
observation.end(); // Always end the observation
733
}
734
```
735
736
### Progressive Updates
737
738
Update observations progressively as information becomes available:
739
740
```typescript
741
const generation = startObservation('llm-call', {
742
model: 'gpt-4',
743
modelParameters: { temperature: 0.7 }
744
}, { asType: 'generation' });
745
746
// Set input
747
generation.update({
748
input: [{ role: 'user', content: 'Hello' }]
749
});
750
751
// Call API
752
const response = await llm.call();
753
754
// Update with output
755
generation.update({
756
output: response.message
757
});
758
759
// Add usage details
760
generation.update({
761
usageDetails: response.usage
762
});
763
764
generation.end();
765
```
766
767
### Meaningful Names
768
769
Use descriptive names that indicate the operation's purpose:
770
771
```typescript
772
// Good: Specific and descriptive
773
const retrieval = startObservation('pinecone-vector-search', {}, { asType: 'retriever' });
774
775
// Avoid: Generic and unclear
776
const span = startObservation('search');
777
```
778
779
### Structured Metadata
780
781
Use consistent metadata structure for better analytics:
782
783
```typescript
784
const observation = startObservation('api-call', {
785
input: request,
786
metadata: {
787
service: 'payment-gateway',
788
version: '2.1.0',
789
environment: 'production',
790
region: 'us-east-1'
791
}
792
});
793
```
794