山东大学软件学院-项目实训-个人开发日志(五):统一问答入口、RAG知识库与多Agent路由开发
引言
大家好,我是山东大学软件学院2023级本科生张钧虹,“字节摇篮队”负责人。
在前几个阶段中,我主要围绕BabyMind项目的基础业务能力推进开发:先完成了用户注册登录与宝宝档案录入,再逐步实现提醒模块、成长时间轴、健康记录和疫苗计划等功能。到了这一阶段,我开始把关注点进一步转向项目中最核心的一部分能力:如何让系统真正具备“可对话、可检索、可联动”的智能问答能力。
因为对于BabyMind来说,提醒、时间轴、健康记录、疫苗计划这些模块虽然已经可以独立运转,但如果它们始终只是静态页面和分散接口,那么整个系统仍然缺少一个真正贴近用户使用场景的交互入口。家长最自然的使用方式并不是逐个打开页面找信息,而是直接问一句:
“宝宝发烧了今晚要不要去医院?”
“这个月龄现在该打什么疫苗?”
“宝宝开始加辅食了,哪些食物更合适?”
因此,这一阶段我重点推进了三项工作:
- 搭建统一问答入口,让健康、时间轴、营养相关问题能够从同一个接口进入系统;
- 接入RAG知识检索能力,让健康问答不再只依赖模型“直接生成”;
- 推动问答结果和已有业务模块联动,让AI回答能够进一步影响健康记录、疫苗计划等系统状态。
一、为什么这一阶段要先做统一问答入口
在项目早期,我也曾想过是否应该为不同模块分别单独做AI入口,比如健康问答页、营养建议页、疫苗咨询页各自独立。但随着项目推进,我越来越意识到,这样做虽然在实现上看似直接,实际上却会让系统交互越来越割裂。
对于用户来说,真实问题往往并不会天然按模块边界出现,比如:
- “宝宝发烧了,这周原本安排的疫苗还能不能打?”
- “宝宝最近腹泻,辅食要不要做调整?”
- “现在这个月龄有哪些事情需要特别注意?”
这些问题本身就同时涉及健康、时间轴、疫苗、营养等不同模块。如果仍然要求用户先判断“这个问题到底属于哪个页面”,其实反而增加了使用成本。
因此,这一阶段我在后端实现了一个统一入口POST /api/v1/qa/ask,对应代码位于app/api/routers/qa.py,核心接口如下:
@router.post("/ask", response_model=QAAskResponse, status_code=status.HTTP_201_CREATED)
def ask_question(payload: QAAskRequest, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)) -> QAAskResponse:
baby = None
if payload.baby_profile_id is not None:
baby = get_baby_profile_for_user(db, current_user, payload.baby_profile_id)
if baby is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Baby profile not found.")
routed_result = answer_question_with_router(
db=db,
user=current_user,
baby=baby,
question=payload.question,
auto_create_health_record=payload.auto_create_health_record,
preferred_agent=None if payload.preferred_agent == "general" else payload.preferred_agent,
conversation_history=payload.conversation_history,
session_id=payload.session_id,
session_title=(payload.question.strip()[:40] or "新的对话") if not payload.session_id else None
)
hr = routed_result.health_record_result
return QAAskResponse.from_history(
routed_result.history,
auto_created_health_record=hr is not None and hr.status == "created",
health_record_id=hr.record.id if hr and hr.record else None,
health_record_status=hr.status if hr else None,
auto_create_reason=hr.reason if hr else None
)
这段逻辑的意义在于:前端不再需要决定“我现在应该调健康接口还是营养接口”,而是统一提交问题,再由后端完成路由和处理。
这说明BabyMind的AI开始有了一个清晰的对外入口,也为后续多Agent协作逻辑预留了统一的承载点。
二、RAG知识库接入:让健康问答不再只靠模型“直觉”
统一入口建立之后,下一个问题就是:AI到底应该依据什么来回答问题?
如果完全依赖大模型直接生成,那么在育儿特别是健康建议场景下,最大的风险就是幻觉。模型可能会给出听起来流畅但并不可靠的结论,这在项目里是必须尽量避免的。
所以这一阶段,我重点推进了RAG能力接入,把本地知识库和问答流程连接起来。RAG相关核心逻辑位于app/services/rag_service.py。
1、知识文件的组织与切分
目前项目中的知识资料主要放在:
data/knowledge_base/
这一目录下又分为:
- health
- nutrition
- development
- vaccine
也就是说,在知识文件层面,我已经开始把内容按育儿场景进行分类整理,而不是混成一个大文本集合。这样做的目的,是为了方便后续按模块检索,也方便继续补充权威资料。
在向量化处理时,我又进一步把这些资料聚合为三类检索集合:
- health
- timeline
- nutrition
其中,疫苗和成长发育资料也会被映射到对应的检索集合中。核心映射关系如下:
MODULE_TO_COLLECTION = {
"health": "health",
"vaccine": "health",
"timeline": "timeline",
"development": "timeline",
"nutrition": "nutrition",
}
随后,我通过文本切分器将知识文件拆分为适合检索的小块,并写入Chroma向量库。对应逻辑如下:
def ingest_knowledge_base(self) -> None:
if not self._vector_stores:
self._vector_stores = self._create_vector_stores()
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter
base = Path(settings.knowledge_base_path)
splitter = RecursiveCharacterTextSplitter(
chunk_size=settings.knowledge_chunk_size,
chunk_overlap=settings.knowledge_chunk_overlap
)
for file_path in self._iter_knowledge_files(base):
text = file_path.read_text(encoding="utf-8").strip()
if not text:
continue
module = self._extract_module_name(base, file_path)
collection = MODULE_TO_COLLECTION.get(module)
if collection is None:
continue
2、从“找到资料”到“结构化生成答案”
仅仅能检索还不够。如果只是把检索到的片段原样返回给前端,用户体验仍然很差。因此,我在这一阶段还重点处理了回答格式问题。
BabyMind中的健康问答,最终不是简单返回一段自然语言,而是尽量约束模型输出结构化结果,包括:
- 核心结论
- 建议步骤
- 注意事项
- 风险等级
- 是否建议就医
对应提示结构位于 app/services/rag_service.py 中,核心要求如下:
Return a JSON object with this schema:
{
"core_conclusion": "short answer in Chinese",
"action_steps": ["specific action 1", "specific action 2"],
"cautions": ["specific caution 1"],
"risk_level": "medium",
"should_seek_medical_help": false
}
这样做的原因很明确:在健康和育儿场景中,我并不希望模型“自由发挥”,而更希望它在固定结构中回答问题。因为一旦输出结构被约束,前端展示、风险提示、后续自动建档等逻辑就都更容易实现。
三、多Agent路由:让不同类型的问题进入不同处理逻辑
在有了统一问答入口和RAG能力之后,另一个关键问题就是:不同类型的问题,应该如何被送往不同的处理逻辑?
我在这一阶段没有一开始就做过重的复杂规划,而是先实现了一个轻量但清晰的多Agent路由方案。核心逻辑位于app/services/agent_router_service.py
我目前将问题主要路由到三类Agent处理逻辑中:
- health
- timeline
- nutrition
路由规则基于问题关键词、对话上下文和优先级进行判断,例如:
ROUTE_RULES = (
RouteRule("nutrition", (...), 3),
RouteRule("timeline", (...), 2),
RouteRule("health", (...), 1),
)
在此基础上,通过 preview_route() 对问题做初步分类:
ROUTE_RULES = (
RouteRule("nutrition", (...), 3),
RouteRule("timeline", (...), 2),
RouteRule("health", (...), 1),
)
这一阶段我是把“多Agent”从一个抽象概念,真正落到了工程实现里。也就是说,现在的BabyMind已经不是只有一个通用聊天模型,而是开始具备如下处理能力:
- 健康问题走健康问答链路,结合RAG知识库生成结构化建议;
- 时间轴类问题读取疫苗计划、提醒、时间轴数据做结构化总结;
- 营养类问题读取喂养阶段、过敏原、饮食限制和食谱数据做个性化回答。
四、从“回答问题”到“影响系统”:问答结果自动联动健康记录和疫苗计划
这一阶段我最在意的一点,并不是“AI能不能回答问题”,而是AI的结果能不能继续影响系统内部状态。
如果AI回答完之后只是显示在屏幕上,那它仍然只是一个聊天功能。只有当问答结果可以继续进入健康记录、疫苗计划、时间轴这些模块时,BabyMind才真正具备“系统级联动”能力。
因此,我在 app/services/health_record_service.py 中实现了从QA结果自动生成健康记录的逻辑。
核心判断逻辑如下:
def should_auto_create_health_record_from_qa(history: QAHistory) -> tuple[bool, str]:
if history.agent_type != "health":
return False, f"agent_type={history.agent_type or 'unknown'} does not support auto health record generation"
if history.baby_profile_id is None:
return False, "baby_profile_id is required to create a health record from QA history"
...
if risk_level in AUTO_HEALTH_RECORD_RISK_LEVELS:
reasons.append(f"risk_level={risk_level}")
if should_seek is True:
reasons.append("should_seek_medical_help=true")
if affects_vaccine_schedule is True:
reasons.append("affects_vaccine_schedule=true")
也就是说,如果一次健康问答中已经明确出现:
- 风险等级较高;
- 建议就医;
- 影响疫苗安排;
- 影响饮食安排;
那么系统就会尝试进一步把这次问答结果转化为健康记录,而不是只停留在对话层面。
更重要的是,健康记录一旦创建成功,后面还会继续触发原有的疫苗延期逻辑。这样就形成了这样一条链路:
健康问答 → 自动生成健康记录 → 影响疫苗计划 → 进入成长时间轴
这条链路说明项目中的不同模块终于不是并列摆放的功能点,而是开始出现真实的业务依赖关系。也就是说,BabyMind的“智能”不再只是会说,而是开始能把说出来的结果继续传递给系统的其他部分。
五、前端问答页与会话历史:让AI交互真正可持续
后端统一入口搭建之后,前端如果仍然只是一个简单输入框,其实也很难发挥这套能力的价值。因此,这一阶段我也同步推进了Android端的问答交互逻辑。前端问答状态管理位于frontend/app/src/main/java/com/babymind/ui/viewmodel/QAViewModel.kt
我在这里重点处理了几件事:
- 支持围绕宝宝上下文发起问题;
- 支持保留最近若干轮对话上下文,用于继续追问;
- 支持记录 session_id,把一次连续对话组织成会话;
- 支持自动/手动选择Agent模式。
核心请求逻辑如下:
val response = RetrofitClient.instance.askQuestion(
"Bearer $token",
QAAskRequest(
question = normalizedQuestion,
babyProfileId = babyId,
autoCreateHealthRecord = true,
preferredAgent = _selectedAgent.value.apiValue,
conversationHistory = buildConversationHistory(priorMessages),
sessionId = currentSessionId
)
)
这一部分最关键的一点是:前端不只是把问题发给后端,而是在主动维护“对话上下文”这件事。
也就是说,用户现在可以不是每次都重新完整描述背景,而是基于上一轮回复继续追问。这比一次性问答更贴近真实使用场景。
除此之外,我还补充了问答历史页的逻辑。对应代码位于frontend/app/src/main/java/com/babymind/ui/viewmodel/QAHistoryViewModel.kt
我将历史记录按 session_id 分组,形成会话历史,而不是简单堆叠成一长串单条消息。这样做的好处是,用户可以更自然地回看“某一次连续咨询”的完整过程。
这意味着BabyMind中的AI交互已经开始具备以下特征:
- 不是单轮请求;
- 有上下文;
- 有会话归档;
- 可以和宝宝档案绑定;
- 可以继续反向写入业务数据。
六、这一阶段的主要问题与后续计划
虽然这一阶段统一问答、RAG和多Agent路由已经有了较清晰的原型,但我也很清楚,目前这部分距离稳定成熟还有不少需要继续打磨的地方。
1、知识库还需要继续扩充与清洗
目前项目已经有了本地知识文件、切分和入库流程,但在真实育儿场景下,仅仅具备“能检索”还不够,更重要的是资料本身是否足够权威、覆盖是否足够全面。后续我会继续补充和清洗育儿领域资料,尤其是在健康建议边界、疫苗说明和营养推荐方面继续完善。
2、当前路由机制仍然偏轻量
目前多Agent路由以关键词规则为主,这种方式在原型阶段实现快、可控性强,但面对更复杂、更模糊的问题时,仍然可能存在分类不够精准的问题。后续我会继续优化路由逻辑,让系统在多轮对话和复杂问题下的判断更加稳定。
3、结构化输出和风险边界仍需继续优化
在健康场景中,最重要的问题始终不是“回答得像不像”,而是“边界是否安全、提示是否稳妥”。后续我还会继续调整Prompt和结构化输出规则,进一步压缩幻觉空间,尽量让系统在高风险场景下更明确地给出谨慎提示,而不是生成模糊建议。
七、结语
这一阶段,我主要围绕统一问答入口、RAG知识检索和多Agent路由推进了BabyMind的AI核心能力开发。相比前几个阶段主要在搭建业务数据结构,这一阶段的重点已经转向:如何让系统真正理解问题、调用知识、输出结构化结果,并继续把回答传递给其他业务模块。
对我来说,这一步非常关键。因为它意味着BabyMind开始从“一个有多个页面和接口的育儿系统”,逐步转向“一个能够基于上下文进行理解、回答和联动的智能育儿辅助平台”。
后续,我会继续在这一基础上完善知识库内容、优化路由策略,并推进语音交互和更多模块联动能力,让BabyMind离“真正可用”的目标再近一步。
欢迎各位老师、同学批评指正!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)