Files
iAOP/templates/ti-cl4/llm-scenarios/tests/test_scenarios.py

135 lines
6.0 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.
# -*- coding: utf-8 -*-
"""Template-Ti 一期 LLM 场景层单元测试(EPIC #11)。
覆盖:
1. 场景配置资产可解析且含验收指标(route_accuracy ≥ 96.5%);
2. 三个业务场景端到端走通(报警解释 / 交接班摘要 / NL 查询驾驶舱),
答案带 RAG 引用溯源;
3. 路由准确率 ≥ 96.5%(Ti 场景查询集,验收指标);
4. 幻觉校验:报警解释为高利害场景,低信度转人工(needs_human)。
"""
import os
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import _bootstrap # noqa: F401
from llm_gateway.router import RouteTarget, SensitivityRouter # noqa: E402
from ti_scenarios.scenarios import ( # noqa: E402
TiScenarioRunner,
router_config_path,
scenarios_config_path,
)
def _load_yaml(path):
import yaml
with open(path, "r", encoding="utf-8") as fh:
return yaml.safe_load(fh) or {}
class TestScenarioConfig(unittest.TestCase):
"""场景配置资产:三场景齐备且验收指标达标。"""
def setUp(self):
self.cfg = _load_yaml(scenarios_config_path())
def test_three_scenarios_defined(self):
names = {s["name"] for s in self.cfg["scenarios"]}
self.assertEqual(names, {"alarm_explain", "shift_handover", "nl_query"})
def test_alarm_explain_acceptance_ge_96p5(self):
for s in self.cfg["scenarios"]:
if s["name"] == "alarm_explain":
self.assertGreaterEqual(s["acceptance"]["route_accuracy"], 0.965)
self.assertTrue(s["acceptance"]["hallucination_check"])
self.assertTrue(s["high_stakes"])
class TestRouteAccuracy(unittest.TestCase):
"""验收:路由准确率 ≥ 96.5%(Ti 场景行业规则 + 内核保底规则)。"""
ROUTE_CASES = [
# (query, expected_target, 说明)
("氯气流量是多少", RouteTarget.LOCAL, "工艺敏感参数(行业规则)"),
("炉温现在多少", RouteTarget.LOCAL, "工艺敏感参数(行业规则)"),
("加料比如何调整", RouteTarget.LOCAL, "工艺配比参数(行业规则)"),
("钛纯度合格标准", RouteTarget.LOCAL, "产品质量指标(行业规则)"),
("紧急停机", RouteTarget.BLOCK, "安全指令(默认保底规则)"),
("氯气泄漏立即停机", RouteTarget.BLOCK, "安全指令(默认保底规则)"),
("海绵钛是什么", RouteTarget.CLOUD, "公开常识(行业规则)"),
("身份证号 110101199001011234 是什么", RouteTarget.LOCAL, "PII(默认保底规则)"),
("电话 13800138000 查一下", RouteTarget.LOCAL, "PII(默认保底规则)"),
("今天车间排产如何安排", RouteTarget.LOCAL, "通用管理问题(默认本地)"),
("氯气流量偏低怎么处理", RouteTarget.LOCAL, "工艺敏感参数"),
("炉温超限怎么处置", RouteTarget.LOCAL, "工艺敏感参数"),
("海绵钛纯度检验标准", RouteTarget.LOCAL, "产品质量指标"),
("交接班注意事项", RouteTarget.LOCAL, "管理流程(默认本地)"),
("驾驶舱能看到哪些指标", RouteTarget.LOCAL, "驾驶舱查询(默认本地)"),
("钛锭强度如何", RouteTarget.LOCAL, "工艺/产品参数"),
("氯化炉操作手册要点", RouteTarget.LOCAL, "工艺文档查询"),
("国标氯气安全要求", RouteTarget.LOCAL, "标准文档查询"),
("停机按钮在哪", RouteTarget.BLOCK, "安全指令关键字"),
("海绵钛和钛合金区别", RouteTarget.LOCAL, "工艺/产品对比(保守默认本地)"),
]
def setUp(self):
self.router = SensitivityRouter.from_template_config(router_config_path())
def test_route_accuracy_ge_96p5(self):
total = len(self.ROUTE_CASES)
hit = 0
for query, expected, desc in self.ROUTE_CASES:
decision = self.router.route(query)
if decision.target == expected:
hit += 1
else:
print(f"[route 偏差] {desc} | {query!r} -> {decision.target}(期望 {expected})")
accuracy = hit / total
self.assertGreaterEqual(accuracy, 0.965,
f"路由准确率 {accuracy:.1%} < 96.5%({hit}/{total})")
class TestScenariosE2E(unittest.TestCase):
"""三个场景端到端(复用 llm-gateway 主编排 + 领域 RAG + 演示文档)。"""
def setUp(self):
self.runner = TiScenarioRunner()
def test_explain_alarm_with_sources(self):
result = self.runner.explain_alarm("炉温超上限报警怎么处理")
self.assertIn("报警", result.query)
self.assertTrue(result.answer)
# 高利害场景应带 SOP 引用溯源(答案回显来源)
self.assertIn("来源", result.answer)
self.assertEqual(result.route.target, RouteTarget.LOCAL)
def test_shift_handover(self):
result = self.runner.generate_handover("甲班:生产平稳,炉温正常,无异常事项")
self.assertTrue(result.answer)
self.assertTrue(result.answer_id)
def test_nl_query_cockpit(self):
result = self.runner.query_cockpit("查询最近一小时的氯气流量趋势")
self.assertTrue(result.answer)
self.assertEqual(result.route.target, RouteTarget.LOCAL)
def test_high_stakes_alarm_low_confidence_needs_human(self):
"""幻觉校验:报警解释低信度 → 转人工(needs_human=True)。"""
result = self.runner.explain_alarm("炉温超上限报警", confidence=0.2)
self.assertTrue(result.needs_human,
"高利害场景低信度应转人工确认")
# 高信度正常通过
ok = self.runner.explain_alarm("炉温超上限报警", confidence=0.95)
self.assertFalse(ok.needs_human)
def test_drain_audits_available(self):
self.runner.explain_alarm("氯气流量报警")
audits = self.runner.drain_audits()
self.assertEqual(set(audits), {"dlp", "router", "prompts", "guard"})
if __name__ == "__main__":
unittest.main()