老年健康主动管理语音智能体 - 项目开发进展与技术思考

这段时间在开发面向老年人的用药健康咨询项目,边实践边梳理,对 LangChain4j 与 RAG 技术栈的落地有了更具体的工程层面体会。作为上一篇博客的续篇,本文结合实际项目进展,谈谈开发过程中的技术思考与问题总结。

最开始规划这个项目时,认知确实比较朴素:无非是前端接收问题,调用大模型接口返回结果。但真正落地后才发现,与上一篇的结论一致——大模型本身只占很小一部分,其余 80% 的工作量都集中在工程问题上。


一、项目背景与设计出发点

这个项目的核心诉求其实很明确。当前老年群体使用智能产品存在显著障碍:首先是输入层面,很多老年人对拼音输入不熟练,手机小键盘的交互方式门槛很高;其次是医疗信息的可信度问题,网络上的健康资讯良莠不齐,老年群体的辨别能力相对较弱。

基于此,我们确立了三个核心设计原则。首先是语音优先的交互方式,用户按住说话即可完成输入,无需打字,语音转写后增加人工确认环节,避免转写偏差导致后续回答跑偏。其次是安全前置的风控机制,医疗场景的容错空间很小,错误的引导可能造成严重后果,因此对高风险情况必须主动干预,引导用户寻求专业医疗帮助。最后是知识来源的可追溯性,所有回答必须基于权威的医疗指南,通过检索增强生成(RAG)技术从根源上抑制大模型幻觉。


二、技术架构与选型分析

整体采用经典的 Spring Boot 三层架构,在基础设施层扩展了向量数据库与大模型能力的支撑:

接入层:SessionController、ChatController、AudioController、KnowledgeController
    ↓
业务层:ChatAgentService、MessageService、KnowledgeService、RiskWarningService
    ↓
基础设施:MySQL、Milvus向量数据库、大语言模型、语音识别服务

选型过程中的核心考量:

LangChain4j 作为 Java 生态最成熟的 LLM 开发框架,与 Spring 生态的集成度高,对于已基于 Spring Boot 构建的项目是最自然的选择。

Milvus 向量数据库的选型其实经过了一轮切换。最开始我们考虑的是用 Elasticsearch 做向量检索,毕竟团队对 ES 更熟悉,运维成本也低。

但实际做了原型对比后还是选择了 Milvus。ES 的向量检索本质是附加功能,底层还是基于倒排索引的引擎,向量计算不是原生设计,而 Milvus 是专门为 Embedding 场景从零开始优化的。实测下来,相同向量维度下,Milvus 的检索延迟只有 ES 的三分之一。同时 Milvus 还有更完善的向量索引体系,原生支持 HNSW、IVF 等多种针对不同场景的索引类型,并且内置了标量过滤能力,不需要开发人员自己维护向量和业务字段之间的关联,这一点在实际项目中能省去不少麻烦。

虽然 ES 也能满足小数据量的需求,而且当前阶段我们的数据量其实很小,但考虑到这是技术实践项目,而且 Milvus 对 RAG 场景的针对性优化确实明显,最终还是完成了从 ES 向 Milvus 的迁移。

all-MiniLM-L6-v2 作为 Embedding 模型,主要考虑其轻量级特性,本地即可运行无需额外 API 成本。缺点是对中文语义的表征能力一般,后续数据规模扩大后可考虑迁移至 bge 系列模型。

大模型最终选择字节跳动豆包,核心考量是国内网络环境可直接访问,接口兼容 OpenAI 协议,接入成本较低。


三、核心模块的设计思路与实现

3.1 RAG 检索系统:流程易跑通,效果在细节

上一篇博客中提到,RAG 不是接入向量数据库就大功告成。在实际项目开发中,对这一点的体会尤其深刻。

当前的检索流水线设计:

用户问题
    ↓
向量化为 384 维特征
    ↓
Milvus 检索 Top3 相似知识片段
    ↓
相似度阈值过滤,低于阈值的结果予以丢弃
    ↓
剩余知识片段组织后注入 Prompt
    ↓
大模型基于约束生成回答
    ↓
风险检测与合规提示
    ↓
返回给用户

有两个设计是项目开发到中期补充的,正是这些细节区分了"玩具项目"和"可落地工程"。

其一是服务降级机制。最初的实现中,Milvus 连接失败会直接抛出异常,导致整个接口不可用。在多轮演示中发现外部依赖未就绪的情况其实很常见。因此增加了降级逻辑:向量检索异常时,使用预置的通用医疗知识完成回答,保证基础服务可用。

try {
    // 向量检索逻辑
} catch (Exception e) {
    // 检索失败使用兜底知识库
}

其二是向量维度一致性。这个问题具有很强的隐蔽性:Embedding 模型的输出维度,必须与 Milvus 集合定义的维度严格对齐,否则检索结果毫无意义,而且整个过程不会抛出明显的错误。这是经过长时间排查才定位到的典型问题。

3.2 会话与上下文管理

对话系统天然需要多轮上下文的支持。LangChain4j 本身提供了 Memory 机制,我们最终选择自行实现会话持久化,主要是为了将对话历史存入数据库,便于后续的效果分析与优化。

当前的设计比较直接,通过 sessionId 隔离不同对话,每个会话包含多条消息,回答时按时间顺序拼装历史上下文。

这个方案在对话轮次较少时运行良好,但也存在明显的局限:上下文长度溢出问题尚未得到妥善解决。目前采用简单的最近消息截断策略,长期来看需要更优雅的记忆压缩机制。

3.3 医疗风险控制体系

医疗场景下,风控是不可逾越的底线。我们设计了三级风险预警机制,在模型输出后追加一层校验逻辑:

风险等级

触发场景

处理策略

0级

普通健康咨询

正常返回回答

1级

常见健康问题

回答末尾追加通用就医提示

2级

用户描述具体症状

回答前置警示提醒

3级

危险症状描述(胸痛、便血、呼吸困难等)

终止知识性回答,强烈建议就医

实现上采用关键词匹配结合语义判断的双重策略。关键词匹配作为兜底,确保明确的高危表述一定能够被拦截;语义判断用于处理各种同义变体表达,提升召回率。

3.4 语音交互链路设计

语音交互是本项目区别于通用聊天机器人的核心特征。

完整交互链路:用户语音输入 → 录音文件上传服务端 → 语音识别转写为文本 → 前端展示转写结果 → 用户确认后提交至对话接口。

额外的确认环节确实增加了一次交互,但对于老年用户群体,语音转写错误他们可能难以察觉,基于错误内容生成的回答会造成更大的困惑。交互路径的延长换来的是整体可用性的提升。


四、典型问题与踩坑总结

项目开发过程中遇到的问题很多,选取几个典型问题做个总结,同类项目可以参考规避。

问题一:国内大模型的 baseUrl 配置

这是排查时间最长的一个基础问题。切换豆包模型时,API Key 与模型名称均已确认正确,通过 curl 测试接口正常,但在项目中始终返回 API Key 认证错误。

经过多轮排查,最终定位原因是:缺少 baseUrl 配置

OpenAI 官方接口无需显式配置 baseUrl,但国内兼容 OpenAI 协议的厂商,服务地址均有差异。豆包、通义、智谱等厂商都有各自的接入域名,这是切换国内模型时最容易被忽略的配置项。

问题二:Spring 热部署配置不刷新

修改配置文件后重启应用,发现新配置始终不生效。多次确认文件内容无误却仍然加载旧值,相当令人困惑。

根本原因是 Spring Boot DevTools 的热部署机制,对于 @Configuration 类中的 @Value 注入,存在一定概率不触发刷新。解决方法比较直接:完全停止应用后重新启动,在配置类增加启动日志,确认配置项的实际加载值。

问题三:Milvus 连接失败导致应用无法启动

Milvus 客户端初始化时,如果服务端不可达,会直接抛出异常,造成整个 Spring 上下文初始化失败。

这个问题目前还在优化中。更合理的设计应该采用懒加载策略,首次检索时再完成初始化,连接失败直接走降级逻辑,以此保证应用正常启动。这也带来一个架构层面的启发:外部网络依赖,尽量不要设计成启动时必须就绪的强依赖。


五、当前进展与后续规划

截至目前,项目整体完成度约 70%,核心聊天链路已经跑通。

模块

完成度

说明

项目基础框架

100%

Spring Boot + MyBatis Plus 搭建完成

会话管理模块

100%

4 个接口开发完成

语音转写模块

100%

上传、转写、确认完整链路

聊天对话核心

100%

RAG 全链路打通

知识库模块

80%

检索逻辑开发完成,待灌入正式数据

风险检测模块

70%

三级预警规则完成,需持续优化召回

已开发接口总计 10 个,核心功能都已具备。

远期的重点工作主要包括几个方面。首先是实现 Milvus 集合的自动初始化,在应用启动时自动检测并创建集合与索引。其次是完成权威医疗知识库的结构化导入,把整理好的资料真正灌入向量库。在此基础上完成前后端接口联调,编写 Docker Compose 编排文件实现 MySQL 与 Milvus 的一键启动。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐