> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getpatter.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tracing (OpenTelemetry)

> Opt-in OpenTelemetry tracing for STT, LLM, TTS, and tool calls.

Patter ships opt-in OpenTelemetry tracing that covers the four hot spots on the voice pipeline: STT, LLM, TTS, and tool calls. Every span carries the current `getpatter.call.id` so you can group by call in your backend.

## Enable tracing

Tracing is disabled by default. Install the optional dependency and set the env flag:

```bash theme={null}
pip install 'getpatter[tracing]'
export PATTER_OTEL_ENABLED=1
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318   # any OTLP/HTTP collector
```

Then call `init_tracing` once at process start — typically from the same module that creates your `Patter` client:

```python theme={null}
from getpatter.observability import init_tracing

init_tracing(
    service_name="my-voice-bot",
    otlp_endpoint="http://localhost:4318",
    resource_attributes={"deployment.environment": "staging"},
)
```

If `PATTER_OTEL_ENABLED` is not set, `init_tracing` returns `False` and every span becomes a no-op — **zero cost** when disabled.

## Emitted spans

| Span name            | Fires                                                                                                | Attributes                                                                       |
| -------------------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `getpatter.call`     | One per call (root span)                                                                             | `getpatter.call.id`, `getpatter.call.direction`, `getpatter.call.provider_mode`  |
| `getpatter.stt`      | One per final transcript                                                                             | `getpatter.stt.text_len`, `getpatter.stt.confidence`                             |
| `getpatter.endpoint` | One per detected end-of-utterance                                                                    | `getpatter.endpoint.silence_ms`                                                  |
| `getpatter.llm`      | One per LLM iteration (incl. tool rounds) — wraps the pipeline LLM call so TTFT is measured per turn | `getpatter.llm.iteration`, `getpatter.llm.history_size`, `getpatter.llm.ttft_ms` |
| `getpatter.tts`      | One per synthesized sentence                                                                         | `getpatter.tts.text_len`                                                         |
| `getpatter.bargein`  | One per barge-in event                                                                               | `getpatter.bargein.cancelled_text_len`                                           |
| `getpatter.tool`     | One per tool invocation                                                                              | `getpatter.tool.name`, `getpatter.tool.transport`                                |

All TS spans use the same `getpatter.*` namespace as Python (the legacy `patter.*` names were normalised in 0.5.3 — both SDKs now emit identical span names so a single dashboard query works across runtimes).

## PII hygiene

Patter never exports user utterances, tool payloads, or LLM content as span attributes. Only sizes, counts, and identifiers are emitted — traces are safe to ship to a shared Jaeger / Honeycomb / Grafana Cloud instance.

## Cost and latency attributes (`patter.*`)

Beyond the spans listed above, every billable hot path stamps `patter.cost.*` and `patter.latency.*` attributes on its span starting in 0.6.0. External aggregators (e.g. the [patter-agent-runner acceptance suite](https://github.com/PatterAI/patter-agent-runner)) read these directly to compute per-call USD and latency without touching the SDK's pricing table.

| Attribute                                                                              | Type            | Where it fires                                                                                           |
| -------------------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------- |
| `patter.call_id`, `patter.side`                                                        | str             | Every cost/latency span (routing tags)                                                                   |
| `patter.cost.telephony_minutes`, `patter.telephony`, `patter.direction`                | float / str     | Twilio + Telnyx adapters on call end                                                                     |
| `patter.cost.stt_seconds`, `patter.stt.provider`                                       | float / str     | Each final transcript (Deepgram, AssemblyAI, Whisper, OpenAI-Transcribe, Soniox, Speechmatics, Cartesia) |
| `patter.cost.tts_chars`, `patter.tts.provider`                                         | int / str       | Each synthesis (ElevenLabs, OpenAI, Cartesia, LMNT, Rime)                                                |
| `patter.cost.llm_input_tokens`, `patter.cost.llm_output_tokens`, `patter.llm.provider` | int / int / str | Each completion (OpenAI, Anthropic, Google Gemini, Groq, Cerebras)                                       |
| `patter.cost.realtime_minutes`, `patter.realtime.provider`                             | float / str     | OpenAI Realtime + ElevenLabs ConvAI on session end                                                       |
| `patter.latency.ttfb_ms`, `patter.latency.turn_ms`                                     | float           | Per completed agent turn (pipeline mode)                                                                 |

The two routing tags (`patter.call_id`, `patter.side`) propagate via asyncio-safe ContextVars set at the top of the per-call WebSocket bridge, so all spans emitted under a call inherit them automatically — no manual wiring per span.

### Attach a custom exporter (`Patter._attach_span_exporter`)

Embedding tools that observe Patter from the outside can wire their own exporter without touching `PATTER_OTEL_ENABLED` or `init_tracing`:

```python theme={null}
from opentelemetry.sdk.trace.export import SpanExporter
from getpatter import Patter

exporter: SpanExporter = my_exporter()  # any OTel SpanExporter
phone = Patter(...)
phone._attach_span_exporter(exporter, side="driver")  # or side="uut" (default)
```

`side` is stamped on every cost/latency span this Patter instance emits during its call lifecycle. It exists to disambiguate two-`Patter`-instances-in-one-process layouts (e.g. driver vs unit-under-test in agent-to-agent acceptance tests). Default is `"uut"` if you only have one instance.

The leading underscore signals this is not part of the customer-facing API surface — it is a stable, public-but-underscore hook for tooling.

## Shutdown

Call `shutdown_tracing()` during graceful shutdown to flush any pending spans:

```python theme={null}
from getpatter.observability import shutdown_tracing

shutdown_tracing()
```

## Troubleshooting

**No spans appear** → confirm `PATTER_OTEL_ENABLED=1` is set *in the process that calls `init_tracing`*. Quick check:

```python theme={null}
from getpatter.observability import is_enabled
print(is_enabled())
```

**Collector refuses spans** → the endpoint defaults to OTLP/HTTP on port 4318. If you're running the gRPC-only OTel Collector image, switch to `otel/opentelemetry-collector-contrib` or enable the HTTP receiver in your collector config.

<Info>
  Tracing has full TypeScript parity since 0.5.3 — span names, attributes, and PII hygiene are identical. See [TS Tracing](/typescript-sdk/tracing).
</Info>
