from __future__ import annotations import asyncio from beaver.engine.providers.base import LLMProvider, LLMResponse from beaver.tasks import MainAgentRouter, TaskRecord class RouterProvider(LLMProvider): def __init__(self, response: str | Exception) -> None: super().__init__() self.response = response self.calls: list[dict] = [] async def chat( self, messages: list[dict], tools: list[dict] | None = None, model: str | None = None, max_tokens: int = 4096, temperature: float = 0.7, thinking_enabled: bool | None = None, ) -> LLMResponse: self.calls.append( { "max_tokens": max_tokens, "temperature": temperature, "model": model, "thinking_enabled": thinking_enabled, } ) if isinstance(self.response, Exception): raise self.response return LLMResponse(content=self.response, finish_reason="stop", provider_name="stub", model="stub-model") def get_default_model(self) -> str: return "stub-model" def _task() -> TaskRecord: return TaskRecord( task_id="task-1", session_id="web:task", description="实现任务连续性", goal="实现任务连续性", constraints=[], priority=0, status="awaiting_feedback", creator="test", created_at="now", updated_at="now", metadata={"short_title": "任务连续性"}, ) def test_router_continues_active_task_from_llm_decision() -> None: provider = RouterProvider('{"action":"continue_task","reason":"related","short_title":"任务连续性"}') decision = asyncio.run( MainAgentRouter().classify( "再把输入框标识也补上", active_task=_task(), provider=provider, ) ) assert decision.is_task assert decision.starts_new_task is False assert decision.short_title == "任务连续性" assert provider.calls[0]["max_tokens"] == 256 def test_router_receives_thinking_mode() -> None: provider = RouterProvider('{"action":"simple_chat","reason":"simple"}') decision = asyncio.run( MainAgentRouter().classify( "你好", provider=provider, thinking_enabled=False, ) ) assert not decision.is_task assert provider.calls[0]["thinking_enabled"] is False def test_router_closes_active_task_from_llm_decision() -> None: decision = asyncio.run( MainAgentRouter().classify( "这个任务结束了", active_task=_task(), provider=RouterProvider('{"action":"close_task","reason":"user said done"}'), ) ) assert not decision.is_task assert decision.closes_task is True def test_router_fallback_keeps_active_task_but_not_new_task() -> None: active = asyncio.run( MainAgentRouter().classify( "继续", active_task=_task(), provider=RouterProvider(RuntimeError("provider down")), ) ) inactive = asyncio.run( MainAgentRouter().classify( "implement something", active_task=None, provider=RouterProvider(RuntimeError("provider down")), ) ) assert active.is_task assert not inactive.is_task