from __future__ import annotations import unittest from app.plugins.base import PluginRegistry from app.schemas.chat import ChatRequest from app.schemas.debug import IntentCandidate, MatcherStageDebug, RoutingDebug from app.schemas.intent import IntentDefinition from app.services.agent_service import AgentService from app.services.intent_registry import IntentRegistry from app.services.planner import PlanningResult from app.services.session_store import InMemorySessionStore class _RouteToCloudRouter: def route(self, text: str): _ = text return type( "RouteResult", (), { "intent": None, "debug": RoutingDebug( selected_intent="cabin_nav_to", matched_stage="fusion", decision="route_to_cloud", decision_reason="local signal is not stable enough, routing to cloud planner", confidence_grade="low", stages=[ MatcherStageDebug( stage="fusion", accepted=False, selected_intent="cabin_nav_to", score=0.88, reason="route to cloud", candidates=[ IntentCandidate(intent_id="cabin_nav_to", score=0.88, reason="fusion", model_name="fusion"), IntentCandidate(intent_id="cabin_play_music", score=0.75, reason="fusion", model_name="fusion"), ], ) ], ), }, )() def extract_slots(self, text: str, intent: IntentDefinition) -> dict[str, object]: _ = (text, intent) return {} class _PlannerRejects: def plan(self, text: str, intents: list[IntentDefinition], context: dict[str, object] | None = None) -> PlanningResult: _ = (text, intents, context) return PlanningResult( accepted=False, workflow_type="single", model_name="qwen3.5-plus", backend="dashscope", reason="cloud planner could not produce a stable executable step", ) class _PlannerOutOfScope: def plan(self, text: str, intents: list[IntentDefinition], context: dict[str, object] | None = None) -> PlanningResult: _ = (text, intents, context) return PlanningResult( accepted=False, workflow_type="single", model_name="qwen3.5-plus", backend="dashscope", reason="The provided intent catalog only contains cabin and service actions. There is no matching intent for ordering food via a third-party app action.", ) def _intent(intent_id: str) -> IntentDefinition: return IntentDefinition( intent_id=intent_id, plugin_id=f"mock.{intent_id}", domain="cabin", keywords=[], examples=[], ) class AgentCloudRouteTests(unittest.TestCase): def test_route_to_cloud_returns_explicit_clarify_feedback_when_planner_does_not_accept(self) -> None: service = AgentService( intent_registry=IntentRegistry([_intent("cabin_nav_to"), _intent("cabin_play_music")]), router=_RouteToCloudRouter(), plugins=PluginRegistry(), session_store=InMemorySessionStore(), planner=_PlannerRejects(), ) response = service.handle_chat( ChatRequest( session_id="sess_cloud_route", user_id="user_1", input_text="带我过去", ) ) self.assertEqual(response.decision, "route_to_cloud") self.assertEqual(response.reply_type, "clarify") self.assertEqual(response.status, "route_to_cloud") self.assertIn("请确认一下", response.reply_text) def test_route_to_cloud_rejects_when_planner_marks_request_out_of_scope(self) -> None: service = AgentService( intent_registry=IntentRegistry([_intent("cabin_nav_to"), _intent("cabin_play_music")]), router=_RouteToCloudRouter(), plugins=PluginRegistry(), session_store=InMemorySessionStore(), planner=_PlannerOutOfScope(), ) response = service.handle_chat( ChatRequest( session_id="sess_cloud_route_reject", user_id="user_1", input_text="去美团叫个外卖", ) ) self.assertEqual(response.reply_type, "reject") self.assertEqual(response.decision, "reject") self.assertEqual(response.status, "rejected") self.assertIn("做不了", response.reply_text) if __name__ == "__main__": unittest.main()