Vaanieval
Quickstart

Python

Record your first voice-agent call from Python and open it in the console — about ten minutes, no agent framework required.

By the end of this page you will have a recorded call sitting in a local dashboard, with audio you can play and a trace you can expand.

Before you start

  • Python 3.10 or newer for the SDK
  • Python 3.11 or newer for the dashboard
  • git and network access to GitHub. The repository is public and supports anonymous Git access; this guide installs from Git. The anonymous PyPI check for vaanieval-observer returned HTTP 404 on 20 September 2026; no release was found under that distribution name.

Source installs and hosted beta

The repositories are public and support anonymous Git access. These guides install the SDKs from public Git. Anonymous registry checks on 20 September 2026 returned HTTP 404 for npm @vaanieal/observer and PyPI vaanieval-observer; no release was found under those names. Source availability does not mean the hosted service is ready for public launch: new public workspaces remain closed.

The quickstarts pin published Python 0.5.7b1 and Node 0.1.1-beta.1 source previews. Both installed previews have uploaded synthetic calls to the isolated hosted development environment. Git/build tooling and explicit revision upgrades are required; this is not registry publication or production-readiness certification.

For setup help, email shubham@vaanieval.com or book a call — a short conversation about your use case tells us whether Vaanieval fits, and helps identify the setup that matches your stack.

For an admitted hosted workspace, use the same pinned SDK below and the three-step setup at beta.vaanieval.com. You do not need to run the local dashboard. Public workspace admissions remain closed. This is a source preview (0.5.7b1), not a PyPI release; Git and Python build tooling are required, and upgrades are explicit.

Nothing here requires LiveKit, OpenAI, or any particular provider. If you are on LiveKit Agents, do this page first anyway — then swap in the LiveKit integration, which wires all of it up for you.

1. Run the dashboard

Clone and install

git clone https://github.com/shubhamofbce/vaanieval-observer-backend.git dashboard
cd dashboard
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Start it

uvicorn app.main:app --host 127.0.0.1 --reload --port 8000

The console is served at http://localhost:8000/. It will be empty — you have not recorded anything yet.

Out of the box the local dashboard accepts any non-empty API key without checking it, and has no tenant isolation. That is deliberate for a developer loop; set VAANI_REQUIRE_API_KEY=1 to make ingest require a real key. Read endpoints stay open either way. Keep this local app on localhost; a key does not make it tenant-safe. Hosted beta uses a separate entrypoint and is under construction. See Self-hosting.

2. Install the SDK

pip install "git+https://github.com/shubhamofbce/vaanieval-observer-python-sdk.git@0797c0fcdac6039f8830e6c726e0e593d36cf646"

The SDK has no required runtime dependencies. Install the extra that matches the HTTP client your agent already uses, so provider calls are instrumented automatically:

pip install "vaanieval-observer[httpx] @ git+https://github.com/shubhamofbce/vaanieval-observer-python-sdk.git@0797c0fcdac6039f8830e6c726e0e593d36cf646"

3. Record a call

This script records one synthetic call: two turns, a transcription span, a model span, and audio on both channels. Run it against the dashboard you just started.

record_a_call.py
import asyncio
import math
import struct

from vaani_observer import VaaniObserver

SAMPLE_RATE = 16000
FORMAT = {"encoding": "pcm_s16le", "sample_rate_hz": SAMPLE_RATE, "channels": 1}


def tone(hz: int, ms: int) -> bytes:
    """A second of audible PCM, so the recording has something to play."""
    frames = int(SAMPLE_RATE * ms / 1000)
    return b"".join(
        struct.pack("<h", int(9000 * math.sin(2 * math.pi * hz * n / SAMPLE_RATE)))
        for n in range(frames)
    )


async def main() -> None:
    vaani = VaaniObserver(
        endpoint="http://localhost:8000",
        api_key="local-dev",              # the local dashboard does not validate this
        spool_directory="./.vaani-spool",
        endpoints=[
            {"id": "llm", "type": "llm", "url": "https://api.openai.com/v1"},
        ],
    )

    session = vaani.start_session(agent_id="quickstart-agent")

    for index in range(2):
        turn = session.start_turn()

        # The caller speaks.
        stt = session.start_operation(type="stt", turn_id=turn.id, provider="demo")
        stt.event("speech_started")
        session.record_inbound_audio(tone(220, 900), FORMAT)
        await asyncio.sleep(0.9)
        stt.event("final_transcript")
        stt.end(status="ok", response={"transcript": f"caller line {index + 1}"})

        # The model thinks.
        llm = session.start_operation(
            type="llm", turn_id=turn.id, provider="demo", model="demo-model"
        )
        await asyncio.sleep(0.4)
        llm.event("first_token")
        llm.end(status="ok", response={"tokens": 128})

        # The agent speaks.
        tts = session.start_operation(type="tts", turn_id=turn.id, provider="demo")
        tts.event("first_byte")
        session.record_outbound_audio(tone(440, 1200), FORMAT)
        tts.end(status="ok")

        turn.end()

    finalized = await session.end(outcome="completed")
    print("package written to", finalized.directory)

    await vaani.upload_package(finalized)
    print("uploaded session", finalized.session_id)


asyncio.run(main())
python record_a_call.py

You should see something like:

package written to ./.vaani-spool/6f1c...-...
uploaded session 6f1c...-...

4. Open the call

Refresh http://localhost:8000/. Your call is in the rail on the left. Click it and you get the waveform, the turn markers, and a trace you can expand down to individual milestones.

An isolated localhost trace for synthetic-review with one visible tool span, one second of silent audio and no measured voice timing; no provider retries are shown.

The documentation screenshot is a separate synthetic, tool-only fixture, not the two-turn script above or a hosted customer call.

Look at the spool directory too — .vaani-spool/&lt;session-id&gt;/ contains manifest.json, events.jsonl and call.audio. These contain the captured evidence; optional evaluation adds external model outputs. See Session package.

What just happened

VaaniObserver was configured, not connected

Constructing the observer installs HTTP and websocket instrumentation in-process and validates your endpoint rules. It does not open a connection to the dashboard. endpoint and api_key are only used by upload_package().

The session spooled to disk as the call ran

Every event and audio chunk was appended to ./.vaani-spool/<session-id>/ by a dedicated writer thread, so no filesystem syscall ran on the event loop. Audio arrives every 20 ms in a real agent, and a blocking write there is audible.

end() finalized the package

It closed open spans, flushed the writer, composed the two mono tracks into one timeline-aligned stereo call.audio (agent left, caller right), and wrote manifest.json last — via a temp file and rename, so a reader never sees a half-written manifest.

upload_package() shipped it, after the call

Three steps: POST /v1/sessions with the manifest, PUT each object to the URL the dashboard returned, then POST .../complete with byte sizes and SHA-256 digests. Upload is always explicit and always post-call — the library never uploads on the live media path.

Wire it into a real agent

The synthetic script called start_operation() by hand. In a real agent you will usually want two things instead:

  • Automatic HTTP capture. Any httpx or aiohttp request made inside with session.context(): whose URL matches one of your configured endpoints is timed and recorded for you, with no code change at the call site.
  • Turn grouping. Wrap the work for one caller utterance in with session.with_turn(turn.id): so auto-instrumented calls land on the right turn.
session = vaani.start_session(agent_id="support")
turn = session.start_turn()

with session.with_turn(turn.id):
    # Instrumented automatically because api.openai.com matches an endpoint rule.
    response = await client.chat.completions.create(...)

Read Recording a call next, or go straight to LiveKit Agents if that is your framework.

Troubleshooting

On this page