最近半年我一直泡在一个“Python + 微信小程序 + AI健康问诊”的个人项目里。起因很简单:家里人经常在小程序里看各种健康问答,但问题描述得零零散散,得到的回答又是通用模板,根本没结合个人的基础情况。我就想,能不能自己搭一个能“问诊”的健康评估系统——用户用普通话描述症状,后端用AI模型做意图识别和症状抽取,结合用户填写的年龄、性别、既往史,输出一份结构化的个人健康风险评估。项目做下来,踩了不少坑,也沉淀了一套可以直接复用的方案,今天把它完整拆开聊聊。
这个系统不只是一个技术Demo,它其实解决了一个很实际的矛盾:用户想要个性化反馈,但传统表单收集信息的效率太低。用对话式问诊的方式,让用户用自然语言说出“我头痛三天了,还有点恶心”,再由程序从中提取“头痛、持续3天、恶心、急性发作”等关键特征,最后映射到对应的健康建议和就医科室推荐。整套链路涉及小程序前端、Python后端、AI接口调用、数据库设计和健康数据合规几个层面,适合有一定开发基础、想动手做一个完整AI应用的朋友参考。
1. 内容整体设计与思路拆解
1.1 这个系统到底在解决什么问题
市面上大多数健康类小程序,核心交互是“选择题”——用户按表单勾选症状、持续时间、严重程度,然后系统给出预置的结论。这种模式有两个明显痛点:第一,写死的问题列表永远覆盖不了真实的描述方式,用户说“我心口像针扎一样疼”,表单里可能只有“胸痛”选项,细节全丢了;第二,选择题暗示性强,用户容易被选项带着走,反而说不准真实情况。
所以我把交互改成了“自由文本 + 结构化抽取”。用户在输入框里写一段话,后端先做一个预处理,把语气词、口语化表述清洗掉,然后交给AI模型做三件事:抽取症状实体、识别症状属性(持续时间、程度、频率)、判断紧急程度。系统再基于抽取结果,叠加用户档案里的年龄、性别、既往病史,生成评估建议。整个过程对用户来说就是“说一句话 + 看一份报告”,对开发者来说则是“自然语言处理 + 规则引擎 + 知识库映射”的组合。
之所以选Python做后端,是因为AI这块的生态太成熟了。不管用现成大模型的API接口,还是本地跑一个轻量的文本分类模型,Python都有现成的库可以快速集成。小程序端则选原生框架,不是因为它最好,而是它最稳,文档齐全,且登录、支付、订阅消息这些能力只有原生微信小程序才最顺滑。
1.2 为什么没有做成纯规则匹配
早期版本我确实试过纯规则方案——维护一个庞大的症状关键词表,用正则去匹配文本。比如文本里出现“头痛”就命中头痛规则,再配合一些程度词(“剧烈”“轻微”)做加权。实测下来,如果用户描述比较标准,准确率尚可,但遇到“脑袋像要炸开一样”这种口语化说法,规则就失灵了。更关键的是,症状之间的关联性很难用规则表达,“头痛伴随呕吐”和“单纯的呕吐”指向的科室完全不同,而规则引擎需要为每种组合单独写逻辑,组合爆炸,根本维护不动。
换成AI方案后,我把“理解文本”和“判定风险”拆成两层。理解层交给大模型,利用它强大的语义泛化能力,把口语表述归一化成结构化的症状列表;判定层则保留规则引擎,因为健康风险评估容不得“胡编”,必须基于明确的医学逻辑和知识库。大模型负责“听懂”,规则负责“判断”,两者职责分离,系统才既灵活又可靠。
这里有个重要的设计决策:AI永远不直接给诊断结论。系统输出的是“可能的健康风险方向”和“建议就诊科室”,而不是“你得了什么病”。因为健康数据极其敏感,任何超出辅助范围的表述都可能带来责任问题。我把这一点直接写进了系统提示词和产品规则里,后续所有内容生成都围绕这条底线展开。
2. 核心细节解析与实操要点
2.1 小程序端的信息采集交互设计
小程序端我重点做了三个页面:对话问诊页、健康档案页、评估报告页。对话问诊页是整个系统的门面,设计上刻意做得很轻——顶部是对话流,底部是输入框,没有复杂组件,降低用户心理门槛。
用户首次进入时,会先让ta填写基础档案:年龄、性别、身高体重、过敏史、既往病史。这些信息是后续评估的底层依据。因为健康信息高度隐私,这一步的交互文案必须解释清楚“为什么需要这些信息”,否则很多人会卡在这一步弃用。我试过两种引导方式,直接列表让用户勾选,以及用对话式追问(“请问您以前有过哪些慢性病史?”),后者完成率明显更高,但耗时更长。最终我做了一个折中:首次进入先用极简表单收集必填的年龄、性别,其他信息允许后补,用户可以先问诊再完善档案。
在问诊对话时,我预设了几种快捷引导语,比如“头疼”“发烧”“胃不舒服”,用户点一下就能作为消息发送,也可以直接手动输入。手动输入的内容通过wx.request发送给后端接口,消息格式统一为JSON:{user_id, session_id, message}。这里 session_id 很重要,它关联同一个用户连续多轮对话的上下文,让AI能够根据前文追问细节。
2.2 后端接口与AI模块的边界划分
后端我用FastAPI搭建,选它的原因很简单:自带Swagger接口文档,调试方便;支持异步,方便并发调用AI接口;类型校验严谨,不容易把脏数据传给下游。整个后端按功能拆成五个模块:用户模块、问诊会话模块、AI抽取模块、评估规则模块、报告生成模块。
AI抽取模块是核心,它不直接输出最终结论,而是输出一个中间JSON结构,类似:
{ "symptoms": [ {"name": "头痛", "attr": {"duration": "3天", "severity": "中", "trend": "持续性"}}, {"name": "恶心", "attr": {"frequency": "间歇", "context": "伴随头痛"}} ], "emergency_level": "low", "missing_info": ["是否发热", "既往偏头痛史"] }这个结构有几个好处:第一,它把自然语言变成了可计算的字段,后续规则引擎可以直接消费;第二,missing_info字段驱动多轮追问,比如用户只说了头痛,AI会追问“有没有发热或呕吐”,补齐关键信息;第三,emergency_level用于前置分流,如果识别到“胸痛伴大汗”“意识模糊”等危险信号,直接走紧急就医提示,不再继续问诊。
这里我用的是现成大模型的接口,通过Python的openai兼容客户端调用。为了让输出格式稳定,提示词里明确规定了输出JSON结构,并且加了few-shot示例。实测发现,大模型在不加示例时经常漏掉missing_info字段,加了两个示例后准确率提升明显。这个方案的好处是开发成本极低,但需要处理网络延迟和异常返回;如果对数据隐私要求高,也可以换成本地部署的中小模型做实体抽取,效果会稍弱,但完全可控。
2.3 健康评估规则引擎的实现思路
AI抽取出的结构化症状信息,需要转成可执行的评估逻辑,这就是规则引擎的活。我没有引入重量级的规则引擎框架,直接用Python字典加判定函数就搞定了。
评估流程分三步。第一步,把症状对照到一个标准化医学实体表。比如“头痛”“脑袋疼”“头部不适”统一映射为“头痛”,“拉肚子”“腹泻”映射为“腹泻”。这个映射表初始由人工整理,后续可以从真实对话数据中筛出高频表达,定期补充。
第二步,对标准化症状做风险打分。每个症状预设一个基础风险分,比如“胸痛”基础分80、“咳嗽”基础分30;再结合用户档案调整:60岁以上老人基础分上调10,有高血压病史者胸痛分上调15。同时考虑症状组合,比如“头痛+呕吐”比单纯头痛风险高一个等级。分数最终落到三个挡位:低风险、中风险、高风险。
第三步,根据风险挡位与症状组合,映射建议内容。低风险给出生活建议、观察指标和线上非处方用药参考;中风险建议线下就诊,并推荐可能适合的科室;高风险直接提示立即就医,附带需要警惕的危险信号列表。所有建议文案都预先写好,而不是由AI临时生成,这样能保证措辞严谨、合法合规,也便于审核。
这个设计让我意识到,规则引擎并不过时,它和AI是互补关系。AI的强项是变通,规则的强项是稳定。健康领域恰恰是“稳定”胜于“变通”的场景,所以规则必须掌握最终决策权。
3. 实操过程与核心环节实现
3.1 从零搭建可运行的最小系统
我先说一个结论:如果从零开始,按“小程序端 → 后端API → AI接入 → 规则评估”的顺序推进,一个可以联调的最小系统大概需要5个工作日。下面是我实际操作时遵循的步骤。
第一步,先建好项目结构:
health_assistant/ ├── backend/ │ ├── app.py │ ├── api/ │ │ ├── user.py │ │ ├── consult.py │ │ └── report.py │ ├── ai/ │ │ └── extractor.py │ ├── rules/ │ │ ├── symptoms.py │ │ └── evaluator.py │ └── models/ │ └── database.py └── miniprogram/ ├── pages/ │ ├── chat/ │ ├── profile/ │ └── report/ └── utils/第二步,配置后端环境。我用的Python版本是3.10,核心依赖就四个:fastapi、uvicorn、pydantic、requests。数据库方面,为了快速开发先用SQLite存用户档案和问诊记录,后续数据量大了再平滑迁移到MySQL。
后端启动代码很常规:
# app.py from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI(title="Health Assistant API") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) @app.get("/health") def health_check(): return {"status": "ok"}启动命令uvicorn app:app --host 0.0.0.0 --port 8000。我专门写了一个/health探活接口,小程序端会先请求它检查后端是否在线,避免用户在服务异常时反复发送消息。
第三步,完成用户登录对接。微信小程序通过wx.login()拿到临时登录凭证code,后端拿到后调用微信接口换取openid。由于个人开发者没有资质调用某些高级接口,我用openid直接作为用户唯一标识,不额外建账号体系。实测在开发工具里,为了拿到真实的openid,需要在开发者工具中开启“不校验合法域名”,否则请求会被拦截。
3.2 多轮问诊会话的上下文管理
问诊不是一次性请求,而是多轮对话。比如用户先说“肚子疼”,AI追问“持续多久了”,用户答“从早上开始”,这时候后端必须知道“从早上开始”指的是肚子疼持续了数小时。如果不维护上下文,第二轮对话就变成无根之木。
我的方案是为每个会话维护一个上下文对象,存放在内存中(生产环境建议换Redis。内存方案在单机开发时足够,但服务重启会丢失,需要注意)。
# 简化版会话状态管理 class SessionManager: def __init__(self): self.sessions = {} def get_context(self, session_id): return self.sessions.setdefault(session_id, { "history": [], "extracted": {}, "missing": [] }) def update(self, session_id, message, extracted): ctx = self.get_context(session_id) ctx["history"].append({"role": "user", "content": message}) ctx["extracted"].update(extracted.get("symptoms", {})) ctx["missing"] = extracted.get("missing_info", [])每次调用AI抽取模块时,我会把history的最后两轮和当前消息一起传入提示词,这样大模型就能结合上下文做抽取。需要注意的是,历史消息不能无限堆,超过一定轮数会超出上下文窗口,也会引入噪声。我设定了最多保留6轮有效对话,超出后丢弃最早的消息,只保留已经抽取到的结构化信息。
当用户确认“没有其他症状了”或者系统判定missing_info已经收集完整,会话进入评估阶段。此时把累积的结构化信息交给规则引擎,生成报告。
3.3 报告生成与数据落库
评估报告是用户最关心的产出,我把它设计成三个Tab:综合评估结果、症状分析明细、生活与就医建议。综合评估结果显示风险等级图标和简要结论,比如“目前症状倾向于轻度上呼吸道感染,建议观察3天,若出现持续高热请及时就诊”。症状分析明细把AI抽取到的每一条症状单独列出来,标注系统识别到的属性和置信度。用户看到“您提到:头痛,已持续3天,程度中等”,就知道系统理解了ta,信任感一下子就上来了。
数据落库我建了三张核心表:用户表、会话表、报告表。会话表和报告表通过session_id关联。另有一个症状抽取表,存每次抽取的原始JSON,方便回看AI的分析过程。这里有一个经验的教训:必须把AI返回的原始JSON原样存储,不要只存最终评估结果。因为如果后续发现评估规则有问题,还能从原始抽取数据回放定位,而不至于丢失中间过程。
数据库操作我直接用sqlite3加少量封装,没有上ORM。项目起步阶段,ORM带来的便利不如直观SQL清晰。等到表关系复杂了,再引入ORM也不迟。
3.4 解决小程序与后端联调的坑
小程序和本地后端联调,最大的坑是域名校验。微信小程序生产环境要求所有请求域名必须HTTPS且已在后台配置,开发环境下可以在“详情-本地设置”里勾选“不校验合法域名、web-view(业务域名)、TLS版本以及 HTTPS 证书”,否则请求直接报request:fail。
另一个常见问题是本地调试时,电脑上的后端服务和小程序开发工具所在网络需要互通。如果后端跑在本机,小程序用http://127.0.0.1:8000通常没问题;但如果用真机预览,这个地址就访问不通了。我当时的做法是在局域网内跑后端,让手机和电脑连同一个WiFi,真机预览时用电脑的局域网IP。这里有一个容易忽略的点:微信开发者工具中,真机预览的请求走的不是开发者工具的网络,而是手机的独立网络,所以必须保证手机能直连到后端IP。
接口层面,我统一把返回格式封装为:
{ "code": 0, "message": "success", "data": {} }code非0表示业务错误,比如参数缺失、AI接口超时。小程序端封装了一个request公共函数,统一判断code,弹出轻提示。这样业务代码里不需要到处处理异常,清爽得多。
4. 常见问题与排查技巧实录
4.1 AI接口响应不稳定怎么办
大模型接口实测最不稳定的点有三个:响应超时、返回格式非法、内容偶尔跑偏。
响应超时的根源是外网调用延迟波动,健康问诊场景里用户等不了十几秒。我的处理方案分三层:第一层,前端设置超时时间,并展示“医生正在思考中”的过渡动画,让用户有等待预期;第二层,后端调用AI接口时,若默认模型超时,自动降级到备用模型,两个模型轮流试;第三层,如果重试两次仍失败,走规则兜底——用关键词表做一次简单的症状匹配,虽然理解能力弱,但至少不会让整个服务挂掉。
返回格式非法是最常见的问题,大模型偶尔会生成“边解释边带JSON”的文本,导致解析报错。我的经验是不要把解析逻辑写死。调用完AI后,先做一个提取动作:用正则找出{...}代码块,再用json.loads解析,解析失败就拼一个默认JSON结构,并把原始返回存入日志,后续人工检查。
def extract_json(text): match = re.search(r'\{[^{}]*\}', text, re.S) if not match: return None try: return json.loads(match.group()) except json.JSONDecodeError: return None这个简单的extract_json函数,帮我在多次调试中避免了崩溃。如果你希望更稳健,还可以让AI用“只输出JSON,不要解释”的提示词约束,再加上严格temperature参数设置为0,降低随机性。
4.2 多轮对话中信息覆盖导致误判
用户在对话里先说“头痛”,然后再问“这种痛是偏头还是整头”,AI抽取时可能把后一句的“偏头”误判成“偏头痛”症状。这类问题纯靠提示词很难完全避免,我的处理是在extracted结构里给每个症状增加一个source_round字段,标记是第几轮出现的。在最终评估时,规则引擎只取source_round最新的同名字症状,避免老信息覆盖新信息。举例来说,第一轮抽到“头痛”,第二轮用户澄清“是胀痛”,那么评估用的就是“胀痛”而非“头痛”。
另外,我发现大模型在长对话中容易把用户的否定词搞反。用户说“没有发热”,AI有时会抽取出“发热”。为此我在提示词里专门加入了一条规则:“对于出现的否定表述,如‘没有”“不’等,对应症状的value必须设置为absent,不能出现在symptoms列表”。实际效果提升明显,误抽取率至少下降一半。这属于经验性修复,后来我把这些规则沉淀成一个前置文本检查步骤,在后端对AI输出做二次校验:如果symptoms里出现了用户最近消息中明确否定的词,直接剔除。
4.3 健康数据的安全合规与存储注意点
健康数据属于敏感个人信息,我在这块投入了大量精力。先说存储:用户档案表中的敏感字段一律加密存储,我用的是Python的cryptography库做对称加密,密钥放在环境变量里,不硬编码在代码或配置文件中。问诊消息和报告内容同样加密落库,这样即使数据库被拖走,数据也是不可读的。
再说合规。小程序类目选择上,涉及健康咨询必须选择“医疗”相关类目,个人开发者基本无法通过这类审核。为了方便开发测试,我的方案是:项目以“健康管理助手”名义开发,不在线上发布,只做个人学习和技术验证;如果确实需要上线,建议以公司主体申请合适的类目,或者与有资质的机构合作,把系统定位为“健康信息管理工具”而非“诊疗服务”。
我还做了一个数据导出功能:用户可以在设置页一键导出自己所有的问诊记录和评估报告。这是为了满足用户对自己数据的知情权和携带权,同时也是对产品价值观的表态——健康数据属于用户本人,系统只是受托管理。
4.4 规则引擎误判的排查方法
规则引擎是确定性逻辑,误判一般来自两个源头:症状映射表缺词,或者风险打分权重不合理。缺词问题很好定位,我做了日志,把所有映射不到标准症状的原始描述单独存一张表,每周翻一遍,把高频新词补进映射表。权重问题比较麻烦,因为健康风险很难靠纯规则完美排序。
我的排查方式是对比测试:我手工整理了30组不同症状组合的“预期风险等级”,然后用规则引擎跑一遍,找出不一致的case,逐个调整权重和特判逻辑。比如最初“头痛+呕吐”的风险分是50+35=85,但我期望的中高风险实际应该归到“中高”而不是“高”,于是给“头痛”加了一个组合规则:与“呕吐”组合时只上调10分而非用原始分叠加。这类微调做完之后,我建议保留测试用例集,后续每次改规则都跑一遍回归,防止修了A组合却坏了B组合。
5. 性能优化与体验打磨
5.1 降低用户等待焦虑的交互手段
健康问诊场景里,用户往往带着焦虑情绪,如果页面转圈超过5秒,很多人会直接退出。我做了两手优化。第一,AI调用改为流式输出,小程序端通过WebSocket接收后端推送的“正在分析症状”中间状态,实时展示分析进度。具体实现是:后端调用大模型时用流式方式接收token,但明确告诉大模型只流式输出JSON字段的进度,同时不同阶段给前端推送不同文案,比如“正在识别您描述的症状”“正在匹配健康知识库”“正在生成评估报告”。第二,把常见问题的结论做了缓存。如果用户描述的症状组合短时间内被其他用户问过,且AI抽取结构和历史某次高度一致,直接返回上一次生成的评估报告模板,再插入个性化字段。实测下来,这类缓存命中率有20%左右,对体验改善明显。
性能侧,FastAPI的异步特性很关键。调用大模型接口时用httpx.AsyncClient,避免阻塞事件循环。SQLite连接设为每次请求短连接,避免多线程下的锁冲突。整个系统在单机、并发20以内的场景下,接口平均响应在600ms左右,AI调用另算,可以接受。
5.2 小程序端的页面渲染与状态管理
小程序原生框架没有像Vuex这样的集中状态库,但跨页面需要共享会话状态。我的方案是使用全局getApp().globalData存一些轻量状态,比如当前会话ID,而真正复杂的报告数据在跳转时通过eventChannel传给报告页,不经过全局变量。这样做的好处是页面撤销或重开时不会读到脏数据。
列表渲染方面,问诊对话流我用了一个简单的数组维护,每次新消息入栈,然后setData重新渲染。消息多到一定量会影响渲染性能,所以做了虚拟滚动:只渲染可视区域附近的消息节点。首次开发我还天真地以为setData一次传整个数组没问题,结果聊到十几轮时,明显卡顿。后来改成增量更新,只setData新增的那条消息,卡顿消失。这是小程序开发里非常典型的性能坑,值得留意。
6. 从项目到产品的经验沉淀
6.1 像维护“知识库”一样维护症状映射表
这个项目做到后期,我发现自己本质上在维护一个不断生长的医学知识库。AI模型负责理解语言,但把语言映射到标准医学概念,仍然依赖持续更新的字典和规则。我从一开始就把映射表做成外部JSON文件,而不是写死在代码里,这样修改映射不需要重新部署后端,只要热加载配置即可。
更新的节奏我定为每周一次。每次更新前,先从数据库里导出“未匹配标准症状”的记录,人工筛选出高频表达,补进映射表。比如我发现“太阳穴疼”在本地语境里频繁出现,但它不等于标准术语“头痛”,于是我建立了“太阳穴疼” → {standard: “头痛”, location: “颞部”}的映射,这样信息更精确。这种逐步积累的方式,比一开始追求大而全更靠谱。
6.2 关注用户反馈数据,而不是只看技术指标
项目跑起来后,我统计了真实(内部测试)用户的使用数据。最重要的指标不是AI抽取准确率,而是“完整问诊完成率”——有多少用户真正走到了评估报告页。我前期发现很多用户在出现“是否发热”这类追问时直接放弃。于是我把追问的文案从生硬的问题改成带解释的表达,比如“为了更准确评估您的情况,想确认一下有没有发热或畏寒呢?”,完成率提升显著。这个教训说明,技术是为体验服务的,别只顾着调模型,忽略了用户面对系统时的真实感受。
我在迭代中还发现,评估报告里的“建议”部分不能太笼统。用户最反感的是“注意休息,多喝水”这种万能句子。我让规则引擎在生成建议时,把具体症状和用户档案拼进模板,生成“您目前的头痛可能与睡眠不足有关,建议今晚保证7小时睡眠,如果明早仍然持续痛,请去神经内科就诊”这类具体建议。用户反馈,这种表述让他们觉得系统真的“听懂”了。
7. 写在最后的实操心得
这个项目让我对“AI落地到垂直领域”有了更实际的认知。首先是架构层面,AI不能当“万能答题机”用,它的定位应该是“信息抽取器”加“意图理解器”,而决策责任必须交给可控的规则或人工审核。特别是健康这类高风险领域,幻觉会带来真实风险。其次是工程层面,尽量把大模型返回内容和规则引擎的输入解耦,用中间JSON结构做接口,这样无论换模型还是调规则,互相都不受影响。
我再来分享一个小技巧:所有AI调用都要留日志,一定要保留原始输入和模型返回。哪怕当时看着没用,后续做分析、排查问题、优化提示词都靠它。我给每条请求生成了唯一的request_id,前端报错时只需要报这个ID,后端就能精确查到当时发生了什么。这个习惯帮我省了大量排查时间,建议所有做AI应用的朋友都养成。
如果你也想做类似的项目,我的建议是别急着把功能做全,先跑通一条“输入一段症状文本 → 输出结构化JSON → 生成简单报告”的最小闭环,再逐步加多轮对话、健康档案、缓存优化。这条路我已经替你走了一遍,整体不复杂,但细节多得让人头疼。等你把那些细节一个个磨平,你会发现自己对系统设计、AI边界和数据合规的理解,都会上一个台阶。