unmute-core
unmute-core is a Python library for voice agents on LiveKit Agents and Pipecat. You keep your framework, your speech and model plugins, and your pipeline. The library adds what a real phone or web call needs on top: typed call state, task steps, tools, handoffs, idle callers, transfers and tracing.
Unmute compiles an agent spec into a LiveKit or Pipecat project. Those projects import this library. You can also use it in an agent you write by hand, one piece at a time or all of it.
Example
A plain LiveKit agent with one tool from the library:
import os
from livekit.agents import Agent, AgentSession
from livekit.plugins import openai, silero, slng
from pydantic import BaseModel
from unmute_core.livekit import function_tools
from unmute_core.tools import ToolRegistry, local_tool
class OrderStatus(BaseModel):
order_id: str
status: str
def order_status(order_id: str) -> OrderStatus:
"""Look up an order by its id. The return annotation is the result's checked shape."""
return OrderStatus(order_id=order_id, status="out for delivery")
registry = ToolRegistry([local_tool(order_status, announce="One moment, let me check.")])
agent = Agent(instructions="You help with orders.", tools=function_tools(registry))
slng_host = f"{os.environ['SLNG_WORLD_PART']}.api.slng.ai"
stt = slng.STT(slng_base_url=slng_host, model="soniox/speech-ai:rt-v5")
session = AgentSession(
stt=stt,
llm=openai.LLM(),
tts=slng.TTS(
slng_base_url=slng_host,
model="slng/fish/tts:s2.1-pro",
voice="8d8aec63a56c460cb7def795bdf754fa",
),
vad=silero.VAD.load(),
)
stt.attach_to_session(session)
The agent stays a normal LiveKit Agent. The library adds the tool and the line the agent says while the tool runs. Speech runs on SLNG: Soniox hears the caller and Fish Audio speaks, with your SLNG_API_KEY. On Pipecat, put tools_schema(registry) on the LLMContext instead.
How it fits
flowchart TD
U[Unmute compiles agent.yaml] --> P[Your LiveKit or Pipecat project]
H[You write agent.py by hand] --> P
P -->|builds the pipeline, calls| A[unmute_core.livekit or unmute_core.pipecat]
A --> C[Core: state, tasks, tools, hooks, handoff, telemetry, model call]
- Your project builds the pipeline. Unmute writes that project for you, or you write it yourself.
- The project calls an adapter:
unmute_core.livekitorunmute_core.pipecat. - The adapter turns framework events into calls to the core. The core never imports LiveKit or Pipecat.
- The same core runs on both frameworks. Shared test scenarios run on both adapters and must give the same result.
The library never reads agent.yaml and never builds the pipeline. Your speech-to-text, text-to-speech and LLM plugins stay yours.
Three ways to use it
| Way | What you write | Start here |
|---|---|---|
| One piece at a time | A plain Agent or pipeline, plus the pieces you want: tools, greeting, idle nudges, tracing |
standalone, examples/minimal |
| A whole agent | One AgentSpec with typed state, tasks and handoffs, run by UnmuteAgent on LiveKit or UnmuteFlows on Pipecat |
livekit, pipecat, examples/salon-concierge-unmute |
| Compiled by Unmute | agent.yaml and Markdown prompts. Unmute writes the Python. |
Unmute, examples/salon-concierge |
An agent can also be defined as data: state as type text like Phone | None, tasks and tools as JSON. See examples/parcel-desk.
Four whole agents show other shapes of call, each on LiveKit and Pipecat from one package, on SLNG speech and Gemini in the EU: a restaurant finder with tools only (nosh), a car claim intake (kestrel), an outbound payment reminder (wasla) and a bilingual language tutor (faro). See example agents and type the path.
Why use it
LiveKit and Pipecat move audio, run the models and call your tools. They do not decide what a good phone call looks like. Every team learns that from its first real calls. unmute-core holds what we learned, so you start from it.
- One agent, two frameworks. State, tasks, tools, prompts and traces run the same on LiveKit and Pipecat. 90 test scenarios run on both frameworks, with plain and realtime models, and must give the same result.
- The model cannot break your data. Each value the model saves is checked against its type. A wrong value is refused with a reason the model can act on, like
caller_phone: expected a phone number with its country code. The model asks the caller again. - Over 100 call optimizations, built in. Some examples:
| On a call without it | What unmute-core does |
|---|---|
| A weak model says "Noted, thank you" and never finishes the step. | The finish check asks the model one narrow question: finish, or not yet. |
| The caller pauses, goes on, and the reply plays over them. | yield_to_caller drops that reply. The next one answers everything they said. |
| A slow tool leaves the caller in silence. | The tool says a line as it starts, and the caller's voice never cancels it. |
| A tool returns the wrong shape, and the model tells the caller it worked. | Every tool names the Pydantic model of its result. A result that does not fit fails the call, and the model reads which field is wrong. |
| The new agent hands the call straight back after a handoff. | Handoff tools are hidden on the first reply after a handoff. |
| A bad key is tried on every fallback model while the caller waits. | Errors that every model would give stop the fallback chain at once. |
| Gemini 3 refuses the next request after a tool call. | Thought signatures are kept on replayed tool calls. |
| The model or voice fails and the caller hears silence. | A short apology plays, then the call ends with a clear reason. |
A webhook tool reaches localhost or a cloud metadata address. |
Each webhook address must be public, and the connection is pinned to it. |
| Each framework traces differently, so dashboards break when you switch. | One trace shape on both, sent to Langfuse, Coval, Logfire or any OpenTelemetry collector. |
Why unmute-core follows one call from start to end. Each feature page then starts with what it fixes.
Install
Python 3.12 or newer. Pick the extra for your framework:
uv add "unmute-core[livekit]"
uv add "unmute-core[pipecat]"
| Extra | What it adds |
|---|---|
livekit |
The LiveKit adapter. LiveKit Agents 1.8.7 or newer within 1.8. |
pipecat |
The Pipecat adapter. Pipecat 1.12 or newer within 1.12. |
slng |
The openai package, for OpenAIModelCall and the SLNG Context Router as a model call. See slng. |
knowledge |
Search over a folder of documents, with llama-index. See knowledge. |
Everything works with your framework's own plugins and no SLNG package. installation covers version ranges and the version check at import.
Docs
The docs tell one story. Start small, then go deeper where you need to.
- Get started: why unmute-core, installation and the quickstart.
- Build a salon agent: a tutorial that grows one agent, one piece per page. It ends at examples/salon-concierge-unmute.
- On your framework: add one piece to your agent, or run a whole agent on LiveKit or Pipecat.
- Guides: state, tools, knowledge, tasks, handoffs, call lifecycle, prefetch, turn taking, hooks, model call, slng, realtime, telephony and telemetry.
- Example agents: overview, type the path, and one page each for nosh, kestrel, wasla and faro.
- Reference: settings and tasks.
Versions
The library is 0.x. Code written for 0.14.x runs on any later 0.14.x. A new minor version, like 0.15.0, may break it. unmute_core.require() checks this when your code imports.
CHANGELOG.md lists what each release changed, and what can break you first.
Develop
uv sync --all-extras
uv run pytest
uv run ruff check . && uv run ruff format --check .
uvx ty check
CONTRIBUTING.md explains how to send a change. SECURITY.md explains how to report a security problem.
License
MIT. See LICENSE.
Metadata
Release files for unmute-core 0.14.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| unmute_core-0.14.0.tar.gz | 418.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| unmute_core-0.14.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 916.2 kB
Release files / unmute_core-0.14.0.tar.gz
| Download URL | unmute_core-0.14.0.tar.gz |
|---|---|
| Size | 418.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a6dcbfb67d1ac9cd9cce608649da90bfc74228a9030a6f9be21b3836729e5b26
|
|
BLAKE2b-256 checksum How to use checksums |
f23bfbbc715458b94b72c9e8414f7169974138bbea2dd3723e393553affba969
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / unmute_core-0.14.0-py3-none-any.whl
| Download URL | unmute_core-0.14.0-py3-none-any.whl |
|---|---|
| Size | 497.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9c5613cca30c11217436ab9ce276ab9c711b6a35c0a30f6a0ed06a6f7ff876c0
|
|
BLAKE2b-256 checksum How to use checksums |
172dbb1902a832b14db02e1619f52c45fe7e6eb6cbc6cabdaa8e6ff70e15e909
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|