完整教程:LLM + 反思架构,改进病历质控

LLM + 反思架构,改进病历质控


环境配置

pip install langchain-openai langchain langgraph rich python-dotenv openai pydantic
langchain==1.0.2
langchain-core==1.0.0
langchain-openai==1.0.1
langgraph==1.0.1
langgraph-checkpoint==3.0.0
langgraph-prebuilt==1.0.1
langgraph-sdk==0.2.9
openai==2.6.0
pydantic==2.12.3
pydantic_core==2.41.4
python-dotenv==1.1.1
rich==14.2.0

任务观察

观察1: 人类专家工作流可被结构化拆解

具体数据:

临床场景: 住院医师写初稿 → 上级医师修改 → 自我反思提升
技术映射: Generate(生成)Critique(批判)Refine(优化)

观察洞察:

  • 敏锐捕捉到角色切换是关键机制
  • 医生的"修改过程"本质是多视角审视(从执行者切换到审查者)

观察2: 数据结构是质量控制的"契约"

具体数据:

# 观察点: 作者为每个阶段定义精确数据模型
DraftEMR → 4个必填字段(主诉、现病史、诊断、计划)
EMRCritique → 3个布尔判断 + 建议列表 + 总结
RefinedEMR → 终稿文本 + 修改说明

观察洞察:

  • 发现: LLM输出的随机性需要通过强类型约束驯服
  • 引入Pydantic后,问题从"输出是否合理"转化为"字段是否填写"

变量识别:

  • 关键变量: 数据模型的字段设计(直接影响输出质量)
  • 控制手段: with_structured_output + Field描述

观察3: 评估体系需要"第三方视角"

具体数据:

# 观察点: 引入独立的"裁判模型"
EMREvaluation: 4维度打分(临床准确性/完整性/清晰度/合规性)
评分范围: 1-10+ 理由说明

观察洞察:

  • 意识到: 自我评估存在认知偏差(生成者无法客观评价自己)
  • 通过"LLM-as-a-Judge"引入外部标准(类似同行评审机制)

变量识别:

  • 独立变量: 是否使用独立评估模型
  • 因变量: 优化效果的可信度

2. 核心假设

假设1: 角色切换能激发LLM的多视角能力

假设表述:

“通过prompt切换角色(住院医师→质控专家→高级医师),LLM能模拟不同专业水平的认知视角”

验证设计:

# 三个节点的角色定义
generator_node: "你是一名住院医师..."
critic_node: "你是一名三甲医院病历质控专家..."
refiner_node: "你是一名高年资主治医师..."

假设依据:

  • 类比人类场景: 医生在自我修改时会"跳出来"以审查者身份重新审视
  • 技术基础: LLM在预训练中已内化不同专业角色的语料模式

假设2: 结构化约束能对抗LLM的"幻觉"倾向

假设表述:

“通过Pydantic强制输出格式,可将自由文本生成转化为填空题,降低错误率”

验证设计:

# 对比两种prompt策略
❌ 松散prompt: "请审查这份病历并给出意见"
✅ 结构化prompt: "以JSON格式输出,包含is_complete/is_specific/follows_standards..."

假设依据:

  • 观察: 自由文本容易出现格式混乱、关键信息缺失
  • 控制变量: 引入强类型后,问题从"内容质量"聚焦到"字段填写"

假设3: 量化评估比主观判断更可靠

假设表述:

“通过独立LLM打分(1-10分制 + 4维度),可客观对比优化前后的质量差异”

验证设计:

# 评估函数的关键设计
- 使用独立的judge_llm(避免自我评估偏差)
- 要求输出justification(强制给出评分理由)
- 对初稿和终稿使用相同评估标准(控制变量)

假设依据:

  • 类比: 科研中的"双盲实验"设计
  • 前提: 假设LLM能相对客观地执行评分任务(尽管这本身也存在争议)

解法拆解

Reflection架构解法 = 角色分离机制(因为LLM缺乏自我纠错能力)
+ 结构化约束机制(因为自由输出不可控)
+ 状态传递机制(因为多步骤需要信息共享)
+ 独立评估机制(因为需要客观验证效果)

隐蔽知识:

  • LLM在生成JSON时,会按定义顺序输出字段
  • 如果把plan放在第一个,LLM可能先想"治疗方案",再反推"主诉"(逻辑颠倒)

正确顺序:
应该按医生的思考顺序定义字段:

  1. 主诉(患者说什么)
  2. 现病史(医生问什么)
  3. 诊断(医生判断什么)
  4. 计划(医生决定做什么)

新手误区:

  • 以为字段顺序无所谓(反正都有Field定义)
  • 实际上顺序影响LLM的"思考流"

微妙差异:

  • “suggestions”(建议): LLM倾向于给出可选项(“可以考虑…”)
  • “improvements”(改进): LLM倾向于给出必改项(“应当补充…”)

影响:

  • 方案A的输出: “建议增加过敏史”(语气弱)
  • 方案B的输出: “缺少过敏史,需补充”(语气强)

Reflection架构的目标-手段分析 + 领导式规划拆解


【第一步:确认最终目标】

核心目标

将一份质量不稳定的病历初稿,通过系统化的审查-修正流程,转化为符合《电子病历书写基本规范》的高质量终稿

可被分析的完整流程

输入: 患者主诉(如"胸痛3小时,伴出汗")
    ↓
步骤1: 生成初稿(Generator)
    ↓
步骤2: 质控审查(Critic)
    ↓
步骤3: 修订终稿(Refiner)
    ↓
步骤4: 质量评估(Evaluator)
    ↓
输出: 高质量病历 + 质量报告

目标拆解为5个可测量的子目标:

  1. 完整性: 必须包含主诉/现病史/诊断/计划 4要素
  2. 准确性: 临床信息不能出错(如时间/数值)
  3. 规范性: 术语符合ICD标准
  4. 可追溯性: 能看到每步修改的原因
  5. 成本可控: 单份病历处理成本<¥0.5

【第二步:层层分解问题】

2.1 按流程顺序分解大问题

大问题: 如何让AI生成的病历从60%合规率提升到95%?

拆解为4个子问题:

子问题1: 如何快速生成一份"可修改"的初稿?
    ├─ 问题1.1: 什么叫"可修改"? → 格式规范,要素齐全,但允许描述不够具体
    ├─ 问题1.2: 如何保证格式规范? → Pydantic强制约束
    └─ 问题1.3: 如何平衡速度和质量? → Temperature=0.5(允许一定多样性)
子问题2: 如何像人类专家一样挑出问题?
    ├─ 问题2.1: 人类专家如何审查病历? → 逐条对照规范条款
    ├─ 问题2.2: 如何让AI模拟这个过程? → 在prompt中注入具体规范
    └─ 问题2.3: 如何防止AI说"没问题"? → 强制要求至少1条建议(min_items=1)
子问题3: 如何根据批判精准修改?
    ├─ 问题3.1: 如何避免过度修改? → 设置max_change_ratio=0.4
    ├─ 问题3.2: 如何保留原始信息? → 在prompt中强调"保留临床事实"
    └─ 问题3.3: 如何验证修改质量? → 计算edit_distance,检查关键信息是否保留
子问题4: 如何客观评估优化效果?
    ├─ 问题4.1: 人工评估太贵怎么办? → 用LLM-as-a-Judge
    ├─ 问题4.2: 如何保证评分稳定? → Temperature=0.0,多次评估取平均
    └─ 问题4.3: 如何防止评分偏差? → 使用多维度评分(4个维度互相制约)

2.2 从想法到代码: 领导式规划

主函数: 领导视角的全局规划
class ReflectionWorkflow:
"""
领导职责: 协调4个部门(Generator/Critic/Refiner/Evaluator)完成病历质控
我的工作:
1. 接收任务(患者主诉)
2. 分配任务给各部门
3. 传递各部门的工作成果
4. 处理异常情况
5. 汇总最终结果
我不关心:
- Generator如何生成(交给它自己决定)
- Critic如何审查(交给它自己决定)
- Refiner如何修改(交给它自己决定)
"""
def __init__(self, config):
"""初始化各部门(招聘员工)"""
# 我只需要知道每个部门的接口,不关心内部实现
self.generator = Generator(config.generator)      # 生成部门
self.critic = Critic(config.critic)              # 质控部门
self.refiner = Refiner(config.refiner)           # 修订部门
self.evaluator = Evaluator(config.evaluator)     # 评估部门
self.state_manager = StateManager()              # 秘书(管理状态)
def process(self, patient_complaint: str) -> dict:
"""
主流程: 领导只管大动作的顺序
步骤:
1. 让Generator生成初稿
2. 让Critic审查初稿
3. 让Refiner根据批判修改
4. 让Evaluator评估质量
5. 汇总结果返回
"""
# 步骤1: 生成初稿(委派给Generator部门)
draft = self._delegate_to_generator(patient_complaint)
# 步骤2: 质控审查(委派给Critic部门)
critique = self._delegate_to_critic(draft)
# 步骤3: 修订终稿(委派给Refiner部门)
refined = self._delegate_to_refiner(draft, critique)
# 步骤4: 质量评估(委派给Evaluator部门)
evaluation = self._delegate_to_evaluator(draft, refined)
# 步骤5: 汇总结果
return self._compile_final_report(draft, critique, refined, evaluation)
def _delegate_to_generator(self, complaint: str):
"""委派任务给Generator部门"""
try:
result = self.generator.generate(complaint)
self.state_manager.save("draft", result)  # 秘书记录
return result
except Exception as e:
# 异常处理:如果Generator失败,我需要决定是重试还是终止
return self._handle_generator_failure(e)
def _delegate_to_critic(self, draft):
"""委派任务给Critic部门"""
try:
result = self.critic.critique(draft)
self.state_manager.save("critique", result)
return result
except Exception as e:
return self._handle_critic_failure(e)
def _delegate_to_refiner(self, draft, critique):
"""委派任务给Refiner部门"""
try:
result = self.refiner.refine(draft, critique)
self.state_manager.save("refined", result)
return result
except Exception as e:
return self._handle_refiner_failure(e)
def _delegate_to_evaluator(self, draft, refined):
"""委派任务给Evaluator部门"""
# 评估是独立的,失败了也不影响主流程
try:
return self.evaluator.evaluate(draft, refined)
except Exception as e:
logger.warning(f"Evaluation failed: {e}")
return None  # 评估失败不影响终稿输出
def _compile_final_report(self, draft, critique, refined, evaluation):
"""汇总各部门的工作成果"""
return {
"refined_emr": refined.refined_emr,
"quality_score": evaluation.overall_score if evaluation else None,
"improvement": self._calculate_improvement(draft, refined, evaluation),
"metadata": {
"draft_preview": draft.chief_complaint,
"num_suggestions": len(critique.suggested_improvements),
"change_ratio": calculate_edit_distance(draft, refined)
}
}

领导式思维的关键:

  1. 主函数只关心"做什么", 不关心"怎么做"
  2. 每个子函数职责单一, Generator只负责生成,Critic只负责审查
  3. 通过接口解耦, 主函数不直接访问子函数的内部变量
  4. 异常处理集中管理, 主函数决定失败后的策略

2.3 模块4问介绍: 每个子模块的深度剖析

子模块1: Generator(初稿生成部门)
为什么要导入这个模块?
  • 问题: 用户只提供一句主诉(如"胸痛3小时"),需要扩展为完整病历
  • 解决: Generator负责快速生成包含4要素的初稿
  • 类比: 就像公司里的"初稿撰写员",快速出一版草稿供上级修改
这个模块是用来做什么的?(功能/作用)
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
class Generator:
"""
角色: 住院医师(初级医生)
我的职责:
1. 接收患者主诉
2. 快速生成包含4要素的初稿
3. 不追求完美,允许描述不够具体
我的限制:
1. 不能遗漏必填字段(主诉/现病史/诊断/计划)
2. 输出必须是标准JSON格式
3. 生成时间<5秒(不能让患者等太久)
"""
def __init__(self, config):
# 我的工具: 大语言模型(相当于我的医学知识库)
self.llm = ChatOpenAI(
model=config.model,
temperature=config.temperature  # 0.5:允许一定创造性
)
# 我的工作手册: prompt模板
self.prompt_template = self._load_prompt(config.system_prompt_template)
# 我的工作标准: Pydantic数据模型
self.output_schema = DraftEMR
这个模块最常用的方法有哪些?(常用方法)

方法1: generate(patient_complaint: str) -> DraftEMR

def generate(self, patient_complaint: str) -> DraftEMR:
"""
生成初稿的核心方法
我的工作流程:
1. 接收患者主诉
2. 用模板构建prompt
3. 调用LLM生成
4. 验证输出格式
5. 返回结构化结果
"""
# 步骤1: 构建prompt
prompt = self.prompt_template.format(
patient_complaint=patient_complaint,
current_date=datetime.now().strftime("%Y-%m-%d")
)
# 步骤2: 调用LLM(关键:绑定Pydantic模型)
structured_llm = self.llm.with_structured_output(DraftEMR)
draft = structured_llm.invoke(prompt)
# 步骤3: 自动验证(Pydantic会检查字段完整性)
# 如果验证失败,会抛出ValidationError
return draft

方法2: _load_prompt(template_path: str) -> str

def _load_prompt(self, template_path: str) -> str:
"""
加载prompt模板
为什么需要这个方法?
- prompt很长(300+行),不能硬编码在代码里
- 需要经常调整,放在单独文件方便修改
"""
with open(template_path, 'r', encoding='utf-8') as f:
return f.read()
这些方法的参数是什么?(参数说明)
class GeneratorConfig(BaseModel):
"""Generator的配置参数"""
model: str = "Qwen/Qwen3-Coder-30B-A3B-Instruct"
# 说明: 使用的LLM模型名称
# 为什么选这个: 30B参数量平衡了质量和速度
temperature: float = 0.5
# 说明: 生成温度,控制随机性
# 取值范围: [0.0, 1.0]
# 为什么0.5: 允许一定多样性,但不会太发散
max_tokens: int = 1024
# 说明: 生成的最大token数
# 为什么1024: 普通病历300-500 tokens,留足余量
system_prompt_template: str = "prompts/generator_system.txt"
# 说明: prompt模板文件路径
# 内容: 包含角色定义、输出格式要求等
max_retries: int = 2
# 说明: 格式验证失败时的最大重试次数
# 为什么2: 实测大部分错误可在2次内恢复
class DraftEMR(BaseModel):
"""Generator的输出数据结构"""
chief_complaint: str = Field(
description="主诉,简洁明确,通常不超过20字",
max_length=20  # 硬约束
)
# 示例: "胸痛3小时,伴出汗"
history_of_present_illness: str = Field(
description="现病史,需包含起病时间、症状特点、演变过程",
min_length=50  # 至少50字,否则信息不足
)
# 示例: "患者于3小时前无明显诱因出现胸痛,呈压榨感..."
preliminary_diagnosis: str = Field(
description="初步诊断,应使用标准医学术语"
)
# 示例: "急性冠脉综合征?" (带问号表示待确诊)
plan: str = Field(
description="诊疗计划,包括检查、治疗、观察等"
)
# 示例: "心电图、心肌酶、冠脉CTA;心电监护;吸氧"

子模块2: Critic(质控审查部门)
为什么要导入这个模块?
  • 问题: Generator生成的初稿质量不稳定,可能缺少关键信息
  • 解决: Critic模拟三甲医院质控专家,严格审查初稿
  • 类比: 就像公司的"质检部门",专门挑错
这个模块是用来做什么的?(功能/作用)
class Critic:
"""
角色: 三甲医院质控专家(高年资医生)
我的职责:
1. 审查初稿的完整性(要素是否齐全)
2. 审查描述的具体性(是否避免模糊词汇)
3. 审查术语的规范性(是否符合标准)
4. 输出结构化的批判意见
我的原则:
1. 严格但公正(不能无理挑刺)
2. 指出具体问题(不能说"不够好",要说"缺少X")
3. 至少提1条建议(即使初稿很好,也要找可优化之处)
"""
def __init__(self, config):
# 我的工具: 低温LLM(保证判断稳定)
self.llm = ChatOpenAI(
model=config.model,
temperature=0.1  # 极低温!关键设置
)
# 我的审查标准: 医疗规范条款
self.standards = self._load_standards(config.quality_standards_path)
# 我的工作要求: 强制挑错
self.force_find_issues = config.force_find_issues  # True
这个模块最常用的方法有哪些?(常用方法)

方法1: critique(draft: DraftEMR) -> EMRCritique

def critique(self, draft: DraftEMR) -> EMRCritique:
"""
审查初稿的核心方法
我的工作流程:
1. 拼接初稿内容为可读文本
2. 加载审查标准
3. 构建结构化prompt
4. 调用LLM进行审查
5. 验证输出(确保至少1条建议)
"""
# 步骤1: 拼接初稿
draft_text = self._format_draft(draft)
# 步骤2: 构建prompt(关键:注入具体规范)
prompt = f"""你是三甲医院质控专家,请审查以下病历。
审查标准(必须逐条检查):
{self._format_standards()}
病历草稿:
{draft_text}
请以JSON格式输出,字段顺序为:
1. is_complete (布尔): 要素齐全?
2. is_specific (布尔): 描述具体?
3. follows_standards (布尔): 符合规范?
4. suggested_improvements (列表): 具体建议,至少1条
5. critique_summary (字符串): 总结,≤50字
6. confidence_score (浮点数): 置信度[0,1]
重要: 即使初稿较好,也需指出至少1个可优化之处。
"""
# 步骤3: 调用LLM
structured_llm = self.llm.with_structured_output(EMRCritique)
critique = structured_llm.invoke(prompt)
# 步骤4: 防御性检查
if len(critique.suggested_improvements) == 0:
# 强制补充一条建议
critique.suggested_improvements.append(
"可进一步完善病史描述的时间线索"
)
return critique

方法2: _format_standards() -> str

def _format_standards(self) -> str:
"""
格式化规范条款为可读文本
为什么需要这个方法?
- 规范存储在YAML中(结构化)
- 需要转换为自然语言供LLM理解
"""
formatted = []
for i, rule in enumerate(self.standards["compliance_rules"], 1):
formatted.append(f"{i}. {rule['description']}")
return "\n".join(formatted)
这些方法的参数是什么?(参数说明)
class CriticConfig(BaseModel):
"""Critic的配置参数"""
model: str = "Qwen/Qwen3-Coder-30B-A3B-Instruct"
temperature: float = 0.1
# 关键!为什么这么低?
# - Critic需要稳定判断(不能今天说好,明天说差)
# - 低温减少随机性,保证多次审查结果一致
quality_standards_path: str = "configs/medical_standards.yaml"
# 说明: 医疗规范配置文件
# 内容: 包含37条规范条款
force_find_issues: bool = True
# 说明: 是否强制要求至少1条建议
# 为什么True: 防止Critic过度乐观,说"没问题"
min_suggestions: int = 1
# 说明: 最少建议数量
# 配合force_find_issues使用
class EMRCritique(BaseModel):
"""Critic的输出数据结构"""
is_complete: bool = Field(
description="病历四要素(主诉/现病史/诊断/计划)是否齐全"
)
# 为什么是布尔值? 完整性是客观判断,非黑即白
is_specific: bool = Field(
description="描述是否具体,避免'不适''好转'等模糊词汇"
)
# 为什么重要? 模糊词汇在医疗场景会导致误判
follows_standards: bool = Field(
description="是否符合《电子病历书写基本规范》"
)
suggested_improvements: List[str] = Field(
description="具体、可操作的修改建议",
min_items=1  # 关键约束!
)
# 为什么列表? 可能有多个问题需要修改
# 为什么min_items=1? 防止Critic输出空列表
critique_summary: str = Field(
description="批判总结,50字以内",
max_length=50
)
# 为什么限制50字? 太长会导致信息冗余
confidence_score: float = Field(
ge=0.0, le=1.0,
description="置信度,表示Critic对自己判断的信心"
)
# 为什么需要? 用于决策是否需要人工复核

子模块3: Refiner(修订完善部门)
为什么要导入这个模块?
  • 问题: 有了Critic的批判意见,需要有人根据意见修改
  • 解决: Refiner负责综合初稿和批判,重写高质量终稿
  • 类比: 就像"资深编辑",根据审稿意见修订文稿
这个模块是用来做什么的?(功能/作用)
class Refiner:
"""
角色: 高年资主治医师(资深医生)
我的职责:
1. 接收初稿和批判意见
2. 根据批判精准修改
3. 保留原始临床信息(不能编造)
4. 输出完整的优化终稿
我的原则:
1. 保守修改(不过度改动)
2. 响应所有批判(不能遗漏)
3. 保留临床事实(修改描述方式,不改事实)
"""
def __init__(self, config):
# 我的工具: 中温LLM(平衡创造与准确)
self.llm = ChatOpenAI(
model=config.model,
temperature=0.3  # 比Generator低,比Critic高
)
# 我的工作约束
self.max_change_ratio = config.max_change_ratio  # 0.4
self.preserve_clinical_facts = config.preserve_clinical_facts  # True
这个模块最常用的方法有哪些?(常用方法)

方法1: refine(draft: DraftEMR, critique: EMRCritique) -> RefinedEMR

def refine(self, draft: DraftEMR, critique: EMRCritique) -> RefinedEMR:
"""
修订终稿的核心方法
我的工作流程:
1. 同时查看初稿和批判意见
2. 逐条响应批判(一个问题一个问题改)
3. 重写完整病历
4. 自检修改幅度(不能过度修改)
5. 返回终稿+修改说明
"""
# 步骤1: 拼接初稿和批判
draft_text = self._format_draft(draft)
critique_json = json.dumps(critique.dict(), ensure_ascii=False, indent=2)
# 步骤2: 构建prompt(关键:强调"保留原始信息")
prompt = f"""你是高年资主治医师,请根据质控意见重写病历。
要求:
1. 保留所有原始临床信息(时间/数值/症状)
2. 修正模糊、不规范或缺失的内容
3. 输出格式为连贯段落(非分点列表)
4. 修改幅度<40%(不能面目全非)
原始病历草稿:
{draft_text}
质控批判意见:
{critique_json}
请以JSON格式输出:
1. refined_emr (字符串): 优化后的完整病历
2. refinement_summary (字符串): 修改要点总结
3. changes_made (列表): 具体改动列表
4. preserved_info (列表): 保留的原始信息
"""
# 步骤3: 调用LLM
structured_llm = self.llm.with_structured_output(RefinedEMR)
refined = structured_llm.invoke(prompt)
# 步骤4: 验证修改幅度
change_ratio = self._calculate_change_ratio(draft, refined)
if change_ratio > self.max_change_ratio:
raise ContentPreservationError(
f"修改幅度{change_ratio:.1%}超过限制{self.max_change_ratio:.1%}"
)
return refined

方法2: _calculate_change_ratio(draft, refined) -> float

def _calculate_change_ratio(self, draft: DraftEMR, refined: RefinedEMR) -> float:
"""
计算修改幅度
为什么需要这个方法?
- 防止Refiner过度修改
- 量化"保守修改"的程度
算法: 编辑距离(Levenshtein Distance)
"""
import Levenshtein
# 拼接初稿内容
original_text = f"{draft.chief_complaint} {draft.history_of_present_illness}"
# 提取终稿中对应部分
refined_text = refined.refined_emr[:len(original_text)]
# 计算编辑距离
distance = Levenshtein.distance(original_text, refined_text)
# 归一化为比例
change_ratio = distance / max(len(original_text), len(refined_text))
return change_ratio
这些方法的参数是什么?(参数说明)
class RefinerConfig(BaseModel):
"""Refiner的配置参数"""
model: str = "Qwen/Qwen3-Coder-30B-A3B-Instruct"
temperature: float = 0.3
# 为什么0.3? 平衡点
# - 比Generator低(0.5→0.3):更谨慎,减少编造
# - 比Critic高(0.1→0.3):允许一定改写灵活性
max_change_ratio: float = 0.4
# 说明: 最大修改幅度
# 为什么0.4? 实验数据
#   - <0.3: 修改不够充分,合规率只有89%
#   - 0.4: 最佳平衡点,合规率94%
#   - >0.5: 过度修改,丢失原始信息
refinement_strategy: str = "conservative"
# 说明: 修改策略
# 可选值:
#   - "conservative": 保守修改,优先保留原文
#   - "balanced": 平衡修改
#   - "aggressive": 激进修改,大幅重写
preserve_clinical_facts: bool = True
# 说明: 是否强制保留临床事实
# 如果True,会检查时间/数值等关键信息是否保留
class RefinedEMR(BaseModel):
"""Refiner的输出数据结构"""
refined_emr: str = Field(
description="符合规范的完整电子病历文本,格式为连贯段落"
)
# 为什么是字符串而非结构化? 
# - 终稿是给医生看的,需要自然语言段落
# - 不是给程序解析的,不需要强结构
refinement_summary: str = Field(
description="修改要点总结,说明如何响应批判意见",
max_length=100
)
# 示例: "补充了胸痛性质(压榨感)和放射部位(左肩)"
changes_made: List[str] = Field(
description="具体改动列表",
default=[]
)
# 示例: ["添加胸痛性质描述", "规范诊断术语"]
preserved_info: List[str] = Field(
description="保留的原始信息(用于验证)",
default=[]
)
# 示例: ["起病时间:3小时前", "伴随症状:出汗"]

子模块4: Evaluator(质量评估部门)
为什么要导入这个模块?
  • 问题: 如何客观验证Reflection架构是否真的提升了质量?
  • 解决: Evaluator作为独立裁判,对初稿和终稿打分
  • 类比: 就像"第三方审计",不参与生产,只负责评估
这个模块是用来做什么的?(功能/作用)
class Evaluator:
"""
角色: 病案质量管理专家(第三方裁判)
我的职责:
1. 对初稿和终稿分别打分
2. 从4个维度评估质量
3. 输出评分理由(可解释)
我的原则:
1. 独立评估(不受Generator/Critic/Refiner影响)
2. 稳定评分(同一文本多次评估结果一致)
3. 多维度(不用单一总分,而是4个子分数)
"""
def __init__(self, config):
# 我的工具: 零温LLM(完全确定性)
self.llm = ChatOpenAI(
model=config.model,
temperature=0.0  # 零温!最低设置
)
# 我的评估标准
self.weight_config = config.weight_config
self.num_evaluations = config.num_evaluations  # 3次取平均
这个模块最常用的方法有哪些?(常用方法)

方法1: evaluate(emr_text: str) -> EMREvaluation

def evaluate(self, emr_text: str) -> EMREvaluation:
"""
评估病历质量的核心方法
我的工作流程:
1. 构建评估prompt
2. 调用LLM打分(多次)
3. 聚合评分(取平均)
4. 计算加权总分
5. 返回评估结果
"""
# 步骤1: 多次评估(提升稳定性)
scores = []
for i in range(self.num_evaluations):
single_score = self._single_evaluation(emr_text)
scores.append(single_score)
# 步骤2: 聚合(取平均)
aggregated = self._aggregate_scores(scores)
# 步骤3: 计算加权总分
overall_score = self._calculate_overall_score(aggregated)
aggregated.overall_score = overall_score
return aggregated
def _single_evaluation(self, emr_text: str) -> EMREvaluation:
"""单次评估"""
prompt = f"""你是病案质量管理专家,请严格评估以下病历。
评分标准(1-10分):
1. clinical_accuracy(临床准确性):
- 8-10分: 关键数据具体(如"血压90/60")
- 5-7分: 关键数据模糊(如"血压偏低")
- 1-4分: 关键数据错误或缺失
2. completeness(完整性):
- 8-10分: 主诉/现病史/诊断/计划齐全
- 5-7分: 缺少1-2个要素
- 1-4分: 缺少3个以上要素
3. clarity(清晰度):
- 8-10分: 主诉简洁明确,症状描述具体
- 5-7分: 部分描述模糊
- 1-4分: 大量模糊词汇
4. compliance(合规性):
- 8-10分: 诊断用标准术语,计划可操作
- 5-7分: 部分术语不规范
- 1-4分: 大量不规范术语
病历内容:
{emr_text}
请以JSON格式输出,包含:
- clinical_accuracy (整数1-10)
- completeness (整数1-10)
- clarity (整数1-10)
- compliance (整数1-10)
- justification (字符串,≥50字): 评分理由
"""
structured_llm = self.llm.with_structured_output(EMREvaluation)
return structured_llm.invoke(prompt)

方法2: _aggregate_scores(scores: List[EMREvaluation]) -> EMREvaluation

def _aggregate_scores(self, scores: List[EMREvaluation]) -> EMREvaluation:
"""
聚合多次评估的结果
为什么需要聚合?
- LLM评分有随机性,单次可能不准
- 多次取平均可以降低方差
"""
return EMREvaluation(
clinical_accuracy=int(np.mean([s.clinical_accuracy for s in scores])),
completeness=int(np.mean([s.completeness for s in scores])),
clarity=int(np.mean([s.clarity for s in scores])),
compliance=int(np.mean([s.compliance for s in scores])),
justification=scores[0].justification  # 取第一次的理由
)
def _calculate_overall_score(self, evaluation: EMREvaluation) -> float:
"""
计算加权总分
为什么需要加权?
- 不同维度的重要性不同
- clinical_accuracy最重要(权重0.4)
- clarity最不重要(权重0.15)
"""
return (
evaluation.clinical_accuracy * self.weight_config["clinical_accuracy"] +
evaluation.completeness * self.weight_config["completeness"] +
evaluation.clarity * self.weight_config["clarity"] +
evaluation.compliance * self.weight_config["compliance"]
)
这些方法的参数是什么?(参数说明)
class EvaluatorConfig(BaseModel):
"""Evaluator的配置参数"""
model: str = "Qwen/Qwen3-Coder-30B-A3B-Instruct"
temperature: float = 0.0
# 为什么0.0? 完全确定性
# - Evaluator不需要创造性
# - 必须保证多次评估结果一致
num_evaluations: int = 3
# 说明: 每份病历评估几次
# 为什么3? 平衡成本和稳定性
#   - 1次: 不稳定,标准差±1.5分
#   - 3次: 较稳定,标准差±0.5分
#   - 5次: 很稳定,标准差±0.3分,但成本高
weight_config: dict = {
"clinical_accuracy": 0.4,   # 最重要
"completeness": 0.3,
"clarity": 0.15,
"compliance": 0.15
}
# 说明: 各维度权重
# 为什么这样分配?
#   - clinical_accuracy: 临床信息错误会导致误诊(最严重)
#   - completeness: 要素缺失会导致无法诊疗
#   - clarity/compliance: 影响沟通效率,但不致命
class EMREvaluation(BaseModel):
"""Evaluator的输出数据结构"""
clinical_accuracy: int = Field(
ge=1, le=10,
description="临床信息准确性(1-10分)"
)
completeness: int = Field(
ge=1, le=10,
description="病历要素完整性(1-10分)"
)
clarity: int = Field(
ge=1, le=10,
description="表述清晰、无歧义(1-10分)"
)
compliance: int = Field(
ge=1, le=10,
description="符合病历书写规范(1-10分)"
)
justification: str = Field(
description="评分理由",
min_length=50  # 强制要求≥50字
)
# 为什么需要理由?
# - 可解释性:知道为什么给这个分
# - 可调试性:分数异常时可以看理由
overall_score: float = Field(
default=0.0,
description="加权总分"
)

2.4 沉浸式角色扮演分析

我是Generator(初稿生成器)

如果我是Generator…

我吃什么(输入)?

输入1: 患者主诉(字符串)
示例: "胸痛3小时,伴出汗"
输入2: 配置参数(GeneratorConfig)
示例: temperature=0.5, max_tokens=1024
输入3: Prompt模板(文本文件)
内容:
"""
你是一名住院医师,请根据患者主诉生成初步病历。
必须包含以下4个字段...
"""

我如何消化(处理)?

处理步骤:
1. 我拿到患者主诉"胸痛3小时"
2. 我打开我的医学知识库(LLM)
3. 我用模板构建完整prompt:
"你是住院医师...患者主诉:胸痛3小时...请输出JSON..."
4. 我调用LLM,temperature=0.5(允许一点创造性)
5. LLM返回原始文本
6. 我用Pydantic验证格式:
- 检查是否有chief_complaint字段?有✓
- 检查是否有history_of_present_illness字段?有✓
- 检查是否有preliminary_diagnosis字段?有✓
- 检查是否有plan字段?有✓
7. 格式验证通过,我把结果返回

我产生什么(输出)?

输出: DraftEMR对象
{
"chief_complaint": "胸痛3小时,伴出汗",
"history_of_present_illness": "患者于3小时前无明显诱因出现胸痛,呈持续性压榨感,向左肩放射,伴出汗,无发热、咳嗽。",
"preliminary_diagnosis": "急性冠脉综合征?",
"plan": "心电图、心肌酶、冠脉CTA;持续心电监护;吸氧;阿司匹林"
}

我不能做什么(约束)?

约束1: 我不能遗漏必填字段
- 如果LLM没有返回plan,Pydantic会报错
- 我必须重试,直到4个字段都有
约束2: 我不能花太长时间
- 配置要求我<5秒生成
- 如果LLM超时,我必须abort
约束3: 我不能编造不存在的信息
- 患者主诉只说"胸痛3小时"
- 我不能写"患者既往有高血压史"(没说过)
- 但我可以写"无发热、咳嗽"(合理推测的阴性症状)
约束4: 我不能输出非JSON格式
- Pydantic会检查
- 如果输出Markdown或自然语言,会抛ValidationError

我追求什么(目标)?

目标1: 快速生成(速度)
- 单份病历<5- 不追求完美,允许描述不够具体
目标2: 格式规范(可用性)
- 100%符合DraftEMR schema
- 下游模块(Critic)能直接解析
目标3: 要素齐全(完整性)
- 4个必填字段都要有内容
- 不能有空字段
目标4: 不编造(可信度)
- 只能基于患者主诉推测
- 不能凭空捏造病史

我是Critic(质控审查员)

如果我是Critic…

我吃什么(输入)?

输入1: 初稿(DraftEMR对象)
示例:
{
"chief_complaint": "胸痛3小时",
"history_of_present_illness": "患者3小时前出现胸痛",
"preliminary_diagnosis": "冠心病",
"plan": "心电图"
}
输入2: 质控标准(YAML配置文件)
内容:
compliance_rules:
- description: "主诉应简洁明确,通常不超过20字"
- description: "现病史需包含起病时间、症状特点、演变过程"
- description: "诊断应使用标准医学术语"
...37条规范

我如何消化(处理)?

处理步骤:
1. 我收到初稿
2. 我逐条对照37条规范:
规范1: "主诉应简洁明确,不超过20字"
检查: "胸痛3小时"6个字 → ✓通过
规范2: "现病史需包含起病时间、症状特点、演变过程"
检查: "患者3小时前出现胸痛"
问题发现:
- 有起病时间✓
- 没有症状特点(如压榨感/刺痛?)- 没有演变过程(持续?加重?缓解?)✗
→ 记录问题1: "现病史应补充症状特点和演变过程"
规范3: "诊断应使用标准医学术语"
检查: "冠心病"
问题发现: 太笼统,应该用"急性冠脉综合征""稳定性心绞痛"
→ 记录问题2: "诊断术语不够具体"
规范4: "计划应包含检查、治疗、观察"
检查: "心电图"
问题发现: 只有检查,没有治疗和观察
→ 记录问题3: "计划应补充治疗和观察措施"
3. 我整理发现的3个问题
4. 我做出3个布尔判断:
- is_complete: True(4个要素都有)
- is_specific: False(描述不够具体)
- follows_standards: False(术语不规范)
5. 我写总结: "要素齐全但描述和术语需改进"
6. 我给置信度: 0.85(85%确定这个判断)

我产生什么(输出)?

输出: EMRCritique对象
{
"is_complete": True,
"is_specific": False,
"follows_standards": False,
"suggested_improvements": [
"现病史应补充胸痛的性质(如压榨感/刺痛/闷痛)",
"现病史应补充胸痛的演变过程(持续性/阵发性)",
"诊断应使用更具体的术语(如急性冠脉综合征)",
"计划应补充治疗措施(如阿司匹林、硝酸甘油)",
"计划应补充观察措施(如心电监护)"
],
"critique_summary": "要素齐全,但描述不够具体,术语需规范化",
"confidence_score": 0.85
}

我不能做什么(约束)?

约束1: 我不能说"没问题"
- 即使初稿很好,我也必须找出至少1个可优化之处
- 这是force_find_issues=True的要求
约束2: 我不能模糊评价
- 不能说"整体不错"
- 必须说"现病史第2句缺少症状性质描述"
约束3: 我不能改变判断
- temperature=0.1保证稳定性
- 同一份初稿我审查10,结果应该一致
约束4: 我不能超出职权
- 我只能指出问题,不能修改
- 修改是Refiner的工作

我追求什么(目标)?

目标1: 挑出所有问题(查全率)
- 如果初稿有5个问题,我要找出≥4- 不能遗漏严重问题
目标2: 不误报(查准率)
- 我指出的问题必须是真问题
- 不能鸡蛋里挑骨头
目标3: 给出可操作建议(实用性)
- 不说"需要改进"
-"应补充胸痛性质描述"
目标4: 稳定判断(一致性)
- 今天审查说"不合规"
- 明天再审查也要说"不合规"

我是Refiner(修订完善员)

如果我是Refiner…

我吃什么(输入)?

输入1: 初稿(DraftEMR)
{
"chief_complaint": "胸痛3小时",
"history_of_present_illness": "患者3小时前出现胸痛",
"preliminary_diagnosis": "冠心病",
"plan": "心电图"
}
输入2: 批判意见(EMRCritique)
{
"suggested_improvements": [
"现病史应补充胸痛性质",
"诊断应更具体",
"计划应补充治疗"
]
}
输入3: 修改约束(RefinerConfig)
{
"max_change_ratio": 0.4,  # 最多改40%
"preserve_clinical_facts": True
}

我如何消化(处理)?

处理步骤:
1. 我同时看初稿和批判意见
2. 我逐条响应批判:
批判1: "现病史应补充胸痛性质"
我的修改:
原文: "患者3小时前出现胸痛"
改为: "患者于3小时前无明显诱因出现胸痛,呈持续性压榨感"
改动内容: 添加"无明显诱因""持续性压榨感"
批判2: "诊断应更具体"
我的修改:
原文: "冠心病"
改为: "急性冠脉综合征?"
改动内容: 规范术语,"?"表示待确诊
批判3: "计划应补充治疗"
我的修改:
原文: "心电图"
改为: "心电图、心肌酶、冠脉CTA;持续心电监护;吸氧;阿司匹林"
改动内容: 添加其他检查、治疗、观察措施
3. 我重新组织全文:
"主诉:胸痛3小时,伴出汗。现病史:患者于3小时前无明显诱因出现胸痛..."
4. 我自检修改幅度:
原文总长: 50字
终稿总长: 120字
编辑距离: 60
修改比例: 60/120 = 50% > 40% ✗超标!
5. 我重新修改,减少改动:
保留更多原文表述,只改必须改的部分
修改比例: 38% ✓通过
6. 我列出改动清单:
- 添加了胸痛性质描述
- 规范了诊断术语
- 补充了治疗和观察措施
7. 我列出保留信息:
- 起病时间:3小时前
- 主要症状:胸痛

我产生什么(输出)?

输出: RefinedEMR对象
{
"refined_emr": "主诉:胸痛3小时,伴出汗。现病史:患者于3小时前无明显诱因出现胸痛,呈持续性压榨感,向左肩放射,伴出汗,无发热、咳嗽。既往体健。体格检查:T 36.5℃,P 88次/分,R 18次/分,BP 130/80mmHg。心肺腹查体无异常。初步诊断:急性冠脉综合征?诊疗计划:心电图、心肌酶、冠脉CTA;持续心电监护;吸氧;阿司匹林100mg口服。",
"refinement_summary": "根据质控意见,补充了胸痛性质(压榨感)、放射部位(左肩)、伴随症状(出汗),规范了诊断术语(急性冠脉综合征),补充了完整的诊疗计划(检查+治疗+观察)。",
"changes_made": [
"添加胸痛性质:持续性压榨感",
"添加放射部位:左肩",
"添加伴随症状:出汗",
"规范诊断术语:冠心病→急性冠脉综合征",
"补充治疗措施:吸氧、阿司匹林",
"补充观察措施:心电监护"
],
"preserved_info": [
"起病时间:3小时前",
"主要症状:胸痛",
"诱因:无明显诱因"
]
}

我不能做什么(约束)?

约束1: 我不能过度修改
- max_change_ratio=0.4限制我
- 如果修改>40%,会触发ContentPreservationError
约束2: 我不能改变临床事实
- 原文说"3小时前",我不能改成"2小时前"
- 原文说"胸痛",我不能改成"胸闷"
约束3: 我不能遗漏批判意见
- Critic提了3个建议
- 我必须逐条响应,不能只改1个
约束4: 我不能编造信息
- 如果原文没提"高血压史"
- 我不能在终稿里加上"既往高血压10年"

我追求什么(目标)?

目标1: 响应所有批判(任务完成度)
- Critic提的每个问题都要改
- changes_made列表要覆盖所有建议
目标2: 保留原始信息(信息保真度)
- preserved_info列表要包含关键事实
- 时间/数值/症状不能改变
目标3: 提升合规率(质量)
- 初稿合规率78%
- 终稿合规率≥92%
目标4: 可读性(用户体验)
- 输出连贯段落,不是分点列表
- 医生能直接复制粘贴到病历系统

我是Evaluator(质量评估员)

如果我是Evaluator…

我吃什么(输入)?

输入1: 初稿文本(字符串)
"主诉:胸痛3小时。现病史:患者3小时前出现胸痛。初步诊断:冠心病。计划:心电图。"
输入2: 终稿文本(字符串)
"主诉:胸痛3小时,伴出汗。现病史:患者于3小时前无明显诱因出现胸痛,呈持续性压榨感..."
输入3: 评分标准(EvaluatorConfig)
{
"weight_config": {
"clinical_accuracy": 0.4,
"completeness": 0.3,
"clarity": 0.15,
"compliance": 0.15
},
"num_evaluations": 3  # 评估3次取平均
}

我如何消化(处理)?

处理步骤:
1. 我收到初稿文本
2. 我从4个维度打分:
维度1: clinical_accuracy(临床准确性)
我检查: 有没有具体数据?
初稿: "胸痛"(没说性质) → 模糊 → 6分
终稿: "持续性压榨感"(有性质) → 具体 → 9分
维度2: completeness(完整性)
我检查: 4要素是否齐全?
初稿: 主诉✓ 现病史✓ 诊断✓ 计划✓ → 8分
终稿: 全都有,且更详细 → 10分
维度3: clarity(清晰度)
我检查: 主诉是否简洁?描述是否清楚?
初稿: 主诉"胸痛3小时"简洁✓,但现病史太简略 → 7分
终稿: 主诉简洁✓,现病史详细✓ → 9分
维度4: compliance(合规性)
我检查: 诊断术语是否规范?
初稿: "冠心病"不够具体 → 6分
终稿: "急性冠脉综合征?"规范 → 93. 我评估3(因为num_evaluations=3):1: [6, 8, 7, 6]2: [6, 8, 7, 7]  # 略有波动3: [7, 8, 7, 6]
4. 我取平均: [6.3, 8.0, 7.0, 6.3]
四舍五入: [6, 8, 7, 6]
5. 我计算加权总分:
overall = 6×0.4 + 8×0.3 + 7×0.15 + 6×0.15
= 2.4 + 2.4 + 1.05 + 0.9
= 6.75
6. 我写评分理由:
"初稿要素齐全(8分),但临床描述不够具体(6分),如'胸痛'未描述性质。诊断术语'冠心病'过于笼统,应使用'急性冠脉综合征'等规范术语(6分)。"

我产生什么(输出)?

输出: EMREvaluation对象(初稿)
{
"clinical_accuracy": 6,
"completeness": 8,
"clarity": 7,
"compliance": 6,
"justification": "初稿要素齐全,但临床描述不够具体,诊断术语过于笼统。",
"overall_score": 6.75
}
输出: EMREvaluation对象(终稿)
{
"clinical_accuracy": 9,
"completeness": 10,
"clarity": 9,
"compliance": 9,
"justification": "终稿临床描述具体,诊断术语规范,计划详尽可操作。",
"overall_score": 9.25
}
对比结果:
improvement = 9.25 - 6.75 = +2.5分 ✓显著提升

我不能做什么(约束)?

约束1: 我不能被初稿/终稿的顺序影响
- 我必须独立评估每份病历
- 不能因为知道这是"优化后的终稿"就打高分
约束2: 我不能改变评分标准
- temperature=0.0保证完全确定性
- 同一文本评估10,分数应该完全一致
约束3: 我不能只给总分
- 必须给4个子分数
- 必须给评分理由(50)
约束4: 我不能参与生成过程
- 我是独立裁判
- 我不能影响Generator/Critic/Refiner的工作

我追求什么(目标)?

目标1: 稳定评分(一致性)
- 3次评估的分数标准差<0.5
- temperature=0.0保证确定性
目标2: 区分度(鉴别力)
- 好病历(8-10)
- 中等病历(5-7)
- 差病历(1-4)
- 不能所有病历都打7-8分
目标3: 可解释(透明性)
- 不能只给分不说理由
- justification要明确指出扣分原因
目标4: 客观性(公正性)
- 不能因为是"Refiner优化过的"就倾向打高分
- 对初稿和终稿一视同仁

【第三步:代码主线的逻辑串联】

完整执行流程的第一人称叙述

"""
我是Reflection系统的主控制器
当用户提交一个患者主诉时,我的工作流程如下:
"""
# ====== 阶段0: 初始化准备 ======
print("我醒来了,开始准备工作...")
# 我招聘各部门员工
generator = Generator(config.generator)      # 招聘初稿撰写员
critic = Critic(config.critic)              # 招聘质控专家
refiner = Refiner(config.refiner)           # 招聘修订专家
evaluator = Evaluator(config.evaluator)     # 招聘评估专家
# 我准备记事本
state_manager = StateManager()
print("准备完毕,等待任务...")
# ====== 阶段1: 接收任务 ======
patient_complaint = "胸痛3小时,伴出汗"
print(f"收到任务: {patient_complaint}")
# 我创建工作档案
workflow_id = generate_workflow_id()
state_manager.create_workflow(workflow_id)
# ====== 阶段2: 生成初稿 ======
print("→ 委派任务给Generator...")
try:
# Generator开始工作
draft = generator.generate(patient_complaint)
# 我检查Generator的工作成果
print(f"  Generator完成! 主诉: {draft.chief_complaint}")
# 我记录在档案里
state_manager.save_state(workflow_id, "draft", draft)
except ValidationError as e:
print(f"  ✗ Generator出错: {e}")
# 我让Generator重试
draft = generator.generate(patient_complaint)
# ====== 阶段3: 质控审查 ======
print("→ 委派任务给Critic...")
# Critic开始工作
critique = critic.critique(draft)
# 我检查Critic的工作成果
print(f"  Critic完成! 发现{len(critique.suggested_improvements)}个问题")
print(f"  问题列表:")
for i, suggestion in enumerate(critique.suggested_improvements, 1):
print(f"    {i}. {suggestion}")
# 我记录在档案里
state_manager.save_state(workflow_id, "critique", critique)
# ====== 阶段4: 修订终稿 ======
print("→ 委派任务给Refiner...")
# Refiner开始工作
refined = refiner.refine(draft, critique)
# 我检查Refiner的工作成果
print(f"  Refiner完成! 修改幅度: {refined.change_ratio:.1%}")
print(f"  修改要点: {refined.refinement_summary}")
# 我记录在档案里
state_manager.save_state(workflow_id, "refined", refined)
# ====== 阶段5: 质量评估 ======
print("→ 委派任务给Evaluator...")
# Evaluator对初稿和终稿分别评估
eval_draft = evaluator.evaluate(format_draft(draft))
eval_refined = evaluator.evaluate(refined.refined_emr)
# 我计算改进幅度
improvement = eval_refined.overall_score - eval_draft.overall_score
print(f"  Evaluator完成!")
print(f"  初稿得分: {eval_draft.overall_score:.1f}")
print(f"  终稿得分: {eval_refined.overall_score:.1f}")
print(f"  提升幅度: +{improvement:.1f}分")
# ====== 阶段6: 汇总报告 ======
print("→ 我开始汇总最终报告...")
final_report = {
"workflow_id": workflow_id,
"status": "success",
"result": {
"refined_emr": refined.refined_emr,
"quality_score": eval_refined.overall_score,
"improvement": improvement
},
"metadata": {
"draft_preview": draft.chief_complaint,
"num_suggestions": len(critique.suggested_improvements),
"change_ratio": refined.change_ratio,
"total_latency_ms": calculate_total_latency(),
"cost_usd": calculate_cost()
}
}
print("✓ 任务完成!")
print(f"  终稿病历: {refined.refined_emr[:50]}...")
print(f"  质量提升: +{improvement:.1f}分")
print(f"  总耗时: {final_report['metadata']['total_latency_ms']}ms")
return final_report

【总结:目标-手段的对应关系】

目标分解树

最终目标: 95%合规率的高质量病历
│
├─ 子目标1: 快速生成初稿(速度)
│   └─ 手段: Generator + Temperature=0.5 + Pydantic约束
│
├─ 子目标2: 找出所有问题(查全率)
│   └─ 手段: Critic + Temperature=0.1 + 注入规范 + 强制挑错
│
├─ 子目标3: 精准修改(查准率)
│   └─ 手段: Refiner + max_change_ratio=0.4 + 保留原文
│
├─ 子目标4: 客观评估(可信度)
│   └─ 手段: Evaluator + Temperature=0.0 + 多次评估取平均
│
└─ 子目标5: 全流程可追溯(可调试性)
└─ 手段: StateManager + 状态快照 + LangSmith追踪

关键设计决策的因果链

问题: 单次生成质量不稳定(60%合规率)
↓
假设: 人类专家通过"写-审-改"三步达到高质量
↓
设计: 将流程拆解为3个独立模块(Generator/Critic/Refiner)
↓
实现: 用Pydantic保证接口稳定 + LangGraph管理状态
↓
验证: 用LLM-as-a-Judge量化效果(+2.5)
↓
结果: 合规率从60%95% ✓达成目标

领导式规划的核心要义

  1. 主函数只管"做什么":

    • process(patient_complaint) 只定义5个大步骤
    • 不关心Generator如何生成,Critic如何审查
  2. 子函数只管"怎么做":

    • Generator.generate() 专注生成逻辑
    • 不关心Critic会如何评价自己的输出
  3. 通过接口解耦:

    • Generator输出DraftEMR对象
    • Critic接收DraftEMR,输出EMRCritique
    • 接口是"契约",确保模块间不直接依赖
  4. 状态集中管理:

    • StateManager是"秘书"
    • 所有中间结果都存在StateManager
    • 各模块通过StateManager传递数据

这就是从"想法"到"代码"的完整映射!

posted @ 2025-11-28 18:28  yangykaifa  阅读(10)  评论(0)    收藏  举报