Observability and Tracing
Monitor LLM latency, track token expenses, trace multi-agent execution spans, and instrument OpenTelemetry telemetry.
TL;DR
- Instrument multi-step agent reasoning loops using OpenTelemetry
startSpan()traces. - Track prompt and completion token counts using structured
logger.info()metadata. - Aggregate dollar expenses in real time utilizing centralized cost
accountingformulas.
OpenTelemetry Span Instrumentation
Span Wrapper for LLM CallsWrap API requests in OpenTelemetry trace spans.
import { trace } from '@opentelemetry/api';
import { SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('ai-service');
async function tracedChat(
msgs: any[], model = 'gpt-4o'
) {
return tracer.startActiveSpan('chat', async s => {
s.setAttributes({ 'gen_ai.model': model });
try {
const res = await callLLM(msgs, model);
s.setStatus({ code: SpanStatusCode.OK });
return res;
} catch (err: any) {
s.recordException(err);
s.setStatus({ code: SpanStatusCode.ERROR });
throw err;
} finally {
s.end();
}
});
}Nested Agent Tool Span HierarchyCreate child spans for nested agent tool executions.
async function traceTool(name: string, fn: () => any) {
const sName = `tool.${name}`;
return tracer.startActiveSpan(sName, async s => {
s.setAttribute('tool.name', name);
try {
return await fn();
} finally {
s.end();
}
});
}Context Propagation Across ServicesPropagate traceparent headers across distributed microservices.
import { propagation } from '@opentelemetry/api';
import { context } from '@opentelemetry/api';
const carrier: Record<string, string> = {};
propagation.inject(context.active(), carrier);Token Metrics & Cost Accounting
Real-Time Dollar Cost CalculatorCalculate financial cost of completion turn from usage data.
const RATES = {
'gpt-4o': { inM: 2.50, outM: 10.00 },
'gpt-4o-mini': { inM: 0.15, outM: 0.60 },
} as const;
type MKey = keyof typeof RATES;
function calcTurnCost(model: MKey, u: any) {
const r = RATES[model];
const inCost = (u.prompt_tokens / 1e6) * r.inM;
const outCost = (u.completion_tokens / 1e6) * r.outM;
return inCost + outCost;
}Token Spend Telemetry EmitterRecord token metrics to monitoring backend.
function recordUsage(
model: string, usage: any, cost: number
) {
metrics.increment('ai.requests', 1, { model });
const inTok = usage.prompt_tokens;
metrics.gauge('ai.in', inTok, { model });
const outTok = usage.completion_tokens;
metrics.gauge('ai.out', outTok, { model });
metrics.gauge('ai.cost.usd', cost, { model });
}User Budget Ceiling GuardHalt user agent execution when monthly budget cap is reached.
async function checkBudget(
userId: string, addedCost: number
) {
const cur = await getMonthlySpend(userId);
if (cur + addedCost > 50.00) {
throw new Error('Monthly AI spend cap exceeded');
}
}Langfuse Integration
Langfuse Client InitializationConnect to Langfuse observability platform.
import { Langfuse } from 'langfuse';
const langfuse = new Langfuse({
publicKey: process.env.LANGFUSE_PUBLIC_KEY!,
secretKey: process.env.LANGFUSE_SECRET_KEY!,
baseUrl: 'https://cloud.langfuse.com',
});Traced Generation ExecutionRecord generation event with full inputs and metadata.
const trace = langfuse.trace({
name: 'rag-query', userId: 'usr_1',
});
const gen = trace.generation({
name: 'generate-answer',
model: 'gpt-4o',
input: query,
});
gen.end({ output: answer, usage: { total: 420 } });User Feedback Score HookAttach thumbs-up user feedback scores directly to trace.
await langfuse.score({
traceId: trace.id,
name: 'user-feedback',
value: 1, // 1 for thumbs up, 0 for thumbs down
});Latency & Anomaly Alerts
Time-to-First-Token (TTFT) StopwatchMeasure latency until initial streaming token reaches client.
async function measureTTFT(stream: any) {
const t0 = performance.now();
for await (const chunk of stream) {
const ttft = performance.now() - t0;
logger.info('streaming_ttft_ms', { ttft });
break;
}
}Repeated Tool Call Spike DetectorDetect infinite agent loops before budget exhaustion.
function detectToolLoop(
toolCalls: string[], threshold = 5
) {
const lastN = toolCalls.slice(-threshold);
const allSame = lastN.every(t => t === lastN[0]);
if (toolCalls.length >= threshold && allSame) {
const err = 'Agent loop anomaly: identical calls';
throw new Error(err);
}
}Observability Flush on ExitEnsure pending telemetry spans flush before process exit.
process.on('SIGTERM', async () => {
await langfuse.flushAsync();
await tracerProvider.shutdown();
process.exit(0);
});Tips
- Attach unique
sessionIdanduserIdattributes to every trace span to isolate aberrant customer usage patterns instantly. - Export spans asynchronously via
BatchSpanProcessorto prevent observability network overhead from degrading user-facing request latency across applications.
Warnings
- Never record raw unmasked customer PII or confidential API keys inside telemetry span
attributespayloads. - Avoid high-cardinality unbounded metric tags that overwhelm time-series databases and inflate external observability cloud
bills.
In Practice
Executes an OpenAI completion, measures execution latency, computes turn cost, and logs observability metrics.
- Record baseline performance start timestamp.
- Execute completion request and capture usage token statistics.
- Compute turn latency and financial dollar expense.
- Log structured telemetry payload for monitoring dashboards.
import OpenAI from 'openai';
const client = new OpenAI();
async function tracedTurn(query: string) {
const t0 = performance.now();
const res = await client.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: query }],
});
const ms = (performance.now() - t0).toFixed(0);
const u = res.usage!;
const cost = ((u.prompt_tokens * 0.15) +
(u.completion_tokens * 0.60)) / 1e6;
const text = res.choices[0].message.content;
return { text, ms, cost };
}
console.log(await tracedTurn('Hello'));FAQ
Traditional APM monitors HTTP routes and database queries, but misses token consumption, prompt versions, tool call parameters, reasoning loops, and LLM cache hit rates.
Each high-level user goal creates a root trace. Every sub-agent delegation, RAG retrieval pass, and tool call creates a child span linked by a common traceId.
Multiply input tokens by the model's published input rate per million, output tokens by the output rate, and factor in cached token discounts.