Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions python/chatbot/elevenlabs-chatbot-protect/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# ElevenLabs Configuration
ELEVENLABS_API_KEY=your-elevenlabs-api-key
ELEVENLABS_AGENT_ID=your-agent-id

# Galileo Configuration (for local instance)
GALILEO_API_KEY=your-galileo-api-key
GALILEO_CONSOLE_URL=
GALILEO_PROJECT_NAME=elevenlabs-voice-poc
GALILEO_LOG_STREAM=voice-conversations

GALILEO_PROTECT_ENABLED=true
GALILEO_PROTECT_STAGE_ID=your-stage-id
1 change: 1 addition & 0 deletions python/chatbot/elevenlabs-chatbot-protect/.python-version
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this file as it forces a Python version

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12.12
75 changes: 75 additions & 0 deletions python/chatbot/elevenlabs-chatbot-protect/README.md
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you link from the top level README please

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# ElevenLabs Voice Chatbot with Galileo Protect

A terminal-based voice chatbot that lets you have real-time voice conversations with an ElevenLabs AI agent, with Galileo Protect guardrails for content moderation.

## What This Example Shows

- Interactive voice chat in your terminal (speak via microphone, hear responses via speakers)
- Real-time guardrails using Galileo Protect to moderate user input and agent output
- Conversation logging and tracing with Galileo Observe
- Session metrics tracking (latency, turn counts, character counts)

## Quick Start

1. Install system dependencies:
```bash
brew install portaudio # Required for audio support on macOS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about Windows or Linux? Most of our enterprise customers are on Windows, so do they need to install anything.

This should be tagged as macOS only

```

2. Create and activate a virtual environment:
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```

3. Install Python dependencies:
```bash
pip install -r requirements.txt
```

4. Set up environment variables:
```bash
cp .env.example .env
# Edit .env with your API keys
```

5. Run the voice chat:
```bash
python conversation.py
```

For best results use a headset with micrphone. The app will start listening through your microphone. Speak to chat with the AI agent and hear responses through your speakers. Press `Ctrl+C` to end the session.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For best results use a headset with micrphone. The app will start listening through your microphone. Speak to chat with the AI agent and hear responses through your speakers. Press `Ctrl+C` to end the session.
For best results use a headset with microphone. The app will start listening through your microphone. Speak to chat with the AI agent and hear responses through your speakers. Press `Ctrl+C` to end the session.


## Configuration

Edit `.env` with your credentials. Note for `ELEVENLABS_*` variables you can signup with a free tier of elevenlabs and use their Agents Platform to create a voice agent to obtain the required API key and Agent Id:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to make it easier to sign up?


| Variable | Description |
|----------|-------------|
| `ELEVENLABS_API_KEY` | Your ElevenLabs API key |
| `ELEVENLABS_AGENT_ID` | Your ElevenLabs Agent ID |
| `GALILEO_API_KEY` | Your Galileo API key |
| `GALILEO_CONSOLE_URL` | Galileo console URL (optional) |
| `GALILEO_PROJECT_NAME` | Project name for logging |
| `GALILEO_PROTECT_STAGE_ID` | Protect stage ID for guardrails (see below) |

### Creating a Galileo Protect Stage

To enable guardrails, you need to create a Galileo Protect stage. Run the included script:

```bash
python scripts/create_stage.py
```

This will create a protect stage with an input toxicity rule and output the stage ID. Copy this ID to your `.env` file as `GALILEO_PROTECT_STAGE_ID`.

## Requirements

- Python 3.9+
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Python 3.9+
- Python 3.10+

We are updating Galileo to 3.10

- Microphone and headphones (to avoid audio feedback)

## Learn More

- [Galileo Documentation](https://docs.galileo.ai/)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- [Galileo Documentation](https://docs.galileo.ai/)
- [Galileo Documentation](https://v2docs.galileo.ai/what-is-galileo)

- [Runtime Protection](https://v2docs.galileo.ai/concepts/protect/overview)
- [ElevenLabs Conversational AI](https://elevenlabs.io/docs/conversational-ai)
32 changes: 32 additions & 0 deletions python/chatbot/elevenlabs-chatbot-protect/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Configuration management for the ElevenLabs Voice POC."""

from pydantic_settings import BaseSettings


class Settings(BaseSettings):
"""Application settings loaded from environment variables."""

# ElevenLabs Configuration
elevenlabs_api_key: str
elevenlabs_agent_id: str

# WebSocket monitoring endpoint
elevenlabs_ws_url: str = "wss://api.elevenlabs.io/v1/convai/conversation"

# Galileo Configuration
galileo_api_key: str = ""
galileo_console_url: str = "http://localhost:3000" # Local Galileo instance
galileo_project_name: str = "elevenlabs-voice-poc"
galileo_log_stream: str = "voice-conversations"
Comment on lines +17 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should load from the env variables so why hard code here?
Also the console doesn't run locally for our users.


galileo_protect_enabled: bool = True
galileo_protect_stage_id: str = ""

class Config:
env_file = ".env"
env_file_encoding = "utf-8"


def get_settings() -> Settings:
"""Get application settings."""
return Settings()
204 changes: 204 additions & 0 deletions python/chatbot/elevenlabs-chatbot-protect/conversation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
"""ElevenLabs Conversation using official SDK - supports voice input/output."""

import os
import uuid
from pathlib import Path

from dotenv import load_dotenv

# Load .env from project root
env_path = Path(__file__).parent / ".env"
load_dotenv(env_path)

from elevenlabs.client import ElevenLabs
from elevenlabs.conversational_ai.conversation import Conversation
from elevenlabs.conversational_ai.default_audio_interface import DefaultAudioInterface

from config import get_settings
from galileo_handler import get_galileo_handler


# Initialize Galileo handler
_galileo = None
_conversation = None


def _get_galileo():
"""Lazy initialization of Galileo handler."""
global _galileo
if _galileo is None:
_galileo = get_galileo_handler()
return _galileo


def on_agent_response(response: str) -> None:
"""Called when agent responds - logs to Galileo."""
print(f"\n[AGENT] {response}")

galileo = _get_galileo()
result = galileo.log_agent_turn(response)
if result.get("blocked"):
print(f"[FAKE_GUARDRAIL] Agent response flagged: {result.get('reason')}")


def on_user_transcript(transcript: str) -> None:
"""Called when user speech is transcribed - logs to Galileo."""
global _conversation
print(f"\n[USER] {transcript}")

galileo = _get_galileo()
result = galileo.log_user_turn(transcript)
if result.get("blocked"):
print(f"\n[GALILEO PROTECT] *** INPUT BLOCKED *** {result.get('reason')}")

# Get the override message from Galileo Protect
override_message = result.get("override_message")

if override_message:
# End current conversation session first
if _conversation:
print(f"[GALILEO PROTECT] Ending conversation session...")
_conversation.end_session()

# Pause briefly to let audio system settle
import time

time.sleep(1.5)

# Print the override message
print(f"\n[AGENT] {override_message}")

# Generate and play the override message audio
try:
import tempfile
import subprocess
import platform

settings = get_settings()

# Get the ElevenLabs client
client = ElevenLabs(api_key=settings.elevenlabs_api_key)

# Generate audio using the text_to_speech module
print(f"[GALILEO PROTECT] Generating audio for override message...")
audio_generator = client.text_to_speech.convert(
text=override_message,
voice_id="cjVigY5qzO86Huf0OWal", # Eric voice ID
model_id="eleven_turbo_v2",
output_format="mp3_22050_32", # Lower quality to match conversational audio
)

# Save audio to a temporary file
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as f:
for chunk in audio_generator:
f.write(chunk)
temp_file = f.name

# Play using system's default audio player
print(f"[GALILEO PROTECT] Playing override message...")
system = platform.system()
if system == "Darwin": # macOS
subprocess.run(["afplay", temp_file], check=True)
elif system == "Windows":
os.startfile(temp_file)
else: # Linux
subprocess.run(["xdg-open", temp_file], check=True)

# Clean up
os.unlink(temp_file)

print(f"[GALILEO PROTECT] Override message delivered via audio")

except Exception as e:
print(f"[GALILEO PROTECT] Failed to generate or play audio: {e}")
print(f"[GALILEO PROTECT] Message was displayed as text above")
import traceback

traceback.print_exc()

# Log the override message to Galileo as an agent turn
# This ensures it shows up in the trace
galileo.log_agent_turn(override_message)

# End the conversation
print("[GALILEO PROTECT] Ending conversation session...")
galileo.end_conversation()
print("[INFO] Conversation ended - logs sent to Galileo")
raise SystemExit("Call terminated by guardrail")
else:
# No override message, just end immediately
if _conversation:
print("[GALILEO PROTECT] Ending session due to guardrail violation...")
_conversation.end_session()
galileo.end_conversation()
raise SystemExit("Call terminated by guardrail")


def on_mode_change(mode: dict) -> None:
"""Called when conversation mode changes (speaking/listening)."""
print(f"[MODE] {mode}")


def run_voice_conversation(use_headphones: bool = True):
"""Run a voice conversation with microphone input and speaker output.

Args:
use_headphones: If True, plays audio through speakers.
Set to False if not using headphones to avoid feedback loop.
"""
settings = get_settings()

# Initialize ElevenLabs client
client = ElevenLabs(api_key=settings.elevenlabs_api_key)

# Initialize Galileo and start conversation trace
galileo = _get_galileo()
session_id = str(uuid.uuid4())
galileo.start_conversation(session_id)

print("\n" + "=" * 60)
print("ElevenLabs Voice POC - Voice Mode + Galileo")
print(f"Session ID: {session_id}")
if use_headphones:
print("*** USE HEADPHONES to avoid audio feedback loop ***")
else:
print("*** Silent mode - no audio playback ***")
print("Speak into your microphone to talk to the agent")
print("Press Ctrl+C to end the session")
print("=" * 60 + "\n")

global _conversation

# Create conversation with audio interface
conversation = Conversation(
client=client,
agent_id=settings.elevenlabs_agent_id,
requires_auth=True, # We're using API key auth
# Required: audio interface for mic/speaker
audio_interface=DefaultAudioInterface(),
# Callbacks for monitoring - connected to Galileo
callback_agent_response=on_agent_response,
callback_user_transcript=on_user_transcript,
# callback_mode_change=on_mode_change, # Optional
)

_conversation = conversation

# Start the conversation (blocking)
print("[INFO] Starting conversation... Speak now!")
conversation.start_session()

# Wait for conversation to end
try:
conversation.wait_for_session_end()
except KeyboardInterrupt:
print("\n[INFO] Ending conversation...")
conversation.end_session()

# End Galileo trace and flush logs
galileo.end_conversation()
print("[INFO] Conversation ended - logs sent to Galileo")


if __name__ == "__main__":
run_voice_conversation()
Loading
Loading