Skip to content

60db integration with livekit#5500

Open
kapilkarda wants to merge 5 commits intolivekit:mainfrom
60db:main
Open

60db integration with livekit#5500
kapilkarda wants to merge 5 commits intolivekit:mainfrom
60db:main

Conversation

@kapilkarda
Copy link
Copy Markdown

60db Integration with livekit which contains the STT, TTS and LLM all from single platform.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 20, 2026

CLA assistant check
All committers have signed the CLA.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment thread livekit-plugins/livekit-plugin-60db/livekit/plugins/_60db/stt.py
Comment on lines +326 to +332
if data.get("flush_completed"):
output_emitter.end_segment()
# Start a new segment for the next batch
segment_id = utils.shortuuid()
output_emitter.start_segment(
segment_id=segment_id
)
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ”΄ TTS SynthesizeStream calls end_segment() twice, and lacks start_segment for multi-flush

In SynthesizeStream._run, a single start_segment is called at line 279. The recv_task calls output_emitter.end_segment() upon each flush_completed (line 327). After asyncio.gather completes, line 345 unconditionally calls output_emitter.end_segment() again. This causes a double end_segment even in the single-flush case. Furthermore, for multi-flush scenarios (user pushes text→flush→text→flush), the first flush_completed ends the only segment, but no new start_segment is ever called — subsequent audio chunks arrive with no active segment, and subsequent flush_completed events call end_segment on an already-ended segment.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 5 new potential issues.

View 11 additional findings in Devin Review.

Open in Devin Review

Comment on lines +326 to +328
if data.get("flush_completed"):
output_emitter.end_segment()

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ”΄ TTS SynthesizeStream missing start_segment for segments after the first flush

start_segment(segment_id=segment_id) is called only once at line 279, before the send/recv tasks start. When recv_task receives a flush_completed at line 326-327, it calls end_segment() β€” closing the segment. If the user pushes more text followed by another flush, the subsequent audio chunks arrive without a corresponding start_segment() call, meaning audio is pushed outside of an active segment. This breaks the expected segment lifecycle contract defined by the AudioEmitter in livekit-agents/livekit/agents/tts/tts.py.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

Comment on lines +204 to +205
except websockets.exceptions.ConnectionClosed as e:
logger.info("60db TTS ChunkedStream: WebSocket closed: %s", e)
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ”΄ TTS ChunkedStream silently swallows WebSocket ConnectionClosed without re-raising

At tts.py:186-188, websockets.exceptions.ConnectionClosed is caught and only logged β€” the method returns normally. The base class ChunkedStream._main_task (livekit-agents/livekit/agents/tts/tts.py:286-326) then checks whether audio was actually pushed. If the connection closed prematurely (e.g., before flush_completed), output_emitter.flush() at line 172 was never called, so audio may be incomplete or missing. The root cause (connection closure) is silently discarded, and the user gets a confusing downstream error like "no audio frames were pushed" instead of a clear connection error. The same pattern exists in SynthesizeStream at tts.py:307-308.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

from __future__ import annotations

import asyncio
import audioop
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ”΄ STT plugin uses audioop module removed in Python 3.13

The STT module (stt.py:4) imports audioop, which was deprecated in Python 3.11 (PEP 594) and removed in Python 3.13 (released Oct 2024). Since pyproject.toml specifies requires-python = ">=3.10.0" with no upper bound, users on Python 3.13+ will get an ImportError at import time. The LiveKit agents framework provides its own audio utilities in livekit.agents.utils (including AudioBuffer and codec helpers) that could be used instead.

Prompt for agents
The stt.py module imports audioop (line 4) and uses it for audio conversion in _convert_audio (lines 291, 295, 304). The audioop module was removed in Python 3.13. Replace audioop usage with a compatible alternative. Options include: (1) use the livekit.agents.utils audio utilities or livekit rtc.AudioResampler already used elsewhere in the codebase, (2) add a dependency on the audioop-lts backport package, or (3) implement the conversions using struct/array directly. The same issue exists in tests/test_services.py line 12 which also imports audioop.
Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

build-backend = "hatchling.build"

[project]
name = "livekit-plugins-60db"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 Plugin directory uses singular plugin instead of plugins convention

The plugin directory is named livekit-plugin-60db (singular) while all 50+ other plugins in the repository follow the pattern livekit-plugins-<provider> (plural), as documented in AGENTS.md: "Each plugin is a separate package following the pattern livekit-plugins-<provider>." The pyproject.toml correctly uses name = "livekit-plugins-60db" (plural), creating a mismatch between the directory name and the established convention.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

Comment on lines +351 to +352
except websockets.exceptions.ConnectionClosed as e:
logger.info("60db TTS SynthesizeStream: WebSocket closed: %s", e)
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ”΄ SynthesizeStream silently swallows ConnectionClosed exception

Same issue as in ChunkedStream β€” at line 351-352, websockets.exceptions.ConnectionClosed is caught and logged but not re-raised as an APIConnectionError. The _run method returns normally, so the base class _main_task in livekit-agents/livekit/agents/tts/tts.py:479 treats it as a successful completion, preventing retries and error propagation.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 12 additional findings in Devin Review.

Open in Devin Review

Comment on lines +216 to +217
except websockets.exceptions.ConnectionClosed as e:
logger.info("60db STT: WebSocket closed: %s", e)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ”΄ STT silently swallows unexpected WebSocket disconnections, preventing error propagation and retry

In SpeechStream._run(), websockets.exceptions.ConnectionClosed is caught at line 216 and only logged at INFO level, then _run() returns normally. The base class RecognizeStream._main_task() (livekit-agents/livekit/agents/stt/stt.py:345-383) treats a normal return from _run() as success. This means if the WebSocket connection drops unexpectedly mid-stream (server crash, network issue, etc.), the STT stream silently ends with no transcriptions and no error β€” the retry mechanism in the base class is never triggered, and the caller has no indication of failure.

Suggested change
except websockets.exceptions.ConnectionClosed as e:
logger.info("60db STT: WebSocket closed: %s", e)
except websockets.exceptions.ConnectionClosed as e:
raise APIConnectionError(f"60db STT: WebSocket closed unexpectedly: {e}") from e
Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants