Langfuse instrumentation methods based on OpenTelemetry
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.
Creates and starts a new Langfuse observation with automatic TypeScript type inference based on observation type.
/**
* Creates and starts a new Langfuse observation with automatic TypeScript type inference.
*
* @param name - Descriptive name for the observation (e.g., 'openai-gpt-4', 'vector-search')
* @param attributes - Type-specific attributes (input, output, metadata, etc.)
* @param options - Configuration options including observation type and timing
* @returns Strongly-typed observation object based on `asType` parameter
*/
function startObservation(
name: string,
attributes?: LangfuseObservationAttributes,
options?: StartObservationOpts
): LangfuseObservation;
// Type-specific overloads for automatic type inference
function startObservation(
name: string,
attributes: LangfuseGenerationAttributes,
options: { asType: "generation" }
): LangfuseGeneration;
function startObservation(
name: string,
attributes: LangfuseEventAttributes,
options: { asType: "event" }
): LangfuseEvent;
function startObservation(
name: string,
attributes: LangfuseAgentAttributes,
options: { asType: "agent" }
): LangfuseAgent;
// ... additional overloads for tool, chain, retriever, evaluator, guardrail, embedding
// Options interface
interface StartObservationOpts {
/** Type of observation to create. Defaults to 'span' */
asType?: LangfuseObservationType;
/** Custom start time for the observation */
startTime?: Date;
/** Parent span context to attach this observation to */
parentSpanContext?: SpanContext;
}
type LangfuseObservationType =
| "span"
| "generation"
| "event"
| "embedding"
| "agent"
| "tool"
| "chain"
| "retriever"
| "evaluator"
| "guardrail";General-purpose observation for tracking operations, functions, and workflows.
import { startObservation } from '@langfuse/tracing';
// Default span creation
const span = startObservation('user-authentication', {
input: { username: 'john_doe', method: 'oauth' },
metadata: { provider: 'google' }
});
try {
const user = await authenticateUser(credentials);
span.update({
output: { userId: user.id, success: true }
});
} catch (error) {
span.update({
level: 'ERROR',
statusMessage: error.message,
output: { success: false, error: error.message }
});
} finally {
span.end();
}Specialized observation for LLM calls and AI model interactions.
const generation = startObservation('openai-gpt-4', {
input: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Explain quantum computing' }
],
model: 'gpt-4-turbo',
modelParameters: {
temperature: 0.7,
maxTokens: 500
}
}, { asType: 'generation' });
const response = await openai.chat.completions.create({
model: 'gpt-4-turbo',
messages: generation.attributes.input,
temperature: 0.7,
max_tokens: 500
});
generation.update({
output: response.choices[0].message,
usageDetails: {
promptTokens: response.usage.prompt_tokens,
completionTokens: response.usage.completion_tokens,
totalTokens: response.usage.total_tokens
},
costDetails: {
totalCost: 0.025,
currency: 'USD'
}
});
generation.end();Observation for AI agent workflows with tool usage and autonomous operations.
const agent = startObservation('research-agent', {
input: {
task: 'Research renewable energy trends',
tools: ['web-search', 'summarizer'],
maxIterations: 3
},
metadata: {
model: 'gpt-4',
strategy: 'react'
}
}, { asType: 'agent' });
// Agent performs multiple operations
const searchResults = await performWebSearch('renewable energy 2024');
const summary = await summarizeResults(searchResults);
agent.update({
output: {
completed: true,
toolsUsed: ['web-search', 'summarizer'],
iterationsRequired: 2,
finalResult: summary
},
metadata: {
efficiency: 0.85,
qualityScore: 0.92
}
});
agent.end();Observation for individual tool calls and external API interactions.
const tool = startObservation('web-search', {
input: {
query: 'latest AI developments',
maxResults: 10
},
metadata: {
provider: 'google-api',
timeout: 5000
}
}, { asType: 'tool' });
try {
const results = await webSearch('latest AI developments');
tool.update({
output: {
results: results,
count: results.length,
relevanceScore: 0.89
},
metadata: {
latency: 1200,
cacheHit: false
}
});
} catch (error) {
tool.update({
level: 'ERROR',
statusMessage: 'Search failed',
output: { error: error.message }
});
} finally {
tool.end();
}Observation for structured multi-step workflows and process chains.
const chain = startObservation('rag-pipeline', {
input: {
query: 'What is renewable energy?',
steps: ['retrieval', 'generation']
},
metadata: {
vectorDb: 'pinecone',
model: 'gpt-4'
}
}, { asType: 'chain' });
// Execute pipeline steps
const docs = await retrieveDocuments('renewable energy');
const response = await generateResponse(query, docs);
chain.update({
output: {
finalResponse: response,
stepsCompleted: 2,
documentsUsed: docs.length,
pipelineEfficiency: 0.87
}
});
chain.end();Observation for document retrieval and search operations.
const retriever = startObservation('vector-search', {
input: {
query: 'machine learning applications',
topK: 10,
similarityThreshold: 0.7
},
metadata: {
vectorDB: 'pinecone',
embeddingModel: 'text-embedding-ada-002',
similarity: 'cosine'
}
}, { asType: 'retriever' });
const results = await vectorDB.search({
query: 'machine learning applications',
topK: 10
});
retriever.update({
output: {
documents: results,
count: results.length,
avgSimilarity: 0.89
},
metadata: {
searchLatency: 150,
cacheHit: false
}
});
retriever.end();Observation for quality assessment and evaluation operations.
const evaluator = startObservation('response-quality-eval', {
input: {
response: 'Machine learning is a subset of artificial intelligence...',
reference: 'Expected high-quality explanation',
criteria: ['accuracy', 'completeness', 'clarity']
},
metadata: {
evaluator: 'custom-bert-scorer',
threshold: 0.8
}
}, { asType: 'evaluator' });
const evaluation = await evaluateResponse({
response: inputText,
criteria: ['accuracy', 'completeness', 'clarity']
});
evaluator.update({
output: {
overallScore: 0.87,
criteriaScores: {
accuracy: 0.92,
completeness: 0.85,
clarity: 0.90
},
passed: true,
grade: 'excellent'
}
});
evaluator.end();Observation for safety checks and compliance enforcement.
const guardrail = startObservation('content-safety-check', {
input: {
content: userMessage,
policies: ['no-toxicity', 'no-hate-speech', 'no-pii'],
strictMode: true
},
metadata: {
guardrailVersion: 'v2.1',
confidence: 0.95
}
}, { asType: 'guardrail' });
const safetyCheck = await checkContentSafety({
text: userMessage,
policies: ['no-toxicity', 'no-hate-speech']
});
guardrail.update({
output: {
safe: safetyCheck.safe,
riskScore: 0.15,
violations: [],
action: 'allow'
}
});
guardrail.end();Observation for text embedding and vector generation operations.
const embedding = startObservation('text-embedder', {
input: {
texts: [
'Machine learning is a subset of AI',
'Deep learning uses neural networks'
],
batchSize: 2
},
model: 'text-embedding-ada-002',
metadata: {
dimensions: 1536,
normalization: 'l2'
}
}, { asType: 'embedding' });
const embedResult = await generateEmbeddings({
texts: embedding.attributes.input.texts,
model: 'text-embedding-ada-002'
});
embedding.update({
output: {
embeddings: embedResult.vectors,
count: embedResult.vectors.length,
dimensions: 1536
},
usageDetails: {
totalTokens: embedResult.tokenCount
},
metadata: {
processingTime: 340
}
});
embedding.end();Observation for point-in-time occurrences or log entries (automatically ended).
// Events are automatically ended at creation
const event = startObservation('user-login', {
input: {
userId: '123',
method: 'oauth',
timestamp: new Date().toISOString()
},
level: 'DEFAULT',
metadata: {
ip: '192.168.1.1',
userAgent: 'Chrome/120.0',
sessionId: 'sess_456'
}
}, { asType: 'event' });
// No need to call event.end() - events are automatically endedAll observation types support creating child observations to build hierarchical traces.
// Parent observation
const workflow = startObservation('ai-pipeline', {
input: { query: 'Explain quantum computing' }
});
// Create child retriever
const retrieval = workflow.startObservation('document-search', {
input: { query: 'quantum computing', topK: 5 }
}, { asType: 'retriever' });
const docs = await searchDocuments('quantum computing', 5);
retrieval.update({ output: { documents: docs, count: docs.length } });
retrieval.end();
// Create child generation
const generation = workflow.startObservation('response-generation', {
input: { query: 'Explain quantum computing', context: docs },
model: 'gpt-4'
}, { asType: 'generation' });
const response = await llm.generate({ prompt, context: docs });
generation.update({
output: response,
usageDetails: { totalTokens: 500 }
});
generation.end();
// Update parent with final results
workflow.update({
output: {
finalResponse: response,
childOperations: 2,
success: true
}
});
workflow.end();All observation classes share common methods for lifecycle management and updates.
Updates the observation with new attributes. Returns the observation for method chaining.
// For span observations
update(attributes: LangfuseSpanAttributes): LangfuseSpan;
// For generation observations
update(attributes: LangfuseGenerationAttributes): LangfuseGeneration;
// Each observation type has its corresponding update signatureExample:
span.update({
output: { result: 'success' },
level: 'DEFAULT',
metadata: { duration: 150 }
});Marks the observation as complete with optional end timestamp.
/**
* Ends the observation, marking it as complete.
*
* @param endTime - Optional end time, defaults to current time
*/
end(endTime?: Date | number): void;Example:
const span = startObservation('operation');
// ... perform operation
span.end(); // End with current timestamp
// Or specify custom end time
const customEndTime = new Date();
span.end(customEndTime);Updates the parent trace with trace-level attributes from within an observation.
/**
* Updates the parent trace with new attributes.
*
* @param attributes - Trace attributes to set
* @returns This observation for method chaining
*/
updateTrace(attributes: LangfuseTraceAttributes): LangfuseObservation;
interface LangfuseTraceAttributes {
/** Human-readable name for the trace */
name?: string;
/** Identifier for the user associated with this trace */
userId?: string;
/** Session identifier for grouping related traces */
sessionId?: string;
/** Version identifier for the code/application */
version?: string;
/** Release identifier for deployment tracking */
release?: string;
/** Input data that initiated the trace */
input?: unknown;
/** Final output data from the trace */
output?: unknown;
/** Additional metadata for the trace */
metadata?: unknown;
/** Tags for categorizing and filtering traces */
tags?: string[];
/** Whether this trace should be publicly visible */
public?: boolean;
/** Environment where the trace was captured */
environment?: string;
}Example:
const observation = startObservation('api-request');
observation.updateTrace({
name: 'user-checkout',
userId: 'user-123',
sessionId: 'session-456',
tags: ['checkout', 'payment'],
metadata: { version: '2.1.0' }
});
observation.end();Creates a new child observation within this observation's context.
/**
* Creates a new child observation within this observation's context.
*
* @param name - Descriptive name for the child observation
* @param attributes - Type-specific attributes
* @param options - Configuration including observation type
* @returns Strongly-typed observation instance based on `asType`
*/
startObservation(
name: string,
attributes?: LangfuseObservationAttributes,
options?: { asType?: LangfuseObservationType }
): LangfuseObservation;Example:
const parent = startObservation('workflow');
// Create child with default type (span)
const child1 = parent.startObservation('step-1', {
input: { data: 'value' }
});
// Create child with specific type
const child2 = parent.startObservation('llm-call', {
model: 'gpt-4',
input: 'prompt'
}, { asType: 'generation' });
child1.end();
child2.end();
parent.end();All observation types support these base attributes:
interface LangfuseSpanAttributes {
/** Input data for the operation being tracked */
input?: unknown;
/** Output data from the operation */
output?: unknown;
/** Additional metadata as key-value pairs */
metadata?: Record<string, unknown>;
/** Severity level of the observation */
level?: "DEBUG" | "DEFAULT" | "WARNING" | "ERROR";
/** Human-readable status message */
statusMessage?: string;
/** Version identifier for the code/model being tracked */
version?: string;
/** Environment where the operation is running */
environment?: string;
}Generation and embedding observations support additional LLM-specific attributes:
interface LangfuseGenerationAttributes extends LangfuseSpanAttributes {
/** Timestamp when the model started generating completion */
completionStartTime?: Date;
/** Name of the language model used */
model?: string;
/** Parameters passed to the model */
modelParameters?: {
[key: string]: string | number;
};
/** Token usage and other model-specific usage metrics */
usageDetails?: {
[key: string]: number;
} | OpenAiUsage;
/** Cost breakdown for the generation */
costDetails?: {
[key: string]: number;
};
/** Information about the prompt used from Langfuse prompt management */
prompt?: {
name: string;
version: number;
isFallback: boolean;
};
}
interface OpenAiUsage {
promptTokens?: number;
completionTokens?: number;
totalTokens?: number;
}Specify a custom start time for backdating observations:
const observation = startObservation('operation', {
input: { data: 'value' }
}, {
startTime: new Date('2024-01-01T10:00:00Z')
});Manually specify parent span context for custom hierarchies:
import { startObservation } from '@langfuse/tracing';
const parent = startObservation('parent');
const parentContext = parent.otelSpan.spanContext();
// Create sibling observation by using same parent context
const child = startObservation('child', {
input: { step: 'processing' }
}, {
parentSpanContext: parentContext
});
child.end();
parent.end();Manual observations require explicit end() calls. Use try-finally blocks to ensure proper cleanup:
const observation = startObservation('operation');
try {
// Perform operation
const result = await performWork();
observation.update({ output: result });
} catch (error) {
observation.update({
level: 'ERROR',
statusMessage: error.message
});
throw error;
} finally {
observation.end(); // Always end the observation
}Update observations progressively as information becomes available:
const generation = startObservation('llm-call', {
model: 'gpt-4',
modelParameters: { temperature: 0.7 }
}, { asType: 'generation' });
// Set input
generation.update({
input: [{ role: 'user', content: 'Hello' }]
});
// Call API
const response = await llm.call();
// Update with output
generation.update({
output: response.message
});
// Add usage details
generation.update({
usageDetails: response.usage
});
generation.end();Use descriptive names that indicate the operation's purpose:
// Good: Specific and descriptive
const retrieval = startObservation('pinecone-vector-search', {}, { asType: 'retriever' });
// Avoid: Generic and unclear
const span = startObservation('search');Use consistent metadata structure for better analytics:
const observation = startObservation('api-call', {
input: request,
metadata: {
service: 'payment-gateway',
version: '2.1.0',
environment: 'production',
region: 'us-east-1'
}
});Install with Tessl CLI
npx tessl i tessl/npm-langfuse--tracing