कस्टम LLM इंटीग्रेशन

Speech Engine SDK का इस्तेमाल करके अपने LLM से Twilio फ़ोन एजेंट चलाएं।

ओवरव्यू

ElevenAgents का नेटिव Twilio इंटीग्रेशन उस स्थिति के लिए है जहाँ ElevenLabs LLM होस्ट करता है। जब आपको अपने सर्वर पर LLM ब्रेन का पूरा नियंत्रण चाहिए — अपना मॉडल, RAG पाइपलाइन, फ़ंक्शन-कॉल रूटिंग या अन्य सर्वर-साइड रीजनिंग — और एजेंट फिर भी Twilio फ़ोन नंबर पर है, तब इस गाइड का इस्तेमाल करें।

कस्टम-LLM वाला हिस्सा Speech Engine SDK से मिलता है। यह ElevenLabs और आपके सर्वर के बीच एक WebSocket खोलता है, ताकि कॉल चलने के दौरान आपका LLM स्ट्रीम करके जवाब वापस भेज सके। Twilio वाला हिस्सा कॉल ऑडियो को एजेंट तक पहुँचाने के लिए Media Streams इस्तेमाल करता है।

आर्किटेक्चर

Speech Engine SDK एजेंट के कन्वर्सेशन सिस्टम में दो WebSocket एंडपॉइंट देता है:

  • ब्रेन WebSocket आपके सर्वर पर चलता है। ElevenLabs ट्रांसक्रिप्ट भेजने और LLM से जनरेट किया गया टेक्स्ट पाने के लिए इससे कनेक्ट करता है।
  • कन्वर्सेशन WebSocket ElevenLabs पर चलता है। क्लाइंट ऑडियो भेजने और सिंथेसाइज़ किया गया ऑडियो वापस पाने के लिए इससे कनेक्ट करते हैं। Twilio ब्रिज साइन किए गए URL से कनेक्ट होता है और दोनों दिशाओं में μ-law ऑडियो रिले करता है।

Twilio Media Streams और Speech Engine, दोनों ulaw_8000 इस्तेमाल करते हैं, इसलिए ब्रिज बिना ट्रांसकोडिंग के base64-एन्कोडेड ऑडियो रिले करता है।

loop [Conversation] Dial number POST /incoming-call TwiML <Connect><Stream> WebSocket /media-stream Open conversation WebSocket (signed URL) Speak media event (μ-law base64) user_audio_chunk user_transcript agent_response (streamed) audio event (μ-law base64) media event Play audio Caller Twilio Bridge Server ElevenLabs (conversation WS) Brain Server

अगर सुविधाजनक हो, तो ब्रिज और ब्रेन सर्वर एक ही प्रोसेस में चल सकते हैं — नीचे का उदाहरण उन्हें जोड़ता है।

इस पैटर्न का इस्तेमाल कब करें

यह गाइड और नेटिव Twilio इंटीग्रेशन, दोनों Twilio फ़ोन नंबर पर एजेंट लगाते हैं। अंतर यह है कि LLM का मालिक कौन है:

  • नेटिव इंटीग्रेशन: ElevenLabs LLM होस्ट करता है, और आप इसे एजेंट के ज़रिए कॉन्फ़िगर करते हैं। आसान।
  • Speech Engine SDK के ज़रिए कस्टम LLM (यह गाइड): आप LLM को अपने सर्वर पर होस्ट करते हैं। मॉडल, RAG, फ़ंक्शन कॉल और बिज़नेस लॉजिक पर पूरा नियंत्रण। ज़्यादा हिस्से।

अगर आपका LLM लॉजिक स्टैंडर्ड एजेंट कॉन्फ़िगरेशन में फिट होता है, तो नेटिव इंटीग्रेशन चुनें। जब आपके ब्रेन को अपने इन्फ़्रास्ट्रक्चर पर कोड चलाना हो, तब इस गाइड का इस्तेमाल करें।

यह पैटर्न Speech Engine SDK का इस्तेमाल करता है, जो आपके सर्वर और ElevenLabs API के बीच संवाद के लिए WebSocket कनेक्शन इस्तेमाल करता है। आप कस्टम LLM गाइड भी इस्तेमाल कर सकते हैं, जो Speech Engine SDK के बजाय OpenAI-संगत HTTP एंडपॉइंट इस्तेमाल करती है।

दोनों के बीच मुख्य अंतर WebSocket और HTTP रिक्वेस्ट का है। WebSocket इस्तेमाल करने का अर्थ है कि हर टर्न के लिए नया HTTP कनेक्शन बनाने के बजाय एक ही कनेक्शन बनाए रखना, जिससे लेटेंसी कम हो सकती है।

ज़रूरी चीज़ें

  • एक Twilio अकाउंट और वॉइस-सक्षम फ़ोन नंबर।
  • Speech Engine रिसोर्स। इसे बनाने और ब्रेन-सर्वर पैटर्न जानने के लिए Speech Engine क्विकस्टार्ट देखें।
  • एक सार्वजनिक HTTPS टनल (जैसे, ngrok)। Twilio सार्वजनिक इंटरनेट से आपके ब्रिज को डायल करता है।
  • Python 3.9+ या Node.js 18+।

एजेंट को μ-law ऑडियो के लिए कॉन्फ़िगर करें

Twilio Media Streams 8 kHz μ-law ऑडियो इस्तेमाल करता है। Speech Engine को वही फ़ॉर्मैट स्वीकार और आउटपुट करने के लिए कॉन्फ़िगर करें, ताकि ब्रिज को ट्रांसकोड न करना पड़े।

import asyncio
import os
from elevenlabs import AsyncElevenLabs
elevenlabs = AsyncElevenLabs(api_key=os.environ["ELEVENLABS_API_KEY"])
async def update_engine():
await elevenlabs.speech_engine.update(
speech_engine_id="seng_8k3m9xr4hjnfg983brhmhkd98n6",
asr={"user_input_audio_format": "ulaw_8000"},
tts={
"model_id": "eleven_flash_v2",
"agent_output_audio_format": "ulaw_8000",
},
speech_engine={
"request_headers": {"x-api-key": os.environ["SHARED_SECRET"]},
},
)
asyncio.run(update_engine())

eleven_flash_v2 टेक्स्ट-टू-स्पीच लेटेंसी को कम रखता है, जो फ़ोन कॉल में अहम है। request_headers ब्लॉक ElevenLabs को हर ब्रेन WebSocket कनेक्शन पर x-api-key: <shared-secret> शामिल करने के लिए कहता है — ब्रेन सर्वर यह सुनिश्चित करने के लिए हेडर जाँचता है कि केवल आपका Speech Engine ही उससे कनेक्ट कर सकता है।

ब्रिज सर्वर बनाएं

ब्रिज तीन रूट सर्व करता है:

  • POST /incoming-call — Twilio वेबहुक। Twilio को /media-stream के लिए Media Stream खोलने का निर्देश देने वाला TwiML लौटाता है।
  • GET /media-stream — Twilio Media Streams WebSocket। Speech Engine कन्वर्सेशन WebSocket पर और उससे ऑडियो रिले करता है।
  • GET /ws — ब्रेन WebSocket। कन्वर्सेशन शुरू होने पर ElevenLabs यहाँ कनेक्ट करता है। स्टैंडर्ड engine.serve() / engine.attach() सर्वर चलाता है।
1

डिपेंडेंसी इंस्टॉल करें

pip install "elevenlabs" "aiohttp" "twilio" "python-dotenv"
2

Speech Engine के लिए साइन किया हुआ URL बनाएं

हर नई कॉल आने पर ब्रिज साइन किया हुआ URL माँगता है। URL में Speech Engine ID और एक बार इस्तेमाल होने वाला सिग्नेचर होता है, इसलिए ब्रिज को कभी रॉ API की की ज़रूरत नहीं पड़ती।

from elevenlabs import AsyncElevenLabs
elevenlabs = AsyncElevenLabs(api_key=os.environ["ELEVENLABS_API_KEY"])
async def signed_url() -> str:
response = await elevenlabs.conversational_ai.conversations.get_signed_url(
agent_id=os.environ["SPEECH_ENGINE_ID"],
)
return response.signed_url
3

TwiML रिस्पॉन्स सर्व करें

कॉल आने पर Twilio /incoming-call पर POST करता है। रिस्पॉन्स में ऐसा TwiML होता है, जो ब्रिज के अपने /media-stream WebSocket के लिए Media Stream खोलता है।

from aiohttp import web
from twilio.request_validator import RequestValidator
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])
async def incoming_call(request: web.Request) -> web.Response:
form = await request.post()
signature = request.headers.get("X-Twilio-Signature", "")
url = str(request.url)
if not validator.validate(url, dict(form), signature):
return web.Response(status=403, text="forbidden")
host = request.headers.get("X-Forwarded-Host") or request.host
twiml = (
'<?xml version="1.0" encoding="UTF-8"?>'
"<Response><Connect>"
f'<Stream url="wss://{host}/media-stream"/>'
"</Connect></Response>"
)
return web.Response(text=twiml, content_type="text/xml")

RequestValidator (Python) और twilio.webhook({ validate: true }) (Node), X-Twilio-Signature हेडर को TWILIO_AUTH_TOKEN के साथ जाँचते हैं। वैलिडेशन के बिना, सार्वजनिक इंटरनेट पर कोई भी व्यक्ति /incoming-call पर POST करके आपके अकाउंट से कॉल का बिल बनवा सकता है।

4

Media Stream को ब्रिज करें

Media Stream एक WebSocket है जो JSON इवेंट की शृंखला भेजता है: connected, start, media (ऑडियो पेलोड), और stop। ब्रिज start पर Speech Engine कन्वर्सेशन WebSocket खोलता है और स्ट्रीम बंद होने तक दोनों दिशाओं में ऑडियो रिले करता है।

import asyncio
import json
import aiohttp
from aiohttp import web
async def media_stream(request: web.Request) -> web.WebSocketResponse:
twilio_ws = web.WebSocketResponse()
await twilio_ws.prepare(request)
stream_sid: str | None = None
el_session: aiohttp.ClientSession | None = None
el_ws: aiohttp.ClientWebSocketResponse | None = None
pump_task: asyncio.Task | None = None
async def pump_el_to_twilio(el: aiohttp.ClientWebSocketResponse):
async for msg in el:
if msg.type != aiohttp.WSMsgType.TEXT:
continue
event = json.loads(msg.data)
etype = event.get("type")
if etype == "audio":
await twilio_ws.send_str(json.dumps({
"event": "media",
"streamSid": stream_sid,
"media": {"payload": event["audio_event"]["audio_base_64"]},
}))
elif etype == "interruption":
await twilio_ws.send_str(json.dumps({
"event": "clear",
"streamSid": stream_sid,
}))
elif etype == "ping":
event_id = event.get("ping_event", {}).get("event_id")
await el.send_str(json.dumps({
"type": "pong", "event_id": event_id,
}))
try:
async for msg in twilio_ws:
if msg.type != aiohttp.WSMsgType.TEXT:
continue
event = json.loads(msg.data)
if event["event"] == "start":
stream_sid = event["start"]["streamSid"]
el_session = aiohttp.ClientSession()
el_ws = await el_session.ws_connect(await signed_url())
await el_ws.send_str(json.dumps({
"type": "conversation_initiation_client_data",
}))
pump_task = asyncio.create_task(pump_el_to_twilio(el_ws))
elif event["event"] == "media" and el_ws is not None:
await el_ws.send_str(json.dumps({
"user_audio_chunk": event["media"]["payload"],
}))
elif event["event"] == "stop":
break
finally:
if pump_task:
pump_task.cancel()
if el_ws and not el_ws.closed:
await el_ws.close()
if el_session and not el_session.closed:
await el_session.close()
return twilio_ws

Speech Engine का interruption इवेंट Twilio स्ट्रीम पर clear इवेंट ट्रिगर करता है, जो बफ़र किया हुआ ऑडियो हटा देता है ताकि बीच में बोलना ठीक से काम करे। कन्वर्सेशन WebSocket को चालू रखने के लिए ping इवेंट का जवाब pong से दिया जाता है।

5

ब्रेन सर्वर साथ में चलाएं

ब्रेन सर्वर क्विकस्टार्ट में दिखाया गया स्टैंडर्ड Speech Engine सर्वर है। इसमें केवल WebSocket अपग्रेड पर शेयर किए गए सीक्रेट की जाँच जोड़ी गई है — कनेक्शन तभी स्वीकार करें जब x-api-key, Speech Engine पर सेट की गई वैल्यू से मेल खाता हो।

import os
from elevenlabs import AsyncElevenLabs
elevenlabs = AsyncElevenLabs(api_key=os.environ["ELEVENLABS_API_KEY"])
SHARED_SECRET = os.environ["SHARED_SECRET"]
async def brain_ws(request: web.Request) -> web.WebSocketResponse:
if request.headers.get("x-api-key") != SHARED_SECRET:
return web.Response(status=401, text="unauthorized")
ws = web.WebSocketResponse()
await ws.prepare(request)
engine = await elevenlabs.speech_engine.get(os.environ["SPEECH_ENGINE_ID"])
session = engine.create_session(ws)
async def on_transcript(transcript):
# Replace this with your own LLM call; see the quickstart.
await session.send_response("Hello, you've reached the demo.")
session.on("user_transcript", on_transcript)
await session.run()
return ws
def make_app() -> web.Application:
app = web.Application()
app.router.add_post("/incoming-call", incoming_call)
app.router.add_get("/media-stream", media_stream)
app.router.add_get("/ws", brain_ws)
return app
if __name__ == "__main__":
web.run_app(make_app(), port=3001)

LLM कॉल और स्ट्रीम किए गए रिस्पॉन्स सहित पूरा on_transcript इंप्लीमेंटेशन देखने के लिए Speech Engine क्विकस्टार्ट देखें।

Twilio को ब्रिज पर पॉइंट करें

1

ब्रिज और सार्वजनिक टनल शुरू करें

ngrok http 3001
python bridge.py

ngrok द्वारा प्रिंट किया गया https:// URL नोट कर लें — Twilio इस पर POST करेगा।

2

Speech Engine ws_url अपडेट करें

speech_engine.ws_url को अपने ब्रेन एंडपॉइंट के सार्वजनिक WebSocket URL पर सेट करें, ताकि ElevenLabs को पता हो कि कहाँ कनेक्ट करना है।

await elevenlabs.speech_engine.update(
speech_engine_id="seng_8k3m9xr4hjnfg983brhmhkd98n6",
speech_engine={"ws_url": "wss://abc123.ngrok.io/ws"},
)
3

Twilio नंबर कॉन्फ़िगर करें

Twilio कंसोल में अपने फ़ोन नंबर का Voice Configuration खोलें:

  • A call comes in: Webhook
  • URL: https://abc123.ngrok.io/incoming-call
  • HTTP method: POST

अगर नंबर Elastic SIP Trunk से जुड़ा है, तो पहले उसे अलग करें — Twilio नंबर या तो ट्रंक पर रूट होता है या वेबहुक पर, दोनों पर नहीं।

4

नंबर पर कॉल करें

किसी भी फ़ोन से नंबर डायल करें। एजेंट जवाब देगा; कॉल में बोलें और आपको एजेंट का जवाब सुनाई देना चाहिए। डिबग लॉगिंग चालू होने पर, ब्रिज हर टर्न के लिए कॉल SID, कन्वर्सेशन ID और ऑडियो फ़ॉर्मैट लॉग करता है।

प्रोडक्शन के लिए ध्यान देने योग्य बातें

  • Webhook validation: /incoming-call पर हमेशा X-Twilio-Signature को वैलिडेट करें। ऊपर दिए गए उदाहरण में Twilio की हेल्पर लाइब्रेरी इस्तेमाल की गई है; इस चरण को न छोड़ें।
  • Shared secret: ब्रेन WebSocket पर shared secret लागू करें। इसके बिना, जो भी आपके ngrok URL का अनुमान लगा लेता है, वह कनेक्ट होकर ElevenLabs की नकल कर सकता है।
  • Stable host: ngrok free tier के URL हर रीस्टार्ट पर बदल जाते हैं। reserved ngrok domain या असली hostname इस्तेमाल करें, ताकि हर रीस्टार्ट के बाद आपको Speech Engine का ws_url और Twilio webhook अपडेट न करना पड़े।
  • Latency: हर कॉल LLM के time-to-first-token के अलावा दो नेटवर्क हॉप्स जोड़ती है। कम latency वाला मॉडल इस्तेमाल करें और perceived latency कम रखने के लिए responses स्ट्रीम करें।
  • One process or two: उदाहरण में bridge और brain को एक ही पोर्ट पर रखा गया है, इसलिए एक ngrok tunnel सब कुछ कवर करता है। प्रोडक्शन में, आप उन्हें दो सर्विसेज़ में बाँट सकते हैं, बशर्ते दोनों का एक public URL हो।
  • Prompt injection: फोन कॉल से मिला बोला गया इनपुट भरोसेमंद यूज़र इनपुट नहीं होता। tool calls या database writes को प्रभावित करने से पहले transcripts को वैलिडेट करें।

अगले चरण