Skip to content

Conversation

@milanperovic
Copy link

@milanperovic milanperovic commented Jan 30, 2026

Summary
Add new livekit-plugins-personaplex plugin for NVIDIA PersonaPlex full-duplex conversational AI
Implements RealtimeModel / RealtimeSession as a WebSocket client connecting to a separately-deployed PersonaPlex server
Binary WebSocket protocol with Opus audio encoding/decoding via sphn
Silence-based generation boundary detection, exponential backoff reconnection, and metrics emission

Summary by CodeRabbit

  • New Features

    • Added PersonaPlex plugin for LiveKit agents for real‑time AI conversations with full‑duplex audio I/O, session lifecycle management and generation events
    • Provides 18 selectable voice options and configurable silence threshold, seed and prompt settings
  • Documentation

    • Added comprehensive README with installation, usage examples, CLI entrypoint, prewarm guidance and environment/configuration details
  • Tests

    • Added test suite covering initialization, options, audio conversions, constants and realtime behaviors

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

milanperovic and others added 3 commits January 30, 2026 11:54
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Guard audio/text channel sends with ChanClosed suppression
- Wrap recv_task message handlers in try/except to prevent single
  malformed frame from breaking the receive loop
- Replace deprecated asyncio.get_event_loop() with get_running_loop()
- Add response_id to GenerationCreatedEvent emissions
- Add exponential backoff (1s -> 30s max) on reconnect instead of
  fixed 1s delay
- Add 32 unit tests covering init, URL building, audio conversion,
  retry backoff, options, and data structures

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove unused imports in tests (ruff F401)
- Fix import sorting (ruff I001)
- Apply ruff format to realtime_model.py
- Add __pdoc__ dict to realtime/__init__.py for docs generation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@CLAassistant
Copy link

CLAassistant commented Jan 30, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

Adds a new PersonaPlex LiveKit plugin: packaging and README, plugin bootstrap (registration, logger, version), voice type definitions, a full RealtimeModel/RealtimeSession implementation (WebSocket + Opus audio I/O, generation lifecycle, retry/backoff, metrics), and tests.

Changes

Cohort / File(s) Summary
Documentation & Manifest
livekit-plugins/livekit-plugins-personaplex/README.md, livekit-plugins/livekit-plugins-personaplex/pyproject.toml
New README with install/usage/prewarm/CLI and package manifest (Hatch build), dependencies, and version config.
Plugin Bootstrap
livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/__init__.py, .../personaplex/version.py, .../personaplex/log.py
Registers PersonaplexPlugin, exposes public symbols, provides module logger and package version.
Type Definitions
livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/models.py
Adds PersonaplexVoice Literal enumerating supported voice IDs.
Realtime Implementation
livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/realtime/__init__.py, .../realtime/realtime_model.py
Implements RealtimeModel and RealtimeSession: WebSocket lifecycle, retry/backoff, PCM↔Opus encoding/decoding, resampling (24kHz mono), generation lifecycle, token filtering, silence finalization, metrics/events.
Tests
livekit-plugins/livekit-plugins-personaplex/tests/test_realtime_model.py
Adds comprehensive tests for model/session init, URL/options handling, token filtering, audio conversions, generation state, and backoff behavior.

Sequence Diagram

sequenceDiagram
    participant Agent as LiveKit Agent
    participant Session as RealtimeSession
    participant Encoder as Opus Encoder
    participant WS as WebSocket
    participant Server as PersonaPlex Server
    participant Decoder as Opus Decoder

    Agent->>Session: push_audio(AudioFrame)
    Session->>Session: resample to 24kHz mono
    Session->>Encoder: encode(PCM)
    Encoder-->>Session: Opus bytes
    Session->>WS: send(MSG_AUDIO, Opus bytes)
    WS->>Server: WebSocket message

    Server->>WS: send(MSG_TEXT / MSG_AUDIO)
    WS->>Session: receive(MSG_TEXT / MSG_AUDIO)

    alt MSG_TEXT
        Session->>Session: filter special tokens
        Session->>Agent: emit(text token)
    else MSG_AUDIO
        Session->>Decoder: decode(Opus bytes)
        Decoder-->>Session: PCM samples
        Session->>Agent: emit(AudioFrame `@24kHz`)
    end

    Note over Session: silence timeout triggers finalization
    Session->>Session: _finalize_generation()
    Session->>Agent: emit(GenerationCreatedEvent + metrics)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • davidzhao
  • tinalenguyen

Poem

🐰 I hopped a WebSocket, bytes in tow,
Opus hums beneath my snow.
Voices threaded, tokens pruned,
Silence sealed the final tune.
Hooray — new hops across the flow!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and generic, using the word 'Feat' without clearly describing the primary change from the developer's perspective. Replace with a more specific title like 'Add PersonaPlex plugin for NVIDIA conversational AI integration' to clearly communicate the main change.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 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 cdb7e55 and 546d03b.

📒 Files selected for processing (1)
  • livekit-plugins/livekit-plugins-personaplex/tests/test_realtime_model.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-personaplex/tests/test_realtime_model.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: livekit/agents PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T07:44:56.353Z
Learning: Follow the Plugin System pattern where plugins in livekit-plugins/ are separate packages registered via the Plugin base class
📚 Learning: 2026-01-30T12:53:12.738Z
Learnt from: milanperovic
Repo: livekit/agents PR: 4660
File: livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/__init__.py:19-21
Timestamp: 2026-01-30T12:53:12.738Z
Learning: In plugin __init__.py files under the livekit-plugins or similar plugin directories, place internal imports (for example, from .log import logger) after the __all__ definition. These imports are used for plugin registration and are not part of the public API. This pattern is used across plugins (e.g., openai, deepgram, ultravox) and helps avoid E402 violations while keeping the public API surface clean.

Applied to files:

  • livekit-plugins/livekit-plugins-personaplex/tests/test_realtime_model.py
🧬 Code graph analysis (1)
livekit-plugins/livekit-plugins-personaplex/tests/test_realtime_model.py (1)
livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/realtime/realtime_model.py (5)
  • RealtimeModel (74-164)
  • _PersonaplexOptions (49-55)
  • _ResponseGeneration (59-71)
  • model (144-145)
  • provider (148-149)
⏰ 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: unit-tests
  • GitHub Check: type-check (3.13)
  • GitHub Check: type-check (3.9)
🔇 Additional comments (8)
livekit-plugins/livekit-plugins-personaplex/tests/test_realtime_model.py (8)

1-21: LGTM!

The imports are well-organized, with from __future__ import annotations ensuring Python 3.9+ compatibility. Importing internal symbols (_PersonaplexOptions, _ResponseGeneration, _SPECIAL_TOKENS) for testing implementation details is appropriate for unit tests.


25-112: LGTM!

Comprehensive test coverage for RealtimeModel initialization:

  • URL handling with all scheme variants (ws/wss/http/https) and SSL detection
  • Environment variable fallback and explicit override behavior
  • All constructor parameters and default values
  • Model properties (model, provider, _label) and capabilities flags

The tests effectively validate the URL resolution logic documented in the RealtimeModel.__init__ docstring.


117-152: LGTM!

The _make_opts helper method is a clean pattern for creating test fixtures with sensible defaults, reducing boilerplate across test methods.


157-167: LGTM!

Clear tests verifying the special token set membership for filtering logic.


172-205: LGTM!

Well-designed audio tests:

  • Constants verification ensures compatibility with the Opus codec requirements
  • The roundtrip test mirrors the actual PCM conversion logic, validating data integrity
  • Clipping test covers edge cases with out-of-range float values

210-235: LGTM!

Good practices in this test:

  • The try/finally block ensures proper channel cleanup even if assertions fail (as noted in commit message)
  • Inline imports isolate test dependencies, which can be useful when running partial test suites

240-254: LGTM!

The exponential backoff sequence test effectively validates the retry logic by verifying both the doubling behavior and the maximum delay cap.


259-284: LGTM!

Tests comprehensively cover all _PersonaplexOptions dataclass fields, including the use_ssl field and seed=None edge case. The test_none_seed correctly relies on the dataclass default value for use_ssl.

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


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.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/__init__.py`:
- Around line 19-21: The import statements for Plugin and logger currently
appear after the module-level __all__, which triggers ruff E402; move the
imports "from livekit.agents import Plugin" and "from .log import logger" so
they are placed above the __all__ definition (ensure __all__ references Plugin
and logger remain correct) to satisfy the linter and keep import ordering
correct.

In
`@livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/realtime/realtime_model.py`:
- Around line 327-337: _build_ws_url currently hardcodes "ws://" which prevents
TLS; update it to choose "wss://" when the provided base URL indicates TLS.
Parse self._opts.base_url (or inspect its original string) for a scheme: if it
starts with "wss://" or "https://" use "wss://", if it starts with "ws://" or
"http://" use that scheme, otherwise default to "wss://" when a separate SSL
flag is present or "ws://" otherwise; ensure you strip any existing scheme
before joining host/path so the returned URL is either
"wss://{host}/api/chat?{query}" or "ws://{host}/api/chat?{query}" accordingly.
Use the _build_ws_url function and self._opts.base_url to locate and implement
this change.
🧹 Nitpick comments (5)
livekit-plugins/livekit-plugins-personaplex/README.md (1)

13-13: Consider using a proper markdown link for the URL.

The bare URL works but wrapping it in markdown link syntax improves rendering consistency across different markdown parsers.

📝 Suggested fix
-You need a running PersonaPlex server on a GPU machine. See https://github.com/NVIDIA/personaplex for setup.
+You need a running PersonaPlex server on a GPU machine. See [NVIDIA PersonaPlex](https://github.com/NVIDIA/personaplex) for setup.
livekit-plugins/livekit-plugins-personaplex/pyproject.toml (1)

25-34: Consider adding Python 3.11 and 3.12 classifiers.

The requires-python = ">=3.9.0" allows Python 3.11 and 3.12, but the classifiers only list 3.9 and 3.10. Adding these classifiers improves discoverability on PyPI.

📝 Suggested addition
     "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",
 ]
livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/realtime/realtime_model.py (3)

239-243: Consider using logger.warning for unsupported feature notification.

When a user provides instructions that won't be applied, a warning level is more appropriate than info to ensure visibility.

📝 Suggested change
         if is_given(instructions):
-            logger.info(
+            logger.warning(
                 "PersonaPlex does not support dynamic instructions. "
                 "Instruction changes require reconnection via update_instructions()."
             )

487-491: Minor: Redundant length check.

if opus_bytes and len(opus_bytes) > 0 is redundant since a truthy bytes object always has length > 0.

📝 Suggested simplification
-        if opus_bytes and len(opus_bytes) > 0:
+        if opus_bytes:
             # Prepend audio message type
             message = bytes([MSG_AUDIO]) + opus_bytes

710-712: Accessing private attribute _input_rate from rtc.AudioResampler.

Accessing self._input_resampler._input_rate relies on an internal implementation detail. If livekit-rtc changes its internals, this could break. Consider storing the input rate alongside the resampler or checking if there's a public property.

📝 Suggested approach
+        self._input_resampler_rate: int | None = None
+
     def _resample_audio(self, frame: rtc.AudioFrame) -> Iterator[rtc.AudioFrame]:
         if self._input_resampler:
-            if frame.sample_rate != self._input_resampler._input_rate:
+            if frame.sample_rate != self._input_resampler_rate:
                 self._input_resampler = None
+                self._input_resampler_rate = None

         if self._input_resampler is None and (
             frame.sample_rate != SAMPLE_RATE or frame.num_channels != NUM_CHANNELS
         ):
             self._input_resampler = rtc.AudioResampler(
                 input_rate=frame.sample_rate,
                 output_rate=SAMPLE_RATE,
                 num_channels=NUM_CHANNELS,
             )
+            self._input_resampler_rate = frame.sample_rate
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 843ffeb and bc1b50f.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • livekit-plugins/livekit-plugins-personaplex/README.md
  • livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/__init__.py
  • livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/log.py
  • livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/models.py
  • livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/py.typed
  • livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/realtime/__init__.py
  • livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/realtime/realtime_model.py
  • livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/version.py
  • livekit-plugins/livekit-plugins-personaplex/pyproject.toml
  • livekit-plugins/livekit-plugins-personaplex/tests/__init__.py
  • livekit-plugins/livekit-plugins-personaplex/tests/test_realtime_model.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-personaplex/livekit/plugins/personaplex/version.py
  • livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/log.py
  • livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/__init__.py
  • livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/models.py
  • livekit-plugins/livekit-plugins-personaplex/tests/test_realtime_model.py
  • livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/realtime/__init__.py
  • livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/realtime/realtime_model.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: Implement Model Interface Pattern for STT, TTS, LLM, and Realtime models with provider-agnostic interfaces, fallback adapters for resilience, and stream adapters for different streaming patterns

Applied to files:

  • livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/realtime/realtime_model.py
🧬 Code graph analysis (3)
livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/__init__.py (2)
livekit-agents/livekit/agents/llm/realtime.py (1)
  • realtime_model (143-144)
livekit-agents/livekit/agents/plugin.py (2)
  • Plugin (13-56)
  • register_plugin (31-36)
livekit-plugins/livekit-plugins-personaplex/tests/test_realtime_model.py (1)
livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/realtime/realtime_model.py (5)
  • RealtimeModel (73-161)
  • _PersonaplexOptions (49-54)
  • _ResponseGeneration (58-70)
  • model (141-142)
  • provider (145-146)
livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/realtime/realtime_model.py (11)
livekit-agents/livekit/agents/metrics/base.py (4)
  • Metadata (8-10)
  • RealtimeModelMetrics (91-132)
  • InputTokenDetails (97-102)
  • OutputTokenDetails (104-107)
livekit-agents/livekit/agents/utils/misc.py (1)
  • is_given (25-26)
livekit-agents/livekit/agents/utils/aio/channel.py (2)
  • Chan (49-178)
  • ChanClosed (17-18)
livekit-agents/livekit/agents/llm/realtime.py (5)
  • MessageGeneration (30-34)
  • RealtimeCapabilities (57-63)
  • realtime_model (143-144)
  • RealtimeSessionReconnectedEvent (133-134)
  • RealtimeModelError (47-53)
livekit-agents/livekit/agents/llm/chat_context.py (3)
  • FunctionCall (179-192)
  • ChatContext (218-656)
  • add_message (234-267)
livekit-agents/livekit/agents/utils/http_context.py (1)
  • http_session (40-51)
livekit-agents/livekit/agents/llm/tool_context.py (2)
  • ToolContext (326-449)
  • message (99-100)
livekit-agents/livekit/agents/utils/audio.py (1)
  • AudioByteStream (41-157)
livekit-agents/livekit/agents/utils/log.py (1)
  • log_exceptions (9-41)
livekit-agents/livekit/agents/voice/agent.py (1)
  • instructions (99-104)
livekit-agents/livekit/agents/voice/speech_handle.py (1)
  • add_done_callback (163-164)
🪛 markdownlint-cli2 (0.20.0)
livekit-plugins/livekit-plugins-personaplex/README.md

[warning] 13-13: Bare URL used

(MD034, no-bare-urls)


[warning] 66-66: Table column style
Table pipe does not align with header for style "aligned"

(MD060, table-column-style)


[warning] 66-66: Table column style
Table pipe does not align with header for style "aligned"

(MD060, table-column-style)


[warning] 66-66: Table column style
Table pipe does not align with header for style "aligned"

(MD060, table-column-style)

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

1-1: LGTM!

Standard version module pattern that integrates correctly with the Hatch build system.

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

1-3: LGTM!

Standard logger setup following the package naming convention.

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

1-24: LGTM!

Clean type alias definition providing compile-time type safety for voice selection.

livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/realtime/realtime_model.py (16)

32-45: LGTM!

Constants are well-documented with clear naming. The message type prefixes and retry parameters are sensibly configured.


48-71: LGTM!

Well-structured dataclasses with appropriate field defaults and type annotations. The _ResponseGeneration properly tracks generation lifecycle state.


73-162: LGTM!

The RealtimeModel class follows the Model Interface Pattern well. URL resolution with fallback chain (parameter → env var → default) is user-friendly, and protocol prefix stripping handles various input formats gracefully.


171-202: LGTM!

Clean initialization with proper resource setup. The 100ms frame size (2400 samples at 24kHz) is appropriate for real-time audio streaming. Starting _main_task in __init__ follows the established pattern for realtime sessions.


215-226: LGTM!

Audio pipeline is well-structured with resampling and the @utils.log_exceptions decorator ensures errors are logged without breaking the caller.


259-278: LGTM!

Methods appropriately handle PersonaPlex limitations with clear comments explaining the full-duplex streaming model.


281-295: LGTM!

The update_instructions method correctly triggers a reconnection since PersonaPlex requires instruction changes at connection time. Other update methods gracefully handle unsupported features.


298-313: LGTM!

Clean shutdown sequence with proper idempotency check and resource cleanup.


338-416: LGTM!

The connection management with exponential backoff (1s → 30s) is well-implemented. Task coordination using asyncio.wait with FIRST_COMPLETED properly handles both normal operation and reconnection scenarios.


417-475: LGTM!

The send/receive tasks have proper error handling. Wrapping message handlers in try/except (lines 447-461) prevents malformed frames from breaking the receive loop, which is good defensive coding per the commit message.


493-531: LGTM!

Audio decoding correctly converts Opus to PCM with proper clipping and channel handling. The silence timer reset on each audio frame implements the generation boundary detection.


532-561: LGTM!

Text token handling correctly filters special tokens and accumulates output for the chat context.


564-611: LGTM!

Generation lifecycle management properly distinguishes between server-initiated (user_initiated=False) and user-requested (user_initiated=True) generations. The UUID-based response IDs aid traceability.


612-639: LGTM!

Clean finalization with proper channel cleanup ordering and defensive close checks. The local chat context update preserves conversation history for potential future use.


691-706: LGTM!

Silence detection using asyncio.get_running_loop().call_later is the correct approach. Using interrupted=False for silence-based finalization correctly indicates natural generation completion.


663-663: No changes needed. The label property is properly defined in the base class llm.RealtimeModel (livekit-agents/livekit/agents/llm/realtime.py:88-90) and returns self._label. PersonaPlex's RealtimeModel correctly calls super().__init__() which initializes the inherited _label attribute, then customizes it. The code at line 663 will work without errors.

livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/realtime/__init__.py (1)

1-12: LGTM!

Clean module re-exports with proper __all__ definition and pdoc3 documentation hygiene for unexported symbols.

livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/__init__.py (3)

1-17: Clear module docstring and explicit exports.

The module docstring and __all__ make the public API straightforward.


24-29: Confirm import-time registration always runs on the main thread.

Plugin.register_plugin raises if called off the main thread; import-time registration can crash if this module is imported from a worker thread. If that’s possible in your loading flow, consider deferring registration or adding a main-thread guard with a clear error/log message.


31-38: Doc cleanup for non-exported symbols looks good.

__pdoc__ suppression aligns with __all__.

livekit-plugins/livekit-plugins-personaplex/tests/test_realtime_model.py (9)

1-20: Imports and internal symbol usage in tests are clear.

The test module setup is straightforward.


25-101: Strong init/metadata coverage.

Default, override, and capability assertions give good confidence in constructor behavior.


106-130: URL/options coverage is focused and sensible.

The opts validation provides a lightweight sanity check.


135-145: Token filter tests are concise and clear.

Good minimal checks around special-token handling.


150-161: Audio constant assertions are straightforward.

These sanity checks are valuable.


164-182: Audio conversion tests cover roundtrip and clipping well.

Nice validation of edge values and clipping semantics.


188-213: ResponseGeneration defaults and cleanup look good.

The test verifies defaults and closes channels explicitly.


218-232: Backoff sequence test is crisp and deterministic.

This matches the expected exponential cap behavior.


237-260: Options dataclass tests cover both seed cases.

Good validation for seed being set or None.

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

milanperovic and others added 2 commits January 30, 2026 13:52
- Preserve wss:// / https:// scheme for TLS deployments
- Use logger.warning for unsupported dynamic instructions
- Remove redundant length check on opus_bytes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@livekit-plugins/livekit-plugins-personaplex/tests/test_realtime_model.py`:
- Around line 248-271: The test_all_fields in TestPersonaplexOptions fails to
assert the dataclass's use_ssl field; update the test to pass a non-default
value for use_ssl when constructing _PersonaplexOptions and add an assertion
verifying opts.use_ssl equals that value (e.g., True), so the
_PersonaplexOptions constructor and field are covered alongside base_url, voice,
text_prompt, seed, and silence_threshold_ms.
🧹 Nitpick comments (3)
livekit-plugins/livekit-plugins-personaplex/tests/test_realtime_model.py (3)

117-141: Consider improving type annotations to avoid type: ignore.

The **kwargs: object annotation combined with # type: ignore[arg-type] on line 127 suggests type system friction. Using Any or explicit keyword arguments would provide better type safety.

Also, as the comment on lines 133-134 notes, test_basic_url and test_seed_in_url don't actually test URL building—they only verify that options are set correctly. Consider either renaming this test class to reflect what it actually tests (e.g., TestSessionOptions) or adding actual URL construction tests if that's testable.

♻️ Suggested improvement for type annotations
+from typing import Any
+
 class TestBuildWsUrl:
-    def _make_session_opts(self, **kwargs: object) -> _PersonaplexOptions:
+    def _make_session_opts(self, **kwargs: Any) -> _PersonaplexOptions:
         defaults = {
             "base_url": "localhost:8998",
             "voice": "NATF2",
             "text_prompt": "You are helpful.",
             "seed": None,
             "silence_threshold_ms": 500,
         }
         defaults.update(kwargs)
-        return _PersonaplexOptions(**defaults)  # type: ignore[arg-type]
+        return _PersonaplexOptions(**defaults)

146-156: Tests verify set membership rather than filtering behavior.

The docstrings describe filtering behavior ("should be filtered out", "should pass through"), but the tests only verify which values are in _SPECIAL_TOKENS. Consider either:

  1. Renaming the class to TestSpecialTokensSet to reflect what's actually being tested
  2. Adding tests that exercise the actual token handling logic (e.g., calling the method that filters tokens and verifying output)

199-224: Consider using a pytest fixture for channel cleanup.

The manual cleanup at lines 220-223 won't execute if an assertion fails earlier in the test. Using a fixture with yield ensures cleanup runs regardless of test outcome.

♻️ Suggested fixture-based approach
`@pytest.fixture`
def response_generation():
    from livekit import rtc
    from livekit.agents import llm, utils

    gen = _ResponseGeneration(
        message_ch=utils.aio.Chan[llm.MessageGeneration](),
        function_ch=utils.aio.Chan[llm.FunctionCall](),
        response_id="test-123",
        text_ch=utils.aio.Chan[str](),
        audio_ch=utils.aio.Chan[rtc.AudioFrame](),
    )
    yield gen
    gen.message_ch.close()
    gen.function_ch.close()
    gen.text_ch.close()
    gen.audio_ch.close()


class TestResponseGeneration:
    def test_defaults(self, response_generation: _ResponseGeneration) -> None:
        gen = response_generation
        assert gen.response_id == "test-123"
        assert gen._first_token_timestamp is None
        # ... rest of assertions
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e4c80d3 and 278b4ed.

📒 Files selected for processing (1)
  • livekit-plugins/livekit-plugins-personaplex/tests/test_realtime_model.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-personaplex/tests/test_realtime_model.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: livekit/agents PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T07:44:56.353Z
Learning: Follow the Plugin System pattern where plugins in livekit-plugins/ are separate packages registered via the Plugin base class
📚 Learning: 2026-01-30T12:53:12.738Z
Learnt from: milanperovic
Repo: livekit/agents PR: 4660
File: livekit-plugins/livekit-plugins-personaplex/livekit/plugins/personaplex/__init__.py:19-21
Timestamp: 2026-01-30T12:53:12.738Z
Learning: In plugin __init__.py files under the livekit-plugins or similar plugin directories, place internal imports (for example, from .log import logger) after the __all__ definition. These imports are used for plugin registration and are not part of the public API. This pattern is used across plugins (e.g., openai, deepgram, ultravox) and helps avoid E402 violations while keeping the public API surface clean.

Applied to files:

  • livekit-plugins/livekit-plugins-personaplex/tests/test_realtime_model.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.13)
  • GitHub Check: type-check (3.9)
  • GitHub Check: unit-tests
🔇 Additional comments (5)
livekit-plugins/livekit-plugins-personaplex/tests/test_realtime_model.py (5)

1-21: LGTM!

Imports are well-organized with clear separation between standard library, third-party, and internal plugin modules. Testing private internals (_PersonaplexOptions, _ResponseGeneration, _SPECIAL_TOKENS) is appropriate for thorough unit testing of implementation details.


25-112: LGTM!

Excellent coverage of initialization scenarios including:

  • URL prefix stripping and SSL inference from ws://, wss://, http://, https://
  • Environment variable handling with proper precedence
  • All configurable options and capability flags

The use of monkeypatch for environment variable tests is clean and idiomatic.


161-172: LGTM!

Constant verification tests are valuable for catching accidental changes to protocol-critical values.


174-194: LGTM!

Solid numerical tests covering:

  • Round-trip conversion with critical edge cases (min/max int16 boundaries)
  • Clipping behavior for out-of-range float values

Using np.testing.assert_array_equal is the correct approach for array comparisons.


229-243: LGTM!

Good validation of retry constants and the exponential backoff sequence. The sequence test effectively documents the expected backoff behavior (1→2→4→8→16→30→30).

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

milanperovic and others added 2 commits January 30, 2026 15:47
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Rename TestBuildWsUrl to TestSessionOptions with proper typed helper
- Rename TestHandleTextToken to TestSpecialTokens to reflect actual scope
- Wrap ResponseGeneration assertions in try/finally for channel cleanup

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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.

2 participants