feat: beaver first commit
This commit is contained in:
@ -4,7 +4,7 @@ import asyncio
|
|||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from typing import Any
|
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 import llm
|
||||||
from livekit.agents.types import (
|
from livekit.agents.types import (
|
||||||
DEFAULT_API_CONNECT_OPTIONS,
|
DEFAULT_API_CONNECT_OPTIONS,
|
||||||
@ -14,7 +14,6 @@ from livekit.agents.types import (
|
|||||||
)
|
)
|
||||||
from livekit.agents.utils import shortuuid
|
from livekit.agents.utils import shortuuid
|
||||||
|
|
||||||
|
|
||||||
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":
|
||||||
|
|||||||
@ -686,18 +686,16 @@ async def entrypoint(ctx: JobContext) -> None:
|
|||||||
stt_stream = stt.StreamAdapter(stt=blackbox_stt, vad=ctx.proc.userdata["vad"])
|
stt_stream = stt.StreamAdapter(stt=blackbox_stt, vad=ctx.proc.userdata["vad"])
|
||||||
|
|
||||||
if LLM_PROVIDER == "beaver":
|
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:
|
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 = (
|
||||||
os.getenv("CUSTOM_BEAVER_PEER_ID")
|
_first_env("CUSTOM_BEAVER_PEER_ID", "BEAVER_PEER_ID", "TERMINAL_PEER_ID")
|
||||||
or os.getenv("BEAVER_PEER_ID")
|
|
||||||
or f"livekit-{ctx.room.name}"
|
or f"livekit-{ctx.room.name}"
|
||||||
)
|
)
|
||||||
beaver_device_name = (
|
beaver_device_name = (
|
||||||
os.getenv("CUSTOM_BEAVER_DEVICE_NAME")
|
_first_env("CUSTOM_BEAVER_DEVICE_NAME", "BEAVER_DEVICE_NAME", "TERMINAL_DEVICE_NAME")
|
||||||
or os.getenv("BEAVER_DEVICE_NAME")
|
|
||||||
or "livekit-custom-agent"
|
or "livekit-custom-agent"
|
||||||
)
|
)
|
||||||
base_llm = BeaverLLM(
|
base_llm = BeaverLLM(
|
||||||
@ -709,10 +707,11 @@ async def entrypoint(ctx: JobContext) -> None:
|
|||||||
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",
|
"Using Beaver gateway url=%s peer_id=%s device_name=%s room=%s",
|
||||||
beaver_url,
|
beaver_url,
|
||||||
beaver_peer_id,
|
beaver_peer_id,
|
||||||
beaver_device_name,
|
beaver_device_name,
|
||||||
|
ctx.room.name,
|
||||||
)
|
)
|
||||||
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()
|
||||||
@ -1000,6 +999,14 @@ def _env_bool(name: str, default: bool) -> bool:
|
|||||||
return default
|
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:
|
def _recording_options_from_env() -> RecordingOptions:
|
||||||
return RecordingOptions(
|
return RecordingOptions(
|
||||||
audio=_env_bool("CUSTOM_RECORD_AUDIO", False),
|
audio=_env_bool("CUSTOM_RECORD_AUDIO", False),
|
||||||
|
|||||||
@ -3,7 +3,10 @@ import json
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
|
try:
|
||||||
from custom.beaver_llm import BeaverLLM, latest_user_text
|
from custom.beaver_llm import BeaverLLM, latest_user_text
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
from beaver_llm import BeaverLLM, latest_user_text
|
||||||
from livekit.agents import ChatContext
|
from livekit.agents import ChatContext
|
||||||
|
|
||||||
|
|
||||||
@ -83,16 +86,13 @@ async def test_beaver_llm_sends_latest_user_text_and_returns_reply(
|
|||||||
await runner.cleanup()
|
await runner.cleanup()
|
||||||
|
|
||||||
assert collected.text == "beaver reply"
|
assert collected.text == "beaver reply"
|
||||||
assert received == [
|
assert received[0] == {
|
||||||
{
|
|
||||||
"type": "connect",
|
"type": "connect",
|
||||||
"peer_id": "livekit-room",
|
"peer_id": "livekit-room",
|
||||||
"device_name": "livekit-custom-agent",
|
"device_name": "livekit-custom-agent",
|
||||||
"capabilities": ["text"],
|
"capabilities": ["text"],
|
||||||
},
|
}
|
||||||
{
|
assert received[1]["type"] == "message"
|
||||||
"type": "message",
|
assert received[1]["message_id"].startswith("livekit-room-")
|
||||||
"message_id": "livekit-room-000001",
|
assert received[1]["message_id"].endswith("-000001")
|
||||||
"text": "hello beaver",
|
assert received[1]["text"] == "hello beaver"
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|||||||
@ -58,6 +58,13 @@ def test_message_id_generator_uses_monotonic_peer_counter() -> None:
|
|||||||
assert generator.counter == 9
|
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(
|
async def test_client_connects_sends_text_and_returns_assistant_reply(
|
||||||
unused_tcp_port: int,
|
unused_tcp_port: int,
|
||||||
) -> None:
|
) -> 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",
|
url=f"http://127.0.0.1:{unused_tcp_port}/api/channels/terminal-dev/ws",
|
||||||
peer_id="device-001",
|
peer_id="device-001",
|
||||||
device_name="desk-terminal",
|
device_name="desk-terminal",
|
||||||
|
message_ids=MessageIdGenerator(peer_id="device-001"),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
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",
|
url=f"http://127.0.0.1:{unused_tcp_port}/api/channels/terminal-dev/ws",
|
||||||
peer_id="device-001",
|
peer_id="device-001",
|
||||||
device_name="desk-terminal",
|
device_name="desk-terminal",
|
||||||
|
message_ids=MessageIdGenerator(peer_id="device-001"),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
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",
|
url=f"http://127.0.0.1:{unused_tcp_port}/api/channels/terminal-dev/ws",
|
||||||
peer_id="device-001",
|
peer_id="device-001",
|
||||||
device_name="desk-terminal",
|
device_name="desk-terminal",
|
||||||
|
message_ids=MessageIdGenerator(peer_id="device-001"),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
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",
|
url=f"http://127.0.0.1:{unused_tcp_port}/api/channels/terminal-dev/ws",
|
||||||
peer_id="device-001",
|
peer_id="device-001",
|
||||||
device_name="desk-terminal",
|
device_name="desk-terminal",
|
||||||
|
message_ids=MessageIdGenerator(peer_id="device-001"),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
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",
|
url=f"http://127.0.0.1:{unused_tcp_port}/api/channels/terminal-dev/ws",
|
||||||
peer_id="device-001",
|
peer_id="device-001",
|
||||||
device_name="desk-terminal",
|
device_name="desk-terminal",
|
||||||
|
message_ids=MessageIdGenerator(peer_id="device-001"),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
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",
|
url=f"http://127.0.0.1:{unused_tcp_port}/api/channels/terminal-dev/ws",
|
||||||
peer_id="device-001",
|
peer_id="device-001",
|
||||||
device_name="desk-terminal",
|
device_name="desk-terminal",
|
||||||
|
message_ids=MessageIdGenerator(peer_id="device-001"),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user