Error in starting session with custom api keys for...
# ask-ai
f
Error in starting session with custom api keys for STT, TTS and LLM model.
t
Thanks for reaching out, Suryansh. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m sorry you hit this error while starting a session with custom API keys for STT, TTS, and an LLM. I can help troubleshoot, but I need a few details to pinpoint the cause. Could you please provide the following? 1. The exact error message or stack trace you see when the session fails. 2. Which providers you’re using for each service (STT, TTS, LLM), e.g., Deepgram, Azure, Spitch, OpenAI, etc. 3. Whether you’re using LiveKit Inference or plugin-based models for these integrations. 4. How you’re supplying the custom API keys (environment variables, inline in code, a secrets manager, or via AgentSession config). 5. A minimal code snippet that shows how you create/configure the
AgentSession
(hide any secret values). 6. Any relevant environment details: SDK language/version, LiveKit server version (if known), and whether this is local dev or production. Quick checks you can run now while you gather that info: • Confirm each API key works directly with the provider (e.g., call Deepgram/OpenAI/Spitch APIs with the same key). • Ensure keys are passed in the exact field/name the integration expects (for plugin-based setup the provider docs show required fields). See the STT/TTS/LLM plugin guides: Deepgram STT, Azure STT, Spitch TTS, OpenAI LLM. • Check for common issues: typos, extra whitespace, wrong environment (using test key in prod), or missing required fields (region, model name). Once you paste the error and a short snippet of how you configure the
AgentSession
, I’ll walk through targeted steps to fix it. If you prefer, include which provider to start with and we’ll debug that one first. Sources: Deepgram STT plugin guide | LiveKit Documentation | OpenAI LLM plugin guide | LiveKit Documentation | Spitch TTS plugin guide | LiveKit Documentation | Azure Speech STT plugin guide | LiveKit Documentation
f
session = AgentSession( userdata={}, stt=inference.STT(model="assemblyai/universal-streaming", language="en", api_key=os.getenv("ASSEMBLYAI_API_KEY")), llm=inference.LLM(model="openai/gpt-4.1-mini", api_key=os.getenv("OPENAI_API_KEY")), tts=inference.TTS( model="cartesia/sonic-3", voice="9626c31c-bec5-4cca-baa8-f8ba9e84c8bc", api_key=os.getenv("CARTESIA_API_KEY") ) ) Error: Error in _stt_task Traceback (most recent call last): File "/Users/suryanshsoni/Watto/prismo-backend/livekit-serve r/.venv/lib/python3.11/site-packages/livekit/agents/stt /stt.py", line 408, in anext val = await self._event_aiter.__anext__() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ StopAsyncIteration During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/Users/suryanshsoni/Watto/prismo-backend/livekit-serve r/.venv/lib/python3.11/site-packages/livekit/agents/uti ls/log.py", line 16, in async_fn_logs return await fn(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/suryanshsoni/Watto/prismo-backend/livekit-serve r/.venv/lib/python3.11/site-packages/livekit/agents/voi ce/audio_recognition.py", line 660, in _stt_task async for ev in node: File "/Users/suryanshsoni/Watto/prismo-backend/livekit-serve r/.venv/lib/python3.11/site-packages/livekit/agents/voi ce/agent.py", line 391, in stt_node async for event in stream: File "/Users/suryanshsoni/Watto/prismo-backend/livekit-serve r/.venv/lib/python3.11/site-packages/livekit/agents/stt /stt.py", line 411, in anext raise exc # noqa: B904 ^^^^^^^^^ File "/Users/suryanshsoni/Watto/prismo-backend/livekit-serve r/.venv/lib/python3.11/site-packages/livekit/agents/stt /stt.py", line 289, in _main_task return await self._run() ^^^^^^^^^^^^^^^^^ File "/Users/suryanshsoni/Watto/prismo-backend/livekit-serve r/.venv/lib/python3.11/site-packages/livekit/agents/inf erence/stt.py", line 416, in _run ws = await self._connect_ws() ^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/suryanshsoni/Watto/prismo-backend/livekit-serve r/.venv/lib/python3.11/site-packages/livekit/agents/inf erence/stt.py", line 469, in _connect_ws ws = await asyncio.wait_for( ^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/suryanshsoni/.pyenv/versions/3.11.3/Library/Fra meworks/Python.framework/Versions/3.11/lib/python3.11/a syncio/tasks.py", line 479, in wait_for return fut.result() ^^^^^^^^^^^^ File "/Users/suryanshsoni/Watto/prismo-backend/livekit-serve r/.venv/lib/python3.11/site-packages/aiohttp/client.py" , line 1494, in send return self._coro.send(arg) ^^^^^^^^^^^^^^^^^^^^ File "/Users/suryanshsoni/Watto/prismo-backend/livekit-serve r/.venv/lib/python3.11/site-packages/aiohttp/client.py" , line 1093, in _ws_connect raise WSServerHandshakeError( aiohttp.client_exceptions.WSServerHandshakeError: 401, message='Invalid response status', url='wss://agent-gateway.livekit.cloud/v1/stt'
t
Thanks — this is very helpful information. You’re seeing a WebSocket handshake 401 when the STT node tries to connect to LiveKit’s inference gateway. I reviewed your code and the error log: you are creating
inference.STT/LLM/TTS
and passing provider API keys (AssemblyAI, OpenAI, Cartesia). That combination causes the 401 because LiveKit Inference expects authentication with your LiveKit credentials, not provider keys. What’s happening (short): •
inference.*
routes through LiveKit Inference (agent gateway) which authenticates with your LiveKit API key/secret. • Passing
api_key=...
to
inference.STT
does not authenticate to the LiveKit gateway and will not forward your provider key. • The gateway responds 401 Unauthorized during the WebSocket handshake to wss://agent-gateway.livekit.cloud/v1/stt. Two ways to fix this (pick one depending on which billing/auth flow you want): 1) Use LiveKit Inference (recommended if you want LiveKit to manage providers) • Remove the provider
api_key
arguments from the
inference.*
constructors. • Ensure your LiveKit credentials are set in the environment: ◦ LIVEKIT_API_KEY ◦ LIVEKIT_API_SECRET ◦ Optionally LIVEKIT_URL if using a custom project URL • Example change:
Copy code
session = AgentSession(
    userdata={},
    stt=inference.STT(model="assemblyai/universal-streaming", language="en"),
    llm=inference.LLM(model="openai/gpt-4.1-mini"),
    tts=inference.TTS(model="cartesia/sonic-3", voice="9626c31c-bec5-4cca-baa8-f8ba9e84c8bc"),
)
• Your usage is billed through LiveKit Inference. 2) Use provider plugins (if you want to use your own provider accounts and keys directly) • Switch from
inference.*
to the plugin classes for each provider so your provider API keys are used directly. • Install/enable plugin extras if required, then use plugin constructors:
Copy code
from livekit.plugins import assemblyai, openai, cartesia

session = AgentSession(
    userdata={},
    stt=assemblyai.STT(api_key=os.getenv("ASSEMBLYAI_API_KEY"), language="en"),
    llm=openai.LLM(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4.1-mini"),
    tts=cartesia.TTS(api_key=os.getenv("CARTESIA_API_KEY"), model="sonic-3",
                     voice="9626c31c-bec5-4cca-baa8-f8ba9e84c8bc"),
)
• This connects directly to the providers and bills those provider accounts. Quick validation steps you can run now: • Confirm you have valid LiveKit credentials set if you chose option 1. • If you chose option 2, confirm each provider key works by calling the provider’s API directly. • Remove any stray whitespace/newlines in environment values and restart your process. If you tell me which option you want (Inference via LiveKit or direct provider plugins), I’ll give the exact code and the small env checks to run next. Sources: How much can LiveKit scale? | LiveKit | Speech-to-text (STT) models overview | LiveKit Documentation | Models overview | LiveKit Documentation
f
cmd to install these extra plugins. I want to go with option 2
t
Great — since you want to use provider plugins (Option 2), here are the install commands for Python and Node.js. I’ll include the specific plugins you mentioned earlier (AssemblyAI, OpenAI, Cartesia). Pick the one that matches your environment. For Python (using uv — recommended for managing virtualenvs/pkgs):
Copy code
uv add "livekit-agents[assemblyai,openai,cartesia]~=1.3"
If you use pip instead:
Copy code
pip install "livekit-agents[assemblyai,openai,cartesia]~=1.3"
For Node.js (pnpm):
Copy code
pnpm add @livekit/agents-plugin-assemblyai@1.x @livekit/agents-plugin-openai@1.x @livekit/agents-plugin-cartesia@1.x
For Node.js (npm):
Copy code
npm install @livekit/agents-plugin-assemblyai@1.x @livekit/agents-plugin-openai@1.x @livekit/agents-plugin-cartesia@1.x
After installing, update your code to use the plugin classes and pass your provider API keys (examples I showed earlier). If you want, paste your environment (Python/Node) and I’ll give the exact import lines and a minimal code snippet for your project. Sources: Pipeline Translator Agent | LiveKit Documentation | Deepgram STT plugin guide | LiveKit Documentation | ElevenLabs STT plugin guide | LiveKit Documentation | OpenAI LLM plugin guide | LiveKit Documentation
f
{"level": "WARNING", "name": "livekit.agents", "message": "failed to recognize speech, retrying in 0.1s\nTraceback (most recent call last):\n File \"/usr/local/lib/python3.13/site-packages/livekit/agents/stt/stt.py\", line 289, in _main_task\n return await self._run()\n ^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.13/site-packages/livekit/plugins/assemblyai/stt.py\", line 296, in _run\n task.result()\n ~~~~~~~~~~~^^\n File \"/usr/local/lib/python3.13/site-packages/livekit/plugins/assemblyai/stt.py\", line 265, in recv_task\n raise APIStatusError(\n ^^^^^^^^^^^^^^^^^^^^^\n \"AssemblyAI connection closed unexpectedly\",\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n ) # this will trigger a reconnection, see the _run loop\n ^\nlivekit.agents._exceptions.APIStatusError: AssemblyAI connection closed unexpectedly (status_code=-1, request_id=None, body=None, retryable=True)", "room": "room-6e4b0b25-90e5-4e0d-b70f-9ff0c0f4351e", "tts": "livekit.plugins.assemblyai.stt.STT", "attempt": 0, "streamed": true, "pid": 74, "job_id": "AJ_KirTVFEeNmAN", "room_id": "RM_bxhdkDfpqLSZ", "timestamp": "2026-02-13T005747.416320+00:00"} {"level": "WARNING", "name": "livekit.agents", "message": "failed to recognize speech, retrying in 2.0s\nTraceback (most recent call last):\n File \"/usr/local/lib/python3.13/site-packages/livekit/agents/stt/stt.py\", line 289, in _main_task\n return await self._run()\n ^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.13/site-packages/livekit/plugins/assemblyai/stt.py\", line 296, in _run\n task.result()\n ~~~~~~~~~~~^^\n File \"/usr/local/lib/python3.13/site-packages/livekit/plugins/assemblyai/stt.py\", line 265, in recv_task\n raise APIStatusError(\n ^^^^^^^^^^^^^^^^^^^^^\n \"AssemblyAI connection closed unexpectedly\",\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n ) # this will trigger a reconnection, see the _run loop\n ^\nlivekit.agents._exceptions.APIStatusError: AssemblyAI connection closed unexpectedly (status_code=-1, request_id=None, body=None, retryable=True)", "room": "room-6e4b0b25-90e5-4e0d-b70f-9ff0c0f4351e", "tts": "livekit.plugins.assemblyai.stt.STT", "attempt": 1, "streamed": true, "pid": 74, "job_id": "AJ_KirTVFEeNmAN", "room_id": "RM_bxhdkDfpqLSZ", "timestamp": "2026-02-13T005747.652900+00:00"} {"level": "WARNING", "name": "livekit.agents", "message": "failed to recognize speech, retrying in 2.0s\nTraceback (most recent call last):\n File \"/usr/local/lib/python3.13/site-packages/livekit/agents/stt/stt.py\", line 289, in _main_task\n return await self._run()\n ^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.13/site-packages/livekit/plugins/assemblyai/stt.py\", line 296, in _run\n task.result()\n ~~~~~~~~~~~^^\n File \"/usr/local/lib/python3.13/site-packages/livekit/plugins/assemblyai/stt.py\", line 265, in recv_task\n raise APIStatusError(\n ^^^^^^^^^^^^^^^^^^^^^\n \"AssemblyAI connection closed unexpectedly\",\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n ) # this will trigger a reconnection, see the _run loop\n ^\nlivekit.agents._exceptions.APIStatusError: AssemblyAI connection closed unexpectedly (status_code=-1, request_id=None, body=None, retryable=True)", "room": "room-6e4b0b25-90e5-4e0d-b70f-9ff0c0f4351e", "tts": "livekit.plugins.assemblyai.stt.STT", "attempt": 2, "streamed": true, "pid": 74, "job_id": "AJ_KirTVFEeNmAN", "room_id": "RM_bxhdkDfpqLSZ", "timestamp": "2026-02-13T005749.797208+00:00"} {"level": "ERROR", "name": "livekit.agents", "message": "AgentSession is closing due to unrecoverable error\nTraceback (most recent call last):\n File \"/usr/local/lib/python3.13/site-packages/livekit/agents/stt/stt.py\", line 289, in _main_task\n return await self._run()\n ^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.13/site-packages/livekit/plugins/assemblyai/stt.py\", line 296, in _run\n task.result()\n ~~~~~~~~~~~^^\n File \"/usr/local/lib/python3.13/site-packages/livekit/plugins/assemblyai/stt.py\", line 265, in recv_task\n raise APIStatusError(\n ^^^^^^^^^^^^^^^^^^^^^\n \"AssemblyAI connection closed unexpectedly\",\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n ) # this will trigger a reconnection, see the _run loop\n ^\nlivekit.agents._exceptions.APIStatusError: AssemblyAI connection closed unexpectedly (status_code=-1, request_id=None, body=None, retryable=True)", "room": "room-6e4b0b25-90e5-4e0d-b70f-9ff0c0f4351e", "pid": 74, "job_id": "AJ_KirTVFEeNmAN", "room_id": "RM_bxhdkDfpqLSZ", "timestamp": "2026-02-13T005751.945774+00:00"} {"level": "ERROR", "name": "livekit.agents", "message": "Error in _stt_task\nTraceback (most recent call last):\n File \"/usr/local/lib/python3.13/site-packages/livekit/agents/stt/stt.py\", line 289, in _main_task\n return await self._run()\n ^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.13/site-packages/livekit/plugins/assemblyai/stt.py\", line 296, in _run\n task.result()\n ~~~~~~~~~~~^^\n File \"/usr/local/lib/python3.13/site-packages/livekit/plugins/assemblyai/stt.py\", line 265, in recv_task\n raise APIStatusError(\n ^^^^^^^^^^^^^^^^^^^^^\n \"AssemblyAI connection closed unexpectedly\",\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n ) # this will trigger a reconnection, see the _run loop\n ^\nlivekit.agents._exceptions.APIStatusError: AssemblyAI connection closed unexpectedly (status_code=-1, request_id=None, body=None, retryable=True)\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n File \"/usr/local/lib/python3.13/site-packages/livekit/agents/utils/log.py\", line 16, in async_fn_logs\n return await fn(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/local/lib/python3.13/site-packages/livekit/agents/voice/audio_recognition.py\", line 660, in _stt_task\n async for ev in node:\n ...<3 lines>...\n await self._on_stt_event(ev)\n File \"/usr/local/lib/python3.13/site-packages/livekit/agents/voice/agent.py\", line 391, in stt_node\n async for event in stream:\n yield event\n File \"/usr/local/lib/python3.13/site-packages/livekit/agents/stt/stt.py\", line 411, in __anext__\n raise exc # noqa: B904\n ^^^^^^^^^\n File \"/usr/local/lib/python3.13/site-packages/livekit/agents/stt/stt.py\", line 296, in _main_task\n raise APIConnectionError(\n f\"failed to recognize speech after {self._num_retries} attempts\",\n ) from e\nlivekit.agents._exceptions.APIConnectionError: failed to recognize speech after 3 attempts (body=None, retryable=True)", "room": "room-6e4b0b25-90e5-4e0d-b70f-9ff0c0f4351e", "pid": 74, "job_id": "AJ_KirTVFEeNmAN", "room_id": "RM_bxhdkDfpqLSZ", "timestamp": "2026-02-13T005751.953319+00:00"}
t
Thanks for the logs — this helps a lot. The AssemblyAI plugin is attempting to reconnect but the connection is closing unexpectedly (status_code=-1), and after three retries the AgentSession aborts. Let’s walk through the most likely causes and step-by-step checks you can run to get this stable. What I’m responding to • The repeated warnings: "AssemblyAI connection closed unexpectedly (status_code=-1)" and the final "failed to recognize speech after 3 attempts". • You’re using the AssemblyAI plugin for STT (plugin-based flow). Quick summary of likely causes • Network/connectivity issues (agent host can’t reach AssemblyAI). • Invalid, missing, or mis-set AssemblyAI API key. • TLS/DNS/firewall blocking outbound traffic to AssemblyAI. • Silent or malformed audio being sent (VAD / noise cancellation issues). • Intermittent provider-side issues (rare but possible). Steps to diagnose and fix (follow in order) 1) Confirm your AssemblyAI API key is present and correct • Ensure the environment variable is set (on the agent host): ◦ ASSEMBLYAI_API_KEY=your_key_here • From the same host where the agent runs, test the key with curl:
Copy code
curl -v <https://api.assemblyai.com/v2/transcript> \
  -H "Authorization: YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"audio_url":"<https://cdn.simplefiles/audio/somefile.mp3>"}'
• If you get a 401 or other auth error, the key is invalid or revoked. If it succeeds, the key is good. 2) Verify network connectivity and DNS • Test basic reachability and TLS:
Copy code
curl -v <https://api.assemblyai.com/v2/>
• Test DNS resolution:
Copy code
nslookup <http://api.assemblyai.com|api.assemblyai.com>
• If curl/lookup fail, check firewall, proxy, or VPC egress rules. AssemblyAI uses standard HTTPS on port 443. 3) Check for intermediate network/proxy/TLS issues • If your host runs behind a corporate proxy or uses custom TLS inspection, that can close connections. Confirm no proxy is intercepting or blocking invites. • Try from a different environment (your laptop or another server) to see if the problem is host-specific. 4) Confirm you’re using the plugin correctly • Use the plugin class (no extra inference descriptors). Example:
Copy code
from livekit.plugins import assemblyai
session = AgentSession(stt=assemblyai.STT(), ...)
• Do not pass an
inference.STT(...)
descriptor when using plugin-based API keys. • If you prefer explicit key passing for testing, you can create the plugin with the key:
Copy code
assemblyai.STT(api_key=os.getenv("ASSEMBLYAI_API_KEY"))
5) Inspect audio input and VAD / noise cancellation • Ensure the agent is receiving audio tracks (not silent) and that VAD is configured correctly. Silent or malformed audio may make AssemblyAI drop connections. • Disable noise-cancelling features (krisp, BVCTelephony, or other aggressive filters) temporarily to rule them out. 6) Enable more verbose logs to capture the failing requests • Increase log level for the plugin/agent to capture raw request attempts and any underlying socket/TLS error. That often surfaces connection timeouts, TLS handshake failures, or socket resets. • Look for underlying errno or SSL errors in the more verbose trace. 7) Retry behavior & fallback • The plugin retries a few times (your logs show attempts 0,1,2). If this is intermittent, you can: ◦ Temporarily increase retry/backoff in your agent code (if configurable) while diagnosing. ◦ Test with a different STT provider (Deepgram) to confirm if the issue is AssemblyAI-specific. If tests show the host cannot reach api.assemblyai.com • Open outbound HTTPS (443) to api.assemblyai.com from the agent host (or configure an allowed egress route). • If behind a proxy, set appropriate environment variables (HTTP(S)_PROXY) so Python & aiohttp can use it. If the key is invalid or revoked • Generate / use a valid AssemblyAI key and place it in the environment, then restart the agent. If everything looks fine locally but errors continue • Collect verbose logs (include stack traces with network/TLS details) and indicate whether the failure is consistently reproducible or intermittent. • As a quick test, switch the STT plugin to another provider plugin (e.g., deepgram) to verify whether the issue is provider-specific. If you’d like, I can: • Provide the exact commands to enable more verbose aiohttp/Python logging. • Give a minimal troubleshooting script to POST a small audio chunk to AssemblyAI to replicate the behavior. • Help you try a different STT plugin and the import/usage snippet for that provider. Tell me which test you want to run first (connectivity test, API-key test, or switch to another STT plugin) and I’ll give the exact commands/snippets. Sources: AssemblyAI plugin guide | LiveKit Documentation | Troubleshooting STT not picking up utterances | LiveKit | Agents Framework | livekit/livekit_composite | DeepWiki
f
session = AgentSession( userdata={}, stt=assemblyai.STT(api_key=os.getenv("ASSEMBLYAI_API_KEY"), model="assemblyai/universal-streaming-english"), llm=openai.LLM(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4.1-mini"), tts=cartesia.TTS(api_key=os.getenv("CARTESIA_API_KEY"), model="sonic-3", voice="9626c31c-bec5-4cca-baa8-f8ba9e84c8bc") ) curl -v https://api.assemblyai.com/v2/ * Host api.assemblyai.com:443 was resolved. * IPv6: (none) * IPv4: 44.253.147.101, 100.20.33.39, 44.224.47.127, 34.213.252.124, 34.211.134.50, 16.145.209.63 * Trying 44.253.147.101:443... * Connected to api.assemblyai.com (44.253.147.101) port 443 * ALPN: curl offers h2,http/1.1 * (304) (OUT), TLS handshake, Client hello (1): * CAfile: /etc/ssl/cert.pem * CApath: none * (304) (IN), TLS handshake, Server hello (2): * (304) (IN), TLS handshake, Unknown (8): * (304) (IN), TLS handshake, Certificate (11): * (304) (IN), TLS handshake, CERT verify (15): * (304) (IN), TLS handshake, Finished (20): * (304) (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256 / [blank] / UNDEF * ALPN: server accepted h2 * Server certificate: * subject: CN=*.assemblyai.com * start date: May 16 000000 2025 GMT * expire date: Jun 13 235959 2026 GMT * subjectAltName: host "api.assemblyai.com" matched cert's "*.assemblyai.com" * issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02 * SSL certificate verify ok. * using HTTP/2 * [HTTP/2] [1] OPENED stream for https://api.assemblyai.com/v2/ * [HTTP/2] [1] [method GET] * [HTTP/2] [1] [scheme https] * [HTTP/2] [1] [authority api.assemblyai.com] * [HTTP/2] [1] [path /v2/] * [HTTP/2] [1] [user-agent: curl/8.7.1] * [HTTP/2] [1] [accept: /]
GET /v2/ HTTP/2
Host: api.assemblyai.com
User-Agent: curl/8.7.1
Accept: /
* Request completely sent off < HTTP/2 404 < server: awselb/2.0 < date: Fri, 13 Feb 2026 010553 GMT < content-type: text/plain; charset=utf-8 < content-length: 9 < * Connection #0 to host api.assemblyai.com left intact Not found%
{"level": "ERROR", "name": "livekit.agents", "message": "Error in _stt_task\nTraceback (most recent call last):\n File \"/app/.venv/lib/python3.11/site-packages/livekit/agents/stt/stt.py\", line 408, in __anext__\n val = await self._event_aiter.__anext__()\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nStopAsyncIteration\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"/app/.venv/lib/python3.11/site-packages/livekit/agents/utils/log.py\", line 16, in async_fn_logs\n return await fn(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/app/.venv/lib/python3.11/site-packages/livekit/agents/voice/audio_recognition.py\", line 660, in _stt_task\n async for ev in node:\n File \"/app/.venv/lib/python3.11/site-packages/livekit/agents/voice/agent.py\", line 391, in stt_node\n async for event in stream:\n File \"/app/.venv/lib/python3.11/site-packages/livekit/agents/stt/stt.py\", line 411, in __anext__\n raise exc # noqa: B904\n ^^^^^^^^^\n File \"/app/.venv/lib/python3.11/site-packages/livekit/agents/stt/stt.py\", line 289, in _main_task\n return await self._run()\n ^^^^^^^^^^^^^^^^^\n File \"/app/.venv/lib/python3.11/site-packages/livekit/plugins/assemblyai/stt.py\", line 296, in _run\n task.result()\n File \"/app/.venv/lib/python3.11/site-packages/livekit/plugins/assemblyai/stt.py\", line 242, in send_task\n await ws.send_bytes(frame.data.tobytes())\n File \"/app/.venv/lib/python3.11/site-packages/aiohttp/client_ws.py\", line 252, in send_bytes\n await self._writer.send_frame(data, WSMsgType.BINARY, compress=compress)\n File \"/app/.venv/lib/python3.11/site-packages/aiohttp/_websocket/writer.py\", line 79, in send_frame\n raise ClientConnectionResetError(\"Cannot write to closing transport\")\naiohttp.client_exceptions.ClientConnectionResetError: Cannot write to closing transport", "room": "room-c3478975-e58a-4c6c-ab24-be8761f96059", "pid": 75, "job_id": "AJ_se8F2pqGqhyx", "room_id": "RM_Ax9ZLTDxjd6Q", "timestamp": "2026-02-13T010459.234421+00:00"}
t
Thanks — this is very helpful context. I can see three things from your latest inputs: • You switched to plugin usage (good):
assemblyai.STT(...)
,
openai.LLM(...)
,
cartesia.TTS(...)
. • A raw curl to https://api.assemblyai.com/v2/ returns HTTP/2 404 (this endpoint returns 404 on GET — that’s expected; AssemblyAI’s APIs are on specific paths like /v2/transcript). • The agent logs now show the AssemblyAI websocket being closed/reset while sending binary frames:
ClientConnectionResetError: Cannot write to closing transport
after prior "connection closed unexpectedly (status_code=-1)". Let’s focus on stabilizing the AssemblyAI STT plugin (the immediate blocker). Below are precise checks and actions you can run, ordered from quickest to more involved. 1) Confirm your AssemblyAI key is being used by the plugin • Ensure your process environment has ASSEMBLYAI_API_KEY set and the agent can read it: ◦ From the running agent host: ▪︎ echo $ASSEMBLYAI_API_KEY ◦ Or in Python:
Copy code
import os
    print(bool(os.getenv("ASSEMBLYAI_API_KEY")))
• Optional: pass the key explicitly when constructing the plugin to make sure the right value is used:
Copy code
stt = assemblyai.STT(api_key=os.getenv("ASSEMBLYAI_API_KEY"), model="assemblyai/universal-streaming-english")
2) Validate AssemblyAI endpoints with an authenticated request • The root
/v2/
path may return 404; test a valid authenticated endpoint instead:
Copy code
curl -v <https://api.assemblyai.com/v2/transcript> \
  -H "Authorization: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"audio_url":"<https://cdn.simplefiles/audio/somefile.mp3>"}'
• If you get 401 → key invalid. If you get a normal JSON error/response → key & network OK. 3) Reproduce a WebSocket handshake/test outside the agent (quick network test) • AssemblyAI’s streaming uses a WebSocket endpoint. From the agent host, run a short script or use
websocat
to check a raw WS connection (this helps reveal TLS/proxy resets). Example (requires websocat):
Copy code
websocat -H "authorization: YOUR_KEY" <wss://api.assemblyai.com/v2/realtime/ws>
• If it immediately closes or errors, this indicates a network/proxy/TLS problem that affects WS specifically. 4) Check for proxy, firewall, or TLS interception • Confirm the agent host can open long-lived outbound WebSocket/TCP connections to AssemblyAI. Firewalls or corporate proxies commonly allow short HTTPS GET but kill persistent WS. • If you are behind a proxy, set HTTP(S)_PROXY for the process so aiohttp can use it: ◦ export HTTPS_PROXY="http://proxy:port" • If your environment performs TLS inspection, ensure the inspection cert is trusted by the agent host. 5) Increase plugin logging & capture aiohttp/TCP errors • Enable DEBUG logging for
livekit.plugins.assemblyai
and
aiohttp
to capture socket/TLS errors:
Copy code
import logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("livekit.plugins.assemblyai").setLevel(logging.DEBUG)
logging.getLogger("aiohttp").setLevel(logging.DEBUG)
• Restart the agent and paste the expanded logs if you want me to read them — look for errno (e.g., ECONNRESET) or SSL handshake errors. 6) Confirm audio frames format and rate • The plugin sends binary frames; if audio frames are malformed, AssemblyAI server may close the connection. Check that you’re sending PCM 16-bit 16k/16kHz or the format AssemblyAI expects for streaming. • For debugging, try sending a single small valid audio file via the REST API (transcript endpoint) to ensure AssemblyAI accepts your audio payloads. 7) Try a quick provider swap to confirm it's AssemblyAI-specific • Temporarily switch STT to another plugin (Deepgram or Google) using your key to see if the same connection resets happen. If other providers work, the issue is AssemblyAI-specific (network, account, or endpoint). Example:
Copy code
from livekit.plugins import deepgram
session = AgentSession(stt=deepgram.STT(api_key=os.getenv("DEEPGRAM_API_KEY")), ...)
8) Consider transient retries and backoff • The plugin already retries; if this is intermittent you can: ◦ Ensure your environment allows reconnections without being rate-limited. ◦ Add longer backoff if AssemblyAI returns connection resets under high load. Common root causes seen in these logs • Host-level outbound WebSocket connections are being reset (proxy, firewall, TLS inspection). • AssemblyAI key invalid/unauthorized (would show 401 on authenticated calls). • Malformed audio frames or incompatible audio format — server closes connection. • Provider transient outage (less common). Next steps I can help with right away • You already ran curl to /v2/ — run the authenticated transcript POST above and paste the response. • If you can, enable DEBUG logs (step 5) and paste the additional trace around the WS/TLS layer. • If you prefer, I can give a minimal Python script to open a WebSocket to AssemblyAI’s realtime endpoint with your key (safe to run locally) to reproduce the socket behavior. Which test do you want to run next — the authenticated transcript POST, websocat/WebSocket test, or turning on DEBUG logs? Tell me which and I’ll provide the exact command/snippet.
f
WARNINGlivekit.agentsfailed to recognize speech, retrying in 0.1s File "/Users/suryanshsoni/Watto/prismo-backend/livekit-server/.venv/lib/python3.11/site-packages/livekit/plugins/assemblyai/stt.py", line 265, in recv_task raise APIStatusError( livekit.agents._exceptions.APIStatusError: AssemblyAI connection closed unexpectedly (status_code=-1, request_id=None, body=None, retryable=True) This happens when await self.session.generate_reply() is happening in the begining and the user havent even said a word
t
Thanks — I hear your frustration. You’re seeing the AssemblyAI connection closing unexpectedly right at session start (during
await session.generate_reply()
), before any user audio is spoken. Let’s focus on why that happens and give a concise plan to stabilize the session. Why this can happen (short) • The STT plugin opens a streaming connection proactively. If no audio arrives or the stream is malformed, AssemblyAI may close the connection. • Network / WebSocket issues (proxy/firewall/TLS inspection) can cause immediate connection resets. • Invalid or missing API key or incorrect plugin usage can cause abrupt closes (though that usually yields a 401 on REST calls). • Aggressive VAD/voice-detection or silence being sent can cause the plugin to retry and then fail. Immediate checks and quick fixes to try now 1) Ensure the plugin isn’t starting streaming before there’s audio • Use VAD so the STT stream only starts when speech is detected. If you haven’t enabled VAD, enable it so the plugin waits for audio instead of opening an idle stream:
Copy code
from livekit.plugins import silero
vad = silero.VAD.load()
session = AgentSession(userdata={}, stt=assemblyai.STT(api_key=..., vad=vad), ...)
• If you’re already using VAD, confirm it’s detecting non-speech vs speech correctly. For debug, temporarily require manual trigger before calling
session.generate_reply()
. 2) Delay calling
session.generate_reply()
until after first audio/VAD event • If your flow calls
generate_reply()
immediately, try deferring that call until after you confirm a participant has spoken (or VAD signals speech). This avoids creating a streaming session with no audio. 3) Confirm AssemblyAI key & network (quick) • You already tested the root endpoint; run an authenticated transcript test to confirm the key:
Copy code
curl -v <https://api.assemblyai.com/v2/transcript> \
  -H "Authorization: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"audio_url":"<https://cdn.simplefiles/audio/somefile.mp3>"}'
• If that succeeds, the key and basic network path are fine. The issue is likely WebSocket-specific or audio-related. 4) Test WebSocket connectivity from the agent host • A WebSocket-level test reveals proxy/firewall/TLS issues. Use
websocat
or a short Python snippet to open a WS to AssemblyAI’s realtime endpoint with your key. If the WS closes immediately, address proxy/firewall/TLS settings. 5) Enable debug logs for the plugin and aiohttp • This will show underlying socket/TLS errors (ECONNRESET, SSL errors) that the high-level stack trace doesn’t reveal:
Copy code
import logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("livekit.plugins.assemblyai").setLevel(logging.DEBUG)
logging.getLogger("aiohttp").setLevel(logging.DEBUG)
6) Validate audio format / frame timing • If your agent immediately sends empty/silent frames or sends frames at an unexpected rate/format, AssemblyAI may drop the connection. Confirm frames are PCM and match the expected sample rate and chunking for AssemblyAI streaming. If you want, I can give exact snippets for: • A minimal VAD + deferred generate_reply flow. • A small Python script to open a WebSocket to AssemblyAI (to reproduce the reset). • The authenticated curl transcript command formatted with your key (you can paste the response). Which would you like to try first: (A) enable VAD and defer generate_reply, (B) run a WebSocket connectivity test, or (C) enable DEBUG logs and send me the expanded trace?
f
Added silero VAD but still failing with same error. Also this is for the curl cmd: curl -v https://api.assemblyai.com/v2/transcript \ -H "Authorization: Key" \ -H "Content-Type: application/json" \ -d '{"audio_url":"https://cdn.simplefiles/audio/somefile.mp3"}' * Host api.assemblyai.com:443 was resolved. * IPv6: (none) * IPv4: 44.253.147.101, 44.224.47.127, 16.145.209.63, 34.213.252.124, 100.20.33.39, 34.211.134.50 * Trying 44.253.147.101:443... * Connected to api.assemblyai.com (44.253.147.101) port 443 * ALPN: curl offers h2,http/1.1 * (304) (OUT), TLS handshake, Client hello (1): * CAfile: /etc/ssl/cert.pem * CApath: none * (304) (IN), TLS handshake, Server hello (2): * (304) (IN), TLS handshake, Unknown (8): * (304) (IN), TLS handshake, Certificate (11): * (304) (IN), TLS handshake, CERT verify (15): * (304) (IN), TLS handshake, Finished (20): * (304) (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256 / [blank] / UNDEF * ALPN: server accepted h2 * Server certificate: * subject: CN=*.assemblyai.com * start date: May 16 000000 2025 GMT * expire date: Jun 13 235959 2026 GMT * subjectAltName: host "api.assemblyai.com" matched cert's "*.assemblyai.com" * issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02 * SSL certificate verify ok. * using HTTP/2 * [HTTP/2] [1] OPENED stream for https://api.assemblyai.com/v2/transcript * [HTTP/2] [1] [method POST] * [HTTP/2] [1] [scheme https] * [HTTP/2] [1] [authority api.assemblyai.com] * [HTTP/2] [1] [path /v2/transcript] * [HTTP/2] [1] [user-agent: curl/8.7.1] * [HTTP/2] [1] [accept: /] * [HTTP/2] [1] [authorization: c9d06ced99c34148b355bb81e53d86f1] * [HTTP/2] [1] [content-type: application/json] * [HTTP/2] [1] [content-length: 58]
POST /v2/transcript HTTP/2
Host: api.assemblyai.com
User-Agent: curl/8.7.1
Accept: /
Authorization: c9d06ced99c34148b355bb81e53d86f1
Content-Type: application/json
Content-Length: 58
* upload completely sent off: 58 bytes < HTTP/2 400 < date: Fri, 13 Feb 2026 012000 GMT < content-type: application/json; charset=UTF-8 < content-length: 119 < access-control-allow-origin: * < access-control-allow-headers: Content-Type, Authorization, X-Requested-With, User-Agent, AssemblyAI-Agent < access-control-allow-methods: POST, PUT, GET, OPTIONS < strict-transport-security: max-age=31536000; includeSubDomains; preload < * Connection #0 to host api.assemblyai.com left intact {"error": "\"speech_models\" must be a non-empty list containing one or more of: \"universal-3-pro\", \"universal-2\""}%
t
Thanks — you’ve given excellent details. You’re seeing AssemblyAI close the realtime stream immediately even after adding VAD, and the REST transcript test returned a schema error (which is expected for that endpoint). Let’s focus on the WebSocket realtime test and a couple small, concrete checks that will very quickly tell us whether this is network/auth or an audio/VAD issue. What I’ll help you do now • Verify the AssemblyAI realtime WebSocket can be established from your agent host. • Confirm the plugin is using the correct model/token and that the token is full and readable by the process. • If the WS succeeds, run a minimal repro to see whether the plugin sends audio frames correctly; if the WS fails, we’ll look at network/proxy/firewall causes. Step A — Quick WebSocket connectivity test (wscat) 1. If you don’t have wscat, install it:
Copy code
npm install -g wscat
1. Run this (replace YOUR_KEY with the exact ASSEMBLYAI key from your env):
Copy code
wscat -c "<wss://api.assemblyai.com/v2/realtime/transcribe?token=YOUR_KEY&sample_rate=16000>"
Expected outcomes: • Success: you get a JSON welcome/connected response and the socket remains open. That means WebSocket auth & network are OK. • Failure: immediate close or handshake error — this points to network, proxy, TLS inspection, or the token being rejected for WS. If wscat fails, run it from a different network (your laptop/home) to check whether the agent host network blocks WS. Step B — Confirm the key your agent process is actually using From the same host/process environment that runs the agent, verify the env variable:
Copy code
# at the shell used by the agent
echo "$ASSEMBLYAI_API_KEY"
# Or inside Python REPL running in the same env:
python -c "import os; print(len(os.getenv('ASSEMBLYAI_API_KEY') or ''))"
• Confirm the key length looks like a full key (not truncated). • Also try creating the plugin explicitly with the key to ensure no env lookup bug:
Copy code
stt = assemblyai.STT(api_key=os.getenv("ASSEMBLYAI_API_KEY"), model="universal-streaming")
Step C — Short Python WebSocket test (if you prefer Python over wscat) Save and run this from the agent host (replace YOUR_KEY). It will attempt a realtime WS handshake:
Copy code
import asyncio
import websockets

async def test():
    url = "<wss://api.assemblyai.com/v2/realtime/transcribe?token=YOUR_KEY&sample_rate=16000>"
    try:
        async with websockets.connect(url) as ws:
            print("Connected OK")
            msg = await ws.recv()
            print("Received:", msg)
    except Exception as e:
        print("WS error:", repr(e))

asyncio.run(test())
Step D — If WebSocket connects, run a minimal LiveKit plugin repro Use a tiny snippet that opens assemblyai.STT stream and sends one small, valid audio frame to validate the plugin’s behavior. This shows whether the plugin or audio format is causing the close. Minimal outline (I can give a full runnable snippet if you want): • Create assemblyai.STT(api_key=...) • Open its streaming API • Send one valid PCM16 frame (16kHz) and then close • Observe whether WS is closed or you get a server response Step E — If WebSocket fails: network and proxy checks • Check for outbound WebSocket/TCP being blocked by firewall or cloud security group. • If behind a corporate proxy or TLS inspection, configure HTTPS_PROXY/HTTP_PROXY for the agent process so aiohttp/WS can use the proxy. • Try from another network to confirm host-specific blocking. Useful quick checks you can run now and paste results of 1) wscat result output (copy/paste). 2) Result of echo "$ASSEMBLYAI_API_KEY" (showing length / masked key). 3) If you already enabled DEBUG logs for aiohttp and assemblyai plugin, paste the first few lines around the WS handshake error (the debug logs often show an underlying errno like ECONNRESET or an SSL error). If the WS connects successfully, but the LiveKit plugin still closes when generate_reply runs: • Paste the debug logs for the plugin around the close — I’ll look for close codes or server reasons. • We’ll check audio format (sample rate, bit depth, frame size) that you are sending — AssemblyAI expects PCM at the given sample rate; if frames are malformed the server may reset. Which test would you like to run right now — (1) wscat test, (2) Python WS test, or (3) run the minimal LiveKit plugin repro? I’ll provide the exact snippet for whichever you pick and help interpret the output. Sources: AssemblyAI plugin guide | LiveKit Documentation | AssemblyAI STT | LiveKit Documentation | OpenAI STT plugin guide | LiveKit Documentation
f
yes give code for step D
t
Good — below is a minimal, runnable Python script that: • Opens a WebSocket to AssemblyAI’s realtime transcription endpoint using your AssemblyAI key. • Sends a short, valid PCM16 (mono, 16 kHz) audio chunk (sine wave) to simulate speech. • Receives and prints server responses. • Cleans up the connection. Run it from the same host/environment as your agent so it tests the same network path. Replace YOUR_KEY with your ASSEMBLYAI_API_KEY (or load from env). If the connection closes immediately or you get an error, paste the output and I’ll help interpret it. Save as test_assemblyai_realtime.py and run with Python 3.11+.
Copy code
import asyncio
import os
import struct
import math
import json
import websockets

# Config
API_KEY = os.getenv("ASSEMBLYAI_API_KEY") or "YOUR_KEY"
SAMPLE_RATE = 16000
SAMPLE_SECONDS = 1.0  # length of generated audio in seconds

if API_KEY == "YOUR_KEY":
    print("Replace YOUR_KEY with your AssemblyAI API key or set ASSEMBLYAI_API_KEY in env.")
    raise SystemExit(1)

# Generate a short 1-second 440Hz sine wave as PCM16 mono at 16kHz
def generate_pcm16_sine(sample_rate: int, seconds: float, freq: float = 440.0):
    num_samples = int(sample_rate * seconds)
    pcm = bytearray()
    for n in range(num_samples):
        t = n / sample_rate
        # amplitude scaled to avoid clipping
        sample = int(0.5 * 32767 * math.sin(2.0 * math.pi * freq * t))
        pcm += struct.pack("<h", sample)
    return bytes(pcm)

async def run():
    url = f"<wss://api.assemblyai.com/v2/realtime/transcribe?token={API_KEY}&sample_rate={SAMPLE_RATE}>"
    print("Connecting to:", url)
    try:
        async with websockets.connect(url, max_size=None) as ws:
            print("WebSocket connected")

            # Receive initial messages from server (welcome/errors)
            try:
                greeting = await asyncio.wait_for(ws.recv(), timeout=3.0)
                print("Server greeting:", greeting)
            except asyncio.TimeoutError:
                print("No greeting received within timeout (this can be okay).")

            # Send a init/config message if AssemblyAI expects JSON init (some realtime APIs accept JSON control msgs)
            # If AssemblyAI does not expect this, the server will ignore unknown control messages.
            # We include a minimal JSON to ensure we didn't need other fields.
            init_msg = {"type": "input_audio_buffer.init"}
            try:
                await ws.send(json.dumps(init_msg))
                print("Sent init/config message")
            except Exception as e:
                print("Init send error:", e)

            # Send audio in chunks as binary frames
            pcm = generate_pcm16_sine(SAMPLE_RATE, SAMPLE_SECONDS)
            chunk_size = 3200  # bytes per chunk (1600 samples, 100ms at 16kHz mono PCM16)
            print(f"Sending {len(pcm)} bytes of PCM audio in chunks of {chunk_size} bytes")

            for i in range(0, len(pcm), chunk_size):
                chunk = pcm[i : i + chunk_size]
                try:
                    await ws.send(chunk)  # binary frame
                    print(f"Sent chunk {i//chunk_size + 1}")
                except Exception as e:
                    print("Error sending chunk:", repr(e))
                    raise

                # small pause to simulate streaming timing
                await asyncio.sleep(0.08)

            # Optionally tell the server we're done sending audio
            try:
                await ws.send(json.dumps({"type": "input_audio_buffer.flush"}))
                print("Sent flush")
            except Exception as e:
                print("Flush send error:", e)

            # Read responses for a short period
            try:
                while True:
                    msg = await asyncio.wait_for(ws.recv(), timeout=5.0)
                    print("Received:", msg)
            except asyncio.TimeoutError:
                print("No more messages within timeout, closing socket normally.")
            except websockets.ConnectionClosed as e:
                print("Connection closed by server:", e)

    except Exception as e:
        print("WebSocket connection error:", repr(e))

if __name__ == "__main__":
    asyncio.run(run())
Notes and what to watch for • Successful connect: you should see "WebSocket connected" and a server greeting or server messages after sending audio. That indicates WS + auth is working. • Immediate close or connection error: copy the printed exception (handshake error, connection reset, SSL error). That indicates a network, proxy, TLS interception, or token issue for WebSocket specifically. • If the socket connects but the server immediately closes after you send audio: look for server response JSON giving the reason (auth, bad format, required params). • If you see JSON errors about model names or fields, paste them and I’ll map them to the plugin config. After you run this, share the full output. If it connects, I’ll provide the equivalent minimal LiveKit plugin snippet to send the same chunk via the plugin's streaming API for a final comparison.
f
the issue was the name of the assembly ai model in STT
t
Great — thanks for the update. I’m glad you found the root cause: the AssemblyAI model name in your STT configuration. Quick confirmation and next steps you can use to avoid similar issues: • Make sure the STT plugin uses the exact supported model string (for example,
universal-streaming
/
universal-streaming-english
or the current model name AssemblyAI expects). A wrong model name will cause immediate connection/errors. • Verify your final STT init looks like:
Copy code
stt = assemblyai.STT(api_key=os.getenv("ASSEMBLYAI_API_KEY"), model="universal-streaming")
(or the exact model name you confirmed works). If everything’s now stable, you should no longer see the connection reset or the "connection closed unexpectedly" errors during
session.generate_reply()
. If you still want, I can: • Review your final AgentSession snippet to confirm everything is configured correctly (VAD, sample rate, model names for STT/LLM/TTS). • Provide a quick checklist for production: env vars, logging levels, retry/backoff settings, and health checks. • Help swap to a fallback STT plugin temporarily if you want redundancy. Would you like me to review your final code snippet to confirm it’s production-ready?