# -*- coding: utf-8 -*- """Ti 场景编排:报警解释 / 交接班摘要 / NL 查询驾驶舱(EPIC #11)。 复用内核组件(零内核改动,换行业只改模板资产): - ``llm_gateway.LLMGateway``:混合网关主编排(敏感度路由 → 生成 → 幻觉校验 → DLP); - ``llm_gateway.prompts.PromptRegistry``:提示词版本库(alarm_explain / shift_handover / qa); - ``llm_gateway.router.SensitivityRouter``:敏感度路由(行业规则 + 内置保底); - ``rag_kb.RagKnowledgeBase``:领域 RAG(工艺规范 / SOP / 国标三类知识源)。 三个场景均通过 ``TiScenarioRunner`` 暴露,业务方只需一行调用: runner = TiScenarioRunner() result = runner.explain_alarm("氯化炉炉温异常") """ from __future__ import annotations import os from typing import Callable, Dict, List, Optional from llm_gateway.gateway import GatewayResult, LLMGateway from llm_gateway.prompts import PromptRegistry from llm_gateway.router import SensitivityRouter from rag_kb import RagKnowledgeBase, load_kb_config # --------------------------------------------------------------------------- # 场景配置资产路径 # --------------------------------------------------------------------------- def _scenarios_dir() -> str: return os.path.dirname(os.path.abspath(__file__)) def _repo_root() -> str: """仓库根目录(scenarios.py 向上 4 层:llm-scenarios → ti-cl4 → templates → 根)。""" return os.path.dirname(os.path.dirname(os.path.dirname( os.path.dirname(os.path.abspath(__file__))))) def scenarios_config_path() -> str: """场景配置资产(scenarios.template.yaml)路径。""" return os.path.join(_scenarios_dir(), "config", "scenarios.template.yaml") def prompts_config_path() -> str: """复用 llm-gateway 提示词版本库资产路径。""" return os.path.join(_repo_root(), "core", "llm-gateway", "config", "prompts.template.yaml") def kb_config_path() -> str: """复用 rag-kb 知识库模板资产路径。""" return os.path.join(_repo_root(), "core", "rag-kb", "config", "kb.template.yaml") # --------------------------------------------------------------------------- # 演示知识文档集(Ti 场景开箱即用;接入真实客户数据时替换 loader 即可) # --------------------------------------------------------------------------- DEMO_KB_DOCS: Dict[str, str] = { "沸腾氯化工艺规范": ( "沸腾氯化炉采用流态化氯化工艺,炉温控制在 850±50℃," "氯气流量按加料量比例调节,加料比保持 1:2.4~1:2.8。" "炉温异常时优先检查氯气流量与加料系统。" ), "沸腾氯化炉操作手册": ( "开机前确认氯气缓冲罐压力、炉体密封与尾气处理系统正常;" "运行中每 30 分钟记录一次炉温、氯气流量与出料量。" ), "沸腾氯化炉异常处置SOP": ( "SOP-CL-001:炉温超上限(>900℃)时立即降低氯气流量并减少加料," "若 10 分钟内未回落则按紧急停机流程处理,并通知当班班长。" ), "交接班报告生成规范": ( "交接班报告须包含:当班生产概况、设备运行状态、异常与处置记录、" "安全注意事项、待办事项。异常事项必须标注发生时间与处理人。" ), "GB/T 氯气安全使用标准": ( "氯气属于剧毒气体,作业场所应配备气体泄漏检测与报警装置," "作业人员须佩戴防护用品,泄漏时启动应急程序并疏散无关人员。" ), "GB/T 钛及钛合金加工标准": ( "海绵钛产品纯度按 GB/T 标准分级,钛纯度 ≥99.5% 为一级品;" "氯化产物杂质含量影响最终钛纯度,须按批次检验并留样。" ), } def demo_kb_loader(title: str) -> str: """演示文档加载器:按标题返回内置演示文本(换数据源时替换本函数)。""" return DEMO_KB_DOCS.get(title, "") # --------------------------------------------------------------------------- # Ti 场景编排 # --------------------------------------------------------------------------- class TiScenarioRunner: """Template-Ti 一期 LLM 场景统一入口(EPIC #11 主体交付)。 构造参数均可注入(便于测试与替换真实组件);缺省使用仓库模板资产 (提示词版本库 + 领域 RAG + 行业路由规则)构建完整场景编排。 """ def __init__( self, gateway: Optional[LLMGateway] = None, kb: Optional[RagKnowledgeBase] = None, prompts: Optional[PromptRegistry] = None, router: Optional[SensitivityRouter] = None, kb_loader: Callable[[str], str] = demo_kb_loader, ) -> None: prompts = prompts or PromptRegistry.from_template_config(prompts_config_path()) kb = kb or RagKnowledgeBase.from_template_config( load_kb_config(kb_config_path()), loader=kb_loader, ) router = router or SensitivityRouter.from_template_config(router_config_path()) self.kb = kb if gateway is not None: # 注入 gateway:由调用方负责 prompt_name / 高利害配置 self._gateways = { "alarm_explain": gateway, "shift_handover": gateway, "nl_query": gateway, } else: # 每个场景绑定各自的提示词模板版本(可复现) base = dict(prompts=prompts, router=router) self._gateways = { # 报警解释:高利害,命中即启用幻觉校验信度阈值 → 低信度转人工 "alarm_explain": LLMGateway( prompt_name="alarm_explain", high_stakes_names={"alarm_explain"}, **base, ), "shift_handover": LLMGateway( prompt_name="shift_handover", **base, ), "nl_query": LLMGateway(prompt_name="qa", **base), } # -- 场景 1:报警根因解释(高利害) ----------------------------------- def explain_alarm(self, alarm: str, confidence: float = 1.0, top_k: int = 3) -> GatewayResult: """解释一条报警的可能原因与处置建议(RAG 检索异常处置 SOP)。""" hits = self.kb.search(alarm, top_k=top_k, categories=None) sources = [h.source for h in hits] return self._gateways["alarm_explain"].ask( alarm, rag_context=sources, confidence=confidence, ) # -- 场景 2:交接班自动摘要 ------------------------------------------- def generate_handover(self, shift_desc: str, confidence: float = 1.0, top_k: int = 3) -> GatewayResult: """按班次情况生成交接班摘要(RAG 检索交接班规范)。""" hits = self.kb.search(shift_desc, top_k=top_k, categories=None) sources = [h.source for h in hits] return self._gateways["shift_handover"].ask( shift_desc, rag_context=sources, confidence=confidence, ) # -- 场景 3:自然语言查询驾驶舱(NL → 查询意图/指标) ----------------- def query_cockpit(self, question: str, confidence: float = 1.0, top_k: int = 3) -> GatewayResult: """把自然语言问题映射为驾驶舱查询意图(RAG 检索工艺指标定义)。""" hits = self.kb.search(question, top_k=top_k, categories=None) sources = [h.source for h in hits] return self._gateways["nl_query"].ask( question, rag_context=sources, confidence=confidence, ) # -- 审计 -------------------------------------------------------------- def drain_audits(self) -> Dict[str, List[Dict[str, object]]]: """取走网关各组件审计记录(DLP/路由/Prompt/幻觉校验)。""" audits: Dict[str, List[Dict[str, object]]] = { "dlp": [], "router": [], "prompts": [], "guard": [], } for gw in set(self._gateways.values()): for key, rows in gw.drain_audits().items(): audits.setdefault(key, []).extend(rows) return audits def router_config_path() -> str: """复用 llm-gateway 行业路由规则资产路径。""" return os.path.join(_repo_root(), "core", "llm-gateway", "config", "router.template.yaml")