Metadata-Version: 2.5
Name: earshot-observability
Version: 0.4.0
Summary: The open-source Voice Observability SDK for real-time voice AI
Project-URL: Homepage, https://github.com/Kkartik14/Earshot
Project-URL: Documentation, https://github.com/Kkartik14/Earshot/tree/main/docs
Project-URL: Repository, https://github.com/Kkartik14/Earshot.git
Project-URL: Issues, https://github.com/Kkartik14/Earshot/issues
Project-URL: Changelog, https://github.com/Kkartik14/Earshot/releases
Author: Earshot contributors
License: Apache-2.0
License-File: LICENSE
Keywords: llmops,observability,opentelemetry,sdk,voice-ai
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: protobuf<7,>=6.33.5
Requires-Dist: pydantic<3,>=2.9
Requires-Dist: rfc8785==0.1.4
Provides-Extra: dev
Requires-Dist: alembic<2,>=1.13; extra == 'dev'
Requires-Dist: boto3<2,>=1.36; extra == 'dev'
Requires-Dist: build<2,>=1.2; extra == 'dev'
Requires-Dist: cryptography<46,>=42; extra == 'dev'
Requires-Dist: fastapi<1,>=0.115; extra == 'dev'
Requires-Dist: googleapis-common-protos<2,>=1.56; extra == 'dev'
Requires-Dist: grpcio-tools==1.81.1; extra == 'dev'
Requires-Dist: httpx<1,>=0.27; extra == 'dev'
Requires-Dist: hypothesis<7,>=6.120; extra == 'dev'
Requires-Dist: numpy<3,>=1.26; extra == 'dev'
Requires-Dist: onnxruntime<2,>=1.20; extra == 'dev'
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.30; extra == 'dev'
Requires-Dist: opentelemetry-proto<2,>=1.30; extra == 'dev'
Requires-Dist: opentelemetry-sdk<2,>=1.30; extra == 'dev'
Requires-Dist: psycopg[binary]<4,>=3.2; extra == 'dev'
Requires-Dist: pyjwt[crypto]<3,>=2.15.1; extra == 'dev'
Requires-Dist: pytest-asyncio<1,>=0.24; extra == 'dev'
Requires-Dist: pytest-cov<7,>=6; extra == 'dev'
Requires-Dist: pytest<9,>=8.3; extra == 'dev'
Requires-Dist: pyyaml<7,>=6; extra == 'dev'
Requires-Dist: ruff<1,>=0.9; extra == 'dev'
Requires-Dist: soundfile<0.14,>=0.13; extra == 'dev'
Requires-Dist: sqlalchemy<3,>=2.0; extra == 'dev'
Requires-Dist: uvicorn<1,>=0.30; extra == 'dev'
Provides-Extra: livekit
Requires-Dist: livekit-agents<1.9,>=1.6.5; extra == 'livekit'
Requires-Dist: opentelemetry-api<2,>=1.30; extra == 'livekit'
Requires-Dist: opentelemetry-sdk<2,>=1.30; extra == 'livekit'
Provides-Extra: otel
Requires-Dist: opentelemetry-api<2,>=1.30; extra == 'otel'
Requires-Dist: opentelemetry-sdk<2,>=1.30; extra == 'otel'
Provides-Extra: pipecat
Requires-Dist: opentelemetry-api<2,>=1.30; extra == 'pipecat'
Requires-Dist: opentelemetry-sdk<2,>=1.30; extra == 'pipecat'
Requires-Dist: pipecat-ai[tracing]<1.7,>=1.6.0; extra == 'pipecat'
Provides-Extra: pipecat-groq
Requires-Dist: opentelemetry-api<2,>=1.30; extra == 'pipecat-groq'
Requires-Dist: opentelemetry-sdk<2,>=1.30; extra == 'pipecat-groq'
Requires-Dist: pipecat-ai[groq,tracing]<1.7,>=1.6.0; extra == 'pipecat-groq'
Provides-Extra: pipecat-matrix
Requires-Dist: opentelemetry-api<2,>=1.30; extra == 'pipecat-matrix'
Requires-Dist: opentelemetry-sdk<2,>=1.30; extra == 'pipecat-matrix'
Requires-Dist: pipecat-ai[assemblyai,cartesia,deepgram,elevenlabs,groq,openai,sarvam,soniox,tracing]==1.6.0; extra == 'pipecat-matrix'
Provides-Extra: postgres
Requires-Dist: psycopg[binary]<4,>=3.2; extra == 'postgres'
Provides-Extra: s3
Requires-Dist: boto3<2,>=1.36; extra == 's3'
Provides-Extra: server
Requires-Dist: alembic<2,>=1.13; extra == 'server'
Requires-Dist: fastapi<1,>=0.115; extra == 'server'
Requires-Dist: opentelemetry-api<2,>=1.30; extra == 'server'
Requires-Dist: opentelemetry-proto<2,>=1.30; extra == 'server'
Requires-Dist: pyjwt[crypto]<3,>=2.15.1; extra == 'server'
Requires-Dist: sqlalchemy<3,>=2.0; extra == 'server'
Requires-Dist: uvicorn<1,>=0.30; extra == 'server'
Provides-Extra: signals
Requires-Dist: numpy<3,>=1.26; extra == 'signals'
Requires-Dist: onnxruntime<2,>=1.20; extra == 'signals'
Requires-Dist: soundfile<0.14,>=0.13; extra == 'signals'
Provides-Extra: spool-encryption
Requires-Dist: cryptography<46,>=42; extra == 'spool-encryption'
Description-Content-Type: text/markdown

# Earshot

**See every call your voice agent makes.**

Earshot is a self-hosted server for voice-agent observability. Your agent sends its
OpenTelemetry traces and its call recordings to Earshot. Earshot groups them into Calls
and keeps each Call's timing, transcript and audio in one place, on your own machines. It
runs on a laptop with SQLite and local disk, or on a team server with Postgres and S3.

Status: pre-v1 alpha. 0.4.0 adds hosted JWT access to Calls, scoped project provisioning,
and a standard span profile for hosts that instrument their own voice pipeline. Hosted
OTLP and Recording writes, Call reads and purge use the `earshot:calls:*` scopes; prices
and audio retrieval stay closed. The call view shows audio with a waveform, the transcript
on the audio, per-turn latency beside the audible gap at the agent, and cost for Pipecat and
LiveKit calls. Compatibility is limited to the versions documented for
each release; see the [alpha compatibility policy](docs/release.md#alpha-compatibility-policy).

## Quick start

This takes about a minute with a Pipecat 1.6 bot you already run.

**1. Install and start Earshot.**

```bash
pip install 'earshot-observability[server,signals]'
earshot serve
```

Earshot listens on `http://127.0.0.1:4319` and keeps everything in `./.earshot`: a SQLite
catalog and a local blob directory. On loopback it needs no API key. The `signals` extra
analyses each recording for the call view's audible gap and waveform.

**2. Point Pipecat's HTTP OTLP exporter at it.** Use the HTTP exporter, not the gRPC one.

```bash
pip install 'pipecat-ai[tracing]>=1.6.0,<1.7.0' opentelemetry-exporter-otlp-proto-http
export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4319
```

```python
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from pipecat.pipeline.worker import PipelineWorker
from pipecat.utils.tracing.setup import setup_tracing

setup_tracing(service_name="my-voice-agent", exporter=OTLPSpanExporter())

worker = PipelineWorker(
    pipeline,  # your pipeline
    enable_tracing=True,
    conversation_id="call-123",
    additional_span_attributes={"earshot.session.id": "call-123"},
)
```

Set `conversation_id` and `earshot.session.id` to the **same** value, your own ID for the
call. That value is the Session Key: Earshot groups the call's spans by it, and the
Recordings API takes it. Pipecat puts `conversation.id` on every `turn` span, and
those arrive first. It puts `earshot.session.id` only on the `conversation` span, which
it sends last, and a trace keeps the first key Earshot sees in it. So if the two values
differ, the Call is keyed by `conversation_id`, and a Recording created with the other
value becomes a second Call because a trace binds to the first Session Key it sees; see
[session keys](docs/otlp-quickstart.md#session-keys).

**3. Upload the recordings.** Create each Recording when the call starts, at its first
audio frame, and upload its bytes after the call ends: one mono file per speaker, or one
stereo file with `channel_speakers: ["user", "agent"]`. `earshot.client` does both steps
for you; see the [quick start](docs/otlp-quickstart.md#pipecat-snippet).

```bash
# When the call starts:
curl -X POST http://127.0.0.1:4319/v1/recordings \
  -H 'Content-Type: application/json' \
  -d '{"recording_id": "call-123-user", "session_key": "call-123", "speaker": "user",
       "content_type": "audio/wav", "started_at_unix_nano": "1792372161387965000"}'
# After the call ends:
curl -X PUT http://127.0.0.1:4319/v1/recordings/call-123-user/content \
  -H 'Content-Type: audio/wav' --data-binary @user.wav
```

A pending Recording holds its Call for up to 10 minutes after the latest of its creation,
the Call's latest evidence, and its end signal. If no upload is streaming by then, Earshot
marks it failed. A PUT already in flight has a separate 10-minute deadline measured from
upload start, so it can keep the Call open beyond the pending deadline. A Recording
created only after the Call has closed appears in a later `late_data` Revision, about 30
seconds after the upload.

**4. See the call.**

```bash
curl http://127.0.0.1:4319/v1/calls
```

A Call is listed as soon as its first Recording is created or Earshot receives a span that
resolves a session key.
Pipecat sends its `conversation` span as an end signal. Earshot creates a final Revision
after a 10-second grace period and any active recording uploads finish or reach their
deadline. Without an end signal, five quiet minutes make a provisional Revision due, but
a pending Recording can hold it until the deadlines above. `GET /v1/calls/<call_id>` shows
the Call and its Revisions. Open http://127.0.0.1:4319/calls to see the calls list and
each call's player, transcript, turn latency, audible gap, and cost. See [the call view](docs/calls.md).

The [OTLP quick start](docs/otlp-quickstart.md) covers API keys, containers, session keys,
close timing, recordings and error handling. [`examples/pipecat_otlp`](examples/pipecat_otlp/)
is a complete, real Pipecat call.

## What Earshot receives today

| Source                                    | Status in 0.4.0                                                                                                                                                                                                 |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Pipecat 1.6 over OTLP/HTTP                | Supported: Calls with timing, transcript, recordings and the call view. Tested against real Pipecat 1.6.0 calls.                                                                                                |
| Any OTLP/HTTP producer                    | Spans are stored and grouped into Calls by session key. Call Revisions include Pipecat, LiveKit and `earshot.host` profile spans; other instrumentation is declared as omitted.                                 |
| LiveKit Agents over OTLP/HTTP             | Supported for 1.6 to 1.8: Calls with timing, the played-text transcript, LiveKit's stereo recording and the call view. Tested against a real headless LiveKit 1.8.8 call (no LiveKit server or room).           |
| Earshot Python SDK                        | Supported: Pipecat 1.6 and LiveKit 1.6 to 1.8 adapters, and Deepgram, Cartesia, Sarvam and OpenAI Realtime event mappers, recorded as portable Incident bundles, and `earshot.client` for recordings and calls. |
| Node (`earshot-observability`)            | A span processor that keys a call's spans, and a recordings and calls client.                                                                                                                                   |
| ElevenLabs, Vapi, Retell, Ringg           | Signed webhook Connectors for finalized deliveries. Synthetic conformance only.                                                                                                                                 |
| Browser capture workspace (`/v1/capture`) | Not published on npm; validated on one browser only. The viewer does not render browser-captured sessions yet.                                                                                                  |

gRPC is not in 0.4.0. Each closed
Call's Revision is an ordinary Incident, so the bundled viewer and the analysis APIs can
open it.

## Capture presets

Each Project has a Capture Preset. The server applies it to spans and recordings before it
stores them.

| Preset          | Stores                                                                                                                                                                  |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `full`          | Timing, transcripts, model and tool payloads, the session key, and recordings. The default for every Project in a new install.                                          |
| `metadata_only` | Timing and structure only. Content, identity and the session-key attributes are removed before storage, and recordings are refused. Upgraded 0.1.x Projects start here. |
| `redacted`      | Not available in 0.4.0.                                                                                                                                                 |

```bash
earshot project set-capture --project default --preset metadata_only
earshot project show default
```

[Privacy](docs/privacy.md) lists exactly what each preset keeps, and how purge, retention
and erasure work.

## Storage

|            | Laptop (default)               | Team server                                                                                   |
| ---------- | ------------------------------ | --------------------------------------------------------------------------------------------- |
| Catalog    | SQLite in `--data-dir`         | Postgres: `EARSHOT_DATABASE_URL=postgresql://…`, the `[postgres]` extra and a correlation key |
| Recordings | Local disk, `<data-dir>/blobs` | S3 or an S3-compatible store: `EARSHOT_BLOB_URL=s3://…` and the `[s3]` extra                  |

```bash
pip install 'earshot-observability[server,postgres,s3]'
```

`earshot serve` migrates the schema on start. `earshot db current` and `earshot db upgrade`
report and apply migrations. See [storage and blobs](docs/storage-and-blobs.md) and the
[production notes](docs/production.md): one server per database, one worker, and a
Collector in front.

## Run it in a container

```bash
docker compose up --build
# then open http://127.0.0.1:4319
```

The image includes the `server`, `postgres`, `s3` and `signals` extras. Compose publishes the port
on `127.0.0.1` only, keeps data in the `earshot-data` volume, and runs with
`EARSHOT_TRUST_LOCAL_NETWORK=true`, so the API is unauthenticated behind that loopback
mapping. A bot in another container needs an API key; see the
[OTLP quick start](docs/otlp-quickstart.md#api-keys-and-the-host-rule). To expose Earshot
beyond localhost, drop that line, set `EARSHOT_TOKEN` or issue API keys, set
`EARSHOT_BEHIND_TLS_PROXY=true`, and put your own TLS proxy in front.

## Also in the box

- **A portable evidence format.** The `v1alpha1` Incident bundle: deterministic protobuf
  and strict JSON, a semantic profile, privacy validation and an omission ledger.
- **A Python SDK** that records Incidents in-process (`earshot.init()`), with bounded,
  non-blocking delivery and crash recovery. See [the SDK](docs/sdk.md).
- **Deterministic analysis** of each Incident: generated, sent, received and rendered
  audio timing, cross-clock uncertainty and evidence-linked diagnoses. Earshot never
  claims a person heard the audio.
- **A viewer** served by `earshot serve`; host integrations use the unpublished
  `@earshot/viewer-ui` workspace source.
- **Hosted mode** with short-lived, project-scoped JWTs (`EARSHOT_AUTH_MODE=hosted_jwt`).
  In 0.4.0, OTLP spans and Recording uploads use `earshot:calls:write`; Call reads and
  purge use `earshot:calls:read` and `earshot:calls:delete`. Audio retrieval, price routes,
  transcript uploads, raw span attributes and receiver counters stay closed.

The Python distribution is `earshot-observability`; the import package and the CLI are
`earshot`. The PyPI project named `earshot` is an unrelated VAD package. The base install
(`pip install earshot-observability`) is the SDK only; every command that opens the store,
`earshot serve` included, needs the `server` extra.

## Develop

```bash
python3.11 -m venv .venv
. .venv/bin/activate
pip install -e '.[dev]'
pytest
pnpm install && pnpm --filter @earshot/viewer bundle   # build the UI into the package
earshot serve --data-dir .earshot
```

See [development](docs/development.md) and [CONTRIBUTING.md](CONTRIBUTING.md).

## Documentation

- [OTLP quick start](docs/otlp-quickstart.md): exporter setup, session keys, close timing,
  recordings, errors.
- [Privacy](docs/privacy.md): capture presets, purge, retention and erasure.
- [Storage and blobs](docs/storage-and-blobs.md): SQLite or Postgres, migrations,
  upgrading from 0.1.x, local disk or S3.
- [Production notes](docs/production.md): topology, Collector, readiness, backups.
- [Server configuration](docs/configuration.md): every variable.
- [The call view](docs/calls.md): the call view, cost, prices, filters and search.
- [0.4.0 release notes](docs/release-notes-0.4.0.md): what's new and compatibility details.
- [All documentation](docs/README.md) and the [changelog](CHANGELOG.md).

## Non-goals

Earshot is not a voice runtime, a carrier, or a hosted multi-tenant product.

## License

Apache-2.0. See [LICENSE](LICENSE).
