Introduction
Voice AI is transforming how we interact with technology, enabling natural spoken conversations with AI agents. This tutorial shows you how to create a complete voice AI system using Claude API for intelligent reasoning and ElevenLabs for high-quality speech-to-text (STT) transcription and text-to-speech (TTS) synthesis.
We'll build a real-time conversation loop:
- Capture audio from your microphone.
- Transcribe it using ElevenLabs STT.
- Send the transcript to Claude (Sonnet 3.5) for a thoughtful response.
- Convert the response to speech with ElevenLabs TTS and play it back.
This setup is perfect for AI agents, virtual assistants, or interactive demos. It's Claude-specific, leveraging its superior reasoning for context-aware replies while maintaining conversation history.
Note: ElevenLabs provides both STT and TTS via their APIs. This tutorial uses Python for a simple, local console app. For production, deploy to a server with WebSockets (e.g., via FastAPI).
Prerequisites
Before starting:
- Python 3.10+ installed.
- API keys:
- Anthropic API key (free tier available).
- ElevenLabs API key (sign up for credits).
- Basic familiarity with Python and virtual environments.
Step 1: Install Dependencies
Create a new directory and virtual environment:
git clone <your-repo> voice-claude-agent # or mkdir voice-claude-agent
cd voice-claude-agent
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
Install required packages:
pip install anthropic elevenlabs pyaudio pydub simpleaudio soundfile numpy requests
Why these?
anthropic: Official Claude SDK.elevenlabs: Official TTS/STT client.pyaudio: Microphone input.pydub&soundfile: Audio handling.simpleaudio: Playback (cross-platform).
ElevenLabs STT Note: ElevenLabs offers real-time STT via WebSockets. For simplicity, we'll use their batch STT API with short recordings. Upgrade to WebSockets for production low-latency.
Step 2: Set Up API Keys
Create a .env file:
ANTHROPIC_API_KEY=your_anthropic_key_here
ELEVENLABS_API_KEY=your_elevenlabs_key_here
Load them in code using python-dotenv (install if needed: pip install python-dotenv):
import os
from dotenv import load_dotenv
load_dotenv()
claude_key = os.getenv('ANTHROPIC_API_KEY')
xi_key = os.getenv('ELEVENLABS_API_KEY')
Step 3: Audio Recording Function
Record 5-second audio clips from the mic. Press Enter to stop early if needed, but for demo, fixed duration.
import pyaudio
import wave
import numpy as np
def record_audio(duration=5, filename='input.wav', sample_rate=16000):
chunk = 1024
format = pyaudio.paInt16
channels = 1
p = pyaudio.PyAudio()
stream = p.open(format=format, channels=channels, rate=sample_rate,
input=True, frames_per_buffer=chunk)
print(f"Recording for {duration} seconds... Speak now!")
frames = []
for _ in range(0, int(sample_rate / chunk * duration)):
data = stream.read(chunk)
frames.append(data)
stream.stop_stream()
stream.close()
p.terminate()
wf = wave.open(filename, 'wb')
wf.setnchannels(channels)
wf.setsampwidth(p.get_sample_size(format))
wf.setframerate(sample_rate)
wf.writeframes(b''.join(frames))
wf.close()
print("Recording complete.")
return filename
This saves mono 16kHz WAV, optimal for STT.
Step 4: Speech-to-Text with ElevenLabs
ElevenLabs STT API transcribes audio files. (For real-time, see their WebSocket docs.)
from elevenlabs.client import ElevenLabs
from elevenlabs.api import AudioTranscribeResponse
client = ElevenLabs(api_key=xi_key)
def transcribe_audio(audio_file):
with open(audio_file, 'rb') as f:
audio = f.read()
response = client.transcribe(
audio=audio,
model_id="eleven_stt_v1", # Latest model
language_code="en" # Adjust as needed
)
return response.text.strip()
Pro Tip: ElevenLabs STT excels at noisy environments and accents—better than basic Whisper for voice agents.
Step 5: Query Claude for Response
Use Anthropic SDK with conversation history for context.
from anthropic import Anthropic, HUMAN_PROMPT, AI_PROMPT
client = Anthropic(api_key=claude_key)
# Global history
conversation_history = []
def get_claude_response(user_input):
global conversation_history
conversation_history.append(f"{HUMAN_PROMPT} {user_input}{'\
\
' + AI_PROMPT}")
full_context = ''.join(conversation_history[-10:]) # Last 10 exchanges
full_context += f"{HUMAN_PROMPT} {user_input}\
\
{AI_PROMPT}"
message = client.messages.create(
model="claude-3-5-sonnet-20240620",
max_tokens=500,
temperature=0.7,
system="You are a helpful voice assistant. Keep responses concise and conversational.",
messages=[{"role": "user", "content": full_context}]
)
response = message.content[0].text
conversation_history.append(response + '\
\
')
return response
Claude's long context (200K tokens) handles extended convos effortlessly.
Step 6: Text-to-Speech with ElevenLabs
Generate and play natural speech. Choose voices like "Adam" or clone your own.
import simpleaudio as sa
import io
def speak_response(text, voice_id="pNInz6obpgDQGcFmaJgB"): # Adam voice
audio = client.generate(
text=text,
voice=voice_id,
model="eleven_multilingual_v2",
output_format="pcm_16000" # Matches recording
)
# Play with simpleaudio
audio_data = np.frombuffer(audio, dtype=np.int16)
play_obj = sa.play_buffer(audio_data, 1, 2, 16000)
play_obj.wait_done()
Customization: List voices via client.voices.list(). Use stream=True for streaming TTS in prod.
Step 7: The Main Conversation Loop
Tie it all together:
def main():
global conversation_history
print("Voice AI ready! Say 'exit' to quit.")
while True:
try:
audio_file = record_audio()
transcript = transcribe_audio(audio_file)
print(f"You: {transcript}")
if 'exit' in transcript.lower():
print("Goodbye!")
break
response = get_claude_response(transcript)
print(f"Claude: {response}")
speak_response(response)
except KeyboardInterrupt:
print("\
Goodbye!")
break
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()
Run with python voice_agent.py. Speak, listen to Claude's voice reply!
Enhancements for Production
- Real-time STT/TTS: Use ElevenLabs WebSockets for <500ms latency.
# Example WebSocket snippet (adapt from docs) from elevenlabs import stream_stt_websocket - Streaming Claude:
stream=Truein messages.create for partial responses. - Tools & Agents: Add Claude's tool_use for actions (e.g., weather API).
tools = [{"name": "get_weather", "input_schema": {...}}] - Deployment: FastAPI + WebSockets for phone integration (Twilio).
- Context Management: Use MCP servers for persistent memory.
- Multi-turn Optimization: Summarize history with Claude to fit token limits.
Word count tip: This basic loop is ~200 LOC, expandable to agents.
Troubleshooting
| Issue | Solution |
|---|---|
| PyAudio errors | Install portaudio: brew install portaudio (Mac), or use pip install pipwin on Win. |
| Rate limits | Upgrade ElevenLabs/Anthropic plans. |
| Latency | Shorten record duration; use streaming. |
| No audio | Check mic permissions. |
Why Claude + ElevenLabs?
- Claude: Best reasoning, handles nuance/ambiguity in speech.
- ElevenLabs: Ultra-realistic voices, low-latency audio.
- Beats GPT + basic TTS: Claude's safety + ElevenLabs expressiveness.
Comparisons:
- Vs. GPT-4o: Claude cheaper, safer for enterprise.
- Vs. Gemini: Better creative responses.
Build your voice agent today—fork on GitHub! Share your tweaks in comments.
~1450 words
Stay ahead of the AI curve
The most important updates, news, and content — delivered in one weekly newsletter.