项目实训开发日志(七):BabyMind:基于多Agent和RAAG的科学育儿辅助平台
在第六周,我们完成了语音识别、语音合成、端到端语音问答接口,以及 RAG 知识库的大规模补充。系统已经具备了比较完整的智能问答和多模态交互能力。但在实际联调和演示准备过程中,我们也发现:一个功能“能跑通”和“能够稳定展示给用户”之间仍然存在差距。
进入第七周后,BabyMind 项目的开发重点继续沿着第六周的方向推进:不再单纯追求新增功能数量,而是围绕“系统是否稳定”“演示是否顺畅”“用户体验是否完整”进行集中打磨。
因此,第七周的工作主要围绕以下几个方向展开:
·继续优化语音问答体验,降低端到端等待感;
·完善 Android 端聊天与语音交互页面;
·优化健康、时间轴、营养三类 Agent 的回答结构;
·修复前后端联调中暴露的接口和数据同步问题;
·整理项目文档与演示流程,为后续答辩展示做准备。
一、本周整体完成内容
1、优化语音问答体验与前端交互流程
第六周我们已经完成了端到端语音问答接口 POST /api/v1/voice/ask,实现了音频上传、ASR 语音识别、问题路由、Agent 回答生成、TTS 语音合成、返回文字答案和音频数据。
但在实际测试中发现,完整链路需要依次调用 ASR、LLM、RAG 和 TTS,整体响应时间相对较长。尤其是在网络波动或者 RAG 检索耗时较高的情况下,用户点击语音按钮后需要等待数秒,容易产生“系统卡住了”的感觉。
因此,本周我们重点优化了前端的语音交互状态流转。Android 端的 VoiceViewModel 中对语音交互过程进行了更细粒度的状态划分:
sealed class VoiceUiState {
object Idle : VoiceUiState()
object Recording : VoiceUiState()
object Uploading : VoiceUiState()
object Thinking : VoiceUiState()
object Speaking : VoiceUiState()
data class Error(val message: String) : VoiceUiState()
}
相比第六周只区分“录音 / 处理中 / 播放中”,本周进一步拆分了:Uploading:音频正在上传;Thinking:后端正在识别并生成回答;Speaking:正在播放语音答案。这样用户在等待过程中可以明确知道系统当前正在做什么,减少误以为应用无响应的情况。
同时,语音按钮的文案和动画也进行了调整:录音时显示“正在聆听……”;上传时显示“正在上传语音……”;思考时显示“正在思考答案……”;播放时显示“正在为你播报……”;出错时显示可读性更强的错误提示。
语音交互入口也进一步融入首页聊天输入栏,使用户既可以输入文字,也可以直接点击麦克风进行提问。
2、补充语音失败场景的容错处理
在第六周的接口设计中,我们已经对 TTS 失败做了容错:即使语音合成失败,也会返回文字答案,不影响主问答流程。
本周我们进一步补充了前端和后端的失败场景处理。主要包括:音频文件为空时返回明确错误;音频时长过短时提示用户重新录制;ASR 无法识别时返回 speech_not_recognized;网络请求超时时提示用户稍后重试;TTS 音频为空时前端只展示文字答案,不进入播放状态;播放音频失败时不影响聊天记录展示。
后端在 voice_service.py 中对 ASR 返回结果进行了更严格的校验:
async def transcribe_audio(audio_file: UploadFile, settings: Settings) -> str:
audio_bytes = await audio_file.read()
if not audio_bytes or len(audio_bytes) < 1024:
raise SpeechNotRecognizedException()
response = httpx_client.post(
settings.siliconflow_asr_url,
headers={"Authorization": f"Bearer {settings.llm_api_key}"},
files={
"file": (
audio_file.filename,
audio_bytes,
audio_file.content_type,
)
},
timeout=20,
)
result = response.json()
text = result.get("text", "").strip()
if not text:
raise SpeechNotRecognizedException()
return text
在前端,VoiceViewModel 对错误码进行了统一映射:
private fun mapVoiceError(code: String?): String {
return when (code) {
"speech_not_recognized" -> "没有听清楚,可以再说一遍吗?"
"audio_too_short" -> "录音时间太短,请重新录制"
"rate_limit_exceeded" -> "请求太频繁,请稍后再试"
else -> "语音服务暂时不可用,请稍后重试"
}
}
这样即便外部语音服务偶尔失败,用户看到的也不再是生硬的错误信息,而是更符合产品语境的提示。
3、完善聊天页面与问答结果展示
本周 Android 端重点完善了聊天页面的交互体验。
前几周中,问答结果主要以简单文本形式展示,能够满足基础调试需求。但在演示场景下,回答内容需要更清晰地区分来源、类型和重点。因此本周我们对聊天消息卡片进行了优化。
目前聊天消息主要分为以下几类:用户文字提问;用户语音提问转写结果;Agent 回答;RAG 引用提示;过敏原确认提醒;系统错误提示。
对于语音提问,页面会同时展示:用户原始语音图标;ASR 识别出的文字;后端回答内容;可重新播放的语音按钮。
例如用户通过语音提问:宝宝六个月了,可以开始吃鸡蛋吗?页面会先显示识别文本:你说:宝宝六个月了,可以开始吃鸡蛋吗?随后展示营养 Agent 的回答,并在底部提供语音播放按钮。
同时,为了让用户知道回答不是模型凭空生成,我们在部分 RAG 问答结果下方增加了“参考知识库”提示区域。如果后端返回了检索来源,前端会展示类似:
参考知识:
- 辅食添加阶段说明
- 常见过敏原引入建议
4、优化三类 Agent 的回答结构
BabyMind 当前主要包含三类核心 Agent:健康 Agent、时间轴 Agent、营养 Agent。
在前几周中,三类 Agent 的基础问答能力已经完成,但回答风格仍然不够统一。有些回答偏长,有些回答缺少结构,有些回答没有明确提醒用户何时需要就医或进一步观察。本周我们针对三类 Agent 的系统提示词和输出结构进行了统一优化,使其更适合育儿场景下的展示。
4.1 健康 Agent 回答结构优化
健康类问题最重要的是风险提示和就医边界。因此我们要求健康 Agent 尽量按照以下结构回答:可能原因;家庭观察建议;可以尝试的护理方式;需要及时就医的情况;简短总结。
例如用户提问:
宝宝有点发烧,38.2 度,应该怎么办?
回答会被组织为:
宝宝 38.2℃ 属于低热范围,可以先观察精神状态、吃奶情况和尿量。
你可以先这样做:
1. 让宝宝适当休息,保持室内通风;
2. 少量多次补充水分或奶;
3. 不要捂得太厚;
4. 定时复测体温。
如果出现以下情况,建议及时就医:
- 宝宝小于 3 个月;
- 精神差、嗜睡或反应弱;
- 持续高热或体温继续升高;
- 呼吸急促、抽搐、皮疹等异常表现。
以上建议不能替代医生诊断,如情况加重请及时就医。
这类结构化回答更适合家长快速阅读,也更符合健康类应用的安全边界。
4.2 时间轴 Agent 回答结构优化
时间轴 Agent 主要负责成长发育、月龄能力、疫苗提醒等内容。本周我们重点优化了“按月龄回答”的能力。
例如用户提问:
8 个月宝宝一般会哪些动作?
系统会结合宝宝档案中的月龄信息和知识库内容,返回:
8 个月左右的宝宝通常会出现以下发展表现:
大运动:
- 能比较稳地独坐;
- 部分宝宝开始尝试爬行;
- 扶着物体时可能会尝试站立。
精细动作:
- 会用手抓取小物体;
- 开始练习拇指和食指配合。
认知和社交:
- 会对熟悉的人表现出明显反应;
- 可能开始怕生;
- 喜欢通过拍打、摇晃探索物品。
每个宝宝发育节奏不同,如果宝宝暂时不会某项能力,可以结合整体情况观察。
回答从原先的自然段形式改为分模块展示,更适合移动端阅读。
4.3 营养 Agent 回答结构优化
营养 Agent 本周重点优化了辅食建议的输出格式,尤其是涉及食物引入、过敏观察和营养搭配的问题。
例如用户提问:
宝宝刚满六个月,第一口辅食吃什么比较好?
回答会按照:推荐食物、添加方式、观察重点、不建议做法、后续过渡建议进行组织。
同时,如果用户提到过敏史,营养 Agent 会结合宝宝档案中的过敏原字段进行提醒。例如档案中已经记录“鸡蛋过敏”,用户询问鸡蛋羹做法时,系统会优先提示:
宝宝档案中已记录鸡蛋过敏,暂不建议尝试鸡蛋羹。可以先选择已确认不过敏的食材,如米粉、南瓜泥、土豆泥等。
这也验证了第六周完成的过敏原档案同步功能已经能够参与实际问答流程。
5、修复前后端接口联调问题
本周联调过程中,我们集中修复了若干前后端对接问题。
5.1 baby_id 传递不一致问题
语音问答接口要求前端通过 Form 提交 baby_id:
baby_id: int = Form(...)
但前端最初在调用接口时将 baby_id 放在 query 参数中,导致后端返回 422 参数校验错误。
修复后,VoiceRepository 中统一使用 MultipartBody.Part 和 RequestBody 提交表单数据:
val babyIdBody = babyId.toString()
.toRequestBody("text/plain".toMediaType())
val voiceBody = voice
.toRequestBody("text/plain".toMediaType())
val speedBody = speed.toString()
.toRequestBody("text/plain".toMediaType())
并在 Retrofit 接口中使用:
@Multipart@POST("/api/v1/voice/ask")suspend fun voiceAsk(
@Part file: MultipartBody.Part,
@Part("baby_id") babyId: RequestBody,
@Part("voice") voice: RequestBody,
@Part("speed") speed: RequestBody
): VoiceAskResponse
5.2 语音 Base64 播放兼容问题
后端 TTS 返回的是 Base64 编码音频,前端最初直接尝试使用 MediaPlayer.setDataSource() 播放字符串,导致播放失败。
本周修复方式是:将 Base64 解码为字节数组,写入临时音频文件,使用 MediaPlayer 播放本地临时文件, 播放完成后释放资源,避免多次播放导致内存泄漏。
val audioBytes = Base64.decode(audioBase64, Base64.DEFAULT)val tempFile = File.createTempFile("tts_", ".mp3", context.cacheDir)
tempFile.writeBytes(audioBytes)
mediaPlayer.reset()
mediaPlayer.setDataSource(tempFile.absolutePath)
mediaPlayer.prepare()
mediaPlayer.start()
5.3 过敏原确认弹窗重复出现问题
第六周完成过敏原自动识别后,前端会根据后端返回的 allergen_changes 展示确认弹窗。但在页面重组或状态恢复时,弹窗可能重复出现。
本周通过为每次问答结果增加本地处理标记,确保同一条消息的过敏原确认只弹出一次。
private val handledAllergenMessageIds = mutableSetOf<String>()
fun handleAllergenChange(messageId: String, changes: AllergenChanges?) {
if (changes == null) return
if (handledAllergenMessageIds.contains(messageId)) return
handledAllergenMessageIds.add(messageId)
_uiState.update {
it.copy(pendingAllergenChanges = changes)
}
}
6、完善项目 README 与接口说明
随着功能逐渐稳定,本周开始整理项目文档,主要包括:项目简介、功能模块说明、后端启动方式、Android 端运行方式、环境变量配置、RAG 知识库构建方式、语音服务配置方式、常用接口说明、测试运行方式。
README 中新增了环境变量示例说明:
DATABASE_URL=sqlite:///./babymind.db
JWT_SECRET_KEY=your-secret-key
LLM_API_KEY=your-api-key
LLM_BASE_URL=https://api.siliconflow.cn/v1
LLM_MODEL=Qwen/Qwen2.5-7B-Instruct
SILICONFLOW_ASR_URL=https://api.siliconflow.cn/v1/audio/transcriptions
SILICONFLOW_TTS_URL=https://api.siliconflow.cn/v1/audio/speech
EMBEDDING_MODEL=BAAI/bge-m3
同时补充了知识库构建命令:
python scripts/build_knowledge_base.py
python scripts/ingest_kb.py
以及后端测试命令:
pytest tests/
接口说明中重点补充了以下接口:
POST /api/v1/qa/ask
POST /api/v1/voice/ask
POST /api/v1/voice/tts
GET /api/v1/voice/voices
PATCH /api/v1/babies/{baby_id}/allergies
GET /api/v1/babies/{baby_id}
POST /api/v1/babies
这些文档为后续项目答辩和代码交接提供了基础。
二、本周核心代码与模块
1、Android 端语音与聊天交互
frontend/.../ui/viewmodel/VoiceViewModel.kt
frontend/.../ui/screens/VoiceButton.kt
frontend/.../ui/screens/ChatScreen.kt
frontend/.../data/VoiceRepository.kt
frontend/.../data/ChatRepository.kt
2、后端语音容错与接口优化
app/services/voice_service.py
app/api/routers/voice.py
app/schemas/voice.py
3、Agent 回答结构优化
app/services/agent_router_service.py
app/services/health_agent_service.py
app/services/timeline_agent_service.py
app/services/nutrition_agent_service.py
4、RAG 与知识库引用
app/services/rag_service.py
scripts/ingest_kb.py
data/knowledge_base/
5、文档与配置
README.md
.env.example
docs/api.md
三、本周遇到的问题与解决思路
1、语音问答等待时间仍然偏长
虽然第六周已经完成端到端语音接口,但实际使用时仍然会出现 4 到 8 秒的等待时间。由于 ASR、RAG、LLM 和 TTS 是串行执行,任何一个环节变慢都会影响整体体验。
目前的解决方式主要是前端层面优化:增加明确的处理状态、显示等待动画、在回答生成后立即展示文字、如果 TTS 失败,不阻塞文字展示。
后续可以进一步考虑接口拆分:先上传音频并返回识别文本、再调用问答接口返回文字、最后异步生成 TTS 音频。
这样可以降低用户的感知延迟,让用户更快看到文字答案。
2、不同 Agent 回答风格不统一
由于三个 Agent 的提示词和调用逻辑是在不同阶段完成的,因此早期回答风格差异较大。有的偏医学说明,有的偏列表,有的偏聊天式回答。
本周通过统一输出格式缓解了这个问题,但仍然存在部分回答过长的问题。后续可以继续优化:限制单次回答长度、移动端展示进行分段、对重点信息加粗或卡片化、根据问题类型返回不同结构。
3、RAG 检索结果有时相关性不足
知识库扩充到 848 个 chunk 后,覆盖范围明显提升,但也带来一个问题:部分问题会检索到相关性较弱的内容。
例如用户问“宝宝半夜哭闹怎么办”,有时会检索到睡眠、肠胀气、出牙等多个方向的内容,回答可能显得不够聚焦。
目前通过调整 top_k 和提示词约束进行了初步优化:
retrieved_docs = rag_service.search(
query=question,
collection=target_collection,
top_k=4,
)
同时在提示词中强调:
如果检索内容与用户问题相关性不足,请明确说明只能提供一般性建议,不要编造知识库中没有的信息。
后续可以继续加入:相似度阈值过滤、rerank 重排序、按 Agent 选择不同知识库集合、对问题进行意图改写后再检索。
4、Android 音频播放资源释放问题
在连续多次进行语音问答时,发现部分设备会出现音频无法播放或播放卡顿的问题。排查后发现是 MediaPlayer 资源没有在播放结束后及时释放。
修复方式是在播放完成、页面销毁和异常场景下都调用释放逻辑:
fun releasePlayer() {
mediaPlayer?.stop()
mediaPlayer?.release()
mediaPlayer = null
}
并在 onCleared() 中统一清理:
override fun onCleared() {
super.onCleared()
releasePlayer()
}
5、演示环境配置不一致
在准备演示时发现,不同成员本地的 .env 配置不完全一致,导致有人可以正常调用 LLM 和语音服务,有人运行时出现 API Key 缺失或向量库未构建的问题。本周通过补充 .env.example 和 README 启动说明,统一了部署步骤
四、阶段性成果
经过第七周开发,BabyMind 项目已经从“核心功能跑通”进一步进入“系统可演示”的阶段。目前系统已经具备以下能力:
·支持用户创建和管理宝宝档案;
·支持健康、时间轴、营养三类智能问答;
·支持基于 RAG 知识库的育儿知识检索;
·支持语音输入和语音播报;
·支持营养问答中的过敏原识别与档案同步;
·支持 Android 端较完整的聊天和语音交互流程;
·支持基础限流、错误处理和异常容错;
·初步具备项目文档和部署说明。
五、下周计划
下一阶段我们计划继续推进以下内容:继续优化 Android 端 UI,提升页面一致性和视觉完成度、对三类 Agent 的典型问答进行测试和人工调优、增加更多演示用例,覆盖健康、营养、发育、语音等核心场景、完善接口文档和项目部署文档、对 RAG 检索效果进行进一步优化,引入相似度阈值或重排序策略
更多推荐


所有评论(0)