from __future__ import annotations import asyncio from types import SimpleNamespace from beaver.engine.providers.base import LLMProvider, LLMResponse from beaver.engine.providers.factory import ProviderBundle from beaver.tasks import TaskExecutionPlanner, TaskRecord class PlannerProvider(LLMProvider): def __init__(self) -> None: super().__init__() self.calls = 0 async def chat( self, messages: list[dict], tools: list[dict] | None = None, model: str | None = None, max_tokens: int = 4096, temperature: float = 0.7, ) -> LLMResponse: self.calls += 1 return LLMResponse( content='{"mode":"team"}', 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="session-1", description="implement workflow", goal="implement workflow", constraints=[], priority=0, status="open", creator="test", created_at="now", updated_at="now", ) def _bundle(provider: PlannerProvider) -> ProviderBundle: return ProviderBundle( main_runtime=SimpleNamespace(model="stub-model", provider_name="stub"), main_provider=provider, ) def test_planner_skips_provider_for_simple_task() -> None: provider = PlannerProvider() task = _task() task.description = "查询深圳天气" task.goal = "查询深圳天气" plan = asyncio.run( TaskExecutionPlanner().plan( task=task, user_message="帮我查一下今天深圳天气", attempt_index=1, provider_bundle=_bundle(provider), ) ) assert plan.mode == "single" assert plan.graph is None assert plan.reason == "planner_skipped_simple_task" assert provider.calls == 0 def test_planner_replaces_team_planning_with_workflow_tools_without_provider_call() -> None: provider = PlannerProvider() plan = asyncio.run( TaskExecutionPlanner().plan( task=_task(), user_message="research and compare workflow options", attempt_index=1, provider_bundle=_bundle(provider), skill_summaries=["docker-debug: Use docker logs before editing config."], tool_hints=["terminal", "search_files"], ) ) assert not plan.is_team assert plan.mode == "single" assert plan.graph is None assert plan.reason == "planner_team_replaced_by_workflow_tools" assert plan.final_synthesis_instruction == "" assert provider.calls == 0 def test_planner_can_be_disabled_by_environment(monkeypatch) -> None: monkeypatch.setenv("BEAVER_AGENT_TEAM_ENABLED", "0") provider = PlannerProvider() plan = asyncio.run( TaskExecutionPlanner().plan( task=_task(), user_message="research and compare workflow options", attempt_index=1, provider_bundle=_bundle(provider), ) ) assert plan.mode == "single" assert plan.reason == "planner_disabled_by_environment" assert provider.calls == 0 def test_planner_no_longer_exposes_json_to_team_graph_parser() -> None: assert not hasattr(TaskExecutionPlanner(), "from_json")