Skip to content

Conversation

@jp-lemon
Copy link
Contributor

@jp-lemon jp-lemon commented Jan 16, 2026

LemonSlice Integration Docs
LemonSlice API Docs

Summary by CodeRabbit

  • New Features

    • Added LemonSlice virtual avatar support for LiveKit Agents (cloud avatars via custom ID or image upload) with runtime plugin and API integration.
  • Documentation

    • New README for the LemonSlice plugin and example agent, plus updated provider lists and example usage with environment and run instructions.
  • Chores

    • Packaging and versioning added for the LemonSlice plugin.

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 2026

📝 Walkthrough

Walkthrough

Adds a LemonSlice virtual-avatar plugin and example integration: new plugin package (API client, avatar session, logging, version, packaging), a LemonSlice-powered agent worker example, and related documentation and READMEs.

Changes

Cohort / File(s) Summary
Documentation & Examples
examples/avatar_agents/README.md, examples/avatar_agents/lemonslice/README.md
Inserted LemonSlice provider entries and added a LemonSlice example README with env var list and run instructions.
Example Agent Worker
examples/avatar_agents/lemonslice/agent_worker.py
New agent worker that composes an AgentSession (STT/LLM/TTS) and a LemonSlice AvatarSession inside an rtc_session(), reads env vars (e.g., LEMONSLICE_IMAGE_URL), and starts avatar + agent flows.
Plugin Bootstrap & Exports
livekit-plugins/.../lemonslice/__init__.py
New LemonSlicePlugin class, plugin registration at import, and exports updated to include AvatarSession, LemonSliceException, and __version__.
API Client
livekit-plugins/.../lemonslice/api.py
New LemonSliceAPI with env/api_key handling, async context management, start_agent_session(...), _post(...) including retries/backoff, timeouts, headers, and API error mapping plus DEFAULT_API_URL and LemonSliceException.
Avatar Session / LiveKit Integration
livekit-plugins/.../lemonslice/avatar.py
New AvatarSession that validates LiveKit creds (args/env), builds AccessToken (JWT) with necessary grants, calls LemonSliceAPI.start_agent_session, and configures DataStreamAudioOutput for room binding.
Logging & Version
livekit-plugins/.../lemonslice/log.py, .../version.py
Added module logger and __version__ = "1.3.11".
Packaging & Workspace
livekit-plugins/livekit-plugins-lemonslice/pyproject.toml, livekit-plugins/livekit-plugins-lemonslice/README.md, pyproject.toml
Added package pyproject.toml (hatchling), package README, and registered the new plugin in workspace sources.

Sequence Diagram(s)

sequenceDiagram
    participant Worker as Agent Worker
    participant Agent as AgentSession
    participant Avatar as AvatarSession
    participant API as LemonSlice API
    participant Room as LiveKit Room
    participant Output as DataStreamAudioOutput

    Worker->>Agent: instantiate (STT, LLM, TTS)
    Worker->>Avatar: instantiate (image_url, prompt)
    Avatar->>Avatar: ensure aiohttp session
    Avatar->>Avatar: build LiveKit AccessToken (JWT)
    Avatar->>API: start_agent_session(payload with livekit token)
    API->>API: POST request (retry/backoff)
    API-->>Avatar: return session_id
    Avatar->>Output: create DataStreamAudioOutput (sample_rate, destination)
    Output->>Room: attach/bind to room (publish on behalf)
    Worker->>Agent: start AgentSession in room
    Agent->>Room: publish audio/text events
    Room->>Output: route audio to LemonSlice avatar
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I bounced in with tokens snug and bright,
I spun up sessions through the coding night,
Voices and pixels in a LemonSlice light,
LiveKit hums — avatars take flight! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 'LemonSlice Plugin' accurately reflects the main change—a new plugin for LemonSlice avatar integration throughout the codebase.

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

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a96583e and 23d795f.

📒 Files selected for processing (1)
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.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

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


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

@CLAassistant
Copy link

CLAassistant commented Jan 16, 2026

CLA assistant check
All committers have signed the CLA.

@davidzhao
Copy link
Member

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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: 3

🤖 Fix all issues with AI agents
In `@examples/avatar_agents/lemonslice/agent_worker.py`:
- Around line 27-31: The environment variable lemonslice_image_url (from
os.getenv("LEMONSLICE_IMAGE_URL")) is used directly as agent_image_url when
constructing lemonslice.AvatarSession which can pass None to the API; add a
validation check after reading lemonslice_image_url to verify it is non-empty
and raise a clear exception (or call sys.exit) if missing, so
AvatarSession(agent_image_url=lemonslice_image_url, ...) is only called with a
valid URL.

In
`@livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/__init__.py`:
- Around line 15-18: The module docstring in __init__.py contains a malformed
documentation URL with a duplicated "https://"
("https://https://docs.livekit.io/..."); update the docstring to remove the
extra scheme so the link reads
"https://docs.livekit.io/agents/models/avatar/plugins/lemonslice/" - edit the
top-level string literal in livekit/plugins/lemonslice/__init__.py accordingly.

In
`@livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.py`:
- Around line 96-121: The retry loop currently iterates
range(self._conn_options.max_retry) and always retries; update it to
range(self._conn_options.max_retry + 1) so attempts = max_retry + 1, and when
catching exceptions, if isinstance(e, APIStatusError) and not e.retryable:
re-raise to avoid retrying 4xx non-retryable errors; keep handling
APIConnectionError separately as before. Replace usage of
self._conn_options.retry_interval with self._conn_options._interval_for_retry(i)
for backoff, and change the sleep/continue condition to i <
self._conn_options.max_retry so you only sleep when another attempt remains.
Ensure these changes are applied around the async with self._session.post(...)
block and the except handler where APIStatusError, APIConnectionError, and i are
referenced.
🧹 Nitpick comments (8)
pyproject.toml (1)

28-28: Minor formatting inconsistency.

Missing space before the closing brace. Other entries use { workspace = true } (with space).

🔧 Suggested fix
-livekit-plugins-lemonslice = { workspace = true}
+livekit-plugins-lemonslice = { workspace = true }
livekit-plugins/livekit-plugins-lemonslice/pyproject.toml (2)

13-13: Minor formatting: missing space after comma in keywords.

🔧 Suggested fix
-keywords = ["voice", "ai", "realtime", "audio", "video", "livekit", "webrtc", "avatar", "agent","lemonslice"]
+keywords = ["voice", "ai", "realtime", "audio", "video", "livekit", "webrtc", "avatar", "agent", "lemonslice"]

20-23: Consider adding classifiers for Python 3.11 and 3.12.

Since requires-python = ">=3.9.0", the package supports newer Python versions. Adding classifiers improves discoverability on PyPI.

🔧 Suggested addition
     "Programming Language :: Python :: 3",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12",
     "Programming Language :: Python :: 3 :: Only",
examples/avatar_agents/lemonslice/agent_worker.py (1)

18-41: Add a Google-style docstring to entrypoint.
As per coding guidelines, public entrypoints should have Google-style docstrings.

livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.py (2)

30-67: Avoid leaking NOT_GIVEN into credentials/payload.
Using or with NOT_GIVEN can keep the sentinel (if it’s truthy), and livekit_url/token are always serialized into JSON even when not provided. Safer to use utils.is_given for defaults and only include provided fields.

🔧 Suggested fix
-        ls_api_key = api_key or os.getenv("LEMONSLICE_API_KEY")
+        ls_api_key = api_key if utils.is_given(api_key) else os.getenv("LEMONSLICE_API_KEY")
         if ls_api_key is None:
             raise LemonSliceException("LEMONSLICE_API_KEY must be set")
         self._api_key = ls_api_key

-        self._api_url = api_url or DEFAULT_API_URL
+        self._api_url = api_url if utils.is_given(api_url) else DEFAULT_API_URL
-        payload: dict[str, Any] = {
-            "transport_type": "livekit",
-            "properties": {
-                "livekit_url": livekit_url,
-                "livekit_token": livekit_token,
-            },
-        }
+        payload: dict[str, Any] = {"transport_type": "livekit", "properties": {}}
+        if utils.is_given(livekit_url):
+            payload["properties"]["livekit_url"] = livekit_url
+        if utils.is_given(livekit_token):
+            payload["properties"]["livekit_token"] = livekit_token
+        if not payload["properties"]:
+            payload.pop("properties")

34-44: Manage aiohttp.ClientSession lifecycle.
Creating a session internally without a close path can leak sockets. Consider adding close() / async context manager support when session isn’t provided.

livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/avatar.py (2)

56-77: Prefer utils.is_given for NOT_GIVEN defaults.
Using or with NOT_GIVEN can keep the sentinel (if it’s truthy), which may bypass required config checks and leak into identity fields. Safer to use utils.is_given consistently.

🔧 Suggested fix
-        self._avatar_participant_identity = avatar_participant_identity or _AVATAR_AGENT_IDENTITY
-        self._avatar_participant_name = avatar_participant_name or _AVATAR_AGENT_NAME
+        self._avatar_participant_identity = (
+            avatar_participant_identity
+            if utils.is_given(avatar_participant_identity)
+            else _AVATAR_AGENT_IDENTITY
+        )
+        self._avatar_participant_name = (
+            avatar_participant_name if utils.is_given(avatar_participant_name) else _AVATAR_AGENT_NAME
+        )
-        livekit_url = livekit_url or (os.getenv("LIVEKIT_URL") or NOT_GIVEN)
-        livekit_api_key = livekit_api_key or (os.getenv("LIVEKIT_API_KEY") or NOT_GIVEN)
-        livekit_api_secret = livekit_api_secret or (os.getenv("LIVEKIT_API_SECRET") or NOT_GIVEN)
+        livekit_url = (
+            livekit_url if utils.is_given(livekit_url) else (os.getenv("LIVEKIT_URL") or NOT_GIVEN)
+        )
+        livekit_api_key = (
+            livekit_api_key
+            if utils.is_given(livekit_api_key)
+            else (os.getenv("LIVEKIT_API_KEY") or NOT_GIVEN)
+        )
+        livekit_api_secret = (
+            livekit_api_secret
+            if utils.is_given(livekit_api_secret)
+            else (os.getenv("LIVEKIT_API_SECRET") or NOT_GIVEN)
+        )

65-73: Add a Google-style docstring to start().
As per coding guidelines, public methods should include Google-style docstrings.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d97b05 and ccd7a69.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • examples/avatar_agents/README.md
  • examples/avatar_agents/lemonslice/README.md
  • examples/avatar_agents/lemonslice/agent_worker.py
  • livekit-plugins/livekit-plugins-lemonslice/README.md
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/__init__.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/avatar.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/log.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/py.typed
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/version.py
  • livekit-plugins/livekit-plugins-lemonslice/pyproject.toml
  • 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-lemonslice/livekit/plugins/lemonslice/version.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/__init__.py
  • examples/avatar_agents/lemonslice/agent_worker.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/avatar.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/log.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.py
🧠 Learnings (1)
📚 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: Follow the Plugin System pattern where plugins in livekit-plugins/ are separate packages registered via the Plugin base class

Applied to files:

  • pyproject.toml
🧬 Code graph analysis (3)
examples/avatar_agents/lemonslice/agent_worker.py (3)
livekit-agents/livekit/agents/voice/agent.py (2)
  • Agent (34-642)
  • instructions (99-104)
livekit-agents/livekit/agents/job.py (1)
  • JobContext (132-598)
livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/avatar.py (2)
  • AvatarSession (27-111)
  • start (65-111)
livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/avatar.py (3)
livekit-agents/livekit/agents/job.py (2)
  • api (288-296)
  • get_job_context (56-63)
livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.py (3)
  • LemonSliceAPI (27-122)
  • LemonSliceException (20-21)
  • start_agent_session (45-81)
livekit-agents/livekit/agents/utils/http_context.py (1)
  • http_session (40-51)
livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.py (3)
livekit-agents/livekit/agents/_exceptions.py (2)
  • APIConnectionError (84-88)
  • APIStatusError (45-81)
livekit-agents/livekit/agents/types.py (1)
  • APIConnectOptions (54-88)
livekit-agents/livekit/agents/utils/misc.py (1)
  • is_given (25-26)
🔇 Additional comments (5)
livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/log.py (1)

1-3: LGTM!

Standard logger setup following the established plugin pattern with the correct namespace.

livekit-plugins/livekit-plugins-lemonslice/README.md (1)

1-5: LGTM!

README follows the standard plugin documentation pattern with appropriate links.

examples/avatar_agents/lemonslice/README.md (1)

1-28: LGTM!

Clear documentation with all required environment variables and startup command.

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

20-40: LGTM!

Plugin structure correctly follows the established pattern with proper exports and auto-registration. Based on learnings, this aligns with the Plugin System pattern where plugins are registered via the Plugin base class.

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

15-15: Version constant looks good.

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

@jp-lemon jp-lemon marked this pull request as ready for review January 20, 2026 18:10
Copy link

@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-lemonslice/livekit/plugins/lemonslice/api.py`:
- Around line 41-44: LemonSliceAPI currently instantiates self._session =
session or aiohttp.ClientSession() but never closes it; add ownership tracking
(e.g., self._owns_session = session is None) in the constructor, implement an
async close method (async def close(self):) that awaits self._session.close()
only when self._owns_session is True, and optionally implement async context
manager methods (__aenter__ returning self and __aexit__ calling await
self.close()) so callers can use "async with LemonSliceAPI(...)" to ensure
proper cleanup; update any usage/tests to call await api.close() or use the
async context manager.
- Around line 62-67: The payload currently always includes livekit_url and
livekit_token which may be NotGivenOr[str] sentinels and will break JSON
serialization; update the payload construction in the function that builds the
transport payload (look for the payload dict in
livekit/plugins/lemonslice/api.py) to only add "livekit_url" and "livekit_token"
inside "properties" when utils.is_given(livekit_url) and
utils.is_given(livekit_token) are true, mirroring how
agent_id/agent_image_url/agent_prompt/idle_timeout are conditionally appended.
🧹 Nitpick comments (4)
examples/avatar_agents/lemonslice/agent_worker.py (2)

18-19: Add return type annotation and docstring.

Per coding guidelines, functions should have type hints and Google-style docstrings. The return type annotation is missing.

♻️ Suggested fix
 `@server.rtc_session`()
-async def entrypoint(ctx: JobContext):
+async def entrypoint(ctx: JobContext) -> None:
+    """LemonSlice avatar agent entrypoint.
+
+    Args:
+        ctx: The job context containing room and session information.
+    """
     session = AgentSession(

30-33: Line exceeds 100 character limit.

Line 32 exceeds the 100-character maximum specified in the coding guidelines. Consider extracting the prompt to a constant or moving the comment.

♻️ Suggested fix
+    # Prompt to guide the avatar's movements
+    avatar_prompt = "Be expressive in your movements and use your hands while talking."
     avatar = lemonslice.AvatarSession(
         agent_image_url=lemonslice_image_url,
-        agent_prompt="Be expressive in your movements and use your hands while talking.",  # Prompt to guide the avatar's movements
+        agent_prompt=avatar_prompt,
     )
livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.py (2)

45-55: Add a Google-style docstring for start_agent_session.
This is a public API surface and should be documented consistently.

As per coding guidelines, please ensure Google-style docstrings for public methods.


27-39: Consider using utils.is_given() for consistency with the rest of the codebase. The current api_key or os.getenv(...) pattern works correctly since NOT_GIVEN is falsy, but the codebase consistently uses utils.is_given() for NotGivenOr checks (as seen in start_agent_session method). Using the same pattern here improves readability and consistency.

🔧 Suggested refactoring
-        ls_api_key = api_key or os.getenv("LEMONSLICE_API_KEY")
-        if ls_api_key is None:
+        if utils.is_given(api_key):
+            ls_api_key = api_key
+        else:
+            ls_api_key = os.getenv("LEMONSLICE_API_KEY")
+        if not ls_api_key:
             raise LemonSliceException("LEMONSLICE_API_KEY must be set")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ccd7a69 and 30429e6.

📒 Files selected for processing (3)
  • examples/avatar_agents/lemonslice/agent_worker.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/__init__.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.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-lemonslice/livekit/plugins/lemonslice/__init__.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.py
  • examples/avatar_agents/lemonslice/agent_worker.py
🧬 Code graph analysis (1)
examples/avatar_agents/lemonslice/agent_worker.py (1)
livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/avatar.py (2)
  • AvatarSession (27-111)
  • start (65-111)
🔇 Additional comments (7)
examples/avatar_agents/lemonslice/agent_worker.py (6)

1-7: LGTM!

Imports are well-organized following the standard convention (stdlib → third-party → local packages).


9-10: LGTM!

Named logger with appropriate log level for an example application.


12-15: LGTM!

Module-level load_dotenv() and AgentServer() initialization is appropriate for this decorator-based pattern.


46-47: LGTM!

Standard CLI entry point for running the agent server.


27-29: LGTM!

The validation for LEMONSLICE_IMAGE_URL is now properly in place, failing fast with a clear error message if the environment variable is unset.


43-43: No action needed. generate_reply() returns asyncio.Future and is designed to support both fire-and-forget (without await) and awaiting patterns. Not awaiting the call is intentional and correct—the reply generation proceeds asynchronously via event loop callbacks. This pattern is used consistently throughout the codebase.

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

15-40: LGTM — clean public surface and plugin registration.

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

Copy link

@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: 3

🤖 Fix all issues with AI agents
In
`@livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.py`:
- Around line 32-54: The constructor currently uses "or" which can leave the
NOT_GIVEN sentinel in place for api_key/api_url; change the resolution in the
LemonSliceAPI initializer to explicitly check the sentinel (use
utils.is_given(api_key) and utils.is_given(api_url)) and then fall back to
os.getenv("LEMONSLICE_API_KEY") or DEFAULT_API_URL respectively, validate the
resolved values are non-empty strings, and raise LemonSliceException if api_key
ends up missing; ensure self._api_key and self._api_url never receive the
NOT_GIVEN sentinel so they aren't leaked into headers or URLs.
- Line 36: Replace PEP 604 union syntax with typing generics for Python 3.9
compatibility: change any parameter or return annotation that uses the `|` union
(e.g., the `session: aiohttp.ClientSession | None` parameter) to use
typing.Optional (import Optional from typing) as
`Optional[aiohttp.ClientSession]`; likewise replace `dict[str, Any]` with
`Dict[str, Any]` (import Dict, Any) and any other `X | None` annotations to
`Optional[X]`; update imports to include Optional, Dict, Type, Any as needed and
adjust annotations in the functions/methods that reference these symbols (for
example the session parameter, any headers/json params and typed returns) to
ensure full Python 3.9 compatibility.

In
`@livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/avatar.py`:
- Around line 51-70: The start method currently uses `or` to fallback to
environment variables which can preserve the NOT_GIVEN sentinel; change the
assignments for `livekit_url`, `livekit_api_key`, and `livekit_api_secret` to
use `utils.is_given` to detect provided values and only when not given read the
env (e.g. if not utils.is_given(livekit_url) then set from os.getenv(...) ),
then validate with `utils.is_given` (or truthiness if env returned a real
string) and raise the existing LemonSliceException if any remain unset; update
references in the `start` method where `_avatar_participant_identity`/name or
credential checks rely on these variables.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30429e6 and a96583e.

📒 Files selected for processing (3)
  • examples/avatar_agents/lemonslice/agent_worker.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/avatar.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:

  • examples/avatar_agents/lemonslice/agent_worker.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/avatar.py
  • livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.py
🧬 Code graph analysis (1)
examples/avatar_agents/lemonslice/agent_worker.py (2)
livekit-agents/livekit/agents/worker.py (1)
  • AgentServer (253-1317)
livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/avatar.py (2)
  • AvatarSession (26-106)
  • start (54-106)
🔇 Additional comments (5)
livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/api.py (2)

69-121: start_agent_session payload gating looks solid.

Conditional inclusion of optional fields prevents sending NOT_GIVEN and keeps the transport properties clean.


158-171: Preserve task cancellation in the retry loop.

except Exception will swallow asyncio.CancelledError on Python 3.9, preventing cancellation and causing unwanted retries.

🐛 Proposed fix
-                except Exception as e:
+                except asyncio.CancelledError:
+                    raise
+                except Exception as e:

Likely an incorrect or invalid review comment.

livekit-plugins/livekit-plugins-lemonslice/livekit/plugins/lemonslice/avatar.py (2)

100-106: Media output wiring looks good.

Clear, explicit attachment of the avatar audio stream to the participant identity.


3-5: The code is already Python 3.9 compatible. The file uses from __future__ import annotations (line 1), which defers all type annotations to strings per PEP 563. This means the PEP 604 union syntax (aiohttp.ClientSession | None) at line 48 is safe on Python 3.9 and does not need to be changed to Optional.

Likely an incorrect or invalid review comment.

examples/avatar_agents/lemonslice/agent_worker.py (1)

18-45: Example flow is clear and fails fast on missing config.

The session setup and avatar start sequence is straightforward and readable for users.

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

@tinalenguyen tinalenguyen merged commit 38ed7b3 into livekit:main Jan 20, 2026
10 checks passed
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.

4 participants