Preformu / core /kernel /skill_base.py
Kevinshh's picture
feat: 意图保真(intent-fidelity) + 描述性梳理技能 + 相容性引擎升级; 修复转置宽表解析/CQA对账/澄清交互/功能切换串显; .gitignore 排除专利与机密Demo数据
0e6887b
Raw
History Blame Contribute Delete
6.27 kB
"""技能契约 PharmaSkill 与三层数据载体类型。
本模块定义「底座 + 可插拔 Skill」平台的核心**类型契约**,用类型签名把
三层职责锁死(需求 1.4、2.1):
- Layer 1 ``extract``:可用 LLM,仅产出结构化数据(``ExtractedData``)。
- Layer 2 ``compute``:**纯 Python**,方法签名不接收任何服务 / LLM 句柄,
从源头杜绝幻觉计算(需求 2.1 的硬保证)。
- Layer 3 ``explain``:LLM 仅将只读计算结果转写为专业文本,禁止任何数值计算。
数据载体一律采用 ``dataclass``,确保三层之间传递的是结构化只读对象。
设计要点(对应 design.md「1. 技能契约」):``compute(self, data)`` **不传 ``svc``**,
任何 Skill 作者即便想在计算阶段调用 LLM,也没有句柄可用;审计追踪通过返回
``ComputeResult.trace`` 由底座统一落库,而非在计算内部调用服务。
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING
if TYPE_CHECKING: # pragma: no cover - 仅供类型检查,避免运行期循环依赖
# ``Services`` 容器由后续任务(kernel/services.py)实现。
# 此处以前向引用方式标注,使本契约模块可独立导入。
from .services import Services
class InputKind(str, Enum):
"""Skill 声明可处理的输入类型,供底座路由匹配(需求 1.5)。"""
FILE = "file"
SMILES = "smiles"
EXCIPIENT = "excipient"
TEXT = "text"
@dataclass
class SkillMeta:
"""Skill 元数据:身份、展示与路由信息(需求 1.4)。"""
id: str
display_name: str
description: str
version: str
input_kinds: set[InputKind]
icon: str = "💊"
order: int = 100 # 导航排序
#: 是否走「理解层 → 任务单 → 用户确认」两阶段流程。
#: 文档驱动、意图可能模糊的分析(稳定性 / 描述性梳理)应为 True(默认);
#: 输入签名确定、用户已显式选定的技能(通用问答 / 相容性)应设为 False,
#: 直接走固定流水线(extract→compute→explain),避免被意图澄清误拦。
two_phase: bool = True
@dataclass
class RawInput:
"""底座收集的原始输入(未结构化)。"""
smiles: str = ""
excipient: str = ""
goal: str = ""
files: list = field(default_factory=list)
extra: dict = field(default_factory=dict)
@dataclass
class ExtractedData:
"""Layer1 输出:结构化数据。
``payload`` 由各 Skill 定义其 schema;``method`` 标注提取方式。
"""
payload: dict
notes: str = ""
method: str = "" # "llm" | "heuristic" | "manual"
@dataclass
class ComputeResult:
"""Layer2 输出:只读计算结果 + 图表数据 + 审计追踪。
- ``summary``:核心计算结论(k 值、R²、外推预测值、货架期等)。
- ``figures``:``chart_service`` 可渲染的结构化图表数据。
- ``trace``:计算追踪条目,由底座统一落审计库(需求 2 / 9.5)。
- ``can_proceed`` / ``refusal``:数据不足等情形的优雅拒绝(需求 8.3)。
"""
summary: dict
figures: dict = field(default_factory=dict)
trace: list = field(default_factory=list)
can_proceed: bool = True
refusal: dict | None = None
@dataclass
class ReportSections:
"""Layer3 输出:各段落文本(HTML 片段)。"""
sections: dict
html: str = ""
class PharmaSkill(ABC):
"""统一技能契约。
每个分析功能作为实现本契约的自包含 Skill 存在。底座固定流水线为
``extract → compute → explain → report``。
**关键架构约束(需求 2.1)**:``compute(self, data)`` 的签名**不含**
``svc`` 等服务 / LLM 句柄——这是从类型层面阻断「大模型幻觉计算」的硬保证。
"""
#: Skill 元数据。子类必须以类属性或实例属性形式提供。
meta: SkillMeta
@abstractmethod
def render_inputs(self, st_ctx) -> RawInput:
"""在 Streamlit 中渲染本 Skill 的输入面板,返回 ``RawInput``。"""
@abstractmethod
def extract(self, raw: RawInput, svc: "Services") -> ExtractedData:
"""Layer1:可用 ``svc.llm`` 做提取,仅产出结构化数据。"""
@abstractmethod
def compute(self, data: ExtractedData) -> ComputeResult:
"""Layer2:纯 Python。
注意签名无 ``svc``——禁止 LLM 介入计算。任何数值结论必须由确定性
Python 计算得出,审计追踪通过返回 ``ComputeResult.trace`` 暴露给底座。
"""
@abstractmethod
def explain(self, result: ComputeResult, svc: "Services") -> ReportSections:
"""Layer3:用 ``svc.llm`` 将只读结果转写为文字,禁止改数值。"""
def can_handle(self, raw: RawInput) -> float:
"""返回 0..1 的可处理置信度,供 Router 兜底打分(可选覆写)。"""
return 0.0
def _t(self, key: str, default: str = "") -> str:
"""取本 Skill 输入面板文案:优先底座注入的绑定翻译器(随语言切换),
否则回退到 ``default``(中文兜底)。
底座在渲染前通过 ``app._safe_render_inputs`` 注入 ``self._i18n_t``
(一个接收局部键、返回本地化文案的可调用对象,需求 3 / 17.4)。无注入
(如无 GUI 的纯逻辑单测)时,返回 ``default`` 以保持原有行为,不抛异常。
"""
translator = getattr(self, "_i18n_t", None)
if callable(translator):
try:
text = translator(key)
# 绑定翻译器在 key 缺失时会回退为 key 本身;此时改用 default 兜底。
if text and text != key and not str(text).endswith(f".{key}"):
return text
except Exception: # noqa: BLE001 - 取用失败回退 default
pass
return default
__all__ = [
"InputKind",
"SkillMeta",
"RawInput",
"ExtractedData",
"ComputeResult",
"ReportSections",
"PharmaSkill",
]