"""Scene routing configuration (架构文档 §8.1: 改模型不改代码).""" from __future__ import annotations import pytest from packages.llm.errors import SceneNotRoutedError from packages.llm.routing import SceneRouter, build_scene_configs from packages.llm.types import Scene def test_defaults_put_the_primary_first_and_the_backup_second() -> None: configs = build_scene_configs(primary_provider="deepseek", backup_provider="zhipu") assert configs[Scene.AGENT_CHAT].providers == ("deepseek", "zhipu") def test_a_missing_backup_yields_a_single_provider_chain() -> None: configs = build_scene_configs(primary_provider="deepseek", backup_provider=None) assert configs[Scene.AGENT_CHAT].providers == ("deepseek",) def test_every_scene_is_routed_and_bound_to_a_prompt_version() -> None: configs = build_scene_configs(primary_provider="deepseek") assert set(configs) == set(Scene) for config in configs.values(): assert config.prompt_id assert config.prompt_version def test_intent_classification_is_critical_so_budget_pressure_cannot_disable_it() -> None: configs = build_scene_configs(primary_provider="deepseek") assert configs[Scene.INTENT_CLASSIFY].critical is True assert configs[Scene.AGENT_CHAT].critical is False def test_agent_chat_output_budget_matches_the_token_plan() -> None: # 架构文档 §7.2: 输出 ≤ 800 assert build_scene_configs(primary_provider="x")[Scene.AGENT_CHAT].max_tokens <= 800 def test_overrides_change_routing_without_a_code_change() -> None: configs = build_scene_configs( primary_provider="deepseek", backup_provider="zhipu", overrides={ "agent_chat": { "max_tokens": 1200, "prompt_version": "v2", "models": {"deepseek": "deepseek-v4-pro"}, "providers": ["zhipu", "deepseek"], } }, ) config = configs[Scene.AGENT_CHAT] assert config.max_tokens == 1200 assert config.prompt_version == "v2" assert config.providers == ("zhipu", "deepseek") assert config.model_for("deepseek", "fallback") == "deepseek-v4-pro" assert config.model_for("zhipu", "fallback") == "fallback" # Untouched scenes keep their defaults. assert configs[Scene.REPORT_COPY].max_tokens == 3000 def test_structured_retry_budget_is_configurable() -> None: configs = build_scene_configs(primary_provider="x", structured_max_retries=5) assert configs[Scene.AGENT_CHAT].structured_max_retries == 5 def test_router_raises_for_an_unconfigured_scene() -> None: router = SceneRouter({}) with pytest.raises(SceneNotRoutedError): router.resolve(Scene.AGENT_CHAT) def test_router_lists_configured_scenes() -> None: router = SceneRouter(build_scene_configs(primary_provider="x")) assert set(router.scenes()) == set(Scene)