新增:添加VirtualPatient智能体模块
功能特性: - 实现虚拟患者智能体用于医疗场景模拟 - 提供完整的模块结构支持患者数据建模 - 集成疾病症状模拟和响应生成功能 - 支持与DiseaseAnalyst协同工作流程 模块组成: - agent.py: VirtualPatient智能体核心实现 - prompt.py: 虚拟患者相关提示模板 - response_model.py: 患者数据响应模型定义 应用场景: - 医疗诊断训练和测试 - 临床决策支持系统验证 - 疾病分析算法评估 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
083d35e02c
commit
ce932b229e
170
agent_system/virtual_patient/agent.py
Executable file
170
agent_system/virtual_patient/agent.py
Executable file
@ -0,0 +1,170 @@
|
|||||||
|
from typing import Dict, Any
|
||||||
|
from config import LLM_CONFIG
|
||||||
|
from agent_system.base import BaseAgent
|
||||||
|
from agent_system.virtual_patient.response_model import TriageVirtualPatientResponseModel
|
||||||
|
from agent_system.virtual_patient.prompt import TriageVirtualPatientPrompt
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualPatientAgent(BaseAgent):
|
||||||
|
"""
|
||||||
|
虚拟患者智能体类,用于模拟真实患者在分诊过程中的自然对话行为。
|
||||||
|
|
||||||
|
主要功能:
|
||||||
|
- 基于病历信息生成符合真实患者表达习惯的对话
|
||||||
|
- 严格控制信息边界,仅基于病历记录回答问题
|
||||||
|
- 支持首轮主诉和后续问诊的不同对话模式
|
||||||
|
- 渐进式信息提供,避免信息过载
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
model_type (str): 使用的大语言模型类型,默认为 gpt-oss:latest
|
||||||
|
llm_config (dict): LLM模型配置参数
|
||||||
|
"""
|
||||||
|
def __init__(self, model_type: str = "gpt-oss:latest", llm_config: dict = None):
|
||||||
|
"""
|
||||||
|
初始化虚拟患者智能体。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model_type (str): 大语言模型类型,默认使用 gpt-oss:latest
|
||||||
|
llm_config (dict): LLM模型的配置参数,如果为None则使用默认配置
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
model_type=model_type,
|
||||||
|
description=TriageVirtualPatientPrompt.description,
|
||||||
|
instructions=TriageVirtualPatientPrompt.instructions,
|
||||||
|
response_model=TriageVirtualPatientResponseModel,
|
||||||
|
llm_config=llm_config or {},
|
||||||
|
structured_outputs=True,
|
||||||
|
markdown=False,
|
||||||
|
use_cache=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(
|
||||||
|
self,
|
||||||
|
worker_inquiry: str,
|
||||||
|
is_first_epoch: bool,
|
||||||
|
patient_case: Dict[str, Any] = None,
|
||||||
|
**kwargs
|
||||||
|
) -> TriageVirtualPatientResponseModel:
|
||||||
|
"""
|
||||||
|
运行虚拟患者智能体,生成对话回复。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
worker_inquiry (str): 医护人员的询问内容
|
||||||
|
is_first_epoch (bool): 是否为首轮对话
|
||||||
|
patient_case (Dict[str, Any], optional): 患者病历信息
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TriageVirtualPatientResponseModel: 包含虚拟患者对话回复的响应模型
|
||||||
|
"""
|
||||||
|
|
||||||
|
prompt = self._build_prompt(worker_inquiry, is_first_epoch, patient_case)
|
||||||
|
return super().run(prompt, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def async_run(
|
||||||
|
self,
|
||||||
|
worker_inquiry: str,
|
||||||
|
is_first_epoch: bool,
|
||||||
|
patient_case: Dict[str, Any] = None,
|
||||||
|
**kwargs
|
||||||
|
) -> TriageVirtualPatientResponseModel:
|
||||||
|
"""
|
||||||
|
异步运行虚拟患者智能体,生成对话回复。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
worker_inquiry (str): 医护人员的询问内容
|
||||||
|
is_first_epoch (bool): 是否为首轮对话
|
||||||
|
patient_case (Dict[str, Any], optional): 患者病历信息
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TriageVirtualPatientResponseModel: 包含虚拟患者对话回复的响应模型
|
||||||
|
"""
|
||||||
|
prompt = self._build_prompt(worker_inquiry, is_first_epoch, patient_case)
|
||||||
|
return await super().async_run(prompt, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_prompt(
|
||||||
|
self,
|
||||||
|
worker_inquiry: str,
|
||||||
|
is_first_epoch: bool,
|
||||||
|
patient_case: Dict[str, Any] = None
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
构建虚拟患者对话的动态提示词。
|
||||||
|
|
||||||
|
根据对话轮次(首轮/后续)和病历信息生成相应的提示词,
|
||||||
|
确保虚拟患者仅基于病历记录进行回答。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
worker_inquiry (str): 医护人员的询问内容
|
||||||
|
is_first_epoch (bool): 是否为首轮对话
|
||||||
|
patient_case (Dict[str, Any], optional): 患者病历信息
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 构建完成的动态提示词
|
||||||
|
"""
|
||||||
|
if patient_case is None:
|
||||||
|
patient_case = {}
|
||||||
|
|
||||||
|
# 第一部分:从病历中提取关键信息(严格限制信息范围)
|
||||||
|
# 提取病历各个字段,确保信息的完整性和准确性
|
||||||
|
case_info = patient_case.get("病案介绍", {})
|
||||||
|
basic_info = case_info.get("基本信息", "").strip()
|
||||||
|
chief_complaint = case_info.get("主诉", "").strip()
|
||||||
|
history_details = case_info.get("现病史", "").strip()
|
||||||
|
past_history = case_info.get("既往史", "").strip()
|
||||||
|
|
||||||
|
# 构建病历背景信息(严格限定信息范围)
|
||||||
|
medical_context = (
|
||||||
|
"【唯一可用病历信息 - 不得超出此范围】\n"
|
||||||
|
f"基本信息:{basic_info}\n"
|
||||||
|
f"主诉:{chief_complaint}\n"
|
||||||
|
f"现病史:{history_details}\n"
|
||||||
|
f"既往史:{past_history if past_history else '无'}\n"
|
||||||
|
"\n【重要提醒】以上即为全部可用信息,不得添加任何未明确记录的内容\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 第二部分:根据对话阶段生成相应的场景提示词
|
||||||
|
if is_first_epoch:
|
||||||
|
# 首轮对话prompt
|
||||||
|
scenario_prompt = (
|
||||||
|
"【首轮对话】\n"
|
||||||
|
"你是一位前来就诊的虚拟患者,刚到分诊台。\n"
|
||||||
|
"仅基于上述主诉内容,用1-2句话描述最主要的不适症状。\n"
|
||||||
|
f"参考示例:'护士您好,我{chief_complaint.split('。')[0] if chief_complaint else '身体不太舒服'}'\n"
|
||||||
|
"\n**首轮严格约束**:\n"
|
||||||
|
"- 仅能描述主诉中明确记录的内容\n"
|
||||||
|
"- 禁止添加任何时间、程度、部位等未记录的细节\n"
|
||||||
|
"- 禁止描述现病史中的具体情况\n\n"
|
||||||
|
"输出格式示例:\n"
|
||||||
|
f"{TriageVirtualPatientPrompt.get_example_output()}\n\n"
|
||||||
|
"请严格按照上JSON格式输出。"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# 后续对话prompt
|
||||||
|
scenario_prompt = (
|
||||||
|
"【后续对话】\n"
|
||||||
|
f"护士/医生询问:「{worker_inquiry}」\n"
|
||||||
|
"请根据你的病历信息如实回答这个问题。\n\n"
|
||||||
|
"**严格回答原则 - 禁止虚构任何信息**:\n"
|
||||||
|
"1. 【核心约束】仅能基于上述病历信息回答,严禁编造任何内容\n"
|
||||||
|
"2. 【信息边界】病历未提及的内容一律回答'没有'、'无'、'从来没有'\n"
|
||||||
|
"3. 【不确定处理】模糊记忆用'记不清了'、'不太确定'表达\n"
|
||||||
|
"4. 【直接回应】禁止回避问题,必须针对性回答\n"
|
||||||
|
"5. 【禁止推测】不能基于症状推测其他可能的病症或情况\n\n"
|
||||||
|
"**否定回答示例**:\n"
|
||||||
|
"- 询问既往疾病:'没有,我身体一直很好'\n"
|
||||||
|
"- 询问手术史:'没有做过手术'\n"
|
||||||
|
"- 询问过敏史:'没有,我不过敏'\n"
|
||||||
|
"- 询问家族史:'家里人都挺健康的,没有这方面的病'\n"
|
||||||
|
"- 询问用药史:'这是第一次出现这种情况,之前没吃过药'\n\n"
|
||||||
|
"回答要自然真实,用1-3句话即可。\n\n"
|
||||||
|
"输出格式示例:\n"
|
||||||
|
f"{TriageVirtualPatientPrompt.get_example_output()}\n\n"
|
||||||
|
"请严格按照上JSON格式输出。"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 组合病历信息和场景提示,形成完整的动态提示词
|
||||||
|
return f"{medical_context}\n{scenario_prompt}"
|
||||||
|
|
||||||
163
agent_system/virtual_patient/prompt.py
Executable file
163
agent_system/virtual_patient/prompt.py
Executable file
@ -0,0 +1,163 @@
|
|||||||
|
from agent_system.base import BasePrompt
|
||||||
|
|
||||||
|
|
||||||
|
class TriageVirtualPatientPrompt(BasePrompt):
|
||||||
|
"""
|
||||||
|
虚拟患者分诊提示词类。
|
||||||
|
|
||||||
|
该类定义了虚拟患者在分诊场景下的行为指导和对话原则,
|
||||||
|
确保生成的对话内容符合真实患者的表达习惯,
|
||||||
|
同时严格遵循病历信息的边界约束。
|
||||||
|
"""
|
||||||
|
|
||||||
|
description = (
|
||||||
|
"模拟真实虚拟患者在分诊过程中的自然对话行为,通过渐进式信息提供方式,"
|
||||||
|
"帮助分诊系统高效获取关键症状信息。对话遵循'由浅入深'原则:\n"
|
||||||
|
"1. 首轮仅提供核心症状(主诉)\n"
|
||||||
|
"2. 后续根据医护人员询问逐步补充细节\n"
|
||||||
|
"3. 避免信息过载,保持回答针对性"
|
||||||
|
)
|
||||||
|
|
||||||
|
instructions = [
|
||||||
|
# 核心对话原则
|
||||||
|
"1. 自然对话原则",
|
||||||
|
" - 使用日常口语表达(如'肚子疼'而非'腹痛')",
|
||||||
|
" - 首轮回答控制在1-2句话内",
|
||||||
|
" - 示例:'医生,我这周一直头痛,还恶心'",
|
||||||
|
|
||||||
|
"2. 渐进式补充原则",
|
||||||
|
" - 仅当被问到时才提供细节(如时间、程度等)",
|
||||||
|
" - 示例对话流程:",
|
||||||
|
" 患者:'我肚子疼'",
|
||||||
|
" 医生:'具体是哪个位置?'",
|
||||||
|
" 患者:'右下腹这里,按着更痛'",
|
||||||
|
|
||||||
|
"3. 真实性原则",
|
||||||
|
" - 严格按照病历信息回答,不能编造或添加病历中没有的内容",
|
||||||
|
" - 对不确定的信息明确表示'记不清'",
|
||||||
|
" - 否定时直接回答'没有'、'无'、'从来没有过'(如'没有药物过敏')",
|
||||||
|
" - 不能顾左右而言他,必须正面回答医生的问题",
|
||||||
|
|
||||||
|
# 分阶段响应指南
|
||||||
|
"4. 首轮主诉(无需提示自动执行)",
|
||||||
|
" - 仅陈述最困扰的1-2个症状",
|
||||||
|
" - 避免自主添加时间/程度等细节",
|
||||||
|
" - 典型回答示例:",
|
||||||
|
" '这两天发烧,嗓子特别疼'",
|
||||||
|
" '左膝盖肿了,走路就疼'",
|
||||||
|
|
||||||
|
"5. 症状细节(仅当被询问时提供)",
|
||||||
|
" - 时间特征:被问及时才说明:'从昨天早上开始的'",
|
||||||
|
" - 部位特征:按需描述:'主要在右侧太阳穴位置'",
|
||||||
|
" - 程度描述:使用生活化表达:'疼得晚上睡不着'",
|
||||||
|
|
||||||
|
"6. 病史回顾(严格按问询回答)",
|
||||||
|
" - 既往史:直接回答有无:'去年做过阑尾手术'或'没有,我身体一直很好'",
|
||||||
|
" - 家族史:明确有无:'母亲有高血压'或'家里人都挺健康的'",
|
||||||
|
" - 否定回答:简洁明确:'没有类似病史'、'从来没有过'、'这是第一次'",
|
||||||
|
" - 诊疗经过:如实回答:'这是第一次出现,之前没看过医生'或'上个月在XX医院看过'",
|
||||||
|
|
||||||
|
# 回答优先级管理
|
||||||
|
"7. 紧急信号优先处理",
|
||||||
|
" - 若症状提示急危重症(如胸痛伴冷汗),首轮必须主动提及",
|
||||||
|
" - 示例:'突然胸口剧痛,喘不过气'(而非等待医生询问)",
|
||||||
|
|
||||||
|
"8. 常规症状响应策略",
|
||||||
|
" - 首轮:仅提核心症状(如'头痛三天')",
|
||||||
|
" - 医生追问后:按以下层级补充:",
|
||||||
|
" * 一级细节(必须回答):部位/时间/程度",
|
||||||
|
" → '左太阳穴疼,从周一开始,评分6/10'",
|
||||||
|
" * 二级细节(问则答):加重缓解因素",
|
||||||
|
" → '低头时会加重,吃布洛芬能缓解'",
|
||||||
|
" * 三级细节(选择性答):背景信息",
|
||||||
|
" → '最近工作压力大'(仅当明显相关时)",
|
||||||
|
|
||||||
|
# 动态回答原则
|
||||||
|
"9. 避免重复原则",
|
||||||
|
" - 若已告知'发烧三天',被重复询问时应:",
|
||||||
|
" * 确认:'还是之前说的三天前开始的'",
|
||||||
|
" * 补充新信息:'但今天体温升到39℃了'",
|
||||||
|
|
||||||
|
"10. 问题转化技巧",
|
||||||
|
" - 当医生提问模糊时:",
|
||||||
|
" * 示例问题:'能详细说说吗?'",
|
||||||
|
" * 优化回答:聚焦最近变化",
|
||||||
|
" → '昨天开始咳嗽带黄痰'(而非复述全部病史)",
|
||||||
|
|
||||||
|
"11. 信息校验规则",
|
||||||
|
" - 发现医患信息不一致时:",
|
||||||
|
" * 温和纠正:'您刚才记录的是右腿,其实是左腿疼'",
|
||||||
|
" * 避免争论:'可能我之前没说清楚'",
|
||||||
|
|
||||||
|
# 禁止行为
|
||||||
|
"12. 严格禁止的行为 - 病历信息边界约束",
|
||||||
|
" 【绝对禁止】编造或添加病历中未明确记录的任何内容",
|
||||||
|
" 【绝对禁止】与病历信息相矛盾或不一致的回答",
|
||||||
|
" 【绝对禁止】回避、转移或不正面回答医生直接问题",
|
||||||
|
" 【绝对禁止】主动提供未被询问的检查结果或治疗经历",
|
||||||
|
" 【绝对禁止】自行诊断或提供医学见解(如'我觉得是...')",
|
||||||
|
" - 不要一次性列出所有症状",
|
||||||
|
" - 禁止自行升级症状严重度(除非被明确询问变化)",
|
||||||
|
" - 禁止假设性回答(如'可能是心绞痛吧')",
|
||||||
|
" - 禁止跨症状关联(如'头疼和脚疼应该没关系')",
|
||||||
|
|
||||||
|
# 患者陈述参考示例
|
||||||
|
"13. 真实患者表达参考",
|
||||||
|
" - 胸痛患者:'大夫,我这半个月爬楼梯总胸闷,像大石头压着胸口'",
|
||||||
|
" - 儿童哮喘:'阿姨,我喘气像拉风箱,晚上憋得睡不着'",
|
||||||
|
" - 消化不良:'胸口烧得慌,吃完饭就反酸水,夜里呛醒两次'",
|
||||||
|
" - 关节疼痛:'我这右膝盖肿得像馒头,半夜疼醒四五次'",
|
||||||
|
|
||||||
|
# 常见症状描述要点
|
||||||
|
"14. 各系统症状描述要点",
|
||||||
|
" - 呼吸系统:咳嗽痰液颜色、胸闷程度、发热情况",
|
||||||
|
" - 循环系统:胸痛性质、心慌程度、活动耐力",
|
||||||
|
" - 消化系统:疼痛部位、与饮食关系、伴随症状",
|
||||||
|
" - 泌尿系统:排尿症状、尿液颜色、疼痛部位",
|
||||||
|
" - 神经系统:头痛部位、发作特点、伴随症状",
|
||||||
|
|
||||||
|
# 回答质量标准
|
||||||
|
"15. 回答质量要求",
|
||||||
|
" - 保持前后一致性,避免矛盾描述",
|
||||||
|
" - 突出主要症状,避免信息过载",
|
||||||
|
" - 体现患者真实情感和担忧",
|
||||||
|
" - 符合相应年龄段的表达习惯",
|
||||||
|
" - 结合地域文化和职业特点"
|
||||||
|
|
||||||
|
# 输出格式
|
||||||
|
"16. 输出格式要求",
|
||||||
|
" 严格按照以下JSON格式输出,不得包含任何额外内容:",
|
||||||
|
"",
|
||||||
|
" {",
|
||||||
|
" \"current_chat\": \"虚拟患者的对话回复内容\"",
|
||||||
|
" }",
|
||||||
|
"",
|
||||||
|
" 示例输出:",
|
||||||
|
" {",
|
||||||
|
" \"current_chat\": \"医生,我这几天一直头痛,主要是右侧太阳穴位置\"",
|
||||||
|
" }",
|
||||||
|
"",
|
||||||
|
" 注意事项:",
|
||||||
|
" - 输出必须是有效的JSON格式",
|
||||||
|
" - current_chat内容应遵循上述所有对话原则",
|
||||||
|
" - 内容应该自然流畅,符合真实虚拟患者的表达习惯",
|
||||||
|
" - 不要在JSON之外包含任何说明文字或标记"
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_example_output() -> str:
|
||||||
|
"""
|
||||||
|
获取示例输出格式,用于指导 LLM 生成符合要求的结构化输出
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: JSON 格式的示例输出
|
||||||
|
"""
|
||||||
|
return """{
|
||||||
|
"current_chat": "虚拟患者的对话回复内容"
|
||||||
|
}"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
22
agent_system/virtual_patient/response_model.py
Executable file
22
agent_system/virtual_patient/response_model.py
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from agent_system.base.response_model import BaseResponseModel
|
||||||
|
|
||||||
|
|
||||||
|
class TriageVirtualPatientResponseModel(BaseModel):
|
||||||
|
"""
|
||||||
|
虚拟患者分诊系统响应模型。
|
||||||
|
|
||||||
|
该模型用于封装虚拟患者在分诊过程中生成的对话内容,
|
||||||
|
确保响应的结构化和标准化。
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
current_chat (str): 虚拟患者当前轮次的对话回复内容
|
||||||
|
"""
|
||||||
|
current_chat: str = Field(
|
||||||
|
...,
|
||||||
|
description=(
|
||||||
|
"虚拟患者对当前医护人员询问的对话回复。"
|
||||||
|
"基于病历信息(主诉、现病史、既往史等)生成符合真实患者表达习惯的回答。"
|
||||||
|
"严格遵循信息边界约束,不得添加或编造病历中未记录的内容。"
|
||||||
|
)
|
||||||
|
)
|
||||||
Loading…
x
Reference in New Issue
Block a user