90 lines
3.5 KiB
Python
90 lines
3.5 KiB
Python
|
|
# ============================================================
|
||
|
|
# 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"
|