DEV Community

Cover image for Tracing LLM Requests End-to-End
Andrei Popescu
Andrei Popescu

Posted on

Tracing LLM Requests End-to-End

Tracing LLM Requests End-to-End

Traditional application logs can tell you that an LLM-powered system is running, but they can't tell you if it's working correctly. End-to-end tracing provides the necessary visibility to debug failures, optimize performance, and understand the complex, multi-step execution paths of modern AI applications.

LLM-powered applications often fail silently. Instead of throwing a 500 error, they return a confident, grammatically perfect, and completely wrong answer. This makes debugging with traditional logs a process of guesswork. When a user gets a bad response, was the cause a poorly formed prompt, a slow database query, a retrieval step that pulled irrelevant context, or a model hallucination? Without a clear view of the application's internal workflow, it's nearly impossible to know.

This is the problem that distributed tracing solves. By recording the path of a single request as it flows through the various components of an application, tracing transforms an opaque black box into a transparent system. It's an essential practice for building reliable AI, especially for complex Retrieval-Augmented Generation (RAG) pipelines and multi-agent systems.

What is an LLM Trace?

An LLM trace is a complete, structured record of a single request's journey through your application. It's composed of a hierarchy of timed operations called spans.

  • Trace: Represents the entire end-to-end execution for a single user request, like a user asking a question to a chatbot. A trace is essentially a collection of all its related spans.
  • Span: Represents a single, discrete unit of work within the trace. In an LLM application, a span could be a call to a vector database, a function that formats a prompt, or an API call to an LLM provider.

Each span contains a name, a start and end time, and a rich set of key-value metadata called attributes. These attributes are critical for LLM observability, capturing details like the model name, prompt/completion content, token counts, and temperature settings.

A stylized, abstract representation of a single timeline branching into several smaller, nested timelines, depicting a t

This hierarchical structure allows developers to visualize the entire workflow, see the duration of each step, and inspect the specific data that flowed through it. If a RAG application returns an irrelevant answer, a trace can immediately show whether the problem was in the retrieval step (e.g., wrong documents were fetched) or the generation step (e.g., the LLM failed to use the provided context correctly).

Why OpenTelemetry is the Standard

To make tracing work across different services, languages, and platforms, a standardized approach is necessary. OpenTelemetry (OTel), a Cloud Native Computing Foundation (CNCF) project, has emerged as the industry standard for instrumenting, generating, and collecting telemetry data. It provides a unified set of APIs and libraries that let you instrument your code once and send the data to any compatible backend.

OpenTelemetry solves the problem of vendor lock-in and fragmented observability. Before OTel, tracing systems used proprietary headers, causing traces to break at the boundaries between services instrumented by different vendors. OTel standardizes this with components like:

  • APIs and SDKs: For instrumenting code in various languages.
  • The OTel Collector: A flexible component for receiving, processing, and exporting telemetry data.
  • OpenTelemetry Protocol (OTLP): A general-purpose protocol for transmitting telemetry data between sources, collectors, and backends.

For LLM applications, projects like OpenLLMetry extend the OpenTelemetry standard with semantic conventions specific to generative AI, ensuring that data like prompt content and token usage are captured consistently.

How Context Propagation Works

The magic that stitches spans together across service boundaries is called context propagation. Distributed tracing relies on passing a unique identifier with every request as it hops between services. The W3C Trace Context specification defines a standard set of HTTP headers that all compliant tools can understand, solving the interoperability problem.

The two key headers are:

  1. traceparent: Carries the essential, universally understood context: a version, a unique trace-id, a parent-id (the ID of the calling span), and trace-flags for sampling decisions.
  2. tracestate: An optional header that allows different tracing vendors to include their own proprietary information without breaking the trace.

OpenTelemetry uses W3C Trace Context as its default format, so any application instrumented with OTel can automatically participate in a distributed trace.

A visual metaphor of several services as distinct islands, with glowing light bridges connecting them, representing W3C

Implementing Tracing in an LLM App

Getting started with tracing involves a few key steps.

  1. Choose a Tracing Framework: For most teams, this means adopting OpenTelemetry. It's vendor-agnostic and has broad support across languages and frameworks like LangChain and LlamaIndex.
  2. Instrument Your Application: Instrumentation is the process of adding code to your application to capture and export trace data.
    • Auto-instrumentation: Many OpenTelemetry SDKs provide automatic instrumentation for common libraries (e.g., HTTP clients, database drivers, LLM SDKs). This is the fastest way to get started.
    • Manual Instrumentation: For more granular control, you can manually create spans to wrap specific functions or business logic. This allows you to define custom attributes and get deeper visibility into your application's behavior.
  3. Configure an Exporter: The instrumented code uses an exporter to send trace data to a backend. The OTLP exporter can send data to an OpenTelemetry Collector or directly to a compatible observability platform.
  4. Select a Backend: A backend is where you store, visualize, and analyze your traces. Options range from open-source tools like Jaeger and Zipkin to comprehensive commercial and open-source observability platforms like LangSmith, Langfuse, Arize, and many others.

Here is a simplified Python example showing manual instrumentation with the OpenTelemetry SDK for a RAG pipeline:

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor

# Configure the tracer to print to the console
trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(
    SimpleSpanProcessor(ConsoleSpanExporter())
)

tracer = trace.get_tracer(__name__)

def retrieve_documents(query: str) -> list[str]:
    with tracer.start_as_current_span("retrieve_documents") as span:
        span.set_attribute("db.query", query)
        # In a real app, this would query a vector database
        documents = [f"Document about '{query}'"]
        span.set_attribute("db.retrieved_count", len(documents))
        return documents

def generate_response(query: str, context: list[str]) -> str:
    with tracer.start_as_current_span("generate_response") as span:
        prompt = f"Query: {query}\n\nContext: {context}"
        span.set_attribute("llm.prompt", prompt)
        span.set_attribute("llm.model_name", "gpt-4")
        # In a real app, this would call an LLM API
        response = f"This is a generated answer about '{query}'."
        span.set_attribute("llm.response", response)
        return response

def rag_pipeline(query: str):
    with tracer.start_as_current_span("rag_pipeline_trace") as parent_span:
        parent_span.set_attribute("user.query", query)
        documents = retrieve_documents(query)
        final_answer = generate_response(query, documents)
        print(final_answer)

rag_pipeline("What is distributed tracing?")
Enter fullscreen mode Exit fullscreen mode

Tracing Beyond the Basics: Multi-Agent Systems

As applications evolve from simple RAG pipelines to complex, multi-agent systems, the need for robust tracing becomes even more critical. In an agentic workflow, an initial user request can trigger a cascade of interactions between different agents, tools, and API calls. Distributed tracing is the only way to visualize these causal chains and understand how an initial prompt leads to a series of handoffs and tool executions.

By instrumenting each agent and tool call as a span, developers can debug non-deterministic behaviors, optimize token usage across an entire fleet of agents, and pinpoint the root cause of failures in complex, emergent workflows.

Tracing is no longer a "nice-to-have" for LLM applications; it is a foundational component of a modern observability stack. It provides the ground truth needed to move from guessing to knowing, enabling teams to build, deploy, and scale reliable AI products with confidence.

Sources

Top comments (0)