个人健康顾问助手
Go 1.25 · gRPC · Python 3.11 · LangGraph · Milvus · PostgreSQL · Redis · React 19 · pytest
出发点
健康场景和一般问答的区别在于错一个数字的代价。所以整个系统的设计纪律只有一条: 不猜值——宁可拒绝并留痕,也不做静默修正。下面三块都是这条纪律在不同层面的展开。
一、OCR + VLM 多模态解析
体检报告的形态非常杂:有可提取文本的 PDF 表格,有纯文本行,也有拍照/扫描的图片。 统一走一条管道必然在某类输入上表现很差,所以先按形态路由:
- PDF 表格 → 表格结构还原;纯文本 → 文本行解析;图片 → OCR + VLM 双通道。
- 冲突裁决:同名指标出现异值时,采信有版面语义理解能力的 VLM(它知道这个数字属于哪一行哪一列),同时对 OCR 侧留痕拒绝,而不是直接覆盖掉。
- 留痕的意义是可追溯:后面发现判错了,能知道当时两路分别读到了什么。
二、多路召回与索引优化
QueryPlanner 先把问题拆成一份带预算的查询计划,再分三路召回: 结构化指标走 SQL,关键词走 BM25(用 Milvus Function 的声明式全文),语义走 Dense。
- 只按排名融合:三路的分数量纲根本不可比,直接加权求和是错的,所以用 RRF 按排名融合,后面再挂 Cross-Encoder 精排兜底。
- 索引按规模分治:语料库数据量小、要的是召回率,走 HNSW;八万余 chunk 的论文库内存是瓶颈,走 IVF_SQ8 量化省内存。同一套索引参数套所有库是在浪费其中一边。
- 权限做在向量层:租户和知识版本用
expr在服务端预过滤,而不是召回完再在应用层筛——后者既慢,又容易在某个分支上漏掉。
三、幻觉防范与记忆治理
无证据不说话。每条建议必须绑定证据 ID,然后过四类确定性校验:
- 引用越过白名单即判伪造;
- 数值与库内实测值比对,不一致就打回;
- (另两类同属确定性规则,不依赖模型自评——因为自评在这里等于没有校验。)
记忆这一侧单独设了门禁,主要防的是投毒:
- 检索到的文档永不入记忆——否则外部语料可以反向写进用户的长期记忆。
- 置信度不足直接丢弃;要落库必须用户确认。
- 存储用 AES-256-GCM 加密,
content_hash去重;注入时限定 token 预算,并在前端透明展示用了哪些记忆。
如果重做
- 「拒绝」是这套系统最重要的输出之一,但拒绝之后怎么把话说清楚、让用户知道下一步该做什么,当时做得还不够。