From 0a50f25dfa8cd325872104a951a5b52d02d2df65 Mon Sep 17 00:00:00 2001 From: 0Xiao0 <511201264@qq.com> Date: Tue, 2 Jun 2026 14:07:56 +0800 Subject: [PATCH] feat: beaver first commit --- beaver_llm.py | 3 +-- custom_agent.py | 19 +++++++++++++------ test_beaver_llm.py | 28 ++++++++++++++-------------- test_beaver_terminal_client.py | 13 +++++++++++++ 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/beaver_llm.py b/beaver_llm.py index e516951..96749f0 100644 --- a/beaver_llm.py +++ b/beaver_llm.py @@ -4,7 +4,7 @@ import asyncio from collections.abc import Sequence from typing import Any -from custom.beaver_terminal_client import BeaverTerminalClient +from beaver_terminal_client import BeaverTerminalClient from livekit.agents import llm from livekit.agents.types import ( DEFAULT_API_CONNECT_OPTIONS, @@ -14,7 +14,6 @@ from livekit.agents.types import ( ) from livekit.agents.utils import shortuuid - def latest_user_text(chat_ctx: llm.ChatContext) -> str: for message in reversed(chat_ctx.messages()): if message.role != "user": diff --git a/custom_agent.py b/custom_agent.py index 5f85fad..00f6d6d 100644 --- a/custom_agent.py +++ b/custom_agent.py @@ -686,18 +686,16 @@ async def entrypoint(ctx: JobContext) -> None: stt_stream = stt.StreamAdapter(stt=blackbox_stt, vad=ctx.proc.userdata["vad"]) if LLM_PROVIDER == "beaver": - beaver_url = os.getenv("CUSTOM_BEAVER_WS_URL") or os.getenv("BEAVER_WS_URL", "").strip() + beaver_url = _first_env("CUSTOM_BEAVER_WS_URL", "BEAVER_WS_URL") if not beaver_url: raise RuntimeError(f"CUSTOM_BEAVER_WS_URL or BEAVER_WS_URL is not set in {CUSTOM_ENV_PATH}") beaver_peer_id = ( - os.getenv("CUSTOM_BEAVER_PEER_ID") - or os.getenv("BEAVER_PEER_ID") + _first_env("CUSTOM_BEAVER_PEER_ID", "BEAVER_PEER_ID", "TERMINAL_PEER_ID") or f"livekit-{ctx.room.name}" ) beaver_device_name = ( - os.getenv("CUSTOM_BEAVER_DEVICE_NAME") - or os.getenv("BEAVER_DEVICE_NAME") + _first_env("CUSTOM_BEAVER_DEVICE_NAME", "BEAVER_DEVICE_NAME", "TERMINAL_DEVICE_NAME") or "livekit-custom-agent" ) base_llm = BeaverLLM( @@ -709,10 +707,11 @@ async def entrypoint(ctx: JobContext) -> None: text_llm = base_llm vision_llm = base_llm logger.info( - "Using Beaver gateway url=%s peer_id=%s device_name=%s", + "Using Beaver gateway url=%s peer_id=%s device_name=%s room=%s", beaver_url, beaver_peer_id, beaver_device_name, + ctx.room.name, ) elif LLM_PROVIDER in {"hermes", "hermes_gateway", "openclaw"}: gateway_url = os.getenv("CUSTOM_HERMES_GATEWAY_URL", "").strip() @@ -1000,6 +999,14 @@ def _env_bool(name: str, default: bool) -> bool: return default +def _first_env(*names: str) -> str | None: + for name in names: + value = os.getenv(name) + if value and value.strip(): + return value.strip() + return None + + def _recording_options_from_env() -> RecordingOptions: return RecordingOptions( audio=_env_bool("CUSTOM_RECORD_AUDIO", False), diff --git a/test_beaver_llm.py b/test_beaver_llm.py index 868e461..e4a2b07 100644 --- a/test_beaver_llm.py +++ b/test_beaver_llm.py @@ -3,7 +3,10 @@ import json import aiohttp from aiohttp import web -from custom.beaver_llm import BeaverLLM, latest_user_text +try: + from custom.beaver_llm import BeaverLLM, latest_user_text +except ModuleNotFoundError: + from beaver_llm import BeaverLLM, latest_user_text from livekit.agents import ChatContext @@ -83,16 +86,13 @@ async def test_beaver_llm_sends_latest_user_text_and_returns_reply( await runner.cleanup() assert collected.text == "beaver reply" - assert received == [ - { - "type": "connect", - "peer_id": "livekit-room", - "device_name": "livekit-custom-agent", - "capabilities": ["text"], - }, - { - "type": "message", - "message_id": "livekit-room-000001", - "text": "hello beaver", - }, - ] + assert received[0] == { + "type": "connect", + "peer_id": "livekit-room", + "device_name": "livekit-custom-agent", + "capabilities": ["text"], + } + assert received[1]["type"] == "message" + assert received[1]["message_id"].startswith("livekit-room-") + assert received[1]["message_id"].endswith("-000001") + assert received[1]["text"] == "hello beaver" diff --git a/test_beaver_terminal_client.py b/test_beaver_terminal_client.py index bb1b00b..9276ec1 100644 --- a/test_beaver_terminal_client.py +++ b/test_beaver_terminal_client.py @@ -58,6 +58,13 @@ def test_message_id_generator_uses_monotonic_peer_counter() -> None: assert generator.counter == 9 +def test_message_id_generator_can_include_nonce() -> None: + generator = MessageIdGenerator(peer_id="device-001", nonce="run12345") + + assert generator.next_id() == "device-001-run12345-000001" + assert generator.next_id() == "device-001-run12345-000002" + + async def test_client_connects_sends_text_and_returns_assistant_reply( unused_tcp_port: int, ) -> None: @@ -113,6 +120,7 @@ async def test_client_connects_sends_text_and_returns_assistant_reply( url=f"http://127.0.0.1:{unused_tcp_port}/api/channels/terminal-dev/ws", peer_id="device-001", device_name="desk-terminal", + message_ids=MessageIdGenerator(peer_id="device-001"), ) try: @@ -180,6 +188,7 @@ async def test_client_returns_cached_duplicate_reply(unused_tcp_port: int) -> No url=f"http://127.0.0.1:{unused_tcp_port}/api/channels/terminal-dev/ws", peer_id="device-001", device_name="desk-terminal", + message_ids=MessageIdGenerator(peer_id="device-001"), ) try: @@ -223,6 +232,7 @@ async def test_client_raises_on_error_frames(unused_tcp_port: int) -> None: url=f"http://127.0.0.1:{unused_tcp_port}/api/channels/terminal-dev/ws", peer_id="device-001", device_name="desk-terminal", + message_ids=MessageIdGenerator(peer_id="device-001"), ) try: @@ -284,6 +294,7 @@ async def test_client_treats_assistant_finish_reason_error_as_failed_turn( url=f"http://127.0.0.1:{unused_tcp_port}/api/channels/terminal-dev/ws", peer_id="device-001", device_name="desk-terminal", + message_ids=MessageIdGenerator(peer_id="device-001"), ) try: @@ -326,6 +337,7 @@ async def test_client_ping_sends_ping_and_waits_for_pong(unused_tcp_port: int) - url=f"http://127.0.0.1:{unused_tcp_port}/api/channels/terminal-dev/ws", peer_id="device-001", device_name="desk-terminal", + message_ids=MessageIdGenerator(peer_id="device-001"), ) try: @@ -398,6 +410,7 @@ async def test_client_reconnects_with_same_peer_id_when_socket_closes_before_sen url=f"http://127.0.0.1:{unused_tcp_port}/api/channels/terminal-dev/ws", peer_id="device-001", device_name="desk-terminal", + message_ids=MessageIdGenerator(peer_id="device-001"), ) try: