Files
ai-device/intelligent_cabin/archive/tests/test_workflow_templates.py
2026-06-11 16:28:00 +08:00

185 lines
7.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import unittest
from pathlib import Path
from app.schemas.configuration import WorkflowTemplatesConfig
from app.services.classifier import ClassificationResult
from app.services.multi_intent_detector import MultiIntentCandidate, MultiIntentDetectionResult
from app.services.planner import CompositeWorkflowPlanner, HeuristicWorkflowPlanner, TemplateWorkflowPlanner
from app.services.intent_registry import IntentRegistry
class FakeClauseClassifier:
def __init__(self, predictions: dict[str, list[dict[str, float | str]]]) -> None:
self._predictions = predictions
def predict(self, text, intents):
_ = intents
return ClassificationResult(
intent=None,
score=0.0,
model_name="fake-bert-clause",
backend_name="fake-bert-clause",
raw_candidates=self._predictions.get(text, []),
)
class FakeMultiIntentDetector:
def __init__(self, predictions: dict[str, list[tuple[str, float]]]) -> None:
self._predictions = predictions
def detect(self, text, intents):
_ = intents
candidates = [
MultiIntentCandidate(intent_id=intent_id, score=score, label=intent_id)
for intent_id, score in self._predictions.get(text, [])
]
return MultiIntentDetectionResult(
detected=len(candidates) >= 2,
candidates=candidates,
reason="fake detector",
backend_name="fake-multi",
)
class WorkflowTemplateTests(unittest.TestCase):
def setUp(self) -> None:
self.registry = IntentRegistry.from_json("app/data/intents.json")
self.templates = WorkflowTemplatesConfig.model_validate_json(
Path("config/workflows.yml").read_text(encoding="utf-8")
)
def test_template_planner_matches_sequence_template(self) -> None:
planner = TemplateWorkflowPlanner(self.templates)
result = planner.plan("打开车窗然后把空调调到20度", self.registry.list())
self.assertTrue(result.accepted)
self.assertEqual(result.backend, "local-template")
self.assertEqual(result.workflow_type, "sequence")
self.assertEqual([step.intent_id for step in result.steps], ["cabin_window_open", "cabin_set_ac"])
self.assertEqual(result.steps[1].slots.get("temperature"), 20)
def test_template_planner_matches_conditional_template(self) -> None:
planner = TemplateWorkflowPlanner(self.templates)
result = planner.plan("查一下订单A123456如果还没发货就取消", self.registry.list())
self.assertTrue(result.accepted)
self.assertEqual(result.workflow_type, "conditional")
self.assertEqual([step.intent_id for step in result.steps], ["cs_query_order", "cs_cancel_order"])
self.assertEqual(result.steps[1].depends_on, [1])
self.assertTrue(result.steps[1].requires_confirmation)
def test_composite_planner_falls_back_to_heuristic_when_template_misses(self) -> None:
planner = CompositeWorkflowPlanner([TemplateWorkflowPlanner(self.templates), HeuristicWorkflowPlanner()])
result = planner.plan("打开车窗,并且播放轻音乐", self.registry.list())
self.assertTrue(result.accepted)
self.assertIn(result.backend, {"local-template", "local-heuristic"})
self.assertEqual(result.workflow_type, "sequence")
def test_heuristic_planner_parses_ac_then_window_close_sequence(self) -> None:
planner = HeuristicWorkflowPlanner()
result = planner.plan("打开空调,再把窗户降下来", self.registry.list())
self.assertTrue(result.accepted)
self.assertEqual(result.backend, "local-heuristic")
self.assertEqual(result.workflow_type, "sequence")
self.assertEqual([step.intent_id for step in result.steps], ["cabin_ac_on", "cabin_window_close"])
def test_planner_metadata_contains_clause_analysis(self) -> None:
planner = HeuristicWorkflowPlanner()
result = planner.plan("打开空调,然后打开车窗", self.registry.list())
self.assertTrue(result.accepted)
self.assertTrue(result.metadata.get("multi_intent_detected"))
clause_analysis = result.metadata.get("clause_analysis", [])
self.assertEqual(len(clause_analysis), 2)
self.assertEqual(clause_analysis[0].get("selected_intent_id"), "cabin_ac_on")
self.assertEqual(clause_analysis[1].get("selected_intent_id"), "cabin_window_open")
def test_heuristic_planner_supports_shared_action_parallel_objects(self) -> None:
planner = HeuristicWorkflowPlanner()
result = planner.plan("打开空调和车窗", self.registry.list())
self.assertTrue(result.accepted)
self.assertEqual(result.workflow_type, "sequence")
self.assertEqual([step.intent_id for step in result.steps], ["cabin_ac_on", "cabin_window_open"])
def test_heuristic_planner_supports_parallel_objects_with_suffix_action(self) -> None:
planner = HeuristicWorkflowPlanner()
result = planner.plan("把车窗和天窗打开", self.registry.list())
self.assertTrue(result.accepted)
self.assertEqual(result.workflow_type, "sequence")
self.assertEqual([step.intent_id for step in result.steps], ["cabin_window_open", "cabin_sunroof_open"])
def test_heuristic_planner_supports_parallel_clause_with_bing_connector(self) -> None:
planner = HeuristicWorkflowPlanner()
result = planner.plan("打开车窗并播放轻音乐", self.registry.list())
self.assertTrue(result.accepted)
self.assertEqual(result.workflow_type, "sequence")
self.assertEqual([step.intent_id for step in result.steps], ["cabin_window_open", "cabin_play_music"])
def test_heuristic_planner_can_use_clause_classifier_to_rescue_semantic_clause(self) -> None:
planner = HeuristicWorkflowPlanner(
clause_classifier=FakeClauseClassifier(
{
"车里太闷了": [
{"label": "cabin_window_open", "intent_id": "cabin_window_open", "score": 0.83},
],
"来点轻音乐": [
{"label": "cabin_play_music", "intent_id": "cabin_play_music", "score": 0.91},
],
}
)
)
result = planner.plan("车里太闷了,然后来点轻音乐", self.registry.list())
self.assertTrue(result.accepted)
self.assertEqual(result.workflow_type, "sequence")
self.assertEqual([step.intent_id for step in result.steps], ["cabin_window_open", "cabin_play_music"])
clause_analysis = result.metadata.get("clause_analysis", [])
self.assertGreater(clause_analysis[0].get("candidates", [])[0].get("model_score", 0.0), 0.8)
def test_heuristic_planner_can_use_multi_intent_detector_prior(self) -> None:
planner = HeuristicWorkflowPlanner(
clause_classifier=FakeClauseClassifier(
{
"来点轻音乐": [
{"label": "cabin_play_music", "intent_id": "cabin_play_music", "score": 0.91},
],
}
),
multi_intent_detector=FakeMultiIntentDetector(
{
"顺便开下车窗,再来点轻音乐": [
("cabin_window_open", 0.87),
("cabin_play_music", 0.82),
]
}
),
)
result = planner.plan("顺便开下车窗,再来点轻音乐", self.registry.list())
self.assertTrue(result.accepted)
self.assertEqual([step.intent_id for step in result.steps], ["cabin_window_open", "cabin_play_music"])
detector_meta = result.metadata.get("multi_intent_detector") or {}
self.assertTrue(detector_meta.get("detected"))
self.assertEqual(len(detector_meta.get("candidates", [])), 2)
if __name__ == "__main__":
unittest.main()