OpenAI-compatible proxy for ElevenLabs Conversational AI.
This project exposes a minimal API surface that looks like OpenAI's Chat Completions API while routing requests to ElevenLabs APIs and WebSocket conversation endpoints.
GET /v1/modelsto list available ElevenLabs LLMs for your API key.POST /v1/chat/completionsto generate a chat response.- Bearer-token auth using your ElevenLabs API key.
- Optional model override forwarded to ElevenLabs.
- Developer/system prompts forwarded as an agent prompt override.
- Multi-turn conversation history support.
temperatureandmax_tokensforwarded to ElevenLabs viacustom_llm_extra_body.- Response ID uses the real ElevenLabs conversation ID.
- Configurable ElevenLabs API base URL via environment variable.
- Client calls
POST /v1/chat/completionswith OpenAI-style messages. - The proxy requests a signed conversation WebSocket URL from ElevenLabs using:
ELEVENLABS_AGENT_ID(environment variable)- Bearer API key from
Authorizationheader
- It opens the WebSocket, sends:
- conversation initiation payload (including prompt/model override)
- the last
usermessage
- It waits for the first
agent_responseevent and returns it as an OpenAI-style chat completion response.
Since each request opens a new WebSocket (stateless), multi-turn context is handled by injecting previous messages into the system prompt.
When a request contains multiple user/assistant messages, the proxy:
- Takes the last
usermessage and sends it as the actual WebSocket user message. - All preceding
userandassistantmessages are formatted as a conversation history block and appended to the system prompt.
For example, given this request:
{
"messages": [
{"role": "system", "content": "Be concise."},
{"role": "user", "content": "Hi, my name is Enzo"},
{"role": "assistant", "content": "Hello Enzo! How can I help you?"},
{"role": "user", "content": "What is my name?"}
]
}The proxy will:
- Set the agent prompt to:
Be concise. ## Conversation history: User: Hi, my name is Enzo Assistant: Hello Enzo! How can I help you? - Send
"What is my name?"as the WebSocket user message.
If the request only has a single user message (no history), nothing extra is added to the prompt and it works as a simple single-turn request.
- Python 3.11+ (Docker image uses
python:3.11-slim) - ElevenLabs API key
- ElevenLabs agent ID
Create a .env file:
ELEVENLABS_AGENT_ID=your_agent_id
ELEVENLABS_API_BASE_URL=https://api.elevenlabs.ioYou can copy from .env.example.
Supported ElevenLabs API base URLs:
https://api.elevenlabs.iohttps://api.us.elevenlabs.iohttps://api.eu.residency.elevenlabs.iohttps://api.in.residency.elevenlabs.io
Install dependencies:
pip install -r requirements.txtStart the server:
uvicorn src.start:app --host 0.0.0.0 --port 10000Build image:
docker build -t elevenlabs-llm-proxy .Run container:
docker run --rm -p 10000:10000 --env-file .env elevenlabs-llm-proxyWindows helper script (optional):
build.batThis script builds the image and exports it to elevenlabs-llm-proxy.tar.
Set base URL:
http://localhost:10000
curl -X GET "http://localhost:10000/v1/models" \
-H "Authorization: Bearer YOUR_ELEVENLABS_API_KEY"/v1/models is backed by ElevenLabs GET /v1/convai/llm/list.
curl -X POST "http://localhost:10000/v1/chat/completions" \
-H "Authorization: Bearer YOUR_ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4.1-mini",
"temperature": 0.5,
"max_tokens": 200,
"messages": [
{"role": "developer", "content": "Be concise."},
{"role": "user", "content": "Write a one-line product tagline."}
]
}'temperature and max_tokens are optional. When provided, they are forwarded to ElevenLabs via custom_llm_extra_body in the conversation initiation payload.
messagesmust include at least oneusermessage (and it must be the last non-system message).developerandsystemmessages are merged and forwarded as prompt override.assistantandusermessages prior to the lastusermessage are injected as conversation history in the prompt.toolmessages are accepted by schema but not used.temperatureandmax_tokensare forwarded to ElevenLabs when provided.- The response
idfield contains the real ElevenLabs conversation ID (e.g.chatcmpl-conv_abc123). - Streaming is not implemented.
usagefields are currently returned as0.
Common cases:
401invalid or missingAuthorizationheader500missingELEVENLABS_AGENT_ID502upstream ElevenLabs HTTP/WebSocket issues
src/start.py- FastAPI app and endpoint logicsrc/environ.py- environment loading helperDockerfile- container build/runtimerequirements.txt- Python dependencies