OpenTelemetry for LLMs: Trace Prompt Latency Across Microservices

OpenTelemetry for LLMs: Trace Prompt Latency Across Microservices
25 views

This guide covers OpenTelemetry for LLMs with production-focused examples and trade-offs.

When an IDE agent feels “slow,” the bottleneck might be retrieval, a tool Lambda cold start, Bedrock queueing, or your own JSON schema validator. Without distributed traces, teams guess and scale the wrong tier. OpenTelemetry for LLM workflows means W3C trace context across every hop — proxy, tools, Bedrock, and databases — with attributes that make token latency debuggable.
⚡ TL;DR: Start a root span per user action (agent.turn). Propagate traceparent through HTTP, SQS, and Bedrock request metadata. Record gen_ai.* attributes (model, input/output tokens, cache hit). Tail-sample errors and slow turns. Illustrative debug win: cut MTTR for “assistant p99 > 8s” from hours to minutes by seeing tool vs model breakdown.

Span model for an agent turn — OpenTelemetry for LLMs

text
agent.turn (root)
  ├─ context.assemble
  ├─ retrieve.kb
  ├─ gen_ai.chat (Bedrock Converse)
  │    └─ gen_ai.tool.search_repo
  │         └─ aws.lambda.invoke
  └─ patch.validate
typescript
import { trace, SpanStatusCode, context, propagation } from "@opentelemetry/api";

const tracer = trace.getTracer("cheatcoders-agent");

export async function runTurn(userId: string, prompt: string) {
  return tracer.startActiveSpan("agent.turn", async (span) => {
    span.setAttribute("user.id", userId);
    span.setAttribute("agent.prompt_chars", prompt.length);
    try {
      const answer = await converseWithTools(prompt);
      span.setStatus({ code: SpanStatusCode.OK });
      return answer;
    } catch (err) {
      span.recordException(err as Error);
      span.setStatus({ code: SpanStatusCode.ERROR });
      throw err;
    } finally {
      span.end();
    }
  });
}

Instrument Bedrock with gen_ai attributes

typescript
import {
  BedrockRuntimeClient,
  ConverseCommand,
} from "@aws-sdk/client-bedrock-runtime";

export async function converse(modelId: string, messages: any[]) {
  return tracer.startActiveSpan("gen_ai.chat", async (span) => {
    span.setAttribute("gen_ai.system", "aws.bedrock");
    span.setAttribute("gen_ai.request.model", modelId);
    const started = Date.now();
    const resp = await new BedrockRuntimeClient({}).send(
      new ConverseCommand({
        modelId,
        messages,
        inferenceConfig: { maxTokens: 2048 },
      })
    );
    span.setAttribute("gen_ai.response.model", modelId);
    span.setAttribute("gen_ai.usage.input_tokens", resp.usage?.inputTokens ?? 0);
    span.setAttribute("gen_ai.usage.output_tokens", resp.usage?.outputTokens ?? 0);
    span.setAttribute("gen_ai.latency_ms", Date.now() - started);
    span.end();
    return resp;
  });
}
Propagate context into tool Lambdas via HTTP headers or the Lambda event envelope. For X-Ray coexistence, use ADOT layers carefully — same tradeoffs discussed in Lambda tracing posts on CheatCoders.

Propagate across queues and AppSync

typescript
// Inject W3C headers when enqueueing tool work
const carrier: Record<string, string> = {};
propagation.inject(context.active(), carrier);
await sqs.send({
  QueueUrl,
  MessageBody: JSON.stringify({ args }),
  MessageAttributes: {
    traceparent: { DataType: "String", StringValue: carrier["traceparent"] },
  },
});
On the consumer, propagation.extract before starting the child span. Missing this link is why service maps “lie” during incidents.

Tail sampling and cost

At IDE volume you cannot keep 100% of successful fast turns.
yaml
# illustrative collector policy
policies:
  - name: errors
    type: status_code
    status_code: ERROR
  - name: slow-agent
    type: latency
    threshold_ms: 5000
  - name: baseline
    type: probabilistic
    sampling_percentage: 5
Export to Amazon Managed Prometheus / Grafana or X-Ray via ADOT. Alert on gen_ai.chat p95 and tool error rates — not just host CPU. Pair with LLM cost controls so latency and spend dashboards sit side by side.

Correlate IDE, CI, and cloud backends

Developers feel latency in the IDE; the spans often live in CI runners and AWS. Carry a session.id / pr.number baggage key from the first hop:
typescript
import { propagation, context } from "@opentelemetry/api";

export function withPrBaggage<T>(pr: number, fn: () => Promise<T>) {
  // Use baggage API in your SDK version; illustrative attribute mirror:
  return tracer.startActiveSpan("ci.agent.job", async (span) => {
    span.setAttribute("ci.pr", pr);
    span.setAttribute("ci.pipeline", process.env.GITHUB_WORKFLOW ?? "local");
    try {
      return await fn();
    } finally {
      span.end();
    }
  });
}
When p99 spikes, filter traces by gen_ai.request.model and tool.name before blaming “Bedrock is slow.” Cold tool Lambdas show up as child gaps — fix with provisioned concurrency schedules, not bigger models. Pair with Lambda cold-start discipline when tools run on Lambda.

Closing checklist

✅ Dos – ✅ One root span per agent turn with stable name – ✅ Record model, tokens, cache hit, tool name attributes – ✅ Propagate traceparent across HTTP, SQS, Lambda – ✅ Tail-sample errors and slow turns – ✅ Dashboard tool vs model latency breakdown ❌ Don’ts – ❌ Don’t log raw prompts containing secrets into span attributes – ❌ Don’t rely on logs alone for cross-service LLM latency – ❌ Don’t sample only at the edge and lose tool children – ❌ Don’t attach megabytes of retrieval chunks onto spans – ❌ Don’t forget AppSync/API Gateway header allowlists for trace context

Related reading

Last updated on September 11, 2026

Related: Day 39: Observability: Traces Across Prompt, Retrieval, Tools · Bedrock Prompt Caching (and Batch Inference): Cut Latency and Cost · Microservices vs Monolith: When to Split and When to Stay Merged

Related: Day 72: Prompt Caching Pitfalls · Prompt Engineering for Developers: The Patterns That 10x Your AI Coding Output · Prompt Engineering for Developers: Patterns That Produce Production-Ready Code Every Time

Watch: 100 Days of AI on YouTube

Short videos from CheatCoders, one AI engineering topic per day.

Open the playlist on YouTube · CheatCoders on YouTube

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

3 Comments

Leave a comment

No account needed. Name and email are optional.