From 52e6d3cd9cd6a08eec97e6571d9c6ffd447b7962 Mon Sep 17 00:00:00 2001 From: 0Xiao0 <511201264@qq.com> Date: Wed, 3 Jun 2026 17:25:08 +0800 Subject: [PATCH] feat: connect to beaver --- .env.example | 35 ++++++++++------------------------- beaver_llm.py | 30 +++++++++++++++++++++++++++++- custom_agent.py | 14 ++++++++------ 3 files changed, 47 insertions(+), 32 deletions(-) diff --git a/.env.example b/.env.example index 5016d4c..a82c734 100644 --- a/.env.example +++ b/.env.example @@ -5,17 +5,10 @@ LIVEKIT_API_SECRET= CUSTOM_AGENT_NAME=my-agent # Beaver terminal text WebSocket -BEAVER_WS_URL=ws://127.0.0.1:8080/api/channels/terminal-dev/ws +BEAVER_WS_URL=ws://terminaltest.1localhost.nip.io:8088/api/channels/terminal-dev/ws TERMINAL_PEER_ID=device-001 TERMINAL_DEVICE_NAME=desk-terminal -# Beaver as a LiveKit LLM backend: asr -> beaver -> tts. -# CUSTOM_LLM_PROVIDER=beaver -# CUSTOM_BEAVER_WS_URL=ws://127.0.0.1:8080/api/channels/terminal-dev/ws -# CUSTOM_BEAVER_PEER_ID=livekit-agent-001 -# CUSTOM_BEAVER_DEVICE_NAME=livekit-custom-agent -# CUSTOM_BEAVER_MODEL=beaver-terminal - # ASR blackbox CUSTOM_ASR_URL=http://localhost:5000/asr-blackbox CUSTOM_ASR_MODEL=qwen @@ -26,35 +19,27 @@ CUSTOM_ASR_ITN= CUSTOM_ASR_CHUNK_MODE= # LLM backend: openai/openai-compatible or hermes_gateway/openclaw. -CUSTOM_LLM_PROVIDER=openai +CUSTOM_LLM_PROVIDER=beaver +CUSTOM_BEAVER_WARMUP_TEXT=初始化连接,请简短回复 ready # OpenAI-compatible LLM # CUSTOM_LLM_BASE_URL=https://oai.bwgdi.com/v1 # CUSTOM_LLM_MODEL=Qwen3.6-35B -# CUSTOM_LLM_API_KEY= +# CUSTOM_LLM_API_KEY=sk- # CUSTOM_LLM_VERIFY_SSL=false -CUSTOM_LLM_BASE_URL=http://localhost/v1 -CUSTOM_LLM_MODEL=Qwen-VL -CUSTOM_LLM_API_KEY= +CUSTOM_LLM_BASE_URL=http:/localhost/v1 +CUSTOM_LLM_MODEL=Mistral-Medium-3.5-128B +CUSTOM_LLM_API_KEY=sk- CUSTOM_LLM_VERIFY_SSL=false -CUSTOM_SAVE_MODEL_IMAGES=false +CUSTOM_SAVE_MODEL_IMAGES=true # CUSTOM_TEXT_LLM_MODEL= # CUSTOM_VISION_LLM_MODEL= -# Hermes Agent via OpenClaw Gateway WebSocket, one Gateway session per LiveKit room. -# CUSTOM_LLM_PROVIDER=hermes_gateway -# CUSTOM_HERMES_GATEWAY_URL=ws://localhost:1977/ws -# CUSTOM_HERMES_AGENT_ID= -# CUSTOM_HERMES_API_KEY= -# CUSTOM_HERMES_SESSION_MODE=per_room -# CUSTOM_HERMES_MODEL=hermes-agent -# CUSTOM_HERMES_REQUEST_TIMEOUT=30 - # CUSTOM_LLM_BASE_URL=https://api.deepseek.com # CUSTOM_LLM_MODEL=deepseek-v4-flash -# CUSTOM_LLM_API_KEY= +# CUSTOM_LLM_API_KEY=sk- # CUSTOM_LLM_VERIFY_SSL=false @@ -95,4 +80,4 @@ CUSTOM_MEMORY_URL=http://localhost:8766/api/room_graph CUSTOM_MEMORY_TIMEOUT=2 CUSTOM_MEMORY_MAX_CHARS=2000 CUSTOM_MEMORY_API_KEY= -CUSTOM_PREEMPTIVE_GENERATION=true +CUSTOM_PREEMPTIVE_GENERATION=false diff --git a/beaver_llm.py b/beaver_llm.py index 96749f0..0017aa4 100644 --- a/beaver_llm.py +++ b/beaver_llm.py @@ -1,10 +1,14 @@ from __future__ import annotations import asyncio +import logging from collections.abc import Sequence from typing import Any -from beaver_terminal_client import BeaverTerminalClient +try: + from beaver_terminal_client import BeaverTerminalClient +except ModuleNotFoundError: + from custom.beaver_terminal_client import BeaverTerminalClient from livekit.agents import llm from livekit.agents.types import ( DEFAULT_API_CONNECT_OPTIONS, @@ -14,6 +18,9 @@ from livekit.agents.types import ( ) from livekit.agents.utils import shortuuid +logger = logging.getLogger("beaver-llm") + + def latest_user_text(chat_ctx: llm.ChatContext) -> str: for message in reversed(chat_ctx.messages()): if message.role != "user": @@ -49,6 +56,27 @@ class BeaverLLM(llm.LLM): def provider(self) -> str: return "beaver" + @property + def session_id(self) -> str | None: + return self._client.session_id + + async def connect(self, *, warmup_text: str | None = None) -> str | None: + warmup_reply: str | None = None + async with self._lock: + await self._client.connect() + if warmup_text and warmup_text.strip(): + warmup_reply = await self._client.send_text(warmup_text.strip()) + + if warmup_reply is None: + logger.info("Beaver handshake completed session_id=%s", self.session_id) + else: + logger.info( + "Beaver handshake warmup completed session_id=%s reply_len=%s", + self.session_id, + len(warmup_reply), + ) + return warmup_reply + def chat( self, *, diff --git a/custom_agent.py b/custom_agent.py index 00f6d6d..aa27740 100644 --- a/custom_agent.py +++ b/custom_agent.py @@ -690,10 +690,7 @@ async def entrypoint(ctx: JobContext) -> None: 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 = ( - _first_env("CUSTOM_BEAVER_PEER_ID", "BEAVER_PEER_ID", "TERMINAL_PEER_ID") - or f"livekit-{ctx.room.name}" - ) + beaver_peer_id = _first_env("CUSTOM_BEAVER_PEER_ID", "BEAVER_PEER_ID") or f"livekit-{ctx.room.name}" beaver_device_name = ( _first_env("CUSTOM_BEAVER_DEVICE_NAME", "BEAVER_DEVICE_NAME", "TERMINAL_DEVICE_NAME") or "livekit-custom-agent" @@ -704,14 +701,19 @@ async def entrypoint(ctx: JobContext) -> None: device_name=beaver_device_name, model_name=os.getenv("CUSTOM_BEAVER_MODEL", "beaver-terminal"), ) + beaver_warmup_text = os.getenv("CUSTOM_BEAVER_WARMUP_TEXT") + warmup_reply = await base_llm.connect(warmup_text=beaver_warmup_text) text_llm = base_llm vision_llm = base_llm logger.info( - "Using Beaver gateway url=%s peer_id=%s device_name=%s room=%s", + "Using Beaver gateway url=%s peer_id=%s device_name=%s room=%s session_id=%s warmup=%s warmup_reply_len=%s", beaver_url, beaver_peer_id, beaver_device_name, ctx.room.name, + base_llm.session_id, + bool(beaver_warmup_text and beaver_warmup_text.strip()), + len(warmup_reply) if warmup_reply is not None else 0, ) elif LLM_PROVIDER in {"hermes", "hermes_gateway", "openclaw"}: gateway_url = os.getenv("CUSTOM_HERMES_GATEWAY_URL", "").strip() @@ -809,7 +811,7 @@ async def entrypoint(ctx: JobContext) -> None: "false_interruption_timeout": 1.0, }, ), - preemptive_generation=_env_bool("CUSTOM_PREEMPTIVE_GENERATION", True), + preemptive_generation=_env_bool("CUSTOM_PREEMPTIVE_GENERATION", LLM_PROVIDER != "beaver"), aec_warmup_duration=3.0, tts_text_transforms=[ "filter_emoji",