From ce932b229ed826e00796959e6d76eba37540d5af Mon Sep 17 00:00:00 2001 From: iomgaa Date: Mon, 11 Aug 2025 00:03:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E6=B7=BB=E5=8A=A0Vi?= =?UTF-8?q?rtualPatient=E6=99=BA=E8=83=BD=E4=BD=93=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 功能特性: - 实现虚拟患者智能体用于医疗场景模拟 - 提供完整的模块结构支持患者数据建模 - 集成疾病症状模拟和响应生成功能 - 支持与DiseaseAnalyst协同工作流程 模块组成: - agent.py: VirtualPatient智能体核心实现 - prompt.py: 虚拟患者相关提示模板 - response_model.py: 患者数据响应模型定义 应用场景: - 医疗诊断训练和测试 - 临床决策支持系统验证 - 疾病分析算法评估 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- agent_system/virtual_patient/agent.py | 170 ++++++++++++++++++ agent_system/virtual_patient/prompt.py | 163 +++++++++++++++++ .../virtual_patient/response_model.py | 22 +++ 3 files changed, 355 insertions(+) create mode 100755 agent_system/virtual_patient/agent.py create mode 100755 agent_system/virtual_patient/prompt.py create mode 100755 agent_system/virtual_patient/response_model.py diff --git a/agent_system/virtual_patient/agent.py b/agent_system/virtual_patient/agent.py new file mode 100755 index 0000000..432d716 --- /dev/null +++ b/agent_system/virtual_patient/agent.py @@ -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}" + diff --git a/agent_system/virtual_patient/prompt.py b/agent_system/virtual_patient/prompt.py new file mode 100755 index 0000000..f85bebf --- /dev/null +++ b/agent_system/virtual_patient/prompt.py @@ -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": "虚拟患者的对话回复内容" +}""" + + + + + + diff --git a/agent_system/virtual_patient/response_model.py b/agent_system/virtual_patient/response_model.py new file mode 100755 index 0000000..9cd91e4 --- /dev/null +++ b/agent_system/virtual_patient/response_model.py @@ -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=( + "虚拟患者对当前医护人员询问的对话回复。" + "基于病历信息(主诉、现病史、既往史等)生成符合真实患者表达习惯的回答。" + "严格遵循信息边界约束,不得添加或编造病历中未记录的内容。" + ) + )