差点就做成一个 /chat
这个医疗问诊演示最早的方案很普通:一个 /chat 接口,用户输入一句话,后端检索、拼 prompt、生成回答。实现快,demo 也容易讲。
但 mock 两轮以后,我觉得不对劲。用户输入“最近胃不舒服”时,真正需要的不是模型立刻甩一段“可能原因、建议检查、注意事项”。那种输出看起来很完整,体验却像搜索结果页。问诊演示最应该展示的,是系统怎么把模糊描述一点点问清楚。
所以后来才有了 A/B 双轨。
A 线负责慢慢问清楚
A 线只做一件事:继续问。它的回答应该短,最好能流式出来,像一个还在收集信息的人,而不是一个急着写报告的模型。
医疗 demo 里的输入经常缺字段:持续多久、有没有伴随症状、有没有既往情况、疼痛位置是不是说清楚了。A 线不应该在这些信息没齐时下结论。它更像是把槽位补齐的过程。
这里要强调一下:这个项目只是医疗问诊教学 demo,不提供真实诊断,也不替代医生。A 线和 B 线的设计,都是为了演示交互和 RAG 编排,不是为了给人看病。
B 线负责写结构化回执
B 线比较重。它要基于检索证据写报告式内容,比如可能方向、需要补充的信息、红旗提示和引用来源。后面还会有 judge 和追加检索,所以它不适合每一轮都跑。
如果把 A、B 两件事塞进一个大 prompt,会出现几个很别扭的问题:
- A 线为了照顾报告格式,开始变长、变啰嗦;
- B 线在信息不足时被迫输出,看起来很像“编得很完整”;
- 调试时分不清问题来自检索、生成,还是问诊节奏;
- 用户预期也乱了:到底是在聊天,还是在等一份报告?
拆开之后,边界就清楚很多:A 线先把信息问够,B 线再拿证据写结构化结果。
技术上也不想一开始就复杂
这个项目是学习和演示,不是生产 SaaS,所以底座我尽量压住:
- 后端先放在一个 FastAPI 进程里,不拆微服务;
- Compose 里只保留 Postgres、Qdrant、Neo4j、API 四件套;
- 检索支持
hybrid和graphrag热切换,但一次请求只跑一种; - 模型和 embedding 默认走云端,Ollama 只作为本地备选。
这几个选择都不是“最佳实践”,只是当时最适合 demo 的折中。先把问诊节奏做对,再谈更复杂的工程化。
我后来也刻意统一了一些名字,比如 recall_bundle、B_MAX_EXTRA_RETRIEVAL。这些词一旦和设计文档对齐,后面写代码、打 LangSmith tag、整理面试叙事都会省很多解释成本。