கு kural
கு
v0.1, v0.2 (provider adapters), and v0.3 (Twilio telephony) all ship. Deepgram STT, ElevenLabs/Kokoro TTS, inbound/outbound calls with call history — all live.

kural

Open-source voice AI agent framework.

Build phone agents with the LLM, speech, and telephony providers of your choice. Bring your own keys. Run for the cost of a phone number.

Why kural

Bring your own keys

Plug in OpenAI, OpenRouter, Groq, Ollama, vLLM, or any OpenAI-compatible endpoint. Same for STT and TTS. No middleman markup.

Free path exists

OpenRouter free models + local Whisper + local Piper TTS + a $1/month Twilio number. Production voice agent for call minutes only.

Self-hostable

Single Python process. Deploy on a $5 VPS, your laptop, or a container. No SaaS lock-in.

Phone-first

Twilio telephony out of the box. Browser audio works too. Built on Pipecat — every stage is a swappable frame processor.

How it works today

kural ships three modes, switchable via KURAL_MODE. voice is the v0.1 cascade; echo is a transport smoke test; telephony (v0.3) runs the same cascade over real phone calls.

Mic ─▶ input ─▶ Whisper STT ─▶ user_aggregator ─▶ LLM ─▶ Piper TTS ─▶ output ─▶ Speakers (Silero VAD) │ ▼ assistant_aggregator

Silero VAD decides when the caller stopped speaking. Each layer is built through a small Adapter Protocol and a registry, so swapping the STT, LLM, or TTS backend is one env var (KURAL_LLM_PROVIDER, KURAL_STT_PROVIDER, KURAL_TTS_PROVIDER). Currently registered: OpenAI-compatible LLM; faster-whisper and Deepgram STT; Piper, ElevenLabs, and Kokoro TTS.

Telephony (v0.3)

KURAL_MODE=telephony runs a FastAPI/uvicorn server instead of a local audio loop. Inbound calls hit a webhook, get bridged onto a Twilio Media Streams WebSocket, and run through the same voice pipeline above — only the transport differs. Each call is its own worker, so multiple calls run concurrently.

POST /telephony/voice (webhook) Caller ─▶ Twilio ─────────────────────────────────────▶ TwiML <Connect><Stream> WS /telephony/media/{call_sid} ◀──────────────────────────────────────────────▶ voice pipeline (STT → LLM → TTS)

Twilio is the first TelephonyAdapter implementation, wired through the same registry pattern as the LLM/STT/TTS layers — Pipecat already ships serializers for Telnyx, Plivo, Exotel, Genesys, and Vonage, so adding another provider is a new adapter class, not a pipeline change. A small REST API (POST /calls/outbound, GET /calls, GET /calls/{sid}) places outbound calls and reads call history from a local SQLite log.

Latency budget (voice mode)

Target on a recent Apple Silicon laptop, all local: < 2 s end-to-end (caller stops speaking → first audio of reply).

StageBudgetNotes
VAD endpointing~250 msSilero start/stop windows
STT (Whisper distil-medium.en)~400 msPer utterance, batched
LLM (small local model)~800 msFirst-token latency dominates
TTS (Piper)~400 msFirst audio chunk
Audio I/O + scheduling~150 msBuffering, sample-rate conversion

Target architecture

Caller → Twilio (PSTN/SIP) → Media stream ↓ [VAD] Silero ↓ [STT] Whisper / Deepgram ↓ [LLM] OpenAI-compatible (any provider) ↓ [TTS] Piper / Kokoro / ElevenLabs ↓ Audio back to caller

Provider matrix

LayerFree / localPaid (BYOK)
LLMOllama, vLLM, OpenRouter free tierOpenAI, Anthropic (via OpenRouter), Groq, Together
STTfaster-whisper, Distil-WhisperDeepgram (shipped), AssemblyAI
TTSPiper (shipped), Kokoro (shipped)ElevenLabs (shipped), Cartesia
PhoneTwilio (shipped); Telnyx, Plivo, Exotel, Genesys, Vonage adapters possible, not yet built

Quickstart

Install once:

# macOS:   brew install portaudio
# Ubuntu:  sudo apt-get install -y portaudio19-dev

git clone https://github.com/nnavnita/kural
cd kural
cp .env.example .env
python -m venv .venv && source .venv/bin/activate
pip install -e .

Voice mode (default) — full STT → LLM → TTS

Cheapest local path: a small model via Ollama.

# 1. Pull a small model once
ollama pull llama3.1:8b
ollama serve

# 2. Point kural at it (or edit .env)
export KURAL_LLM_BASE_URL=http://localhost:11434/v1
export KURAL_LLM_API_KEY=ollama          # ignored by Ollama; any string
export KURAL_LLM_MODEL=llama3.1:8b

kural                                    # or: python -m kural.server

Whisper and Piper auto-download weights on first run (~1 GB total). Wear headphones, speak, hear the reply.

Echo mode — transport smoke test, no API keys

KURAL_MODE=echo kural

Telephony mode — Twilio inbound/outbound calls

Needs a publicly reachable URL for Twilio's webhooks — ngrok http 8000 works for local dev.

# 1. Tunnel a public URL to your machine
ngrok http 8000

# 2. Configure (or edit .env)
export KURAL_MODE=telephony
export KURAL_PUBLIC_BASE_URL=https://your-tunnel.ngrok.app
export TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export TWILIO_AUTH_TOKEN=your_auth_token
export TWILIO_PHONE_NUMBER=+15551234567

kural                                    # serves webhook + media stream + REST API on :8000

Point the Twilio number's voice webhook at <public-url>/telephony/voice. Place an outbound call with curl -X POST :8000/calls/outbound -d '{"to":"+15557654321"}'; list history with curl :8000/calls.

Configuration

Settings.from_env reads:

VariableDefaultMeaning
KURAL_MODEvoicevoice (STT→LLM→TTS), echo (passthrough), or telephony (Twilio server)
KURAL_SAMPLE_RATE16000Audio input rate; 16 kHz matches Whisper/Silero
KURAL_OUTPUT_SAMPLE_RATE24000Audio output rate; higher for smoother TTS playback
KURAL_LLM_PROVIDERopenaiRegistry key selecting the LLM adapter
KURAL_LLM_BASE_URLOpenAI defaultOpenAI-compatible endpoint (Ollama, OpenRouter, vLLM, …)
KURAL_LLM_API_KEYunsetAPI key for the LLM endpoint
KURAL_LLM_MODELgpt-4o-miniModel identifier passed to the provider
KURAL_STT_PROVIDERwhisperRegistry key selecting the STT adapter (whisper or deepgram)
KURAL_STT_MODELdistil-medium.enfaster-whisper model id, or Deepgram model name (e.g. nova-3-general)
DEEPGRAM_API_KEYunsetRequired when KURAL_STT_PROVIDER=deepgram
KURAL_TTS_PROVIDERpiperRegistry key selecting the TTS adapter (piper, kokoro, or elevenlabs)
KURAL_TTS_VOICEen_US-amy-mediumPiper/Kokoro voice id, or ElevenLabs voice ID
ELEVENLABS_API_KEYunsetRequired when KURAL_TTS_PROVIDER=elevenlabs
KURAL_AGENT_PROMPTbuilt-inSystem prompt seeded into the LLM context
KURAL_LOG_LEVELINFOloguru level
KURAL_TELEPHONY_PROVIDERtwilioRegistry key selecting the telephony adapter
KURAL_PUBLIC_BASE_URLunsetPublicly reachable URL for Twilio webhooks; required for telephony mode
TWILIO_ACCOUNT_SID / TWILIO_AUTH_TOKEN / TWILIO_PHONE_NUMBERunsetTwilio credentials; required for telephony mode
KURAL_DB_PATHkural.dbSQLite call log path (telephony mode)
KURAL_PORT8000Bind port for the telephony server

Stop the agent with Ctrl+C. See .env.example for the full list.

Roadmap