feat: connect to beaver
This commit is contained in:
35
.env.example
35
.env.example
@ -5,17 +5,10 @@ LIVEKIT_API_SECRET=
|
|||||||
CUSTOM_AGENT_NAME=my-agent
|
CUSTOM_AGENT_NAME=my-agent
|
||||||
|
|
||||||
# Beaver terminal text WebSocket
|
# 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_PEER_ID=device-001
|
||||||
TERMINAL_DEVICE_NAME=desk-terminal
|
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
|
# ASR blackbox
|
||||||
CUSTOM_ASR_URL=http://localhost:5000/asr-blackbox
|
CUSTOM_ASR_URL=http://localhost:5000/asr-blackbox
|
||||||
CUSTOM_ASR_MODEL=qwen
|
CUSTOM_ASR_MODEL=qwen
|
||||||
@ -26,35 +19,27 @@ CUSTOM_ASR_ITN=
|
|||||||
CUSTOM_ASR_CHUNK_MODE=
|
CUSTOM_ASR_CHUNK_MODE=
|
||||||
|
|
||||||
# LLM backend: openai/openai-compatible or hermes_gateway/openclaw.
|
# 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
|
# OpenAI-compatible LLM
|
||||||
# CUSTOM_LLM_BASE_URL=https://oai.bwgdi.com/v1
|
# CUSTOM_LLM_BASE_URL=https://oai.bwgdi.com/v1
|
||||||
# CUSTOM_LLM_MODEL=Qwen3.6-35B
|
# CUSTOM_LLM_MODEL=Qwen3.6-35B
|
||||||
# CUSTOM_LLM_API_KEY=
|
# CUSTOM_LLM_API_KEY=sk-
|
||||||
# CUSTOM_LLM_VERIFY_SSL=false
|
# CUSTOM_LLM_VERIFY_SSL=false
|
||||||
|
|
||||||
CUSTOM_LLM_BASE_URL=http://localhost/v1
|
CUSTOM_LLM_BASE_URL=http:/localhost/v1
|
||||||
CUSTOM_LLM_MODEL=Qwen-VL
|
CUSTOM_LLM_MODEL=Mistral-Medium-3.5-128B
|
||||||
CUSTOM_LLM_API_KEY=
|
CUSTOM_LLM_API_KEY=sk-
|
||||||
CUSTOM_LLM_VERIFY_SSL=false
|
CUSTOM_LLM_VERIFY_SSL=false
|
||||||
CUSTOM_SAVE_MODEL_IMAGES=false
|
CUSTOM_SAVE_MODEL_IMAGES=true
|
||||||
|
|
||||||
# CUSTOM_TEXT_LLM_MODEL=
|
# CUSTOM_TEXT_LLM_MODEL=
|
||||||
# CUSTOM_VISION_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_BASE_URL=https://api.deepseek.com
|
||||||
# CUSTOM_LLM_MODEL=deepseek-v4-flash
|
# CUSTOM_LLM_MODEL=deepseek-v4-flash
|
||||||
# CUSTOM_LLM_API_KEY=
|
# CUSTOM_LLM_API_KEY=sk-
|
||||||
# CUSTOM_LLM_VERIFY_SSL=false
|
# CUSTOM_LLM_VERIFY_SSL=false
|
||||||
|
|
||||||
|
|
||||||
@ -95,4 +80,4 @@ CUSTOM_MEMORY_URL=http://localhost:8766/api/room_graph
|
|||||||
CUSTOM_MEMORY_TIMEOUT=2
|
CUSTOM_MEMORY_TIMEOUT=2
|
||||||
CUSTOM_MEMORY_MAX_CHARS=2000
|
CUSTOM_MEMORY_MAX_CHARS=2000
|
||||||
CUSTOM_MEMORY_API_KEY=
|
CUSTOM_MEMORY_API_KEY=
|
||||||
CUSTOM_PREEMPTIVE_GENERATION=true
|
CUSTOM_PREEMPTIVE_GENERATION=false
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from typing import Any
|
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 import llm
|
||||||
from livekit.agents.types import (
|
from livekit.agents.types import (
|
||||||
DEFAULT_API_CONNECT_OPTIONS,
|
DEFAULT_API_CONNECT_OPTIONS,
|
||||||
@ -14,6 +18,9 @@ from livekit.agents.types import (
|
|||||||
)
|
)
|
||||||
from livekit.agents.utils import shortuuid
|
from livekit.agents.utils import shortuuid
|
||||||
|
|
||||||
|
logger = logging.getLogger("beaver-llm")
|
||||||
|
|
||||||
|
|
||||||
def latest_user_text(chat_ctx: llm.ChatContext) -> str:
|
def latest_user_text(chat_ctx: llm.ChatContext) -> str:
|
||||||
for message in reversed(chat_ctx.messages()):
|
for message in reversed(chat_ctx.messages()):
|
||||||
if message.role != "user":
|
if message.role != "user":
|
||||||
@ -49,6 +56,27 @@ class BeaverLLM(llm.LLM):
|
|||||||
def provider(self) -> str:
|
def provider(self) -> str:
|
||||||
return "beaver"
|
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(
|
def chat(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
|
|||||||
@ -690,10 +690,7 @@ async def entrypoint(ctx: JobContext) -> None:
|
|||||||
if not beaver_url:
|
if not beaver_url:
|
||||||
raise RuntimeError(f"CUSTOM_BEAVER_WS_URL or BEAVER_WS_URL is not set in {CUSTOM_ENV_PATH}")
|
raise RuntimeError(f"CUSTOM_BEAVER_WS_URL or BEAVER_WS_URL is not set in {CUSTOM_ENV_PATH}")
|
||||||
|
|
||||||
beaver_peer_id = (
|
beaver_peer_id = _first_env("CUSTOM_BEAVER_PEER_ID", "BEAVER_PEER_ID") or f"livekit-{ctx.room.name}"
|
||||||
_first_env("CUSTOM_BEAVER_PEER_ID", "BEAVER_PEER_ID", "TERMINAL_PEER_ID")
|
|
||||||
or f"livekit-{ctx.room.name}"
|
|
||||||
)
|
|
||||||
beaver_device_name = (
|
beaver_device_name = (
|
||||||
_first_env("CUSTOM_BEAVER_DEVICE_NAME", "BEAVER_DEVICE_NAME", "TERMINAL_DEVICE_NAME")
|
_first_env("CUSTOM_BEAVER_DEVICE_NAME", "BEAVER_DEVICE_NAME", "TERMINAL_DEVICE_NAME")
|
||||||
or "livekit-custom-agent"
|
or "livekit-custom-agent"
|
||||||
@ -704,14 +701,19 @@ async def entrypoint(ctx: JobContext) -> None:
|
|||||||
device_name=beaver_device_name,
|
device_name=beaver_device_name,
|
||||||
model_name=os.getenv("CUSTOM_BEAVER_MODEL", "beaver-terminal"),
|
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
|
text_llm = base_llm
|
||||||
vision_llm = base_llm
|
vision_llm = base_llm
|
||||||
logger.info(
|
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_url,
|
||||||
beaver_peer_id,
|
beaver_peer_id,
|
||||||
beaver_device_name,
|
beaver_device_name,
|
||||||
ctx.room.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"}:
|
elif LLM_PROVIDER in {"hermes", "hermes_gateway", "openclaw"}:
|
||||||
gateway_url = os.getenv("CUSTOM_HERMES_GATEWAY_URL", "").strip()
|
gateway_url = os.getenv("CUSTOM_HERMES_GATEWAY_URL", "").strip()
|
||||||
@ -809,7 +811,7 @@ async def entrypoint(ctx: JobContext) -> None:
|
|||||||
"false_interruption_timeout": 1.0,
|
"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,
|
aec_warmup_duration=3.0,
|
||||||
tts_text_transforms=[
|
tts_text_transforms=[
|
||||||
"filter_emoji",
|
"filter_emoji",
|
||||||
|
|||||||
Reference in New Issue
Block a user