中医智能诊疗系统-阶段一
一、项目概述我们要做什么1.1 项目背景中医诊疗强调“辨证论治”需要医生通过多轮问诊收集患者信息综合分析后给出诊断。传统的线上问诊系统要么是填表单冰冷、不灵活要么是单轮问答缺乏引导。我们希望构建一个AI中医智能诊疗系统让AI像真正的医生一样通过多轮引导式问诊收集症状进行辨证分析并给出可解释的诊疗建议系统也可以作为中医问诊的辅助工具。1.2 第一阶段目标第一阶段的核心任务是搭建可运行的基础设施实现模块目标项目架构建立前后端分离的项目骨架配置开发环境智能问诊实现会话Agent支持多轮引导式对话记忆功能管理会话上下文结构化提取症状数据库持久化存储会话、消息、诊疗记录简单说让系统能“聊起来”并且“记住”聊了什么。二、技术选型与架构设计2.1 技术栈层级技术选型理由前端Vue3 Pinia响应式框架状态管理清晰后端FastAPI Python异步支持好开发效率高数据库PostgreSQL SQLAlchemyFAISS关系型数据库ORM成熟大模型LongCat/DeepSeek API成本低中文理解优秀部署Uvicorn轻量级ASGI服务器2.2 架构分层text┌─────────────────────────────────────────────────────────┐ │ 前端Vue3 │ │ ┌──────────┬──────────────────┬──────────────────────┐ │ │ │ 左侧导航 │ 中间聊天区域 │ 右侧诊疗面板 │ │ │ │ 历史记录 │ 多轮问诊交互 │ 症状提取实时展示 │ │ │ └──────────┴──────────────────┴──────────────────────┘ │ └─────────────────────────────────────────────────────────┘ │ HTTP/SSE ┌─────────────────────────────────────────────────────────┐ │ 后端FastAPI │ │ ┌─────────────────────────────────────────────────────┐│ │ │ API层路由 ││ │ │ conversation.py / diagnosis.py ││ │ └─────────────────────────────────────────────────────┘│ │ ┌─────────────────────────────────────────────────────┐│ │ │ Agent层智能体 ││ │ │ ChatAgent / BaseAgent / orchestrator ││ │ └─────────────────────────────────────────────────────┘│ │ ┌─────────────────────────────────────────────────────┐│ │ │ Core层核心能力 ││ │ │ SessionMemory / LLM / prompts / symptom_extractor ││ │ └─────────────────────────────────────────────────────┘│ │ ┌─────────────────────────────────────────────────────┐│ │ │ 数据层持久化 ││ │ │ PostgreSQL SQLAlchemy async CRUD ││ │ └─────────────────────────────────────────────────────┘│ └─────────────────────────────────────────────────────────┘分层设计的原则依赖方向从上到下API → Agent → Core → 数据层下层不依赖上层保证可测试性和可替换性三、项目初始化搭建开发环境3.1 后端目录结构textbackend/ ├── main.py # FastAPI入口 ├── config.py # 配置管理单例模式 ├── requirements.txt # 依赖清单 ├── .env # 环境变量不入库 ├── .env.example # 环境变量模板 │ ├── api/ # 路由层 │ ├── conversation.py # 会话管理API │ └── diagnosis.py # 诊疗API │ ├── agent/ # 智能体层 │ ├── base.py # Agent基类 │ ├── chat.py # 会话Agent │ └── orchestrator.py # Agent编排器 │ └── agents/ # 多个诊疗agent ├── core/ # 核心能力层 │ ├── llm.py # LLM封装单例模式 │ ├── memory.py # 会话记忆管理 │ ├── prompts.py # 提示词模板 │ └── symptom_extractor.py # 症状提取器 │ ├── db/ # 数据层 │ ├── base.py # ORM基类 │ ├── session.py # 数据库会话管理 │ ├── models/ # 表结构定义 │ └── crud/ # CRUD操作 │ ├── models/ # Pydantic模型 │ └── schemas.py # API请求/响应校验 │ └── utils/ # 工具函数 ├── logger.py # 日志配置 └── helpers.py # 辅助函数3.2 配置管理设计配置管理的核心是环境隔离和敏感信息保护配置管理的两个关键技术点单例模式确保整个程序只有一个配置实例避免配置不一致环境变量模板.env.example提交到仓库.env不入库保障API密钥安全3.3 数据库表设计-- 会话表顶层容器 CREATE TABLE sessions ( id VARCHAR(36) PRIMARY KEY, name VARCHAR(200), type ENUM(diagnosis, tongue), created_at TIMESTAMP, updated_at TIMESTAMP ); -- 消息表聊天记录 CREATE TABLE messages ( id BIGINT AUTO_INCREMENT PRIMARY KEY, session_id VARCHAR(36), role ENUM(user, assistant), content TEXT, created_at TIMESTAMP, FOREIGN KEY (session_id) REFERENCES sessions(id) ); -- 诊疗记录表一次“开始诊疗”一条记录 CREATE TABLE diagnoses ( id VARCHAR(36) PRIMARY KEY, session_id VARCHAR(36), sequence_num INT, -- 第几次诊疗 symptoms JSON, -- 症状快照 diagnosis TEXT, prescription TEXT, created_at TIMESTAMP, FOREIGN KEY (session_id) REFERENCES sessions(id) );设计点sequence_num字段记录会话内第几次诊疗为后续的多次诊疗对比功能预留扩展空间。四、核心功能一LLM封装与单例模式4.1 设计目标LLM封装层需要统一管理大模型调用提供稳定的接口给上层Agent使用。4.2 技术亮点单例模式为什么用单例问题没有单例有单例连接池管理每次请求创建新连接池内存泄漏一个连接池复用HTTP连接配置一致性可能创建不同配置的实例全局配置统一API限流多个实例并发更容易触发限流统一管理请求速率我的理解单例模式是解决资源管理问题的工程实践。LLM实例封装了HTTP客户端每个客户端维护连接池如果每次调用都创建新实例会导致连接泄漏和内存膨胀。4.4 技术点异步安全考虑当前LLM()构造函数是同步的用全局变量None检查即可。但如果未来需要异步初始化如加载远程配置需要改用asyncio.Lock_lock asyncio.Lock() async def get_llm(): global _llm_instance if _llm_instance is None: async with _lock: if _llm_instance is None: _llm_instance await create_llm_async() return _llm_instance这种“双重检查锁定”模式确保了异步环境下的线程安全。五、核心功能二会话记忆管理5.1 设计目标记忆系统需要支持多会话独立存储诊疗会话和舌诊会话互不干扰消息历史管理限制数量防止内存溢出症状结构化提取从自然语言中抽取标准化症状诊疗记录保存支持多次诊疗对比5.2 技术亮点内存-数据库双重存储存储用途读写速度持久性内存SessionMemory当前活跃会话读写微秒级服务重启丢失数据库PostgreSQL历史数据归档毫秒级永久保存一致性策略先写数据库成功后更新内存。如果数据库写入失败不更新内存保持状态一致。5.3 踩坑记录历史会话显示不完全现象前端只能看到当前会话历史会话部分消失。原因get_patient_sessions方法返回的是self.memory.get_all_sessions()而内存中的_metadata只在用户与某个会话交互后才会同步导致大量历史会话“消失”。解决方案查询操作直接返回数据库查询结果不依赖内存async def get_patient_sessions(self, patient_id: str, db: AsyncSession): # 直接从数据库查询 db_sessions await self.db_manager.get_sessions_by_patient(db, patient_id) return [{session_id: s.id, name: s.name, ...} for s in db_sessions]教训内存只应用于加速当前活跃会话的读写归档性质的读取操作应该直接从数据库读取。六、核心功能三会话Agent与提示词工程6.1 设计目标会话Agent是系统的“前台医生”需要理解用户症状描述按照中医问诊逻辑引导对话每轮只问1-2个问题不给用户压力自动提取症状并结构化存储6.2 提示词工程从“无序提问”到“状态机问诊”问题如果只是简单告诉LLM“你是一名中医医生”它的问诊往往是无序的、重复的甚至会在用户刚说“头痛”时就问“您胃口好吗”。解决方案将中医“十问歌”的逻辑编码为结构化提示词# backend/core/prompts.py class TCMPrompts: staticmethod def chat_agent(chat_history: str, current_message: str, extracted_symptoms: List[str] None) - str: return f你是一位经验丰富的中医医生。 【核心原则 - 必须遵守】 1. 每次只问1-2个问题 2. 遵循问诊顺序主诉→伴随症状→寒热→汗→饮食→二便→睡眠 3. 不要重复问已回答的问题 4. 先表示共情再追问 【问诊顺序参考】 - 第一步了解主诉哪里不舒服 - 第二步询问伴随症状还有其他不舒服吗 - 第三步问寒热怕冷还是怕热 - 第四步问汗出汗多吗 - 第五步问饮食胃口怎么样 - 第六步问二便大便成形吗 【示例回复 - Few-shot】 - “我了解了头痛确实很影响生活。请问您头痛的同时有没有怕冷或者发热的感觉呢” - “感谢您的描述。根据您说的怕冷、不出汗我再问一下您最近胃口怎么样” 【已有症状】{, .join(extracted_symptoms) if extracted_symptoms else 无} 【历史对话】 {chat_history} 用户最新消息{current_message} 请直接输出你的回复50-120字 6.3 技术点Few-shot学习与约束设计提示词中用了4个示例回复本质上是在做模式匹配示例适用场景作用示例1用户刚描述主诉先共情再追问寒热示例2用户已回答部分信息总结已有信息追问缺失项示例3信息已比较完整给出初步分析不再追问示例4用户描述模糊请求具体化描述这相当于给LLM提供了4种场景下的“标准答案模板”让AI不再随意发挥。6.4 会话Agent核心流程# backend/agent/chat.py class ChatAgent: async def process_message(self, session_id: str, user_message: str, db: AsyncSession) - Dict[str, Any]: # 1. 确保会话存在 if not self.memory.get_session_metadata(session_id): await self._load_session_from_db(session_id, db) # 2. 提取症状LLM智能提取 new_symptoms await self.memory.extract_and_update_symptoms( session_id, user_message ) # 3. 添加用户消息到内存 self.memory.add_message(session_id, user, user_message) # 4. 保存到数据库 await self.db_manager.add_message(db, session_id, user, user_message) # 5. 构建提示词并调用LLM prompt self._build_prompt(session_id, user_message) response await self.llm.generate(prompt) # 6. 保存助手回复 self.memory.add_message(session_id, assistant, response) await self.db_manager.add_message(db, session_id, assistant, response) return {response: response, symptoms: new_symptoms}七、核心功能四Agent基类与三级容错7.1 设计目标后续需要实现6个诊疗Agent症状解析、辨证、方药等需要一个统一的基类来统一LLM调用接口统一状态管理统一输出解析统一进度推送7.2 三级容错JSON解析问题LLM输出格式极其不可控经常在JSON前后添加解释文字、Markdown标记等。错误类型实际输出示例添加解释文字好的这是结果{\symptoms\: [\头痛\]}Markdown包裹json\n{symptoms: [头痛]}\n自然语言混搭根据您的描述症状是头痛、怕冷解决方案三级降级容错# backend/agent/base.py def _extract_json(self, text: str) - Dict[str, Any]: 三级容错JSON提取器 # 第一级直接解析 try: return json.loads(text) except: pass # 第二级提取 json ... 代码块 json_pattern rjson\s*(.*?)\s* match re.search(json_pattern, text, re.DOTALL) if match: try: return json.loads(match.group(1)) except: pass # 第三级提取花括号内容 brace_pattern r\{[\s\S]*\} match re.search(brace_pattern, text, re.DOTALL) if match: try: return json.loads(match.group(0)) except: pass raise ValueError(f无法提取JSON: {text[:200]})效果解析成功率从约60%提升到90%以上。7.3 之所以是三级级别处理场景成功率第一级纯净JSON约40%第二级Markdown包裹的JSON约30%累计70%第三级自然语言包裹的JSON约26%累计96%全部失败完全非结构化输出约4%核心认知没有哪一级能覆盖所有情况。LLM的输出模式是多样的必须用多层“筛子”逐级过滤。八、API接口设计8.1 会话管理接口接口方法功能/api/conversation/createPOST创建新会话/api/conversation/messagePOST发送消息/api/conversation/history/{session_id}GET获取会话历史/api/conversation/rename/{session_id}PUT重命名会话/api/conversation/delete/{session_id}DELETE删除会话/api/conversation/patient/{patient_id}GET获取患者所有会话8.2 关键代码from fastapi import APIRouter, Depends from sqlalchemy.ext.asyncio import AsyncSession from agent.chat import get_chat_agent router APIRouter(prefix/api/conversation, tags[conversation]) router.post(/message) async def send_message( session_id: str, message: str, db: AsyncSession Depends(get_async_db) ) - Dict[str, Any]: chat_agent get_chat_agent() return await chat_agent.process_message(session_id, message, db)8.3 路由注册# backend/main.py from api.conversation import router as conversation_router app FastAPI(title中医智能诊疗系统) app.include_router(conversation_router)