Demo OpenTelemetry Overlay¶
The optional demo/docker/compose/otel.yml overlay instruments the demo
deployment. It does not add telemetry behavior to the
langgraph-openai-serve package.
The demo applications send standard OTLP signals to one local OpenTelemetry Collector. That Collector filters, enriches, batches, and forwards them to the gateway selected by the deployment. The remote gateway and observability backend are intentionally not bundled.
Signal Path¶
flowchart LR
subgraph demo["Demo Compose deployment"]
direction TB
clients["Chainlit and Open WebUI"]
gateways["Bifrost or LiteLLM"]
apis["Demo API<br/>coding-agent API"]
worker["Hatchet background worker"]
collector["Local OpenTelemetry Collector"]
clients -->|"traces"| collector
gateways -->|"traces; LiteLLM metrics"| collector
apis -->|"traces, metrics, and logs"| collector
worker -->|"traces, metrics, and logs"| collector
end
collector -->|"OTLP/HTTP"| gateway["External Collector gateway"]
gateway --> backend["External observability backend<br/>(for example Grafana LGTM)"]
apis -.->|"LangGraph observations"| langfuse["Langfuse"]
The local Collector handles standard OTLP signals. Langfuse remains a separate native export path from the API and worker processes.
Run The Overlay¶
Copy demo/.env.example to demo/.env, then configure the required values:
OTEL_COLLECTOR_GATEWAY_ENDPOINT is an OTLP/HTTP base URL, not an observability
UI URL. The Collector appends /v1/traces, /v1/metrics, and /v1/logs.
Use https:// unless the gateway deliberately accepts cleartext traffic.
Generate a provider-free request after the stack becomes healthy:
Exact environment settings are listed in Demo Settings And Commands.
Signal Ownership¶
| Producer | Exported signals | Demo integration |
|---|---|---|
| LGOS API processes | Traces, metrics, and logs | Python auto-instrumentation, FastAPI's native request telemetry, LGOS workflow telemetry, and Hatchet's native producer spans |
| Coding-agent API | Traces, metrics, and logs | Python auto-instrumentation, FastAPI's native request telemetry, and LGOS workflow telemetry |
| Files API | Traces, metrics, and logs | Python auto-instrumentation and FastAPI's native request telemetry |
| Hatchet background worker | Traces, metrics, and logs | Python auto-instrumentation, LGOS workflow telemetry, and Hatchet's native task spans |
| Chainlit | Traces | Python auto-instrumentation and FastAPI's native request telemetry; the long-lived Socket.IO connection and prompt-recording OpenAI instrumentors are excluded |
| Open WebUI | Traces | Open WebUI's native OpenTelemetry settings |
| Bifrost | Traces | Bifrost's OpenTelemetry plugin with content logging disabled |
| LiteLLM | Traces and GenAI metrics | LiteLLM's native OpenTelemetry v2 integration with message-content capture disabled |
| Local Collector | Its own metrics | Direct OTLP/HTTP export to the configured gateway |
LGOS reports each graph run as a GenAI workflow span and duration metric; see
the package OpenTelemetry reference.
opentelemetry-instrument configures each demo process's providers, OTLP
export, logging, and client instrumentation. Each demo FastAPI application
records its own requests with FastAPI's native
OpenTelemetry through
those providers and sets auto_configure to False, so FastAPI adds no second
export. Health checks and Chainlit's Socket.IO connection are not traced. Routes
include the mount, for example http.route=/v1/responses. Commands that Codex
runs inside its own process are progress statuses, not spans. W3C trace context
connects requests across the UI, proxy, gateway, and API when every hop
preserves traceparent.
Bifrost's managed Responses route forwards Idempotency-Key, traceparent,
and tracestate through the explicit client header allowlist in
demo/docker/configs/bifrost/config.json. The trace's root service,
lgos-chainlit or lgos-openwebui, identifies the originating UI. Each UI also
sends its name as User-Agent, which the gateway's request span records. The
Collector removes Bifrost's high-cardinality idempotency header attribute before
export; the header does not replace or alter W3C trace context.
LiteLLM continues an incoming W3C traceparent and forwards it to every model
target, so its HTTP, authentication, database, and model-call spans stay in the
same UI-to-LGOS trace. The upstream models receive the header too. Confirm
that any other provider accepts it before adding that provider. LiteLLM's native GenAI histograms cover operation duration, token
usage, cost, time to first token, time per output token, and provider response
duration. The bundled configuration limits metric labels to operation and
provider; requested model remains on model-call spans. LiteLLM adds token type
and error type where applicable. This keeps the Prometheus series bounded while
retaining the dimensions used by gateway dashboards.
Prompt and response bodies remain excluded from spans through
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=no_content. LiteLLM's
database spend-log setting is independent of this OTLP policy.
LiteLLM exception events remain disabled
The bundled image can create correlated OpenTelemetry log events for failed model operations, but its pinned OTLP encoder drops the current body-less events. The overlay therefore leaves event export disabled while the upstream bug remains. Operational LiteLLM logs remain available on stdout.
Use these values when querying LGOS Responses telemetry:
| Signal | Attribute or span name |
|---|---|
| HTTP request metrics | http.route=/v1/responses (http_route in Prometheus) |
| API request span | POST /v1/responses |
| Graph execution span | invoke_workflow {model}, with gen_ai.operation.name=invoke_workflow |
| Graph run metric | gen_ai.invoke_workflow.duration (gen_ai_invoke_workflow_duration_seconds in Prometheus) |
| Failed graph run | error.type on the workflow span and metric |
| Originating UI | The trace's root service: lgos-chainlit or lgos-openwebui |
| Conversation correlation | gen_ai.conversation.id, supplied through metadata.conversation_id |
LGOS records the workflow span and metric; HTTP spans and metrics come from
FastAPI. A run that raised an exception or stopped before its output, through a
Stop click, a closed stream, or a cancelled background Response, carries
error.type, so failure panels include cancellations. Its value, such as
asyncio.exceptions.CancelledError or GeneratorExit, tells them apart.
The /v1/models diagnostic above verifies export, but does not populate
Responses request panels. Send a message from either UI to verify
those panels and conversation links.
The API also keeps structured JSON logs on stdout. Enabling OTLP logs adds a
second delivery path for those standard-library records; it does not remove
container diagnostics. X-Request-ID, background Idempotency-Key, the LGOS
interrupt operation ID, and the OpenTelemetry trace ID remain separate
correlation values. See
Production Logging for their ownership.
Hatchet Background Runs¶
Enable the background worker and run the same
just demo/compose --dev --otel command. The worker uses service name
lgos-background-worker and sends telemetry to the local Collector alongside
the demo API.
Both processes use the SDK's
HatchetInstrumentor. The API's
hatchet.run_workflow span continues the HTTP request trace. Hatchet carries
its W3C traceparent through native task metadata, so the worker's
hatchet.start_step_run span continues that trace after the request finishes.
The worker span includes hatchet.workflow_run_id for correlation with Hatchet.
opentelemetry-instrument configures the shared provider, exporters, and exit
flush in each process. The demo passes enable_hatchet_otel_collector=False
to the Hatchet instrumentor so all spans follow the existing Collector path.
Instrumentation is enabled when OTEL_TRACES_EXPORTER selects an exporter.
Hatchet's native otel extra supplies its instrumentation dependencies.
The API and worker route Hatchet SDK logs through the shared JSON and OTel root
handlers. The worker also sets the root level to INFO, which Hatchet uses as
the threshold for forwarding task logs to its own log viewer.
Keep this setting from demo/.env.example in demo/.env:
The SDK excludes task inputs and caller metadata from span attributes while
preserving trace propagation. Hatchet still stores task inputs and results as
part of normal execution. To verify tracing without an LLM provider, submit a
background Response to background-mock and find its producer and
worker spans under the same trace ID.
Query Foreground And Background Runs¶
In background mode, the API span ends after submission; the worker's workflow
span and measurement carry the execution duration and gen_ai.conversation.id.
Requiring a conversation ID on POST /v1/responses omits these runs. Only graph
services emit the workflow metric, so graph run, failure, and duration panels
need no service filter:
sum(increase(gen_ai_invoke_workflow_duration_seconds_count{service_namespace="lgos", error_type!=""}[$__range]))
For a Chainlit conversation table, query the workflow span in both execution modes:
{ resource.service.namespace = "lgos" && resource.deployment.environment.name = "$environment" && resource.service.name = "lgos-chainlit" }
>> { resource.service.namespace = "lgos" && resource.deployment.environment.name = "$environment" && span.gen_ai.operation.name = "invoke_workflow" && span.gen_ai.conversation.id != nil }
| select(span.gen_ai.conversation.id)
Use lgos-openwebui for the other client. The descendant operator preserves
client attribution through the gateway and Hatchet. Durations exclude Hatchet
queue time, and graph rows appear after the span finishes.
HTTP request metrics need a service filter because LiteLLM also reports
http.route=/v1/responses. Fill it from the services that emit the workflow
metric rather than a fixed list, for example with a Grafana variable:
Adding a graph service then needs no dashboard change.
Graph-run counters start when a process records its first run. Prometheus 3.7
and later counts that first value in increase() and rate() only with
--enable-feature=created-timestamp-zero-ingestion, which writes each OTLP
series' start time as a zero sample. Without it, low-traffic panels undercount.
Collector Behavior¶
The local Collector:
- accepts OTLP/gRPC and OTLP/HTTP only on its internal ingest network;
- removes Open WebUI's streamed ASGI transport spans and known prompt/response payload attributes before data reaches its persistent queue;
- adds the configured service namespace, environment, and host identity;
- retries and batches export through a file-backed queue capped at 256 MiB; and
- forwards traces, metrics, and logs over OTLP/HTTP.
These filters reduce accidental payload export but are not a complete redaction boundary. Exceptions, tracebacks, and caller-controlled values can still contain sensitive data.
To verify the pipeline, locate service lgos-demo-api in the configured
backend and correlate the request with lgos-otel-e2e. Monitor the Collector's
exporter queue, capacity, send-failure, and rejected-data metrics in that backend.
Langfuse Remains Separate¶
When LGOS_ENABLE_LANGFUSE=True, LGOS adds the Langfuse callback to graph runs.
Langfuse records its observations through a tracer provider of its own and
exports them to Langfuse directly, so they never enter this Collector's
pipeline; the local Collector is not a Langfuse proxy. LGOS's workflow span
stays out of Langfuse, so each Langfuse trace starts at the callback's
lgos.graph_run observation, and it shares its trace ID with the run's
OpenTelemetry trace. Do not add a second Langfuse exporter unless the deployment
intentionally owns and tests that additional path.
Langfuse stores model inputs and outputs. Configure its own masking and retention policy before enabling it for sensitive workloads.
Deployment Responsibilities¶
The overlay does not choose the remote backend or its authentication, retention, sampling, access-control, and capacity policies. It also does not replace ingress access logs. Before production use, the deployment must:
- secure the OTLP gateway and validate TLS and authentication;
- measure representative trace volume and size backend retention accordingly;
- define payload minimization and redaction rules; and
- monitor the Collector queue and remote-export failures.
The OpenTelemetry Collector and sensitive-data guides define the upstream operational boundaries.