133 lines
4.8 KiB
Python
133 lines
4.8 KiB
Python
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()
|