Skip to main content
Using a coding agent? Paste this prompt directly in your voice agent codebase to jumpstart your Cekura OTel integration.

Overview

OpenTelemetry (OTel) tracing gives you deep visibility into your voice agent’s execution - every LLM call, TTS request, STT transcription, and tool invocation captured as spans with timing, token usage, and metadata. Once traces are flowing into Cekura, you can:
  • View span timelines and waterfall diagrams for each call
  • Identify latency bottlenecks (slow LLM responses, TTS delays)
  • Track token usage and model performance across calls
  • Debug tool call failures with full input/output context
Using Pipecat? The Cekura Python SDK instruments your pipeline automatically - no manual setup needed. See the Pipecat Tracing guide.

Prerequisites

  • A Cekura account with an API key
  • An Agent ID or Project ID from the Cekura dashboard
  • Python 3.8+ (examples use Python, but any OTel-compatible language works)

Install Dependencies

Endpoints

ProtocolEndpointWhen to use
gRPCotel.cekura.ai:443Recommended for most use cases. Lower overhead, better for high-volume tracing.
HTTPotel-http.cekura.ai:443Use when gRPC is not available (e.g. environments that don’t support HTTP/2).

Authentication

Every request to the OTel endpoint requires two headers:
HeaderRequiredDescription
x-cekura-api-keyYesYour Cekura API key
x-cekura-agent-idYes*The agent ID to associate traces with
x-cekura-project-idYes*Alternative to agent ID - use project-level scoping
* Provide either x-cekura-agent-id or x-cekura-project-id. These are passed as exporter headers (HTTP) or gRPC metadata, not as span attributes.

Setup

1

Configure the OTel exporter

Set up the exporter with your Cekura credentials:
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource

# Create the exporter with Cekura auth headers
exporter = OTLPSpanExporter(
    endpoint="otel.cekura.ai:443",
    headers=(
        ("x-cekura-api-key", "your-api-key"),
        ("x-cekura-agent-id", "your-agent-id"),
    ),
)

# Set up the tracer provider
resource = Resource.create({"service.name": "my-voice-agent"})
provider = TracerProvider(resource=resource)
provider.add_span_processor(BatchSpanProcessor(exporter))

# Get a tracer
tracer = provider.get_tracer("my-voice-agent")
2

Instrument your agent code

Create spans around each service call in your agent. Use the span names from the naming conventions for best results in the Cekura UI.
def handle_conversation_turn(user_audio):
    # Speech-to-text
    with tracer.start_as_current_span("stt") as stt_span:
        transcript = stt_service.transcribe(user_audio)
        stt_span.set_attribute("stt.provider", "deepgram")
        stt_span.set_attribute("stt.transcript", transcript)

    # LLM call
    with tracer.start_as_current_span("llm") as llm_span:
        response = llm_service.chat(transcript)
        llm_span.set_attribute("gen_ai.system", "openai")
        llm_span.set_attribute("gen_ai.request.model", "gpt-4o")
        llm_span.set_attribute("gen_ai.usage.input_tokens", response.usage.prompt_tokens)
        llm_span.set_attribute("gen_ai.usage.output_tokens", response.usage.completion_tokens)

    # Text-to-speech
    with tracer.start_as_current_span("tts") as tts_span:
        audio = tts_service.synthesize(response.text)
        tts_span.set_attribute("tts.provider", "elevenlabs")
        tts_span.set_attribute("tts.characters", len(response.text))

    return audio
3

Capture the trace ID

Extract the trace ID from your root span - you’ll need this to correlate the trace with the call log in Cekura.
from opentelemetry.trace import format_trace_id

# Start a root span for the entire call
with tracer.start_as_current_span("conversation") as root_span:
    # Get the trace ID (32-character hex string)
    trace_id = format_trace_id(root_span.get_span_context().trace_id)

    # Run your agent conversation loop
    while call_active:
        handle_conversation_turn(user_audio)

# trace_id is now something like "4bf92f3577b34da6a3ce929d0e0e4736"
4

Send the trace ID with your call log

When sending the call log to Cekura via the Send Calls API, include the trace_id field. This links the OTel trace to the call in the dashboard.
import requests

requests.post(
    "https://api.cekura.ai/observability/v1/observe/",
    headers={
        "x-cekura-api-key": "your-api-key",
    },
    json={
        "agent": your_agent_id,
        "call_id": "unique-call-id",
        "trace_id": trace_id,  # 32-char hex from step 3
        "transcript_json": transcript_data,
        # ... other fields
    },
)
Once the call log is submitted with a trace_id, the trace will be visible in the call details page in the Cekura dashboard.

Span Naming Conventions

Cekura recognizes these span names and renders them with specialized UI:
Span NamePurposeRecommended Attributes
sttSpeech-to-textstt.provider, stt.transcript, stt.confidence
llmLLM / model callgen_ai.system, gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens
ttsText-to-speechtts.provider, tts.characters
tool_callTool / function callfunction.name, function.input, function.output
Custom span names work too - they just won’t have specialized styling in the UI.

Example: Full Voice Agent

Here’s a complete example showing a voice agent with OTel tracing and Cekura integration:
import os
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.trace import format_trace_id

CEKURA_API_KEY = os.getenv("CEKURA_API_KEY")
AGENT_ID = os.getenv("CEKURA_AGENT_ID")

# 1. Set up OTel tracing
exporter = OTLPSpanExporter(
    endpoint="otel.cekura.ai:443",
    headers=(
        ("x-cekura-api-key", CEKURA_API_KEY),
        ("x-cekura-agent-id", AGENT_ID),
    ),
)

provider = TracerProvider(
    resource=Resource.create({"service.name": "my-voice-agent"})
)
provider.add_span_processor(BatchSpanProcessor(exporter))
tracer = provider.get_tracer("my-voice-agent")


# 2. Agent conversation with tracing
def run_call(call_id, user_audio_stream):
    with tracer.start_as_current_span("conversation") as root_span:
        trace_id = format_trace_id(root_span.get_span_context().trace_id)
        transcript = []

        for audio_chunk in user_audio_stream:
            # STT
            with tracer.start_as_current_span("stt") as span:
                text = stt.transcribe(audio_chunk)
                span.set_attribute("stt.provider", "deepgram")
                span.set_attribute("stt.transcript", text)
                transcript.append({"role": "user", "content": text})

            # LLM
            with tracer.start_as_current_span("llm") as span:
                response = llm.chat(transcript)
                span.set_attribute("gen_ai.request.model", "gpt-4o")
                span.set_attribute("gen_ai.usage.input_tokens", response.usage.prompt_tokens)
                span.set_attribute("gen_ai.usage.output_tokens", response.usage.completion_tokens)
                transcript.append({"role": "assistant", "content": response.text})

            # TTS
            with tracer.start_as_current_span("tts") as span:
                audio = tts.synthesize(response.text)
                span.set_attribute("tts.provider", "elevenlabs")

            yield audio

    # 3. Flush traces and send call log to Cekura
    provider.force_flush()

    requests.post(
        "https://api.cekura.ai/observability/v1/observe/",
        headers={"x-cekura-api-key": CEKURA_API_KEY},
        json={
            "agent": int(AGENT_ID),
            "call_id": call_id,
            "trace_id": trace_id,
            "transcript_json": transcript,
        },
    )

Next Steps

  • Pipecat Tracing - auto-instrumentation for Pipecat agents
  • Send Calls API - full API reference for the observe endpoint
  • Custom Metrics - evaluate agent performance from your call data
  • Cekura MCP Server - use MCP tools to create tests, run simulations, and monitor production calls directly from your IDE or AI assistant