Skip to content

Conversation

@ashotbagh
Copy link
Contributor

@ashotbagh ashotbagh commented Oct 7, 2025

Summary by CodeRabbit

  • New Features
    • Adds an AsyncAI text-to-speech plugin with real-time streaming audio, configurable model, language (en, de, es, fr, it), encoding (PCM variants), voice selection, tokenizer support, and connection pooling with lifecycle management.
  • Documentation
    • Adds a README with installation steps, API key prerequisite, and integration links.
  • Chores
    • Adds packaging and version metadata for distribution.

✏️ Tip: You can customize this high-level summary in your review settings.

@davidzhao
Copy link
Member

Thanks for the PR!

While testing it, I'm finding a few issues:

  1. the websocket connection would time out, giving users an error:

should the connection be kept open? if not then you shouldn't use a connection pool, but just using it for each synthesis

2025-12-14 00:51:52,631 - WARNING livekit.plugins.asyncai - unexpected message {'error_code': 'TIMEOUT', 'message': 'Timeout: no message received in 60.0 seconds', 'request_id': 'unknown', 'extra': None} {"room": "sbx-104ei0-mcZwM3K6KcttVEyXaVjnRX", "pid": 16917, "job_id": "AJ_gjHdmUFbjtCx"}
2025-12-14 00:51:52,641 - WARNING livekit.agents - failed to synthesize speech, retrying in 0.1s
Traceback (most recent call last):
  File "/Users/davidzhao/dev/livekit/agents-async/livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py", line 283, in _run
    await asyncio.gather(*tasks)
  File "/Users/davidzhao/dev/livekit/agents-async/livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py", line 253, in _recv_task
    raise APIStatusError(
        "Async connection closed unexpectedly", request_id=request_id
    )
livekit.agents._exceptions.APIStatusError: Async connection closed unexpectedly (status_code=-1, request_id=74718f843e3b, body=None, retryable=True)
  1. the final message never comes, so the synthesis isn't successful

please use basic_agent.py as a test case to ensure the TTS could perform in a conversation of 5 mins long. also, the branch is a bit out of date from main, it'd be great if you could rebase the PR to main

Copy link
Member

@tinalenguyen tinalenguyen left a comment

Choose a reason for hiding this comment

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

Hey, thank you for submitting this PR! I left a few comments.

I also experienced the same timeout error as @davidzhao. I believe it's related to the context_id; if we implement the multi-context approach, the websocket connection shouldn't close

@ashotbagh
Copy link
Contributor Author

Hey @davidzhao , @tinalenguyen
Thanks for the review.

The earlier version of this PR was indeed out of date. Since then, our AsyncAI WebSocket endpoint has changed and now supports multi-context sessions, which required updating the connection handling logic. I’ve updated the implementation accordingly and rebased the branch on the latest main.
I’ve tested the updated version locally using basic_agent.py, and did not observe the timeout or any other issue.

Happy to make further adjustments if you still see any issues or have any other suggestions.

@tinalenguyen
Copy link
Member

@ashotbagh Thanks for making the changes!

I still see these errors when interrupting the agent:

    raise APIStatusError(
        "Async connection closed unexpectedly", request_id=request_id
    )
livekit.agents._exceptions.APIStatusError: Async connection closed unexpectedly (status_code=-1, request_id=33b99ecfaa23, body=None, retryable=True)

Could it be that each context must be flushed or closed after each synthesis?

I also occasionally see empty audio messages like:
livekit.…s.asyncai unexpected message {'final': False, 'audio': '', 'context_id': '470f507b-0a3a-46c6-a5af-796eb148a5ad'}

@ashotbagh
Copy link
Contributor Author

@tinalenguyen Thanks for the comments!

Regarding the “connection closed unexpectedly” issue: I’ve tried to reproduce it extensively (including interruption scenarios), but haven’t been able to trigger it locally so far. At the moment, the context flush happens on the final chunk, where we explicitly send:
end_pkt = {} end_pkt["transcript"] = "" end_pkt["context_id"] = asyncai_context_id await ws.send_str(json.dumps(end_pkt))

I’ve also made a small update to silently ignore empty audio messages (e.g. audio == "" on non-final frames), instead of logging them as unexpected, and applied a few minor fixes to satisfy ruff checks.

Could you please let me know if the issue still persists with the latest changes, and, if possible, share a reliable way to reproduce it? I’d be happy to dig deeper once I can observe it on my side.

@davidzhao
Copy link
Member

testing with basic_agent, after it performs a tool call, the agent would freeze on me.

@ashotbagh
Copy link
Contributor Author

Thanks for the report.

The freeze after a tool call was caused by a deadlock in the Async TTS streaming path: the receive loop was waiting for input_sent_event, which was only set when at least one sentence token was emitted. In tool-call flows, it’s possible for the tokenizer to emit no tokens before the end packet is sent, which caused the recv task to wait indefinitely.

I’ve fixed this by ensuring the receive loop is unblocked even when no tokens are emitted, so the stream always progresses correctly after tool calls.

I also cleaned up import ordering to satisfy ruff checks.

I’ve re-tested with basic_agent.py, including repeated tool calls, and the agent no longer hangs. Please let me know if you still see any issues or edge cases I should cover.

@ashotbagh
Copy link
Contributor Author

Hi @davidzhao , @tinalenguyen .
Just a quick follow-up, I pushed a fix for the tool-call hang (now reproducible and resolved) and cleaned up the remaining ruff issues.
When you have a moment, could you please take another look and let me know if everything looks good on your side? Happy to make further adjustments if needed.
Thanks!

@ashotbagh ashotbagh force-pushed the feature/asyncai-tts branch from fa89709 to 09e5b73 Compare January 21, 2026 10:28
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 21, 2026

📝 Walkthrough

Walkthrough

Adds a new AsyncAI LiveKit plugin package with TTS streaming via WebSocket, packaging metadata, plugin registration, constants, logger, typed TTS models/languages, versioning, README, and a substantial streaming TTS implementation including session/pool management and stream lifecycle.

Changes

Cohort / File(s) Summary
Package & Plugin Setup
livekit-plugins/livekit-plugins-asyncai/pyproject.toml, livekit-plugins/livekit-plugins-asyncai/README.md, livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/version.py, livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/__init__.py
New package metadata and build config, README, __version__, plugin initializer that registers AsyncAIPlugin and exposes public API entries
Configuration & Types
livekit-plugins-livekit-plugins-asyncai/livekit/plugins/asyncai/constants.py, .../models.py, .../log.py
Added API header/version constants, typed Literals for TTS encodings/models/languages, default voice id, and module logger
Core TTS Implementation
livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py
New TTS and SynthesizeStream with WebSocket/HTTP connection handling, session and pool management, sentence tokenization, streaming send/receive loop, error/timeouts, update options, and graceful close

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant TTS
    participant Stream as SynthesizeStream
    participant Tokenizer
    participant AsyncAI_WS as AsyncAI (WebSocket)
    participant Emitter as AudioEmitter

    Client->>TTS: stream()
    TTS->>Stream: create(tts, conn_options)
    Stream->>AsyncAI_WS: _connect_ws(init payload)
    AsyncAI_WS-->>Stream: connected
    Stream->>Tokenizer: tokenize(input text)
    Tokenizer-->>Stream: sentences
    par Streaming
        Stream->>AsyncAI_WS: send(sentences)
        AsyncAI_WS-->>Stream: audio segments / events
        Stream->>Emitter: push(audio data / final signals)
    end
    Emitter-->>Client: audio stream
    Client->>Stream: aclose()/stop
    Stream->>AsyncAI_WS: _close_ws()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • davidzhao
  • tinalenguyen

Poem

🐰 I hopped in with code and cheer,

Websockets hum and voices near,
Tokens tumble, audio streams,
Small rabbit claps and dreams in beams,
"Play on," says the rabbit, ear to ear 🎶

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: integrating AsyncAI TTS engine into LiveKit as a new plugin. It is concise, specific, and aligns with the primary objective of the pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py`:
- Around line 1-14: The file
livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py is not
formatted to ruff standards; run the formatter (e.g., `ruff format` or your
project's `uv run ruff format`) on that file and commit the resulting reformat
so the repo passes `ruff format --check`; no code changes required beyond
applying ruff formatting to tts.py.
- Around line 72-103: The docstring for Async TTS __init__ incorrectly states
language defaults to "en" while the method signature sets language: str | None =
None; update one of them so they match — either change the signature default to
language: str = "en" in __init__ if the implementation expects a default
language, or update the docstring to state that language defaults to None (and
describe behavior when None), ensuring references to the parameter name
"language" and the constructor "__init__" are consistent.
- Around line 210-213: The synthesize method in the AsyncAI TTS implementation
currently is a stub that returns None and therefore violates the abstract TTS
base class contract which requires returning a ChunkedStream; replace the pass
with an explicit runtime error (e.g., raise NotImplementedError or a custom
RuntimeError) that states AsyncAI TTS only supports streaming and direct
synthesize() is unsupported, and update the method signature to include the
correct return type annotation (ChunkedStream) to match the base class; locate
and modify the synthesize(self, text: str, *, conn_options: APIConnectOptions =
DEFAULT_API_CONNECT_OPTIONS) method in this class to implement those changes.
- Around line 144-163: The WebSocket URL is built by raw f-string interpolation
in _connect_ws using API_AUTH_HEADER, API_VERSION_HEADER and self._opts.api_key
which can break if the API key contains reserved URL characters; update
_connect_ws to build the query string with urllib.parse.urlencode (encode
API_AUTH_HEADER=self._opts.api_key and API_VERSION_HEADER=API_VERSION) and pass
that encoded query to self._opts.get_ws_url instead of interpolating directly so
the URL is safely encoded.

In `@livekit-plugins/livekit-plugins-asyncai/pyproject.toml`:
- Around line 14-25: The pyproject declarations are missing the aiohttp runtime
dependency used by tts.py; update the dependencies list (the dependencies =
[...] entry) to include "aiohttp" alongside "livekit-agents>=1.2.14", and
optionally extend the classifiers array (the classifiers = [...] entry) to add
"Programming Language :: Python :: 3.11" and "Programming Language :: Python ::
3.12" if you have verified compatibility and tests pass for those versions.

In `@livekit-plugins/livekit-plugins-asyncai/README.md`:
- Around line 3-5: Replace the un-hyphenated phrase "text to speech" with the
hyphenated "text-to-speech" in the line that reads "Support for text to speech
with [Async](https://async.ai/)." and convert the bare URL
"https://docs.livekit.io/agents/integrations/tts/asyncai/" into a proper
Markdown link with descriptive text (e.g., "See the LiveKit Async TTS docs" or
"See the Async TTS docs") so the README uses "text-to-speech" and avoids a bare
URL.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d9c8fb and 09e5b73.

📒 Files selected for processing (8)
  • livekit-plugins/livekit-plugins-asyncai/README.md
  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/__init__.py
  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/constants.py
  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/log.py
  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/models.py
  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py
  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/version.py
  • livekit-plugins/livekit-plugins-asyncai/pyproject.toml
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Format code with ruff
Run ruff linter and auto-fix issues
Run mypy type checker in strict mode
Maintain line length of 100 characters maximum
Ensure Python 3.9+ compatibility
Use Google-style docstrings

Files:

  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/constants.py
  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/log.py
  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/models.py
  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/version.py
  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py
  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/__init__.py
🧬 Code graph analysis (1)
livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/__init__.py (1)
livekit-agents/livekit/agents/plugin.py (2)
  • Plugin (13-56)
  • register_plugin (31-36)
🪛 GitHub Actions: CI
livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py

[error] 1-1: Command 'uv run ruff format --check .' failed with exit code 1. Ruff format would reformat 1 file: livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py. Run 'ruff format' to fix.

🪛 LanguageTool
livekit-plugins/livekit-plugins-asyncai/README.md

[grammar] ~3-~3: Use a hyphen to join words.
Context: ...gin for LiveKit Agents Support for text to speech with [Async](https://async.ai/...

(QB_NEW_EN_HYPHEN)


[grammar] ~3-~3: Use a hyphen to join words.
Context: ... for LiveKit Agents Support for text to speech with Async. ...

(QB_NEW_EN_HYPHEN)

🪛 markdownlint-cli2 (0.18.1)
livekit-plugins/livekit-plugins-asyncai/README.md

5-5: Bare URL used

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: unit-tests
🔇 Additional comments (13)
livekit-plugins/livekit-plugins-asyncai/pyproject.toml (3)

1-3: LGTM!

Build system configuration using hatchling is appropriate and consistent with other livekit plugin packages.


27-30: LGTM!

Project URLs are correctly configured and consistent with other LiveKit plugins.


32-39: LGTM!

Hatch configuration correctly follows the namespace package pattern used by other livekit plugins, and the version file is properly located at the specified path.

livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/log.py (1)

1-3: LGTM — consistent module logger setup.

livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/version.py (1)

1-15: Version module looks good.

livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/constants.py (1)

1-3: LGTM — clear, centralized constants.

livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/__init__.py (1)

30-35: Verify import-time registration occurs on the main thread.

Plugin.register_plugin raises if called off the main thread. Since registration happens at import time, ensure import/discovery always runs on the main thread; otherwise consider deferring registration to an explicit init hook.

livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/models.py (1)

1-7: LGTM — clear typed config surface.

livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py (5)

54-68: Centralized option/URL helpers look good.
Keeps configuration and URL construction localized and easy to extend.


178-208: Option updates and stream factory look solid.


215-221: Graceful stream/pool cleanup looks good.


223-228: Stream setup and option snapshotting look good.


229-333: Streaming loop and cleanup sequencing are solid.
The empty-audio guard and end-of-input handling should help avoid stalls.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py`:
- Around line 55-62: The _TTSOptions dataclass currently types the language
field as str but the TTS class __init__ accepts language: str | None and passes
that through, causing a mypy type error; update the _TTSOptions declaration for
the language attribute to language: str | None so it matches the constructor and
the downstream None checks in methods like the TTS implementation that inspect
language for None.
♻️ Duplicate comments (3)
livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py (3)

91-94: Align language docstring defaults with the signature.

The docstrings state a default of "en", but the signature defaults to None. Please align them to avoid confusion.

Also applies to: 192-194


17-25: URL-encode WebSocket query params.

Raw interpolation can break if the API key contains reserved characters. Use urllib.parse.urlencode() for the query string.

🔧 Proposed fix
+from urllib.parse import urlencode
@@
-        url = self._opts.get_ws_url(
-            f"/text_to_speech/websocket/ws?{API_AUTH_HEADER}={self._opts.api_key}&{API_VERSION_HEADER}={API_VERSION}"
-        )
+        query = urlencode({API_AUTH_HEADER: self._opts.api_key, API_VERSION_HEADER: API_VERSION})
+        url = self._opts.get_ws_url(f"/text_to_speech/websocket/ws?{query}")

Also applies to: 144-148


210-213: synthesize() should fail fast instead of returning None.

This stub violates the base class contract. Raise an explicit error (and optionally annotate the return type) to make the unsupported path clear.

🔧 Proposed fix
-    def synthesize(
-        self, text: str, *, conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS
-    ):
-        pass
+    def synthesize(
+        self, text: str, *, conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS
+    ) -> tts.ChunkedStream:
+        raise NotImplementedError(
+            "AsyncAI TTS currently supports streaming only; use stream()."
+        )
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09e5b73 and 652cbbf.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Format code with ruff
Run ruff linter and auto-fix issues
Run mypy type checker in strict mode
Maintain line length of 100 characters maximum
Ensure Python 3.9+ compatibility
Use Google-style docstrings

Files:

  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: livekit/agents PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T07:44:56.353Z
Learning: Follow the Plugin System pattern where plugins in livekit-plugins/ are separate packages registered via the Plugin base class
📚 Learning: 2026-01-16T07:44:56.353Z
Learnt from: CR
Repo: livekit/agents PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T07:44:56.353Z
Learning: Applies to **/*.py : Run ruff linter and auto-fix issues

Applied to files:

  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py
📚 Learning: 2026-01-16T07:44:56.353Z
Learnt from: CR
Repo: livekit/agents PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T07:44:56.353Z
Learning: Applies to **/*.py : Format code with ruff

Applied to files:

  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py
🧬 Code graph analysis (1)
livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py (6)
livekit-agents/livekit/agents/_exceptions.py (3)
  • APIConnectionError (84-88)
  • APIStatusError (45-81)
  • APITimeoutError (91-95)
livekit-agents/livekit/agents/types.py (1)
  • APIConnectOptions (54-88)
livekit-agents/livekit/agents/utils/misc.py (2)
  • is_given (25-26)
  • shortuuid (21-22)
livekit-agents/livekit/agents/utils/http_context.py (1)
  • http_session (40-51)
livekit-agents/livekit/agents/utils/connection_pool.py (1)
  • connection (73-86)
livekit-plugins/livekit-plugins-inworld/livekit/plugins/inworld/tts.py (1)
  • mime_type (81-91)

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py`:
- Around line 292-297: The code currently passes possibly-None segment_id into
output_emitter.start_segment (via data.get("context_id")), which can break
downstream logic; modify the handling in the block around current_segment_id,
segment_id and output_emitter.start_segment so that if data.get("context_id") is
None you fall back to an existing local context (e.g., current_segment_id or a
designated default_context_id), only call output_emitter.start_segment when the
resolved segment_id is non-None, and ensure current_segment_id is set to that
resolved id; update the logic in the function/method containing
current_segment_id, segment_id and output_emitter.start_segment accordingly.
- Around line 185-195: The docstring for update_options incorrectly states fixed
default values for model and language; clarify that model and language are
optional and that omitting them will retain the current instance values (i.e.,
"keep existing if omitted") rather than defaulting to
"asyncflow_multilingual_v1.0" or "en". Update the docstring on the
update_options method to reflect that behavior, remove the misleading Defaults:
entries, and mention that voice, model, and language are optional and will not
change if not provided; reference the update_options function and the TTSModels
type in the description so readers can locate the method and expected types.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 652cbbf and aaea7c1.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • livekit-plugins/livekit-plugins-asyncai/README.md
  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/py.typed
  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py
  • livekit-plugins/livekit-plugins-asyncai/pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • livekit-plugins/livekit-plugins-asyncai/README.md
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Format code with ruff
Run ruff linter and auto-fix issues
Run mypy type checker in strict mode
Maintain line length of 100 characters maximum
Ensure Python 3.9+ compatibility
Use Google-style docstrings

Files:

  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: livekit/agents PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T07:44:56.353Z
Learning: Follow the Plugin System pattern where plugins in livekit-plugins/ are separate packages registered via the Plugin base class
📚 Learning: 2026-01-16T07:44:56.353Z
Learnt from: CR
Repo: livekit/agents PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T07:44:56.353Z
Learning: Applies to **/*.py : Run ruff linter and auto-fix issues

Applied to files:

  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py
📚 Learning: 2026-01-16T07:44:56.353Z
Learnt from: CR
Repo: livekit/agents PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T07:44:56.353Z
Learning: Applies to **/*.py : Format code with ruff

Applied to files:

  • livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: type-check (3.9)
  • GitHub Check: type-check (3.13)
  • GitHub Check: unit-tests
🔇 Additional comments (11)
livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py (10)

55-69: LGTM — options container and URL helpers are straightforward.


73-135: LGTM — constructor wiring and initialization look solid.


137-143: LGTM — lightweight properties are clear and consistent.


145-164: LGTM — connection setup is clean and easy to follow.


166-176: LGTM — close/ensure/prewarm helpers are minimal and clear.


203-220: LGTM — stream API, explicit unsupported synthesize, and cleanup are good.


224-228: LGTM — stream init is concise and correct.


229-238: LGTM — emitter initialization is well structured.


239-272: LGTM — input/tokenization flow and end-packet handling are solid.


314-336: LGTM — task orchestration, cleanup, and error mapping look robust.

livekit-plugins/livekit-plugins-asyncai/pyproject.toml (1)

1-42: LGTM — packaging metadata and build configuration are consistent.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +185 to +195
"""
Update the Text-to-Speech (TTS) configuration options.

This method allows updating the TTS settings, including model type, language and voice.
If any parameter is not provided, the existing value will be retained.

Args:
model (TTSModels, optional): The Async TTS model to use. Defaults to "asyncflow_multilingual_v1.0".
language (str, optional): The language code for synthesis. Defaults to "en".
voice (str, optional): The voice ID.
"""
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Docstring defaults in update_options() don’t match behavior.
model and language are “keep existing if omitted,” not defaulting to fixed values. Align the docstring to avoid confusion.

📘 Suggested docstring alignment
-            model (TTSModels, optional): The Async TTS model to use. Defaults to "asyncflow_multilingual_v1.0".
-            language (str, optional): The language code for synthesis. Defaults to "en".
+            model (TTSModels, optional): The Async TTS model to use. If omitted, the current value is kept.
+            language (str, optional): The language code for synthesis. If omitted, the current value is kept.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"""
Update the Text-to-Speech (TTS) configuration options.
This method allows updating the TTS settings, including model type, language and voice.
If any parameter is not provided, the existing value will be retained.
Args:
model (TTSModels, optional): The Async TTS model to use. Defaults to "asyncflow_multilingual_v1.0".
language (str, optional): The language code for synthesis. Defaults to "en".
voice (str, optional): The voice ID.
"""
"""
Update the Text-to-Speech (TTS) configuration options.
This method allows updating the TTS settings, including model type, language and voice.
If any parameter is not provided, the existing value will be retained.
Args:
model (TTSModels, optional): The Async TTS model to use. If omitted, the current value is kept.
language (str, optional): The language code for synthesis. If omitted, the current value is kept.
voice (str, optional): The voice ID.
"""
🤖 Prompt for AI Agents
In `@livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py`
around lines 185 - 195, The docstring for update_options incorrectly states
fixed default values for model and language; clarify that model and language are
optional and that omitting them will retain the current instance values (i.e.,
"keep existing if omitted") rather than defaulting to
"asyncflow_multilingual_v1.0" or "en". Update the docstring on the
update_options method to reflect that behavior, remove the misleading Defaults:
entries, and mention that voice, model, and language are optional and will not
change if not provided; reference the update_options function and the TTSModels
type in the description so readers can locate the method and expected types.

Comment on lines +292 to +297
data = json.loads(msg.data)
segment_id = data.get("context_id")
if current_segment_id is None:
current_segment_id = segment_id
output_emitter.start_segment(segment_id=segment_id)
final = data.get("final")
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against missing context_id in responses.
If AsyncAI ever omits context_id, output_emitter.start_segment() receives None, which can break downstream assumptions. Consider falling back to the local context id.

🛠️ Suggested fallback
-                segment_id = data.get("context_id")
+                segment_id = data.get("context_id") or asyncai_context_id
🤖 Prompt for AI Agents
In `@livekit-plugins/livekit-plugins-asyncai/livekit/plugins/asyncai/tts.py`
around lines 292 - 297, The code currently passes possibly-None segment_id into
output_emitter.start_segment (via data.get("context_id")), which can break
downstream logic; modify the handling in the block around current_segment_id,
segment_id and output_emitter.start_segment so that if data.get("context_id") is
None you fall back to an existing local context (e.g., current_segment_id or a
designated default_context_id), only call output_emitter.start_segment when the
resolved segment_id is non-None, and ensure current_segment_id is set to that
resolved id; update the logic in the function/method containing
current_segment_id, segment_id and output_emitter.start_segment accordingly.

@ashotbagh
Copy link
Contributor Author

@davidzhao
I’ve addressed the review comments and CI failures:
• Fixed mypy issues,
• Resolved formatting and linting issues,
• Rebased the branch on the latest main

All checks should be passing now. Please let me know if there’s anything else you’d like me to adjust - happy to iterate. Thanks!

@davidzhao davidzhao merged commit 0b55e7e into livekit:main Jan 21, 2026
10 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 21, 2026
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