fciannella commited on
Commit
76744fc
·
1 Parent(s): c63b8b6

Now generating the credentials for Twilio inside the service

Browse files
examples/voice_agent_webrtc_langgraph/agents/requirements.txt CHANGED
@@ -11,4 +11,5 @@ docling
11
  pymongo
12
  yt_dlp
13
  requests
14
- protobuf==6.31.1
 
 
11
  pymongo
12
  yt_dlp
13
  requests
14
+ protobuf==6.31.1
15
+ twilio
examples/voice_agent_webrtc_langgraph/pipeline.py CHANGED
@@ -66,6 +66,71 @@ pcs_map: dict[str, SmallWebRTCConnection] = {}
66
  contexts_map: dict[str, OpenAILLMContext] = {}
67
 
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  ice_servers = (
70
  [
71
  IceServer(
@@ -392,7 +457,9 @@ async def websocket_endpoint(websocket: WebSocket):
392
  logger.info(f"Reusing existing connection for pc_id: {pc_id}")
393
  await pipecat_connection.renegotiate(sdp=request["sdp"], type=request["type"])
394
  else:
395
- pipecat_connection = SmallWebRTCConnection(ice_servers)
 
 
396
  await pipecat_connection.initialize(sdp=request["sdp"], type=request["type"])
397
 
398
  @pipecat_connection.event_handler("closed")
@@ -450,26 +517,18 @@ async def get_prompt():
450
  # RTC config endpoint must be registered before mounting static at "/"
451
  @app.get("/rtc-config")
452
  async def rtc_config():
453
- """Expose browser RTC ICE configuration based on environment variables.
454
 
455
- Reads TURN_SERVER_URL, TURN_USERNAME, TURN_PASSWORD and returns a structure
456
- consumable by the browser: { "iceServers": [ { urls, username?, credential? } ] }.
457
- Always includes a public STUN as a fallback.
458
  """
459
- ice_servers: list[dict] = []
460
- turn_url = os.getenv("TURN_SERVER_URL") or os.getenv("TURN_URL")
461
- turn_user = os.getenv("TURN_USERNAME") or os.getenv("TURN_USER")
462
- turn_pass = os.getenv("TURN_PASSWORD") or os.getenv("TURN_PASS")
463
- if turn_url:
464
- server: dict = {"urls": turn_url}
465
- if turn_user:
466
- server["username"] = turn_user
467
- if turn_pass:
468
- server["credential"] = turn_pass
469
- ice_servers.append(server)
470
- # Public STUN fallback to aid connectivity when TURN is not provided
471
- ice_servers.append({"urls": "stun:stun.l.google.com:19302"})
472
- return {"iceServers": ice_servers}
473
 
474
 
475
  # Serve static UI (if bundled) after API/WebSocket routes so they still take precedence
 
66
  contexts_map: dict[str, OpenAILLMContext] = {}
67
 
68
 
69
+ # Helper: Build ICE servers for client (browser) using Twilio token if configured
70
+ def _build_client_ice_servers() -> list[dict]:
71
+ # Prefer Twilio dynamic credentials
72
+ sid = os.getenv("TWILIO_ACCOUNT_SID")
73
+ tok = os.getenv("TWILIO_AUTH_TOKEN")
74
+ if sid and tok:
75
+ try:
76
+ # Import lazily to avoid hard dependency when not configured
77
+ from twilio.rest import Client # type: ignore
78
+
79
+ client = Client(sid, tok)
80
+ token = client.tokens.create()
81
+ servers: list[dict] = []
82
+ # Twilio may return either 'ice_servers' with 'url' or 'urls'
83
+ for s in getattr(token, "ice_servers", []) or []:
84
+ url_val = s.get("urls") if isinstance(s, dict) else getattr(s, "urls", None)
85
+ if not url_val:
86
+ url_val = s.get("url") if isinstance(s, dict) else getattr(s, "url", None)
87
+ entry: dict = {"urls": url_val}
88
+ u = s.get("username") if isinstance(s, dict) else getattr(s, "username", None)
89
+ c = s.get("credential") if isinstance(s, dict) else getattr(s, "credential", None)
90
+ if u:
91
+ entry["username"] = u
92
+ if c:
93
+ entry["credential"] = c
94
+ if entry.get("urls"):
95
+ servers.append(entry)
96
+ # Always include a public STUN fallback
97
+ servers.append({"urls": "stun:stun.l.google.com:19302"})
98
+ return servers
99
+ except Exception as e: # noqa: BLE001
100
+ logger.warning(f"Twilio TURN fetch failed, falling back to env/static: {e}")
101
+ # Static env fallback
102
+ servers: list[dict] = []
103
+ turn_url = os.getenv("TURN_SERVER_URL") or os.getenv("TURN_URL")
104
+ turn_user = os.getenv("TURN_USERNAME") or os.getenv("TURN_USER")
105
+ turn_pass = os.getenv("TURN_PASSWORD") or os.getenv("TURN_PASS")
106
+ if turn_url:
107
+ server: dict = {"urls": turn_url}
108
+ if turn_user:
109
+ server["username"] = turn_user
110
+ if turn_pass:
111
+ server["credential"] = turn_pass
112
+ servers.append(server)
113
+ servers.append({"urls": "stun:stun.l.google.com:19302"})
114
+ return servers
115
+
116
+
117
+ # Helper: Convert client ICE dicts to server IceServer objects
118
+ def _build_server_ice_servers() -> list[IceServer]:
119
+ out: list[IceServer] = []
120
+ for s in _build_client_ice_servers():
121
+ urls = s.get("urls")
122
+ username = s.get("username", "")
123
+ credential = s.get("credential", "")
124
+ # urls may be a list or a string. Normalize to list for safety.
125
+ if isinstance(urls, list):
126
+ for u in urls:
127
+ out.append(IceServer(urls=u, username=username, credential=credential))
128
+ elif isinstance(urls, str) and urls:
129
+ out.append(IceServer(urls=urls, username=username, credential=credential))
130
+ return out
131
+
132
+
133
+ # Backward-compatible static servers (unused when Twilio configured)
134
  ice_servers = (
135
  [
136
  IceServer(
 
457
  logger.info(f"Reusing existing connection for pc_id: {pc_id}")
458
  await pipecat_connection.renegotiate(sdp=request["sdp"], type=request["type"])
459
  else:
460
+ # Build dynamic servers (Twilio or env) for new connections
461
+ dynamic_servers = _build_server_ice_servers()
462
+ pipecat_connection = SmallWebRTCConnection(dynamic_servers if dynamic_servers else ice_servers)
463
  await pipecat_connection.initialize(sdp=request["sdp"], type=request["type"])
464
 
465
  @pipecat_connection.event_handler("closed")
 
517
  # RTC config endpoint must be registered before mounting static at "/"
518
  @app.get("/rtc-config")
519
  async def rtc_config():
520
+ """Expose browser RTC ICE configuration based on environment variables or Twilio.
521
 
522
+ Uses Twilio dynamic TURN credentials when TWILIO_ACCOUNT_SID/TWILIO_AUTH_TOKEN are set.
523
+ Falls back to TURN_* env vars. Always includes a public STUN fallback.
 
524
  """
525
+ try:
526
+ servers = _build_client_ice_servers()
527
+ return {"iceServers": servers}
528
+ except Exception as e: # noqa: BLE001
529
+ logger.warning(f"rtc-config dynamic build failed: {e}")
530
+ # Final safe fallback
531
+ return {"iceServers": [{"urls": "stun:stun.l.google.com:19302"}]}
 
 
 
 
 
 
 
532
 
533
 
534
  # Serve static UI (if bundled) after API/WebSocket routes so they still take precedence