From a5c95b460af259a54a7ac274d3fb42902d802ea3 Mon Sep 17 00:00:00 2001 From: hailin Date: Sun, 1 Mar 2026 18:29:59 -0800 Subject: [PATCH] fix: patch aiohttp SSL verification for OpenAI Realtime STT WebSocket The OpenAI Realtime STT uses aiohttp WebSocket connections (not httpx), so the existing httpx verify=False fix does not apply. LiveKit's http_context creates aiohttp.TCPConnector without ssl=False, causing SSL certificate verification errors when OPENAI_BASE_URL points to a proxy with a self-signed certificate. Monkey-patch http_context._new_session_ctx to inject ssl=False into the aiohttp connector, fixing the "CERTIFICATE_VERIFY_FAILED" error for Realtime STT WebSocket connections. Co-Authored-By: Claude Opus 4.6 --- packages/services/voice-agent/src/agent.py | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/services/voice-agent/src/agent.py b/packages/services/voice-agent/src/agent.py index 85d2bf3..b77d0dd 100644 --- a/packages/services/voice-agent/src/agent.py +++ b/packages/services/voice-agent/src/agent.py @@ -10,7 +10,9 @@ Usage: import json import logging +import ssl +import aiohttp from livekit.agents import ( Agent, AgentServer, @@ -20,9 +22,56 @@ from livekit.agents import ( cli, room_io, ) +from livekit.agents.utils import http_context from livekit.plugins import silero from .config import settings + +# --------------------------------------------------------------------------- +# Monkey-patch: disable SSL verification for aiohttp sessions. +# +# The OpenAI Realtime STT uses aiohttp WebSocket (not httpx), so passing +# verify=False to the httpx/OpenAI client does NOT help. LiveKit's +# http_context._new_session_ctx creates an aiohttp.TCPConnector without +# ssl=False, causing SSL errors when OPENAI_BASE_URL points to a proxy +# with a self-signed certificate. +# +# We replace _new_session_ctx to inject ssl=False into the connector. +# --------------------------------------------------------------------------- +_original_new_session_ctx = http_context._new_session_ctx + +_no_verify_ssl = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) +_no_verify_ssl.check_hostname = False +_no_verify_ssl.verify_mode = ssl.CERT_NONE + + +def _patched_new_session_ctx(): + """Same as the original but with ssl verification disabled.""" + _g_session = None + + def _new_session(): + nonlocal _g_session + if _g_session is None or _g_session.closed: + from livekit.agents.job import get_job_context + + try: + http_proxy = get_job_context().proc.http_proxy + except RuntimeError: + http_proxy = None + + connector = aiohttp.TCPConnector( + limit_per_host=50, + keepalive_timeout=120, + ssl=_no_verify_ssl, + ) + _g_session = aiohttp.ClientSession(proxy=http_proxy, connector=connector) + return _g_session + + http_context._ContextVar.set(_new_session) + return _new_session + + +http_context._new_session_ctx = _patched_new_session_ctx from .plugins.agent_llm import AgentServiceLLM from .plugins.whisper_stt import LocalWhisperSTT from .plugins.kokoro_tts import LocalKokoroTTS, patch_misaki_compat