☰
基于大模型与Agent的答疑机器人实战:知识库切片、混合检索与流式输出
2026/9/28 7:02:12 网站建设 项目流程

1. 为什么我要用大模型搭一个答疑机器人

先说结论:我搭这个答疑机器人,核心诉求就三个字——省人力。团队里每天有大量重复问题,产品怎么用、接口怎么调、报错怎么解,翻来覆去就那些。之前靠人工在群里轮值回答,一个人一天至少搭进去两小时,还经常答得不一致。用大模型加一层知识库和 Agent 逻辑来做,能把这部分重复劳动接过去,人只需要处理真正复杂的边角问题。

这个答疑机器人本质上是一个LLM 驱动的 Agent 应用:底层接大模型,中间挂知识库(也就是常说的 llm wiki 知识库),上层用 Agent 框架编排"检索—推理—回答"的流程,输出侧用流式输出(SSE)把回答实时渲染出来,让用户感觉像在跟真人打字聊天。它解决的问题很明确:把散落在文档、历史问答、工单里的知识,变成一个随时能问、答得靠谱的对话入口。

适合谁参考?如果你是会写点后端代码、想给自己项目或团队加一个智能问答入口的开发者,这篇可以直接抄作业。如果你只是想了解 Agent 和 LLM 应用大概怎么落地,也能从里面看到完整的思路和踩坑记录。我不打算写成教科书,就按我自己搭这套东西的真实顺序来讲,包括选型时纠结了什么、哪些地方返工了、哪些参数是拍脑袋定的后来又改的。

需要提前说明的是,答疑机器人和通用聊天机器人最大的区别在于**"答得对"比"答得花哨"重要得多**。通用聊天可以天马行空,答疑不行,答错一个接口参数,用户照着做就出事故。所以整套设计里,我把大量精力放在了知识召回准确性和回答可控性上,而不是让模型自由发挥。下面从整体设计开始拆。

2. 整体架构设计与技术选型思路

2.1 答疑机器人的四层结构拆解

我把整个系统拆成四层,这样每层职责清晰,出问题也好定位。

第一层是接入层,负责接收用户提问、维持会话、把流式结果推回前端。这一层用 SSE(Server-Sent Events)做流式输出,因为答疑场景大多是纯文本,SSE 比 WebSocket 更轻,浏览器原生支持 EventSource,前端几乎零成本接入。

第二层是编排层,也就是 Agent 的核心。它决定这次提问要不要查知识库、查几轮、要不要调用工具(比如查订单状态、查接口文档版本),最后把上下文拼好交给模型。这一层是答疑机器人跟"直接调 API"最大的区别所在。

第三层是知识与模型层,包含向量库、知识切片、大模型本身。知识库负责"记得住",模型负责"说得清"。

第四层是数据与运维层,包括对话日志、召回命中率统计、badcase 回收。这层最容易被忽略,但它是机器人能不能越用越准的关键。

提示:四层不是必须严格物理隔离,小项目完全可以塞进一个服务里,但逻辑上一定要分清楚,否则后期加功能会互相打架。

2.2 为什么选 Agent 而不是简单 RAG

很多人第一反应是"答疑嘛,RAG 就够了,检索加生成"。我一开始也这么想,但实际跑下来发现纯 RAG 有几个硬伤。

纯 RAG 是"一问一检索一回答"的单轮模式。可真实提问经常是模糊的,比如"那个接口报 401 怎么办",它既没说是哪个接口,也没说是什么场景。纯 RAG 会把"401"直接拿去检索,召回一堆不相关的东西。而 Agent 可以先做一步意图澄清或查询改写:先判断这是认证类问题,再把查询改写成"接口认证失败 401 排查",召回质量立刻不一样。

再比如多跳问题:"A 功能的配置项在哪,改完要不要重启"。这需要先查 A 功能配置文档,再查重启策略,两次检索的结果拼起来才能答。Agent 能编排这种多步流程,纯 RAG 做不到。

所以我的选型结论是:用 Agent 框架做编排,把 RAG 当作 Agent 的一个工具。Agent 负责决策"要不要查、查什么、查几次",RAG 负责具体召回。这样既保留了 RAG 的准确性,又有了 Agent 的灵活性。

2.3 大模型选型:能力、成本、延迟的三角权衡

选模型这块我踩过坑,值得单独说。答疑场景对模型的要求排序是:指令遵循 > 知识准确性 > 表达流畅度 > 创造力。创造力在这里几乎没用,甚至有害。

我对比过几类模型。通用大模型能力强、指令遵循好,但成本和延迟偏高,适合做复杂推理和最终生成。轻量模型便宜、快,适合做意图分类、查询改写这类"小判断"。所以最后我用的是大小模型配合:小模型做路由和改写,大模型做最终回答生成。

具体怎么定?我列了个简单的评估维度表,你可以照着给自己的场景打分。

维度权重说明我的取值参考
指令遵循高能否严格按格式、按边界回答必须支持 system 角色强约束
知识准确性高结合知识库后的事实正确率靠知识库兜底,模型别乱编
首字延迟中影响流式体验首 token 控制在 1.5s 内
单次成本中决定能不能长期跑按日调用量算总账
上下文长度中决定能塞多少知识至少 8K,最好 32K 以上

注意:不要一上来就上最强的模型。先用中等模型跑通全流程,把知识库和 Agent 逻辑调好,最后再决定要不要换更强的模型。我见过太多人卡在选型上,结果流程根本没跑起来。

2.4 流式输出为什么是体验的分水岭

答疑机器人如果等模型全部生成完再一次性返回,用户要盯着空白屏幕等三五秒,体感极差。流式输出把回答一个字一个字推出来,首字延迟可能只有几百毫秒,用户立刻知道"它在答了"。

技术上,SSE 是最省事的选择。服务端保持一个长连接,模型每吐出一个 token 就通过data:事件推给前端,前端用 EventSource 监听,收到就追加到消息气泡里。整个过程不需要前端做轮询,也不需要 WebSocket 那样的双向握手。

但流式输出有个副作用:一旦开始推,就没法回头改。如果模型前面答错了,后面想纠正也来不及。所以我在流式之前加了一道"预检"——先用小模型判断这个问题该不该答、要不要走知识库,确认没问题再开流。这个细节后面会展开。

3. 核心细节解析与实操要点

3.1 知识库切片:答疑准确率的命门

知识库这块,切片策略直接决定召回质量。我一开始图省事,按固定字数切,500 字一段,结果召回经常"断章取义"——一个完整的操作步骤被切成两半,模型只拿到后半段,答出来的步骤缺了第一步。

后来改成按语义结构切:优先按标题层级切,一个三级标题下的内容作为一段;如果某段太长,再按段落切;代码块和表格尽量不拆开。这样每段都是语义完整的,召回后模型能直接读懂。

切片长度我最后定在300 到 800 字之间。太短信息不全,太长会稀释相关性、还占上下文。这个区间是实测出来的:300 字以下经常缺上下文,800 字以上召回精度明显下降。

还有一个容易忽略的点:给每段加元数据。我在每段前面附上来源文档名、章节路径、更新时间。这样召回后模型能知道"这段来自哪个文档的哪一节",回答时可以引用来源,用户也更信任。元数据还能用于过滤,比如只召回某个产品版本的文档。

3.2 检索策略:向量、关键词还是混合

纯向量检索对语义相似的问题很友好,但对精确匹配的词(比如错误码、参数名)反而不敏感。用户问"错误码 E1024",向量检索可能召回一堆"错误处理"的泛泛内容,就是找不到 E1024。

所以我的方案是混合检索:向量检索负责语义召回,关键词检索(BM25 之类)负责精确匹配,两路结果用加权融合排序。权重上,我让关键词检索在"包含明确错误码、参数名、版本号"的查询里占更高权重,其余情况以向量为主。

融合之后还要做一步重排(rerank)。初筛召回 20 条,用重排模型按相关性重新排序,取前 3 到 5 条喂给大模型。这一步能显著提升精度,因为初筛追求"不漏",重排追求"精准"。

检索方式擅长短板我的用法
向量检索语义相近、口语化提问精确词不敏感主力召回
关键词检索错误码、参数名、专有名词语义泛化差补充召回
混合+重排兼顾两者实现稍复杂最终方案

3.3 Agent 编排:让机器人学会"先想再答"

Agent 编排是这套系统的灵魂。我给它设计了一个简单的决策流程,用提示词工程约束模型的行为。

第一步是意图识别:判断用户是在提问、在闲聊、还是在反馈问题。闲聊直接走通用回答,不查知识库,省成本。

第二步是查询改写:把口语化、模糊的提问改写成适合检索的查询。比如"登录不上"改写成"登录失败 排查 认证"。这一步用轻量模型做,快且便宜。

第三步是检索决策:判断需要查几次知识库。简单问题查一次,多跳问题查多次,每次基于上一次结果决定下一步查什么。

第四步是生成与引用:把召回的知识和用户问题一起给大模型,要求它"只基于给定资料回答,资料里没有的就说不知道,并给出资料来源"。

这套流程用提示词就能实现,不一定非要上复杂的 Agent 框架。我一开始用现成的 Agent 框架,后来发现流程其实很固定,直接用提示词加几个函数调用反而更可控、更好调试。框架是工具,不是目的,这点想清楚能省很多事。

3.4 提示词工程:把边界写死

答疑机器人的提示词,核心不是"让模型答得好",而是"让模型别乱答"。我在 system 提示词里写死了几条硬约束:

  • 只依据提供的资料回答,资料没有的内容明确说"暂未收录"。
  • 涉及操作步骤时,必须按资料原文顺序输出,不得自行增删步骤。
  • 涉及参数、错误码时,必须原样引用,不得改写。
  • 回答末尾附上资料来源的文档名和章节。

这几条看起来简单,但效果立竿见影。加了"资料没有就说不知道"之后,模型胡编的情况大幅下降。这里的关键是给模型一个明确的"退路",让它知道说"不知道"是被允许的,而不是硬编一个答案。

实操心得:提示词里的约束要具体、可验证。"回答要准确"这种话没用,"参数必须原样引用"才有用。模型对具体规则的遵循度远高于抽象要求。

4. 实操过程与核心环节实现

4.1 从零搭建:环境与依赖准备

我用的技术栈是 Python 为主,向量库用轻量级的本地方案起步,模型通过 API 调用。这样起步快,不用一上来就搞复杂的部署。

核心依赖大概这几类:大模型 SDK、向量库客户端、Web 框架(提供 SSE 接口)、文本处理库。版本上我建议锁死,因为大模型 SDK 更新频繁,接口偶尔会变,锁版本能避免"昨天还好好的今天跑不起来"。

环境准备好之后,先跑一个最小闭环:用户提问 → 调模型 → 返回回答。这一步不接知识库、不做流式,就是确认模型能通。很多人跳过这步直接上复杂架构,结果出问题时分不清是模型的问题还是自己代码的问题。

4.2 知识入库:把文档变成可检索的切片

知识入库分三步:解析、切片、向量化。

解析阶段要把各种格式的文档(Markdown、PDF、网页)统一转成纯文本,同时保留结构信息(标题层级)。这一步的坑在于 PDF,格式一乱,解析出来的文本顺序就错,切片也跟着错。我的做法是优先用结构化的源文档(比如 Markdown),PDF 只作为补充。

切片阶段按前面说的语义结构切,每段附上元数据。这里有个细节:切片之间要有重叠。我在相邻切片间保留 50 到 100 字的重叠,避免关键信息正好卡在切分点上被割裂。

向量化阶段把每段文本转成向量存进向量库。这里要注意向量模型和检索时的查询向量必须用同一个模型,否则向量空间不一致,检索结果会莫名其妙地差。我一开始换过向量模型但忘了重建索引,召回质量直接崩了,排查半天才发现。

4.3 对话主流程:一次完整问答的代码骨架

下面是一次完整问答的流程骨架,用伪代码表示,重点是流程而不是具体语法。

def answer(user_query, session): # 1. 意图识别(轻量模型) intent = classify_intent(user_query) if intent == "chitchat": return stream_generate(user_query) # 直接生成,不检索 # 2. 查询改写 rewritten = rewrite_query(user_query) # 3. 混合检索 + 重排 candidates = hybrid_search(rewritten, top_k=20) docs = rerank(user_query, candidates, top_n=4) # 4. 拼上下文 context = build_context(docs) # 5. 流式生成 return stream_generate(user_query, context, session)

stream_generate里就是 SSE 的核心:模型每吐一个 token,就通过yield推给前端。前端收到data:事件就追加渲染。整个链路是"边生成边推送",用户看到的是逐字出现的回答。

4.4 SSE 流式输出的实现与中断处理

SSE 服务端的关键是设置正确的响应头,然后持续推送事件。响应头里Content-Type要是text/event-stream,并且要关闭缓冲,否则内容会被攒着一起发,流式就失效了。

前端用 EventSource 监听,收到消息就更新界面。这里有个体验细节:流式过程中要允许用户中断。用户看到答偏了,想停下来重新问,得有个"停止"按钮。实现上就是前端关闭 EventSource 连接,服务端检测到连接断开后停止生成,避免白白消耗 token。

中断处理还有个坑:如果服务端还在生成但连接断了,要确保资源被正确释放,否则并发一高就会堆积僵尸任务。我的做法是在生成循环里定期检查连接状态,断了就 break。

注意:流式输出和"预检"要配合好。预检阶段(意图识别、检索)是同步的,会有一点延迟,但通常几百毫秒内能完成。如果预检太慢,用户会感觉"点了没反应",所以预检用的模型一定要轻。

4.5 参数计算:上下文预算怎么分配

上下文窗口是有限资源,得精打细算。假设模型上下文是 8K token,我的分配是这样的:

用途预算(token)说明
system 提示词500固定约束和角色设定
历史对话1500保留最近几轮,超出就截断
召回知识30004 段,每段约 750
用户当前问题300一般够用
回答预留2000给生成留空间
缓冲700应对估算误差

这个分配不是死的,得根据实际调整。如果发现回答经常被截断,就压缩历史对话;如果发现召回知识不够用,就减少历史、增加知识预算。核心原则是优先保证知识召回和回答空间,历史对话可以适当牺牲。

token 估算上,中文大致按"1 个字约 1.5 个 token"粗算,英文按"1 个词约 1.3 个 token"。不用太精确,留足缓冲就行。

5. 常见问题与排查技巧实录

5.1 回答不准:从召回和提示词两头查

回答不准是最常见的问题,排查要分两头。先看召回对不对:把这次问答召回的知识片段打出来看,如果召回的内容本身就答非所问,那是检索的问题,得调切片或检索策略。如果召回对了但回答还是错,那是生成的问题,得调提示词。

我遇到过一次典型情况:召回的知识完全正确,但模型答的时候"自作主张"补充了资料里没有的步骤。这就是提示词约束不够,加了"不得自行增删步骤"之后就好了。所以排查顺序永远是先看召回,再看生成,别一上来就怀疑模型不行。

5.2 流式中断与超时:连接层的坑

流式输出最常见的故障是"推到一半断了"。原因通常有几类:模型 API 超时、网络抖动、服务端缓冲没关、前端连接被浏览器回收。

排查时先看服务端日志,确认是模型侧断了还是推送侧断了。如果是模型侧,加超时重试;如果是推送侧,检查响应头有没有正确关闭缓冲。浏览器对空闲连接有回收机制,如果两次推送间隔太久,连接可能被断,所以生成要尽量连续,别在中间做耗时操作。

5.3 幻觉与越界回答:给模型划死红线

答疑机器人最怕的就是"一本正经地胡说"。防幻觉的核心手段有三个:一是提示词里明确"资料没有就说不知道";二是召回时提高精度,宁可少召回也别召回错的;三是回答里强制附来源,让用户能自己核对。

我还会定期做badcase 回收:把答错的案例收集起来,看是知识库缺内容、还是检索没召回、还是模型乱答,分别处理。知识库缺内容就补文档,检索问题就调策略,模型问题就调提示词。这个闭环跑起来,机器人会越用越准。

5.4 常见问题速查表

现象可能原因排查方向解决手段
回答答非所问召回不准打印召回片段调切片、改检索策略
回答缺步骤切片割裂检查切片边界按语义切、加重叠
模型胡编提示词约束弱看是否越界加"不知道"退路、强制引用
流式中断连接或超时看服务端日志关缓冲、加重试
首字很慢预检太重计时各阶段换轻量模型做预检
成本偏高大模型调用过多统计调用量小模型做路由、缓存结果

5.5 几个我踩过的坑

第一个坑是向量模型换了没重建索引,召回质量断崖式下跌,排查了大半天。教训是:向量模型和索引必须绑定,换模型必须重建。

第二个坑是切片太长,一段塞了两千字,召回后模型抓不住重点,回答很泛。改成 300 到 800 字后明显改善。

第三个坑是没做预检直接开流,结果模型答到一半发现方向错了,但已经推给用户了,只能尴尬地让用户重新问。加了预检之后,方向不对的问题在开流前就被拦下了。

第四个坑是历史对话无限累积,聊得越久上下文越满,最后把知识召回的空间挤没了,回答质量反而下降。后来改成只保留最近几轮,并做摘要压缩。

6. 让答疑机器人越用越准的迭代思路

搭起来只是第一步,真正决定它好不好用的是后续迭代。我现在的做法是每周看一次对话日志,挑出答得不好的案例,归类到"知识缺失""检索失败""生成越界"三类,分别处理。知识缺失就补文档,检索失败就调策略,生成越界就改提示词。

还有一个我觉得很值的方向是把高频问题沉淀成标准问答对。有些问题被问了几十次,与其每次让模型现场生成,不如直接维护一个标准答案,命中就直接返回,又快又准。模型只处理那些没见过的、需要推理的问题。这样既省成本,又保证了高频问题的稳定性。

另外,知识库的更新要跟上产品迭代。文档改了但知识库没更新,机器人就会答旧版本的内容,这比不答还危险。我的做法是把知识库更新接进文档发布的流程里,文档一改就触发重新切片和向量化,保证知识库和文档同步。

这套东西我从零搭到能用,前后大概花了两三周,其中一半时间花在调切片和检索上。模型和框架反而是最省心的部分,因为现成方案足够成熟。如果你也想搭一个,我的建议是先把知识库和检索调好,再考虑模型和 Agent 的花活,因为答疑机器人的上限,取决于它能不能找到对的知识,而不是模型有多聪明。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询