from __future__ import annotations import unittest from app.plugins.base import PluginRegistry from app.plugins.mock import MockPluginExecutor from app.services.classifier import MockIntentClassifier from app.services.agent_service import AgentService from app.services.intent_registry import IntentRegistry from app.services.response_policy import ResponsePolicy from app.services.rewrite_engine import ContextRewriteEngine from app.services.router import HeuristicSlotExtractor, IntentRouter, build_matcher_pipeline from app.services.session_store import InMemorySessionStore from app.schemas.chat import ChatRequest, FillSlotsRequest class _BertLikeMockClassifier(MockIntentClassifier): def predict(self, text, intents): result = super().predict(text, intents) result.model_name = "bert-local" result.backend_name = "bert-local" return result def _build_service() -> AgentService: registry = IntentRegistry.from_json("app/data/intents.json") plugins = PluginRegistry() MockPluginExecutor().register(plugins) router = IntentRouter( matcher=build_matcher_pipeline( registry, ["classifier"], classifier=_BertLikeMockClassifier(threshold=0.0, top_k=3), ), slot_extractor=HeuristicSlotExtractor(), ) return AgentService( intent_registry=registry, router=router, plugins=plugins, session_store=InMemorySessionStore(), rewrite_engine=ContextRewriteEngine(), response_policy=ResponsePolicy(), planner=None, ) class IntentCoverageAndStopTests(unittest.TestCase): def setUp(self) -> None: self.service = _build_service() self.registry = IntentRegistry.from_json("app/data/intents.json") def test_intent_catalog_has_at_least_30_items(self) -> None: self.assertGreaterEqual(len(self.registry.list()), 30) def test_close_ac_routes_to_power_off(self) -> None: response = self.service.handle_chat( ChatRequest( session_id="sess_close_ac", user_id="user_1", input_text="关闭空调", ) ) self.assertEqual(response.reply_type, "workflow_result") self.assertEqual(response.intent, "cabin_ac_off") self.assertIn("已关闭空调", response.reply_text) def test_open_window_is_covered(self) -> None: response = self.service.handle_chat( ChatRequest( session_id="sess_window_open", user_id="user_1", input_text="打开车窗", ) ) self.assertEqual(response.reply_type, "workflow_result") self.assertEqual(response.intent, "cabin_window_open") self.assertIn("已打开车窗", response.reply_text) def test_stop_current_task_while_waiting_for_slot(self) -> None: first = self.service.handle_chat( ChatRequest( session_id="sess_stop_task", user_id="user_1", input_text="空调调到", ) ) self.assertEqual(first.reply_type, "ask_slot") self.assertEqual(first.pending_slots, ["temperature"]) stopped = self.service.handle_fill_slots( FillSlotsRequest( session_id="sess_stop_task", user_id="user_1", input_text="不用了", ) ) self.assertEqual(stopped.reply_type, "text") self.assertEqual(stopped.status, "stopped") self.assertEqual(stopped.pending_slots, []) self.assertIn("已停止当前任务", stopped.reply_text) def test_relative_ac_adjustment_uses_two_degree_step(self) -> None: session = self.service.session_store.get_or_create("sess_ac_lower", "user_1") session.current_intent = "cabin_set_ac" session.context_memory["last_temperature"] = 24 self.service.session_store.save(session) response = self.service.handle_chat( ChatRequest( session_id="sess_ac_lower", user_id="user_1", input_text="调低一点", ) ) self.assertEqual(response.reply_type, "workflow_result") self.assertEqual(response.intent, "cabin_set_ac") self.assertEqual(response.filled_slots.get("temperature"), 22) self.assertIn("22", response.reply_text) def test_relative_ac_adjustment_without_history_uses_default_baseline(self) -> None: session = self.service.session_store.get_or_create("sess_ac_lower_default", "user_1") session.current_intent = "cabin_ac_on" self.service.session_store.save(session) response = self.service.handle_chat( ChatRequest( session_id="sess_ac_lower_default", user_id="user_1", input_text="调低一点", ) ) self.assertEqual(response.reply_type, "workflow_result") self.assertEqual(response.intent, "cabin_set_ac") self.assertEqual(response.filled_slots.get("temperature"), 22) self.assertIn("22", response.reply_text) def test_temperature_is_clamped_before_execution(self) -> None: self.assertEqual(self.service._normalize_temperature_value(-1), 16) self.assertEqual(self.service._normalize_temperature_value(40), 30) if __name__ == "__main__": unittest.main()