# ============================================================ # M-F 黄金测试:多轮澄清槽位机(低置信 → 澄清卡 → 序号回填) # ============================================================ from __future__ import annotations import pytest from server.agent_core import dialog from server.contracts import AgentReply, IntentResult from server.state.seed import seed_world class FakeStore: def __init__(self): self.data = seed_world() self._counters: dict[str, int] = {} def next_id(self, kind: str) -> int: self._counters[kind] = self._counters.get(kind, 0) + 1 return self._counters[kind] def save(self) -> None: pass @pytest.fixture(autouse=True) def _isolate(monkeypatch): monkeypatch.setenv("APS_DB_DISABLED", "1") dialog._SESSIONS.clear() yield dialog._SESSIONS.clear() def test_unknown_intent_with_domain_yields_clarify_card(): store = FakeStore() intent = IntentResult(intent="unknown", params={}, confidence=0.2, source="RULE_FAST") reply = dialog.post_route(store, "s1", "那个排产的东西弄一下", intent) assert isinstance(reply, AgentReply) assert reply.blocks and reply.blocks[0].type == "clarify" options = reply.blocks[0].props["options"] assert any(o["intent"] == "schedule.wizard" for o in options) assert "1." in reply.text def test_clarify_resolves_by_index(): store = FakeStore() intent = IntentResult(intent="unknown", params={}, confidence=0.2, source="RULE_FAST") dialog.post_route(store, "s2", "订单那块看看", intent) resolved = dialog.pre_route(store, "s2", "第一个") assert isinstance(resolved, IntentResult) assert resolved.intent == "order.pool" assert resolved.confidence == 1.0 # 回填后澄清态清空 assert dialog._SESSIONS["s2"]["clarify"] is None def test_clarify_resolves_by_label_keyword(): store = FakeStore() intent = IntentResult(intent="unknown", params={}, confidence=0.3, source="RULE_FAST") dialog.post_route(store, "s3", "主数据相关", intent) resolved = dialog.pre_route(store, "s3", "工时维护情况") assert isinstance(resolved, IntentResult) assert resolved.intent == "readiness.query" def test_clarify_gives_up_after_two_rounds(): store = FakeStore() intent = IntentResult(intent="unknown", params={}, confidence=0.2, source="RULE_FAST") assert dialog.post_route(store, "s4", "排产啊", intent) is not None # 第 1 轮 assert dialog.pre_route(store, "s4", "听不懂你说啥") is None # 未回填 assert dialog.post_route(store, "s4", "就是那个啥", intent) is not None # 第 2 轮 assert dialog.pre_route(store, "s4", "还是不懂") is None # 第 3 轮:放弃澄清 → 回兜底 assert dialog.post_route(store, "s4", "嗯", intent) is None def test_high_confidence_skips_clarification(): store = FakeStore() intent = IntentResult(intent="flex.schedule", params={}, confidence=0.95, source="RULE_FAST") assert dialog.post_route(store, "s5", "柔性排产", intent) is None def test_mid_confidence_triggers_guess_confirm(): store = FakeStore() intent = IntentResult(intent="flex.capacity", params={}, confidence=0.6, source="LLM") reply = dialog.post_route(store, "s6", "产能那个看下", intent) assert isinstance(reply, AgentReply) # 首选候选 = 识别出的意图本身(猜测+确认) assert reply.blocks[0].props["options"][0]["intent"] == "flex.capacity"