Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a new Anam Avatar plugin package and workspace entry, implements AnamAvatarPublisher to stream agent audio/video to Anam, includes docs, an example CLI agent, tests, and exposes the plugin via optional extras. (50 words) Changes
Sequence Diagram(s)sequenceDiagram
participant Agent as Agent
participant Publisher as AnamAvatarPublisher
participant Anam as AnamClient
participant Tracks as Audio/Video Tracks
Agent->>Publisher: attach_agent()
Publisher->>Publisher: subscribe to TTS & realtime audio events
Publisher->>Anam: connect() / create session
Anam-->>Publisher: connection_established / session_ready
Publisher->>Tracks: start receiver tasks
Agent->>Publisher: emit TTS/realtime audio chunk
Publisher->>Publisher: resample to 24kHz mono (serialize)
Publisher->>Anam: send_audio_input_chunk()
Anam-->>Tracks: video_frames() / audio_frames()
Tracks->>Agent: published frames delivered
Agent->>Publisher: other participant turn starts
Publisher->>Anam: interrupt()
Agent->>Publisher: finish()
Publisher->>Anam: close()
Publisher->>Tracks: stop & cancel tasks
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 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 docstrings
🧪 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: 3
🧹 Nitpick comments (5)
plugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.py (3)
218-221: Background tasks lack exception handling callbacks.If
_audio_receiveror_video_receiverraises an unhandled exception, it will be silently lost. Consider adding a done callback to log exceptions, similar to patterns used elsewhere in the codebase.♻️ Suggested improvement
+ def _task_done_callback(self, task: asyncio.Task) -> None: + if not task.cancelled() and task.exception() is not None: + logger.exception( + "Background task failed", exc_info=task.exception() + ) + async def _connect(self) -> None: # ... existing code ... if self._audio_receiver_task is None: self._audio_receiver_task = asyncio.create_task(self._audio_receiver()) + self._audio_receiver_task.add_done_callback(self._task_done_callback) if self._video_receiver_task is None: self._video_receiver_task = asyncio.create_task(self._video_receiver()) + self._video_receiver_task.add_done_callback(self._task_done_callback)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.py` around lines 218 - 221, The background tasks created for _audio_receiver and _video_receiver are started without done callbacks so their exceptions can be lost; update the task creation in the block that assigns self._audio_receiver_task and self._video_receiver_task to attach a done callback (e.g., a helper like _log_task_exceptions or an inline callback) which checks task.exception() and logs any exception via the existing logger, ensuring both asyncio.create_task(self._audio_receiver()) and asyncio.create_task(self._video_receiver()) register that callback so errors aren’t silently dropped.
248-257: Clearing_connectedevent infinallymay cause issues on reconnection.If the connection succeeds but a subsequent operation fails, clearing the event in
finallymeans a reconnection attempt would need to wait for theCONNECTION_ESTABLISHEDevent again, which may never fire if already connected.Consider clearing only on timeout or explicit disconnect.
♻️ Suggested fix
async def _wait_connected(self) -> None: try: await asyncio.wait_for( self._connected.wait(), timeout=self._connect_timeout ) except asyncio.TimeoutError: logger.error("Timed out waiting for Anam connection to be established") + self._connected.clear() raise - finally: - self._connected.clear()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.py` around lines 248 - 257, The current _wait_connected method clears the self._connected event in a finally block which can wipe the connected flag even when connection succeeded; change it to only clear the event on timeout or explicit disconnect: remove the finally self._connected.clear(), move the clear into the asyncio.TimeoutError except block (call self._connected.clear() there before re-raising), and ensure any disconnect logic (e.g., an explicit disconnect handler or method like _handle_disconnect/_on_disconnect) is responsible for clearing self._connected when the connection actually drops so reconnection waits behave correctly.
259-268: Same concern for_session_readyevent clearing.The
finallyblock clears_session_readyunconditionally, which could cause the same reconnection issue as_wait_connected.♻️ Suggested fix
async def _wait_session_ready(self) -> None: try: await asyncio.wait_for( self._session_ready.wait(), timeout=self._session_ready_timeout ) except asyncio.TimeoutError: logger.error("Timed out waiting for Anam session to get ready") + self._session_ready.clear() raise - finally: - self._session_ready.clear()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.py` around lines 259 - 268, The finally block in _wait_session_ready unconditionally clears the asyncio.Event _session_ready causing spurious reconnection behavior; change it so the event is only cleared after a successful wait: remove the unconditional finally clear and instead call self._session_ready.clear() immediately after the await in _wait_session_ready (inside the try after await completes), leaving the except asyncio.TimeoutError handler to log and re-raise without clearing the event; reference _wait_session_ready and _session_ready to locate the change.plugins/anam/README.md (1)
1-3: Incomplete README documentation.The README contains only a placeholder TODO. Consider documenting the plugin's purpose, configuration options (e.g.,
ANAM_API_KEY,ANAM_AVATAR_IDenvironment variables), and a basic usage example before merging.Would you like me to draft documentation content based on the implementation in
anam_avatar_publisher.py?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/anam/README.md` around lines 1 - 3, The README currently has a TODO; update plugins/anam/README.md to document the plugin purpose, required configuration and usage: explain what the Anam Avatar Plugin does, list required environment variables (e.g., ANAM_API_KEY, ANAM_AVATAR_ID) and any optional settings, and provide a minimal usage example showing how to invoke the functionality exposed by anam_avatar_publisher.py (reference the main function(s) or CLI entry in anam_avatar_publisher.py and demonstrate expected inputs/outputs and error handling). Keep it concise, include mention of authentication, where to find logs or returned avatar artifacts, and any runtime prerequisites.plugins/anam/example/anam_avatar_example.py (1)
2-2: Use modern type annotation syntax.Per coding guidelines, prefer
dict[str, T]overDict. Also,Anyis discouraged in type annotations.♻️ Suggested fix
-from typing import Any, Dict +from typing import AnyAnd update line 22:
- async def get_weather(location: str) -> Dict[str, Any]: + async def get_weather(location: str) -> dict[str, object]:🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/anam/example/anam_avatar_example.py` at line 2, Replace the legacy typing imports and Any usage: remove "from typing import Any, Dict" and use the built-in generic alias (e.g., dict[str, ...]) instead, and replace the vague Any with a concrete type (or object if truly generic); update the type annotation used at line 22 to use dict[str, X] (where X is a specific type like str, int, or object) instead of Dict or Any so the code follows modern annotation syntax and avoids Any.
🤖 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/anam/example/anam_avatar_example.py`:
- Line 47: Fix the typo in the comment string "Have the agent join the
call/roomz" by replacing "roomz" with "room" so the comment reads "Have the
agent join the call/room"; update the comment where it appears (search for the
exact text "Have the agent join the call/roomz") to ensure correct spelling.
In `@plugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.py`:
- Around line 137-138: The except block currently catches Exception broadly when
calling self._exit_stack.aclose() in the close() path of AnamAvatarPublisher,
which violates the guideline to catch specific exceptions; inspect what context
managers are registered in self._exit_stack and identify the concrete exceptions
they may raise (for example: asyncio.CancelledError, OSError, RuntimeError, or
any SDK-specific exceptions from the Anam SDK) and replace the bare except
Exception with explicit except clauses for those expected exceptions (e.g.,
except asyncio.CancelledError: and except OSError: and except AnamSdkError:),
preserving the logger.warning("Failed to close Anam avatar publisher",
exc_info=True) behavior for each specific except branch and leaving other
unexpected exceptions to propagate.
- Around line 230-239: The _on_connection_closed callback currently calls await
self.close(), which can re-enter because close() triggers _exit_stack.aclose()
and fires the CONNECTION_CLOSED event again; remove the direct call to close()
from _on_connection_closed and instead update internal state (e.g., set a
_connected or _closed boolean) and log/emit any non-reentrant notifications so
the application-level lifecycle can perform shutdown; also make close()
idempotent by adding a _closed guard (check/set self._closed at start of
close()) so concurrent/duplicate calls are safe and no recursive await occurs
from _on_connection_closed or event handlers.
---
Nitpick comments:
In `@plugins/anam/example/anam_avatar_example.py`:
- Line 2: Replace the legacy typing imports and Any usage: remove "from typing
import Any, Dict" and use the built-in generic alias (e.g., dict[str, ...])
instead, and replace the vague Any with a concrete type (or object if truly
generic); update the type annotation used at line 22 to use dict[str, X] (where
X is a specific type like str, int, or object) instead of Dict or Any so the
code follows modern annotation syntax and avoids Any.
In `@plugins/anam/README.md`:
- Around line 1-3: The README currently has a TODO; update
plugins/anam/README.md to document the plugin purpose, required configuration
and usage: explain what the Anam Avatar Plugin does, list required environment
variables (e.g., ANAM_API_KEY, ANAM_AVATAR_ID) and any optional settings, and
provide a minimal usage example showing how to invoke the functionality exposed
by anam_avatar_publisher.py (reference the main function(s) or CLI entry in
anam_avatar_publisher.py and demonstrate expected inputs/outputs and error
handling). Keep it concise, include mention of authentication, where to find
logs or returned avatar artifacts, and any runtime prerequisites.
In `@plugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.py`:
- Around line 218-221: The background tasks created for _audio_receiver and
_video_receiver are started without done callbacks so their exceptions can be
lost; update the task creation in the block that assigns
self._audio_receiver_task and self._video_receiver_task to attach a done
callback (e.g., a helper like _log_task_exceptions or an inline callback) which
checks task.exception() and logs any exception via the existing logger, ensuring
both asyncio.create_task(self._audio_receiver()) and
asyncio.create_task(self._video_receiver()) register that callback so errors
aren’t silently dropped.
- Around line 248-257: The current _wait_connected method clears the
self._connected event in a finally block which can wipe the connected flag even
when connection succeeded; change it to only clear the event on timeout or
explicit disconnect: remove the finally self._connected.clear(), move the clear
into the asyncio.TimeoutError except block (call self._connected.clear() there
before re-raising), and ensure any disconnect logic (e.g., an explicit
disconnect handler or method like _handle_disconnect/_on_disconnect) is
responsible for clearing self._connected when the connection actually drops so
reconnection waits behave correctly.
- Around line 259-268: The finally block in _wait_session_ready unconditionally
clears the asyncio.Event _session_ready causing spurious reconnection behavior;
change it so the event is only cleared after a successful wait: remove the
unconditional finally clear and instead call self._session_ready.clear()
immediately after the await in _wait_session_ready (inside the try after await
completes), leaving the except asyncio.TimeoutError handler to log and re-raise
without clearing the event; reference _wait_session_ready and _session_ready to
locate the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f6da3e73-d7dc-4c82-9d24-97cc8dd50423
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (9)
agents-core/pyproject.tomlplugins/anam/README.mdplugins/anam/example/anam_avatar_example.pyplugins/anam/py.typedplugins/anam/pyproject.tomlplugins/anam/tests/test_anam_plugin.pyplugins/anam/vision_agents/plugins/anam/__init__.pyplugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.pypyproject.toml
plugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.py
Outdated
Show resolved
Hide resolved
62bd38b to
ee84704
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
plugins/anam/tests/test_anam_plugin.py (1)
14-19: Consider adding type annotation to**overrides.For completeness with the coding guidelines requiring type annotations everywhere:
♻️ Proposed refinement
-def _make_publisher(**overrides) -> AnamAvatarPublisher: +def _make_publisher(**overrides: str | int | None) -> AnamAvatarPublisher:🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/anam/tests/test_anam_plugin.py` around lines 14 - 19, Add a type annotation for the variadic kwargs in _make_publisher: change the signature to annotate **overrides as Dict[str, Any] (or dict[str, Any] for py3.9+) and import Any and Dict from typing if not already present; keep the rest of the function and the call to AnamAvatarPublisher the same so the merged kwargs remain type-checked.plugins/anam/example/anam_avatar_example.py (2)
18-25: Add short Google-style docstrings to public functions.The functions
setup_llm,create_agent, andjoin_callare public entry points lacking docstrings. Add concise Google-style docstrings with Args and Returns sections where applicable.Proposed diff
def setup_llm(model: str = "gemini-3.1-flash-lite-preview") -> gemini.LLM: + """Create an LLM client and register example weather tool. + + Args: + model: Gemini model name. + + Returns: + Configured Gemini LLM instance. + """ llm = gemini.LLM(model) @@ async def create_agent(**kwargs) -> Agent: + """Create and configure an avatar-enabled agent. + + Returns: + Configured agent instance. + """ llm = setup_llm() @@ async def join_call(agent: Agent, call_type: str, call_id: str, **kwargs) -> None: + """Join a call and run a short starter interaction. + + Args: + agent: Agent instance. + call_type: Call type. + call_id: Call identifier. + """ call = await agent.create_call(call_type, call_id)Also applies to lines 28-41 and 44-53.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/anam/example/anam_avatar_example.py` around lines 18 - 25, Add concise Google-style docstrings to the public functions setup_llm, create_agent, and join_call: for each function (setup_llm, create_agent, join_call) add a one-line summary, an Args section listing parameters and types (e.g., model: str for setup_llm), and a Returns section describing the return type (e.g., gemini.LLM for setup_llm or Agent/None as appropriate). Keep docstrings short, placed immediately under each def, and use Google-style formatting with parameter types and brief descriptions.
28-28: Add type annotations to variadic kwargs.Both callback signatures accept
**kwargsbut lack type annotations. Per the coding guideline to "use type annotations everywhere with modern syntax," add**kwargs: objectto both functions:Proposed diff
-async def create_agent(**kwargs) -> Agent: +async def create_agent(**kwargs: object) -> 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: object) -> None:🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/anam/example/anam_avatar_example.py` at line 28, The functions using variadic keyword args (e.g., create_agent and the related callback functions in this file) currently declare **kwargs without type annotations; update their signatures to annotate the kwargs as **kwargs: object to comply with the project’s modern type-annotation guideline (replace occurrences of **kwargs with **kwargs: object in create_agent and the other callback function signatures).
🤖 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/anam/example/anam_avatar_example.py`:
- Around line 2-3: Remove the unused legacy typing import "Any" from the import
statement "from typing import Any, Dict" and update the type annotation that
currently uses "Dict" (the dict type at the top-level annotation on line where
Dict is used) to the modern built-in generic syntax "dict[str, object]" instead
of "Dict[str, Any]"; after this change you can drop the typing import entirely
(or only import names still needed) so the module uses Python's built-in
generics and no longer relies on Any.
In `@plugins/anam/tests/test_anam_plugin.py`:
- Around line 46-49: The test test_init_custom_resolution currently only asserts
the track type; update it to also verify the custom resolution by calling
_make_publisher(width=640, height=480), obtaining track via
publish_video_track(), asserting isinstance(track, QueuedVideoTrack) and then
asserting the track's reported dimensions match 640x480 (e.g., check attributes
or a settings method such as track.width and track.height or
track.get_settings()['width']/['height'] as appropriate) so the test confirms
the resolution was applied.
---
Nitpick comments:
In `@plugins/anam/example/anam_avatar_example.py`:
- Around line 18-25: Add concise Google-style docstrings to the public functions
setup_llm, create_agent, and join_call: for each function (setup_llm,
create_agent, join_call) add a one-line summary, an Args section listing
parameters and types (e.g., model: str for setup_llm), and a Returns section
describing the return type (e.g., gemini.LLM for setup_llm or Agent/None as
appropriate). Keep docstrings short, placed immediately under each def, and use
Google-style formatting with parameter types and brief descriptions.
- Line 28: The functions using variadic keyword args (e.g., create_agent and the
related callback functions in this file) currently declare **kwargs without type
annotations; update their signatures to annotate the kwargs as **kwargs: object
to comply with the project’s modern type-annotation guideline (replace
occurrences of **kwargs with **kwargs: object in create_agent and the other
callback function signatures).
In `@plugins/anam/tests/test_anam_plugin.py`:
- Around line 14-19: Add a type annotation for the variadic kwargs in
_make_publisher: change the signature to annotate **overrides as Dict[str, Any]
(or dict[str, Any] for py3.9+) and import Any and Dict from typing if not
already present; keep the rest of the function and the call to
AnamAvatarPublisher the same so the merged kwargs remain type-checked.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7532a06b-467f-483d-bb2f-11d57f9f123e
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (9)
agents-core/pyproject.tomlplugins/anam/README.mdplugins/anam/example/anam_avatar_example.pyplugins/anam/py.typedplugins/anam/pyproject.tomlplugins/anam/tests/test_anam_plugin.pyplugins/anam/vision_agents/plugins/anam/__init__.pyplugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.pypyproject.toml
✅ Files skipped from review due to trivial changes (4)
- agents-core/pyproject.toml
- plugins/anam/README.md
- pyproject.toml
- plugins/anam/pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (2)
- plugins/anam/vision_agents/plugins/anam/init.py
- plugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.py
Agent stops the published tracks automatically after it leaves the call. Stopping track before that will cause errors in logs
35dfcab to
954edce
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
plugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.py (1)
232-239:⚠️ Potential issue | 🟠 MajorClear stale publisher state when the connection closes.
This callback only logs, so
_real_session,_audio_input_stream, and the receiver task refs remain populated after an unexpected disconnect. Later audio/interrupt handlers will still target a dead session, and_connect()cannot establish a new one because it short-circuits once_real_sessionis set.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.py` around lines 232 - 239, In _on_connection_closed, clear the stale publisher state so future reconnects and handlers don't target a dead session: reset self._real_session to None, set self._audio_input_stream to None, and cancel/await and clear the receiver task reference (e.g., self._receiver_task) if it exists; do this inside the existing async _on_connection_closed method (before/after the log as appropriate) so that _connect() will not short-circuit on a leftover _real_session and audio/interrupt handlers won't use dead streams.
🤖 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/anam/vision_agents/plugins/anam/anam_avatar_publisher.py`:
- Around line 47-48: Validate that constructor width and height are even
integers in the QueuedVideoTrack __init__ (the parameters width and height
shown) because the placeholder frame is generated from raw constructor sizes and
odd dimensions produce an odd first frame; if either dimension is not an even
positive int, raise a ValueError with a clear message (e.g., "width must be a
positive even integer") and mirror the same validation for the other
constructor(s) referenced around lines 66-68 so all entry points that create
placeholder frames enforce even dimensions; keep add_frame normalization as-is
but ensure __init__ prevents invalid sizes up front.
- Around line 220-223: The current background tasks created for
_audio_receiver() and _video_receiver() can fail silently; replace the bare
asyncio.create_task calls so failures are observed—either use the standardized
helper _run_tool_in_background(self._audio_receiver(), ...) and
_run_tool_in_background(self._video_receiver(), ...) or attach an
add_done_callback to each created task that logs exceptions and runs cleanup
(e.g., task.add_done_callback(lambda t: _log_and_cleanup_bg_task(t,
"audio_receiver"))). Ensure you reference and update the members
_audio_receiver_task and _video_receiver_task so they store the returned wrapped
task from _run_tool_in_background or the task with the done-callback attached.
- Around line 126-142: The close() method must stop the QueuedVideoTrack so its
recv() no longer serves the last frame; locate the video track instance used by
this publisher (e.g. the QueuedVideoTrack stored on the class such as
self._video_track or similar) and call its stop() before or when
cancelling/awaiting video tasks (referencing QueuedVideoTrack.recv and
self._video_receiver_task). Add a safe nil-check (if track is not None) and call
track.stop() (catch/log exceptions if needed) so downstream consumers are not
left with a frozen avatar after close().
---
Duplicate comments:
In `@plugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.py`:
- Around line 232-239: In _on_connection_closed, clear the stale publisher state
so future reconnects and handlers don't target a dead session: reset
self._real_session to None, set self._audio_input_stream to None, and
cancel/await and clear the receiver task reference (e.g., self._receiver_task)
if it exists; do this inside the existing async _on_connection_closed method
(before/after the log as appropriate) so that _connect() will not short-circuit
on a leftover _real_session and audio/interrupt handlers won't use dead streams.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 30062fb1-bc6f-485f-853b-7c038bfe3b12
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (9)
agents-core/pyproject.tomlplugins/anam/README.mdplugins/anam/example/anam_avatar_example.pyplugins/anam/py.typedplugins/anam/pyproject.tomlplugins/anam/tests/test_anam_plugin.pyplugins/anam/vision_agents/plugins/anam/__init__.pyplugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.pypyproject.toml
✅ Files skipped from review due to trivial changes (6)
- plugins/anam/vision_agents/plugins/anam/init.py
- pyproject.toml
- agents-core/pyproject.toml
- plugins/anam/pyproject.toml
- plugins/anam/README.md
- plugins/anam/example/anam_avatar_example.py
🚧 Files skipped from review as they are similar to previous changes (1)
- plugins/anam/tests/test_anam_plugin.py
| width: int = 1920, | ||
| height: int = 1080, |
There was a problem hiding this comment.
Validate custom video dimensions in __init__.
QueuedVideoTrack only normalizes frames passed through add_frame(). Its placeholder frame is created from the raw constructor size, so an odd custom width or height can still emit an odd first frame before avatar video arrives.
🛠️ Suggested validation
avatar_id = avatar_id or os.getenv("ANAM_AVATAR_ID")
if not avatar_id:
raise ValueError("Anam avatar ID not provided")
+ if width <= 0 or height <= 0:
+ raise ValueError("width and height must be positive")
+ if width % 2 or height % 2:
+ raise ValueError("width and height must be even")As per coding guidelines, "Raise ValueError with a descriptive message for invalid constructor arguments. Prefer custom domain exceptions over generic ones".
Also applies to: 66-68
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@plugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.py` around
lines 47 - 48, Validate that constructor width and height are even integers in
the QueuedVideoTrack __init__ (the parameters width and height shown) because
the placeholder frame is generated from raw constructor sizes and odd dimensions
produce an odd first frame; if either dimension is not an even positive int,
raise a ValueError with a clear message (e.g., "width must be a positive even
integer") and mirror the same validation for the other constructor(s) referenced
around lines 66-68 so all entry points that create placeholder frames enforce
even dimensions; keep add_frame normalization as-is but ensure __init__ prevents
invalid sizes up front.
| async def close(self) -> None: | ||
| """ | ||
| Close the Anam avatar publisher, cancel audio & video processing tasks | ||
| and release resources. | ||
| """ | ||
| if self._audio_receiver_task is not None: | ||
| await cancel_and_wait(self._audio_receiver_task) | ||
| if self._video_receiver_task is not None: | ||
| await cancel_and_wait(self._video_receiver_task) | ||
|
|
||
| try: | ||
| await self._exit_stack.aclose() | ||
| await self._client.close() | ||
| except Exception: | ||
| logger.warning("Failed to close Anam avatar publisher", exc_info=True) | ||
| finally: | ||
| logger.debug("Anam avatar publisher closed") |
There was a problem hiding this comment.
Stop the video track during shutdown.
close() tears down the session, but QueuedVideoTrack.recv() keeps serving the last frame until stop() is called. Without that, downstream consumers can stay pinned on a frozen avatar after the publisher is closed.
🛠️ Suggested fix
async def close(self) -> None:
"""
Close the Anam avatar publisher, cancel audio & video processing tasks
and release resources.
"""
+ self._video_track.stop()
if self._audio_receiver_task is not None:
await cancel_and_wait(self._audio_receiver_task)
if self._video_receiver_task is not None:
await cancel_and_wait(self._video_receiver_task)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@plugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.py` around
lines 126 - 142, The close() method must stop the QueuedVideoTrack so its recv()
no longer serves the last frame; locate the video track instance used by this
publisher (e.g. the QueuedVideoTrack stored on the class such as
self._video_track or similar) and call its stop() before or when
cancelling/awaiting video tasks (referencing QueuedVideoTrack.recv and
self._video_receiver_task). Add a safe nil-check (if track is not None) and call
track.stop() (catch/log exceptions if needed) so downstream consumers are not
left with a frozen avatar after close().
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!
| if self._audio_receiver_task is None: | ||
| self._audio_receiver_task = asyncio.create_task(self._audio_receiver()) | ||
| if self._video_receiver_task is None: | ||
| self._video_receiver_task = asyncio.create_task(self._video_receiver()) |
There was a problem hiding this comment.
Observe receiver task failures instead of letting them die silently.
If _audio_receiver() or _video_receiver() raises, the task just stays failed in the background and media forwarding stops with no immediate log or cleanup. Please attach a done callback or use the shared background-task pattern so failures are surfaced as soon as they happen.
Based on learnings, _run_tool_in_background is being standardized across realtime plugins specifically so add_done_callback-based exception logging handles background task failures uniformly.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@plugins/anam/vision_agents/plugins/anam/anam_avatar_publisher.py` around
lines 220 - 223, The current background tasks created for _audio_receiver() and
_video_receiver() can fail silently; replace the bare asyncio.create_task calls
so failures are observed—either use the standardized helper
_run_tool_in_background(self._audio_receiver(), ...) and
_run_tool_in_background(self._video_receiver(), ...) or attach an
add_done_callback to each created task that logs exceptions and runs cleanup
(e.g., task.add_done_callback(lambda t: _log_and_cleanup_bg_task(t,
"audio_receiver"))). Ensure you reference and update the members
_audio_receiver_task and _video_receiver_task so they store the returned wrapped
task from _run_tool_in_background or the task with the done-callback attached.
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!
| if self._audio_receiver_task is None: | ||
| self._audio_receiver_task = asyncio.create_task(self._audio_receiver()) | ||
| if self._video_receiver_task is None: | ||
| self._video_receiver_task = asyncio.create_task(self._video_receiver()) |
| async def close(self) -> None: | ||
| """ | ||
| Close the Anam avatar publisher, cancel audio & video processing tasks | ||
| and release resources. | ||
| """ | ||
| if self._audio_receiver_task is not None: | ||
| await cancel_and_wait(self._audio_receiver_task) | ||
| if self._video_receiver_task is not None: | ||
| await cancel_and_wait(self._video_receiver_task) | ||
|
|
||
| try: | ||
| await self._exit_stack.aclose() | ||
| await self._client.close() | ||
| except Exception: | ||
| logger.warning("Failed to close Anam avatar publisher", exc_info=True) | ||
| finally: | ||
| logger.debug("Anam avatar publisher closed") |
| @@ -0,0 +1,104 @@ | |||
| # Anam Avatar Plugin for Vision Agents | |||
|
|
|||
| Add real-time interactive avatar video to your AI agents using Anam's avatar API. | |||
An implementation of Anam avatar plugin
plugins/anam/example/anam_avatar_example.pyANAM_API_KEYwith itANAM_AVATAR_IDwith itSummary by CodeRabbit
New Features
anamand included inall-plugins.Documentation
Examples
Tests
Chores