Place real phone calls with an AI voice agent without revealing your real name.
The agent dials real numbers through Vobiz (telephony / SIP carrier) connected to LiveKit Cloud, speaks and listens via Sarvam AI (Telugu by default), and reasons with Grok through OpenRouter. You get a live terminal dashboard, optional browser listen-in, and transcripts — while the bot only uses a public alias.
- Anonymous identity — agent uses a configurable alias (e.g. “Alex”), never your real name, phone, or email
- Outbound phone calls — Vobiz SIP trunk + LiveKit Cloud realtime
- Multilingual voice — Sarvam STT/TTS with Telugu (
te-IN) default; English and other Indian languages supported - Low-latency LLM — Grok via OpenRouter with light reasoning tuned for natural phone speech
- Live call monitor — dialing / ringing / answered / ended, timers, and live transcript
- Listen-only mode — hear the call on speakers or in the browser without joining the conversation
- Noise-resistant barge-in — short coughs and noise are less likely to cut the agent off mid-sentence
- Auto hang-up — silence / max duration / goodbye detection
| Layer | Provider |
|---|---|
| Telephony (SIP) | Vobiz — outbound SIP trunk for real phone calls |
| Realtime media | LiveKit Cloud (rooms, agents, SIP participant bridge) |
| STT | Sarvam AI (saaras:v3, auto-detect incl. Telugu) |
| LLM | OpenRouter → Grok (x-ai/grok-4.3 by default) |
| TTS | Sarvam AI (bulbul:v3, default Telugu te-IN) |
| VAD | Silero (via LiveKit agents) |
This project places real PSTN/mobile calls using a Vobiz outbound SIP trunk registered in LiveKit:
- Create a trunk in the Vobiz dashboard (or equivalent Vobiz SIP credentials).
- Add that trunk as an outbound SIP trunk in LiveKit Cloud (SIP → Trunks).
- Copy the LiveKit trunk id (
ST_…) into.envasSIP_OUTBOUND_TRUNK_ID.
When you run make_call.py, LiveKit creates a SIP participant and Vobiz dials the destination number. Call states (dialing, ringing, active, hangup) come from LiveKit SIP attributes over that trunk.
- Python 3.11+ recommended
- Accounts / keys:
- LiveKit Cloud project (
LIVEKIT_URL, API key + secret) - Vobiz telephony account + outbound SIP trunk linked in LiveKit →
SIP_OUTBOUND_TRUNK_ID - Sarvam AI API key
- OpenRouter API key
- LiveKit Cloud project (
- Windows, macOS, or Linux (Windows users should run the included local-inference patch)
git clone https://github.com/Iamnanisyam/voice-agent.git
cd voice-agent
python -m venv venv
# Windows
.\venv\Scripts\Activate.ps1
# macOS / Linux
# source venv/bin/activate
pip install -r requirements.txt
# Windows only — avoid livekit-local-inference ACCESS_VIOLATION crash
python patch_local_inference.py
# Download model assets (Silero VAD, etc.)
python agent.py download-filescopy .env.example .envEdit .env and set at least:
| Variable | Description |
|---|---|
LIVEKIT_URL |
e.g. wss://your-project.livekit.cloud |
LIVEKIT_API_KEY |
LiveKit API key |
LIVEKIT_API_SECRET |
LiveKit API secret |
SARVAM_API_KEY |
Sarvam STT + TTS |
OPENROUTER_API_KEY |
OpenRouter key for Grok |
OPENROUTER_MODEL |
Default: x-ai/grok-4.3 |
SIP_OUTBOUND_TRUNK_ID |
LiveKit id of your Vobiz outbound SIP trunk (ST_…) |
Optional identity (privacy):
AGENT_PUBLIC_NAME=Alex
AGENT_REPRESENTS_AS=a private client
AGENT_LANGUAGE=te-IN
AGENT_SPEAKER=shubhNever commit .env. It is listed in .gitignore.
Keep this terminal running:
.\venv\Scripts\Activate.ps1
python agent.py dev.\venv\Scripts\Activate.ps1
python make_call.py `
--to +919876543210 `
--purpose "Confirm whether the meeting tomorrow at 3pm still works" `
--callee "Ravi" `
--context "Meeting is optional; if busy, suggest Friday afternoon"By default the script watches the call live in that terminal.
While a call is active, make_call.py and the worker show:
| Status | Meaning |
|---|---|
INITIATING |
Dispatch accepted, agent joining |
DIALING… |
SIP INVITE via Vobiz trunk |
RINGING… |
Remote phone ringing |
LIFTED (active) |
Callee answered |
BUSY / REJECTED / NO ANSWER |
Failed outcomes |
ENDED |
Hangup |
Also shown: elapsed time since dial, talk time since answer, and a LIVE TRANSCRIPT (THEM = callee, AGENT = bot).
Dispatch only (no watch loop):
python make_call.py --to +919876543210 --purpose "…" --no-watch| Flag | Meaning |
|---|---|
--to |
E.164 number (required), e.g. +919876543210 |
--purpose |
Why the agent is calling (required for a useful call) |
--callee |
Name of the person being called |
--context |
Extra background for the agent |
--public-name |
Alias used on the call (not your real name) |
--represents-as |
Who the agent says it represents |
--language |
TTS language (te-IN, en-IN, hi-IN, …) |
--speaker |
Sarvam voice (shubh, priya, …) |
--no-watch |
Exit after dispatch instead of live monitoring |
While a call is active:
# Speakers only (subscribe-only, no mic)
python listen.py
# or pin a room from make_call output:
python listen.py --room call-91xxxxxxxxxx-abc
# Browser player + optional recording
python listen.py --browserYou join as a listen-only participant and hear agent + callee audio.
Verify STT / LLM / TTS without dialing:
python agent.py consolepython validate_voice.py --speaker shubhlk dispatch create \
--new-room \
--agent-name anonymous-caller \
--metadata "{\"phone_number\": \"+919876543210\", \"purpose\": \"Confirm the appointment\", \"public_name\": \"Alex\", \"represents_as\": \"a private client\"}"On every call the agent:
- Introduces itself with a public alias (default:
Alex) — not your name - Says it is calling on behalf of a private client (configurable)
- Refuses to share your real name, phone, email, address, or workplace
- Can still complete a task (confirm appointments, ask questions, leave messages)
Configure in .env:
AGENT_PUBLIC_NAME=Alex
AGENT_REPRESENTS_AS=a private clientIf short noise still cuts the agent off, raise barge-in thresholds in .env:
MIN_INTERRUPTION_DURATION=1.5
MIN_INTERRUPTION_WORDS=2
FALSE_INTERRUPTION_TIMEOUT=1.5Other useful knobs (see .env.example):
MIN_ENDPOINTING_DELAY/MAX_ENDPOINTING_DELAY— how fast the agent replies after you stop talkingAUTO_HANGUP_SILENCE_SEC/AUTO_HANGUP_MAX_CALL_SEC— call length limitsLLM_MAX_TOKENS/LLM_REASONING_EFFORT— reply length and reasoning costTTS_PACE— speaking speed
voice-agent/
agent.py # LiveKit worker + anonymous call logic
make_call.py # Dispatch helper + live status
listen.py # Subscribe-only live listen / browser player
call_monitor.py # Terminal dashboard + transcript logging
validate_voice.py # Voice / TTS sanity checks
patch_local_inference.py # Windows ACCESS_VIOLATION workaround
requirements.txt
.env.example # Template — copy to .env locally
.gitignore
- Never commit
.env. Only ship.env.examplewith placeholders. - Rotate API keys if they were ever shared in chat, screenshots, or a private fork.
- Only call numbers you are authorized to contact. Comply with local telemarketing, consent, and recording laws.
Use and modify freely for your own projects. Add a formal license file if you redistribute under specific terms.