Learn how to add observability to your agents with distributed tracing using LangSmith, LangFuse, and Arize Phoenix.
- Overview
- Quick Start
- Tracing Providers
- Recording Traces
- Spans and Metrics
- Agent Integration
- Best Practices
The TracingService provides distributed tracing capabilities for debugging and monitoring your AI agents.
Features:
- Multiple tracing backends (LangSmith, LangFuse, Phoenix)
- Automatic span timing
- Metric collection
- Metadata sanitization
- Context propagation
Supported Platforms:
- LangSmith - LangChain's tracing platform
- LangFuse - Open-source LLM observability
- Arize Phoenix - ML observability platform
// config/services.php
return [
'tracing' => [
'enabled' => true,
'providers' => ['langsmith'], // or ['langfuse'], ['phoenix']
'langsmith' => [
'api_key' => getenv('LANGSMITH_API_KEY'),
'project' => 'my-agent-project',
'endpoint' => 'https://api.smith.langchain.com',
],
],
];use ClaudeAgents\Services\ServiceManager;
use ClaudeAgents\Services\ServiceType;
$manager = ServiceManager::getInstance();
$tracing = $manager->get(ServiceType::TRACING);
// Start a trace
$tracing->startTrace('trace-123', 'agent-run', [
'user_id' => 'user-123',
'model' => 'claude-opus-4-5',
]);
// Your agent code here...
// End the trace
$tracing->endTrace('trace-123', [
'result' => 'success',
'tokens_used' => 150,
]);Perfect for LangChain-based applications:
// .env
LANGSMITH_API_KEY=lsv2_pt_xxx
LANGCHAIN_PROJECT=my-project
// config/services.php
return [
'tracing' => [
'enabled' => true,
'providers' => ['langsmith'],
'langsmith' => [
'api_key' => getenv('LANGSMITH_API_KEY'),
'project' => getenv('LANGCHAIN_PROJECT', 'default'),
],
],
];Open-source alternative with rich analytics:
// .env
LANGFUSE_PUBLIC_KEY=pk-lf-xxx
LANGFUSE_SECRET_KEY=sk-lf-xxx
// config/services.php
return [
'tracing' => [
'enabled' => true,
'providers' => ['langfuse'],
'langfuse' => [
'public_key' => getenv('LANGFUSE_PUBLIC_KEY'),
'secret_key' => getenv('LANGFUSE_SECRET_KEY'),
'endpoint' => 'https://cloud.langfuse.com',
],
],
];ML observability focused:
// config/services.php
return [
'tracing' => [
'enabled' => true,
'providers' => ['phoenix'],
'phoenix' => [
'endpoint' => 'http://localhost:6006',
],
],
];Send traces to multiple platforms:
return [
'tracing' => [
'enabled' => true,
'providers' => ['langsmith', 'langfuse'], // Both!
// Configure both...
],
];$traceId = uniqid('trace_', true);
// Start
$tracing->startTrace($traceId, 'user-query', [
'user_id' => 'user-123',
'query' => 'What is the weather?',
'model' => 'claude-sonnet-4-5',
]);
try {
// Your agent logic
$result = $agent->process($query);
// Success
$tracing->endTrace($traceId, [
'result' => $result,
'tokens' => 150,
]);
} catch (\Exception $e) {
// Error
$tracing->endTrace($traceId, [
'error' => $e->getMessage(),
]);
throw $e;
}// Parent trace
$parentId = 'parent-trace';
$tracing->startTrace($parentId, 'agent-pipeline');
// Child traces
$step1Id = 'step-1';
$tracing->startTrace($step1Id, 'retrieval-step');
// ... do retrieval ...
$tracing->endTrace($step1Id, ['docs_found' => 5]);
$step2Id = 'step-2';
$tracing->startTrace($step2Id, 'generation-step');
// ... do generation ...
$tracing->endTrace($step2Id, ['tokens' => 100]);
// End parent
$tracing->endTrace($parentId, ['status' => 'completed']);Spans are sub-operations within a trace:
// Automatic timing
$result = $tracing->recordSpan('database-query', function() {
return $db->query('SELECT * FROM users');
});
// The span automatically records:
// - Start time
// - End time
// - Duration
// - Success/failure
// - Error message (if failed)$tracing->startTrace('trace-123', 'complex-operation');
// Span 1: Load data
$data = $tracing->recordSpan('load-data', function() {
return loadData();
});
// Span 2: Process data
$processed = $tracing->recordSpan('process-data', function() use ($data) {
return processData($data);
});
// Span 3: Save results
$tracing->recordSpan('save-results', function() use ($processed) {
saveResults($processed);
});
$tracing->endTrace('trace-123', ['status' => 'complete']);// Record individual metrics
$tracing->recordMetric('llm.tokens.input', 100, [
'model' => 'claude-opus-4-5',
]);
$tracing->recordMetric('llm.tokens.output', 50, [
'model' => 'claude-opus-4-5',
]);
$tracing->recordMetric('llm.latency.ms', 1250.5, [
'model' => 'claude-opus-4-5',
]);class TracedAgent
{
private TracingService $tracing;
public function __construct(TracingService $tracing)
{
$this->tracing = $tracing;
}
public function run(string $input): string
{
$traceId = uniqid('agent_', true);
$this->tracing->startTrace($traceId, 'agent.run', [
'input' => substr($input, 0, 100),
]);
try {
$result = $this->execute($input);
$this->tracing->endTrace($traceId, [
'result' => substr($result, 0, 100),
'success' => true,
]);
return $result;
} catch (\Exception $e) {
$this->tracing->endTrace($traceId, [
'error' => $e->getMessage(),
'success' => false,
]);
throw $e;
}
}
private function execute(string $input): string
{
return $this->tracing->recordSpan('llm-call', function() use ($input) {
// Call LLM
return "Processed: {$input}";
});
}
}class ObservableReActAgent extends ReactAgent
{
private TracingService $tracing;
public function run(string $input): AgentResult
{
$traceId = uniqid('react_', true);
$startTime = microtime(true);
$this->tracing->startTrace($traceId, 'react-agent', [
'input' => $input,
'max_iterations' => $this->maxIterations,
]);
try {
$iteration = 0;
while ($iteration < $this->maxIterations) {
$iteration++;
// Trace each iteration
$thought = $this->tracing->recordSpan(
"iteration-{$iteration}",
fn() => $this->think($input)
);
if ($thought->isComplete) {
break;
}
}
$result = $this->finalizeResult();
$duration = (microtime(true) - $startTime) * 1000;
$this->tracing->endTrace($traceId, [
'iterations' => $iteration,
'duration_ms' => $duration,
'success' => true,
]);
return $result;
} catch (\Exception $e) {
$this->tracing->endTrace($traceId, [
'error' => $e->getMessage(),
'success' => false,
]);
throw $e;
}
}
}class TracedMultiAgentSystem
{
private TracingService $tracing;
public function orchestrate(string $task): array
{
$traceId = 'orchestration-' . uniqid();
$this->tracing->startTrace($traceId, 'multi-agent-orchestration', [
'task' => $task,
'agents' => ['planner', 'executor', 'reviewer'],
]);
// Agent 1: Planner
$plan = $this->tracing->recordSpan('planner-agent', function() use ($task) {
return $this->plannerAgent->plan($task);
});
// Agent 2: Executor
$result = $this->tracing->recordSpan('executor-agent', function() use ($plan) {
return $this->executorAgent->execute($plan);
});
// Agent 3: Reviewer
$review = $this->tracing->recordSpan('reviewer-agent', function() use ($result) {
return $this->reviewerAgent->review($result);
});
$this->tracing->endTrace($traceId, [
'plan_steps' => count($plan),
'review_score' => $review['score'],
]);
return [
'plan' => $plan,
'result' => $result,
'review' => $review,
];
}
}// ❌ Bad
$tracing->startTrace('trace-1', 'run');
// ✅ Good
$tracing->startTrace('trace-1', 'customer-support-query', [
'category' => 'billing',
'priority' => 'high',
]);// ✅ Good metadata
$tracing->startTrace($id, 'agent-run', [
'user_id' => $userId,
'model' => 'claude-opus-4-5',
'temperature' => 0.7,
'max_tokens' => 1000,
'language' => 'en',
]);// ✅ Use try-finally
$traceId = uniqid();
$tracing->startTrace($traceId, 'operation');
try {
$result = doWork();
$tracing->endTrace($traceId, ['result' => $result]);
} catch (\Exception $e) {
$tracing->endTrace($traceId, ['error' => $e->getMessage()]);
throw $e;
}The service automatically removes API keys, passwords, etc., but be careful:
// ✅ Automatically sanitized
$tracing->startTrace($id, 'api-call', [
'api_key' => 'sk-xxx', // Removed
'password' => 'secret', // Removed
]);
// ⚠️ Watch out for nested sensitive data
$tracing->startTrace($id, 'operation', [
'user' => [
'name' => 'John',
'secret_token' => 'xxx', // Add 'secret' to sensitive keywords
],
]);// ✅ Good - Clear hierarchy
$tracing->startTrace($id, 'full-pipeline');
$tracing->recordSpan('step-1-retrieval', fn() => retrieve());
$tracing->recordSpan('step-2-rerank', fn() => rerank());
$tracing->recordSpan('step-3-generate', fn() => generate());
$tracing->endTrace($id);// ✅ Consistent naming and tags
$tracing->recordMetric('agent.tokens.input', $inputTokens, [
'model' => $model,
'agent_type' => 'react',
]);
$tracing->recordMetric('agent.tokens.output', $outputTokens, [
'model' => $model,
'agent_type' => 'react',
]);
$tracing->recordMetric('agent.latency.ms', $duration, [
'model' => $model,
'agent_type' => 'react',
]);// Avoid overhead when disabled
if ($tracing->isEnabled()) {
$tracing->startTrace($id, 'operation');
// ... traced code ...
$tracing->endTrace($id);
}You've learned:
✅ How to configure tracing providers
✅ Recording traces and spans
✅ Collecting metrics
✅ Integrating with agents
✅ Best practices for observability
Next: Check out TelemetryService Tutorial for metrics!