Skip to main content

callharness-sdk

Python SDK for CallHarness — open-source call analytics for voice AI agents.

Send your agent's calls to a CallHarness server, which runs post-call LLM analysis (summary, sentiment, outcome, why a call transferred or didn't complete) and shows it on a dashboard. Self-hosted, so your transcripts stay on your own infrastructure.

Install

pip install callharness-sdk            # REST client + turn assembly
pip install "callharness-sdk[pipecat]" # also installs pipecat-ai for the observers

The [pipecat] extra only adds pipecat-ai. Skip it if you're on LiveKit, a custom stack, or calling the REST API directly — callharness_sdk.pipecat still imports cleanly without it, and only raises if you actually instantiate an observer.

Direct ingestion

from callharness_sdk import CallHarnessClient

client = CallHarnessClient("http://localhost:8010")
call = client.ingest_call(
    agent_id="my-agent",
    end_reason="completed",
    turns=[
        {"role": "assistant", "text": "Hi, how can I help?"},
        {"role": "user", "text": "I'd like to book an appointment."},
    ],
)
client.upload_recording(call["id"], "recording.wav")

Pipecat integration

from pipecat.processors.transcript_processor import TranscriptProcessor
from callharness_sdk.pipecat import create_recorder

transcript = TranscriptProcessor()
recorder = create_recorder("http://localhost:8010", agent_id="my-agent")
recorder.attach(transcript)

# include transcript.user() after STT and transcript.assistant() after TTS
# in your pipeline, then when the call ends:
await recorder.flush(end_reason="completed")

To capture per-turn STT/LLM/TTS latency, add the metrics observer to your task:

from callharness_sdk.pipecat import CallHarnessMetricsObserver

task = PipelineTask(
    pipeline,
    params=PipelineParams(enable_metrics=True),
    observers=[CallHarnessMetricsObserver(recorder)],
)

Pipecat without TranscriptProcessor

If your pipeline doesn't use TranscriptProcessor (e.g. you capture transcripts at the frame level), use the all-in-one frame observer instead — it captures transcript turns, end-to-end response latency, STT/LLM/TTS components, interruptions, tool calls, transfers, and a deterministic end_reason, all from one observer:

from callharness_sdk.pipecat import CallHarnessFrameObserver, create_recorder

recorder = create_recorder("http://localhost:8010", agent_id="my-agent")
observer = CallHarnessFrameObserver(
    recorder, stt=stt, tts=tts, transfer_tool_names={"transfer_to_human"}
)
# add `observer` to your PipelineTask/PipelineWorker observers, then on call end:
await recorder.flush(
    end_reason=observer.finalize_end_reason(),  # "completed" | "transferred" | "error" | ...
    transferred=observer.transferred,
    recording_bytes=wav_bytes,   # optional in-memory recording upload
)

finalize_end_reason() gives you the best reason available right now: "error" if a fatal ErrorFrame occurred, otherwise the explicit reason= you passed to EndTaskFrame/CancelTaskFrame if the pipeline already saw one before you called this (e.g. EndTaskFrame(reason="silence_timeout") from a UserIdleProcessor callback), otherwise "transferred" if a transfer fired, otherwise "completed" (pass a different default= if that's not right for your integration).

Call finalize_end_reason() — not the raw observer.end_reason attribute — from your own disconnect/teardown handler (e.g. a transport's on_client_disconnected). That fires before an EndFrame/CancelFrame has necessarily propagated through the pipeline, so .end_reason may still be None at that point; finalize_end_reason() only depends on state (fatal error, transferred) that's already known live during the call, so it's correct regardless of teardown ordering. observer.last_error holds the most recent error message, useful to drop into metadata for debugging.

If your agent already has its own call pipeline

Mature agents usually already collect a transcript, their own record of tool calls, and write to their own database. Adopting CallRecorder would mean maintaining two sources of truth — so instead, hand CallHarness what you already have:

from callharness_sdk import CallHarnessClient, LatencyCollector, assemble_turns
from callharness_sdk.pipecat import CallHarnessMetricsObserver

latency = LatencyCollector()          # satisfies what the observer expects
task = PipelineTask(
    pipeline,
    params=PipelineParams(enable_metrics=True),   # required, or no metrics are emitted
    observers=[CallHarnessMetricsObserver(latency)],
)

# ...at the end of the call, from your own save routine:
turns = assemble_turns(
    transcript=my_transcript,        # [{role, content, timestamp}, ...]
    tool_calls=my_function_calls,    # [{function_name, parameters, result, timestamp}]
    latency=latency,
    started_at=call_started_at,
)
CallHarnessClient("http://localhost:8010").ingest_call(
    agent_id="my-agent", turns=turns, external_id=my_call_id,
    transferred=..., metadata={"my_own_verdict": ...},
)

assemble_turns() does the fiddly part: a tool call and a latency sample both happen while a reply is being produced, before its text exists, so both are matched by timestamp to the assistant turn they actually belong to. It accepts either field naming (name/function_name, arguments/parameters, content/text), truncates oversized tool results, and never records a tool as successful unless it can prove it.

Anything you send in metadata is stored alongside the call — useful if your agent already classifies its own calls and you want to compare that against CallHarness's independent verdict.

Full example

See examples/pipecat_bot.py for a complete working bot.

Licence

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

callharness_sdk-0.1.0.tar.gz (13.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

callharness_sdk-0.1.0-py3-none-any.whl (16.3 kB view details)

Uploaded Python 3

File details

Details for the file callharness_sdk-0.1.0.tar.gz.

File metadata

  • Download URL: callharness_sdk-0.1.0.tar.gz
  • Upload date:
  • Size: 13.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.10

File hashes

Hashes for callharness_sdk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f4b6d4aa14449c447aa2773b943d635f3e1edfb37c06d4ba7c11207f31dcb07f
MD5 d1a257abf3ec86523a82921a89a16025
BLAKE2b-256 db1d56020a5df560021884c6fae9a939e0d4110cf5bc5e4b55bf07c7084ffb6d

See more details on using hashes here.

File details

Details for the file callharness_sdk-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for callharness_sdk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b8e180103f40f9fe1f05df69d361b34bb15f1bb7dea9e60a8df4745b45a2c189
MD5 a0aae94dc421491dcee22ec888e6ef94
BLAKE2b-256 6a3bbd78a82310a376ce3df4783fe9fd6e84fd0f705e003a9caa9d07bac6dc60

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page