Update project and configurations

This commit is contained in:
Zou-Seay
2026-06-11 16:28:00 +08:00
parent 12d3922091
commit a29a91867d
237 changed files with 164880 additions and 90 deletions

View File

@@ -0,0 +1,184 @@
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()