Conversation
📝 WalkthroughWalkthroughAdds a new LemonSlice avatar plugin and examples, including a REST client, LiveKit RTC manager, avatar publisher, tests, docs, and workspace/config updates; also adds EventManager.has_subscribers and a sample environment variable for LEMONSLICE_API_KEY. Changes
Sequence Diagram(s)sequenceDiagram
actor Agent
participant Publisher as LemonSliceAvatarPublisher
participant Client as LemonSliceClient
participant RTCMgr as LemonSliceRTCManager
participant LiveKit
participant LemonSlice as LemonSlice API
Agent->>Publisher: attach_agent()
Publisher->>Publisher: _subscribe_to_audio_events()
Agent->>Publisher: start()
Publisher->>RTCMgr: connect()
RTCMgr->>LiveKit: Room.connect()
LiveKit-->>RTCMgr: connected
RTCMgr->>RTCMgr: _generate_token()
RTCMgr-->>Publisher: Credentials(livekit_url, token)
Publisher->>Client: create_session(livekit_url, token)
Client->>LemonSlice: POST /sessions
LemonSlice-->>Client: 201 + session_id
Client-->>Publisher: session_id
Publisher-->>Agent: ready for streaming
sequenceDiagram
participant Agent
participant EventMgr as EventManager
participant Publisher as LemonSliceAvatarPublisher
participant RTCMgr as LemonSliceRTCManager
participant AudioTrack as AudioTrack
participant VideoTrack as VideoTrack
Agent->>EventMgr: emit(TTSAudioEvent)
EventMgr->>Publisher: handle TTS audio
Publisher->>Publisher: _on_audio_frame(pcm)
Publisher->>RTCMgr: send_audio(pcm)
RTCMgr->>AudioTrack: write PCM bytes
Agent->>EventMgr: emit(RealtimeAudioOutputEvent)
EventMgr->>Publisher: handle realtime audio
Publisher->>RTCMgr: send_audio(pcm)
RTCMgr->>AudioTrack: write PCM bytes
VideoTrack->>Publisher: new frame
Publisher->>RTCMgr: forward video frame
RTCMgr->>VideoTrack: publish frame to LiveKit
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (4)
tests/test_events.py (1)
272-280: Consider adding aValueErrorpath test for completeness.The happy path is well-covered, but
has_subscribersalso raisesValueErrorwhen the event class has notypeattribute. A small companion test would close that branch:async def test_has_subscribers_no_type_raises(self): manager = EventManager() class NoTypeEvent: pass with pytest.raises(ValueError): manager.has_subscribers(NoTypeEvent)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/test_events.py` around lines 272 - 280, Add a companion test that exercises the ValueError branch of EventManager.has_subscribers: create an EventManager, define a local class NoTypeEvent with no type attribute, and assert that calling manager.has_subscribers(NoTypeEvent) raises ValueError using pytest.raises; place this as a new async test function (e.g., test_has_subscribers_no_type_raises) alongside the existing test_has_subscribers.plugins/lemonslice/vision_agents/plugins/lemonslice/lemonslice_avatar_publisher.py (1)
84-86: Type annotation for_agentshould beAny | None.The initial value is
None, so the type should reflect that. Per guidelines: use modern syntax for unions.✏️ Suggested fix
- self._agent: Any = None + self._agent: Any | None = NoneAs per coding guidelines: "Use type annotations everywhere. Use modern syntax:
X | Yunions."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/lemonslice/vision_agents/plugins/lemonslice/lemonslice_avatar_publisher.py` around lines 84 - 86, Update the type annotation for the instance attribute _agent to reflect it can be None: change its annotation from Any to Any | None (i.e., _agent: Any | None = None) so the declared type matches the initial value; locate the declaration of self._agent in the class (the line with "self._agent: Any = None") and replace the annotation accordingly, ensuring no other references break type checks.plugins/lemonslice/tests/test_lemonslice_plugin.py (1)
12-20: Add type annotation to the factory helper.Per coding guidelines, type annotations should be used everywhere. The
**overridesparameter and return type are untyped.✏️ Suggested fix
-def _make_publisher(**overrides) -> LemonSliceAvatarPublisher: - default_kwargs = { +def _make_publisher(**overrides: object) -> LemonSliceAvatarPublisher: + default_kwargs: dict[str, object] = {As per coding guidelines: "Use type annotations everywhere."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/lemonslice/tests/test_lemonslice_plugin.py` around lines 12 - 20, The helper _make_publisher lacks a type annotation for its variadic overrides; update its signature to accept a typed mapping (e.g., overrides: Dict[str, Any] or overrides: Mapping[str, Any]) and ensure typing.Any and typing.Dict/Mapping are imported; keep the existing return annotation LemonSliceAvatarPublisher on the function so callers and linters have full type information.plugins/lemonslice/vision_agents/plugins/lemonslice/lemonslice_rtc_manager.py (1)
201-219: Video frame path does an extra copy through PIL.The RGBA buffer goes through
Image.frombuffer→av.VideoFrame.from_image, which means a PIL decode + re-encode round-trip. For real-time video this is on the hot path. Not a blocker, but worth a TODO if frame rates become a bottleneck.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/lemonslice/vision_agents/plugins/lemonslice/lemonslice_rtc_manager.py` around lines 201 - 219, The video path in _consume_video is creating an unnecessary PIL round-trip (Image.frombuffer → av.VideoFrame.from_image); replace that with a zero-copy conversion from lk_frame.data into a numpy ndarray (reshape to (lk_frame.height, lk_frame.width, 4) with dtype=uint8 using np.frombuffer) and then call av.VideoFrame.from_ndarray(..., format='rgba') to create the VideoFrame directly; update references in _consume_video (replace Image.frombuffer and av.VideoFrame.from_image) and add a TODO noting the optimization for real-time performance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@agents-core/vision_agents/core/events/manager.py`:
- Around line 295-298: The code uses getattr to read event_class.type; replace
that with direct attribute access inside a try/except: try to read event_type =
event_class.type and if that raises AttributeError raise the same
ValueError(f"{event_class.__name__} has no 'type' attribute"); then return
bool(self._handlers.get(event_type)). Update the block around
event_type/event_class in the manager method that checks handlers (the snippet
using event_type = getattr(...)) to use this try/except pattern instead of
getattr/# noqa.
In `@examples/01_simple_agent_example/simple_agent_example.py`:
- Around line 33-37: Remove the dev-only LemonSlice integration from the shared
example: delete the LemonSliceAvatarPublisher entry in the processors list
(reference: LemonSliceAvatarPublisher and the processors parameter) and restore
the previously commented-out simple_response logic so the agent emits the
default response (reference: simple_response variable / assignment). Ensure the
example is self-contained and does not require LemonSlice API keys by reverting
any hardcoded agent_id or plugin-specific code added to the example.
In
`@plugins/lemonslice/vision_agents/plugins/lemonslice/lemonslice_avatar_publisher.py`:
- Around line 104-111: The close() method can leak the HTTP client if
self._rtc_manager.close() raises; update LemonSliceAvatarPublisher.close to
mirror the pattern used in LemonSliceRTCManager.close by wrapping the await
self._rtc_manager.close() call in a try/finally (or try/except/finally) so that
await self._client.close() always runs even on exceptions, ensure
self._video_track.stop() is invoked before awaiting closes, and still set
self._connected = False and log the shutdown in the finally block so resources
are always cleaned up.
- Around line 128-133: The _connect method may leave the RTC room connected if
self._client.create_session fails; update _connect to catch
LemonSliceSessionError (and optionally other exceptions) raised by
self._client.create_session(credentials.url, credentials.token), call the RTC
cleanup method on self._rtc_manager (e.g., await self._rtc_manager.disconnect()
or equivalent) to release the room handle, ensure self._connected remains False,
and then re-raise the exception so callers see the failure; reference the
_connect method, self._rtc_manager.connect(), self._client.create_session(),
self._rtc_manager.disconnect(), and the _connected flag when making the change.
In `@plugins/lemonslice/vision_agents/plugins/lemonslice/lemonslice_client.py`:
- Around line 93-110: The code calls response.json() after a 201 but doesn't
handle JSON parse errors, so wrap the response.json() call in a try/except
catching JSONDecodeError (or ValueError for compatibility) and raise a
LemonSliceSessionError with a clear message and the original exception info;
update the method that posts to "/sessions" (the block using
self._http_client.post("/sessions", json=payload) and assigning self._session_id
from data = response.json()) to perform the JSON parsing inside the try/except,
and if parsing fails raise LemonSliceSessionError("Failed to parse session
creation response", status_code=response.status_code) (including the original
exception details).
In
`@plugins/lemonslice/vision_agents/plugins/lemonslice/lemonslice_rtc_manager.py`:
- Around line 118-133: The current handlers on_participant_disconnected and
on_disconnected both schedule self._on_disconnect(), causing duplicate tasks;
fix by guarding so only one schedules the callback: in
on_participant_disconnected, set self._connected = False and call
self._create_task(self._room.disconnect()) but do NOT call
self._create_task(self._on_disconnect()) (remove that line), and in
on_disconnected keep the single self._create_task(self._on_disconnect()) path;
alternatively, if you prefer the inverse, only schedule self._on_disconnect() in
on_participant_disconnected and call self._room.disconnect() conditionally
(e.g., if self._connected) so that self._on_disconnect() is invoked exactly
once. Ensure references to on_participant_disconnected, on_disconnected,
self._on_disconnect, self._room.disconnect, and self._connected are updated
accordingly.
---
Nitpick comments:
In `@plugins/lemonslice/tests/test_lemonslice_plugin.py`:
- Around line 12-20: The helper _make_publisher lacks a type annotation for its
variadic overrides; update its signature to accept a typed mapping (e.g.,
overrides: Dict[str, Any] or overrides: Mapping[str, Any]) and ensure typing.Any
and typing.Dict/Mapping are imported; keep the existing return annotation
LemonSliceAvatarPublisher on the function so callers and linters have full type
information.
In
`@plugins/lemonslice/vision_agents/plugins/lemonslice/lemonslice_avatar_publisher.py`:
- Around line 84-86: Update the type annotation for the instance attribute
_agent to reflect it can be None: change its annotation from Any to Any | None
(i.e., _agent: Any | None = None) so the declared type matches the initial
value; locate the declaration of self._agent in the class (the line with
"self._agent: Any = None") and replace the annotation accordingly, ensuring no
other references break type checks.
In
`@plugins/lemonslice/vision_agents/plugins/lemonslice/lemonslice_rtc_manager.py`:
- Around line 201-219: The video path in _consume_video is creating an
unnecessary PIL round-trip (Image.frombuffer → av.VideoFrame.from_image);
replace that with a zero-copy conversion from lk_frame.data into a numpy ndarray
(reshape to (lk_frame.height, lk_frame.width, 4) with dtype=uint8 using
np.frombuffer) and then call av.VideoFrame.from_ndarray(..., format='rgba') to
create the VideoFrame directly; update references in _consume_video (replace
Image.frombuffer and av.VideoFrame.from_image) and add a TODO noting the
optimization for real-time performance.
In `@tests/test_events.py`:
- Around line 272-280: Add a companion test that exercises the ValueError branch
of EventManager.has_subscribers: create an EventManager, define a local class
NoTypeEvent with no type attribute, and assert that calling
manager.has_subscribers(NoTypeEvent) raises ValueError using pytest.raises;
place this as a new async test function (e.g.,
test_has_subscribers_no_type_raises) alongside the existing
test_has_subscribers.
| event_type = getattr(event_class, "type", None) # noqa: B009 | ||
| if not event_type: | ||
| raise ValueError(f"{event_class.__name__} has no 'type' attribute") | ||
| return bool(self._handlers.get(event_type)) |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
getattr violates the project's explicit coding guideline — use direct attribute access instead.
The # noqa: B009 silences ruff/bugbear, but the project rule says "Avoid getattr, hasattr, delattr, setattr; prefer normal attribute access". Use a try/except AttributeError block instead:
♻️ Proposed fix
def has_subscribers(self, event_class: type) -> bool:
"""Check whether any handler is registered for the given event class."""
- event_type = getattr(event_class, "type", None) # noqa: B009
- if not event_type:
+ try:
+ event_type = event_class.type
+ except AttributeError:
raise ValueError(f"{event_class.__name__} has no 'type' attribute")
return bool(self._handlers.get(event_type))As per coding guidelines, "Avoid getattr, hasattr, delattr, setattr; prefer normal attribute access."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@agents-core/vision_agents/core/events/manager.py` around lines 295 - 298, The
code uses getattr to read event_class.type; replace that with direct attribute
access inside a try/except: try to read event_type = event_class.type and if
that raises AttributeError raise the same ValueError(f"{event_class.__name__}
has no 'type' attribute"); then return bool(self._handlers.get(event_type)).
Update the block around event_type/event_class in the manager method that checks
handlers (the snippet using event_type = getattr(...)) to use this try/except
pattern instead of getattr/# noqa.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
| async def close(self) -> None: | ||
| self._video_track.stop() | ||
|
|
||
| await self._rtc_manager.close() | ||
| await self._client.close() | ||
|
|
||
| self._connected = False | ||
| logger.debug("LemonSlice avatar publisher closed") |
There was a problem hiding this comment.
close() can leak the HTTP client if _rtc_manager.close() raises.
If _rtc_manager.close() throws, _client.close() is never called and the httpx.AsyncClient leaks. The sibling LemonSliceRTCManager.close() (lines 184-199 in lemonslice_rtc_manager.py) already uses try/finally for exactly this reason.
🛡️ Proposed fix
async def close(self) -> None:
- self._video_track.stop()
-
- await self._rtc_manager.close()
- await self._client.close()
-
- self._connected = False
- logger.debug("LemonSlice avatar publisher closed")
+ try:
+ self._video_track.stop()
+ await self._rtc_manager.close()
+ finally:
+ await self._client.close()
+ self._connected = False
+ logger.debug("LemonSlice avatar publisher closed")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@plugins/lemonslice/vision_agents/plugins/lemonslice/lemonslice_avatar_publisher.py`
around lines 104 - 111, The close() method can leak the HTTP client if
self._rtc_manager.close() raises; update LemonSliceAvatarPublisher.close to
mirror the pattern used in LemonSliceRTCManager.close by wrapping the await
self._rtc_manager.close() call in a try/finally (or try/except/finally) so that
await self._client.close() always runs even on exceptions, ensure
self._video_track.stop() is invoked before awaiting closes, and still set
self._connected = False and log the shutdown in the finally block so resources
are always cleaned up.
| async def _connect(self) -> None: | ||
| credentials = await self._rtc_manager.connect() | ||
| await self._client.create_session(credentials.url, credentials.token) | ||
|
|
||
| self._connected = True | ||
| logger.info("LemonSlice avatar connection established") |
There was a problem hiding this comment.
_connect doesn't clean up the RTC connection if create_session fails.
If create_session raises LemonSliceSessionError, the LiveKit room stays connected with no way to retry cleanly (since _connected remains False and the room handle is held by _rtc_manager).
🛡️ Proposed fix
async def _connect(self) -> None:
credentials = await self._rtc_manager.connect()
- await self._client.create_session(credentials.url, credentials.token)
+ try:
+ await self._client.create_session(credentials.url, credentials.token)
+ except Exception:
+ await self._rtc_manager.close()
+ raise
self._connected = True
logger.info("LemonSlice avatar connection established")📝 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.
| async def _connect(self) -> None: | |
| credentials = await self._rtc_manager.connect() | |
| await self._client.create_session(credentials.url, credentials.token) | |
| self._connected = True | |
| logger.info("LemonSlice avatar connection established") | |
| async def _connect(self) -> None: | |
| credentials = await self._rtc_manager.connect() | |
| try: | |
| await self._client.create_session(credentials.url, credentials.token) | |
| except LemonSliceSessionError: | |
| await self._rtc_manager.close() | |
| raise | |
| self._connected = True | |
| logger.info("LemonSlice avatar connection established") |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@plugins/lemonslice/vision_agents/plugins/lemonslice/lemonslice_avatar_publisher.py`
around lines 128 - 133, The _connect method may leave the RTC room connected if
self._client.create_session fails; update _connect to catch
LemonSliceSessionError (and optionally other exceptions) raised by
self._client.create_session(credentials.url, credentials.token), call the RTC
cleanup method on self._rtc_manager (e.g., await self._rtc_manager.disconnect()
or equivalent) to release the room handle, ensure self._connected remains False,
and then re-raise the exception so callers see the failure; reference the
_connect method, self._rtc_manager.connect(), self._client.create_session(),
self._rtc_manager.disconnect(), and the _connected flag when making the change.
| response = await self._http_client.post("/sessions", json=payload) | ||
|
|
||
| if response.status_code != 201: | ||
| raise LemonSliceSessionError( | ||
| f"Failed to create session: {response.status_code} - {response.text}", | ||
| status_code=response.status_code, | ||
| ) | ||
|
|
||
| data = response.json() | ||
| self._session_id = data.get("session_id") | ||
|
|
||
| if not self._session_id: | ||
| raise LemonSliceSessionError( | ||
| f"Session creation returned no session_id: {data}" | ||
| ) | ||
|
|
||
| logger.info(f"LemonSlice session created: {self._session_id}") | ||
| return self._session_id |
There was a problem hiding this comment.
Unhandled JSON decode failure on a 201 response.
If the server returns 201 with a malformed body, response.json() raises a raw JSONDecodeError that leaks implementation details. Wrap it so callers see a consistent LemonSliceSessionError.
🛡️ Proposed fix
- data = response.json()
- self._session_id = data.get("session_id")
+ try:
+ data = response.json()
+ except ValueError as exc:
+ raise LemonSliceSessionError(
+ f"Invalid JSON in session response: {response.text}"
+ ) from exc
+ self._session_id = data.get("session_id")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@plugins/lemonslice/vision_agents/plugins/lemonslice/lemonslice_client.py`
around lines 93 - 110, The code calls response.json() after a 201 but doesn't
handle JSON parse errors, so wrap the response.json() call in a try/except
catching JSONDecodeError (or ValueError for compatibility) and raise a
LemonSliceSessionError with a clear message and the original exception info;
update the method that posts to "/sessions" (the block using
self._http_client.post("/sessions", json=payload) and assigning self._session_id
from data = response.json()) to perform the JSON parsing inside the try/except,
and if parsing fails raise LemonSliceSessionError("Failed to parse session
creation response", status_code=response.status_code) (including the original
exception details).
| @room.on("participant_disconnected") | ||
| def on_participant_disconnected(participant: rtc.RemoteParticipant) -> None: | ||
| logger.info( | ||
| f"Participant disconnected: {participant.identity}; " | ||
| f"reason: {participant.disconnect_reason}" | ||
| ) | ||
| self._connected = False | ||
| self._create_task(self._on_disconnect()) | ||
| if self._room is not None: | ||
| self._create_task(self._room.disconnect()) | ||
|
|
||
| @room.on("disconnected") | ||
| def on_disconnected(reason: str) -> None: | ||
| logger.info(f"Room disconnected; reason: {reason}") | ||
| self._connected = False | ||
| self._create_task(self._on_disconnect()) |
There was a problem hiding this comment.
Double invocation of _on_disconnect callback.
When the avatar disconnects, on_participant_disconnected fires — calling self._on_disconnect() and then self._room.disconnect(). The latter triggers the "disconnected" event, which calls self._on_disconnect() a second time. The callback is idempotent (just sets a bool and logs), so it won't break, but it spawns a redundant task and produces duplicate log noise.
Guard one of the paths:
✏️ Proposed fix
`@room.on`("disconnected")
def on_disconnected(reason: str) -> None:
logger.info(f"Room disconnected; reason: {reason}")
- self._connected = False
- self._create_task(self._on_disconnect())
+ if self._connected:
+ self._connected = False
+ self._create_task(self._on_disconnect())🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@plugins/lemonslice/vision_agents/plugins/lemonslice/lemonslice_rtc_manager.py`
around lines 118 - 133, The current handlers on_participant_disconnected and
on_disconnected both schedule self._on_disconnect(), causing duplicate tasks;
fix by guarding so only one schedules the callback: in
on_participant_disconnected, set self._connected = False and call
self._create_task(self._room.disconnect()) but do NOT call
self._create_task(self._on_disconnect()) (remove that line), and in
on_disconnected keep the single self._create_task(self._on_disconnect()) path;
alternatively, if you prefer the inverse, only schedule self._on_disconnect() in
on_participant_disconnected and call self._room.disconnect() conditionally
(e.g., if self._connected) so that self._on_disconnect() is invoked exactly
once. Ensure references to on_participant_disconnected, on_disconnected,
self._on_disconnect, self._room.disconnect, and self._connected are updated
accordingly.
| event_type = getattr(event_class, "type", None) # noqa: B009 | ||
| if not event_type: | ||
| raise ValueError(f"{event_class.__name__} has no 'type' attribute") | ||
| return bool(self._handlers.get(event_type)) |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
plugins/lemonslice/example/lemonslice_avatar_example.py (1)
26-26: Missing type annotation on**kwargsand missing Google-style docstrings.Both
create_agentandjoin_callaccept**kwargswithout a type annotation, and neither has a docstring. Per the coding guidelines, type annotations are required everywhere and Google-style docstrings should be present.♻️ Proposed fix
+from typing import Any + -async def create_agent(**kwargs) -> Agent: +async def create_agent(**kwargs: Any) -> Agent: + """Create and return a configured LemonSlice avatar agent. + + Args: + **kwargs: Forwarded from AgentLauncher; unused directly. + + Returns: + A fully configured Agent instance. + """ return Agent( ... ) -async def join_call(agent: Agent, call_type: str, call_id: str, **kwargs) -> None: +async def join_call(agent: Agent, call_type: str, call_id: str, **kwargs: Any) -> None: + """Join a call with the given agent and block until it finishes. + + Args: + agent: The Agent to use. + call_type: Stream call type (e.g. "default"). + call_id: Unique call identifier. + **kwargs: Forwarded from AgentLauncher; unused directly. + """ call = await agent.create_call(call_type, call_id)Also applies to: 45-45
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/lemonslice/example/lemonslice_avatar_example.py` at line 26, The functions create_agent and join_call accept untyped **kwargs and lack Google-style docstrings; update both function signatures to annotate **kwargs (e.g., **kwargs: dict[str, Any] or **kwargs: Any and import Any from typing) and add Google-style docstrings for each function that describe Args (including **kwargs), Returns (Agent or appropriate type), and Raises (if applicable), ensuring the docstrings appear immediately below the def lines for create_agent and join_call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@plugins/lemonslice/example/lemonslice_avatar_example.py`:
- Around line 38-40: The example currently passes a hardcoded string to
LemonSliceAvatarPublisher via the agent_id parameter which will be sent to the
LemonSlice API; update the example in lemonslice_avatar_example.py to avoid a
literal "your-agent-id" by either passing None, using a clearly documented
placeholder (e.g. agent_id=None or agent_id=os.getenv("LEMONSLICE_AGENT_ID")) or
adding an inline comment to show the developer must substitute a real ID; change
the call to LemonSliceAvatarPublisher(...) to use one of these safer patterns
and ensure the example comment explains that the agent_id must be replaced or
provided via environment/config.
---
Nitpick comments:
In `@plugins/lemonslice/example/lemonslice_avatar_example.py`:
- Line 26: The functions create_agent and join_call accept untyped **kwargs and
lack Google-style docstrings; update both function signatures to annotate
**kwargs (e.g., **kwargs: dict[str, Any] or **kwargs: Any and import Any from
typing) and add Google-style docstrings for each function that describe Args
(including **kwargs), Returns (Agent or appropriate type), and Raises (if
applicable), ensuring the docstrings appear immediately below the def lines for
create_agent and join_call.
|
@dangusev perhaps for another issue/PR but there is a bug in our pipeline if you were to try this with a VLM model instead of a regular LLM. The VLM is picking up and describing the video track from the avatar, not the user |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
This is the final PR Bugbot will review for you during this billing cycle
Your free Bugbot reviews will reset on March 7
Details
Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| lemonslice.LemonSliceAvatarPublisher( | ||
| agent_id="agent_d966c62a73645e66", width=800, height=600 | ||
| ), | ||
| ], # processors can fetch extra data, check images/audio data or transform video |
There was a problem hiding this comment.
Simple agent example accidentally modified with lemonslice testing code
High Severity
The primary getting-started example (01_simple_agent_example) now imports lemonslice and adds a LemonSliceAvatarPublisher with a hardcoded development agent_id="agent_d966c62a73645e66". It also comments out the simple_response call. This example is referenced in README.md and DEVELOPMENT.md as the entry point for new users, and its own README doesn't mention LemonSlice as a prerequisite. These changes break the basic example for anyone without LemonSlice credentials. A dedicated lemonslice example already exists at plugins/lemonslice/example/.


Summary by CodeRabbit
New Features
Documentation
Configuration
Note
Medium Risk
Adds a new realtime audio/video streaming integration (LiveKit + external LemonSlice API) that can affect call media paths and introduces new async lifecycle/network failure modes; existing core changes are small and covered by tests.
Overview
Introduces a new
vision-agents-plugins-lemonslicepackage that creates LemonSlice sessions and uses LiveKit to forward agent audio and receive synchronized avatar video/audio, exposed viaLemonSliceAvatarPublisher(anAudioPublisher/VideoPublisherprocessor). Adds docs, a runnable example, and unit tests for plugin initialization/validation and event subscription wiring.Extends
EventManagerwithhas_subscribers(event_class)and adds coverage for it. Wires the plugin into workspace/optional deps (vision-agents[lemonslice]andall-plugins), updates.env.example, and refreshes lockfile deps (addslivekit/livekit-api).Written by Cursor Bugbot for commit 824c509. This will update automatically on new commits. Configure here.