☰
LangChain实战:RAG检索增强+工具调用+流式输出,打造能查会算的问答助手
2026/10/16 7:53:52 网站建设 项目流程

1. 从"只会聊天"到"能查会算":这个助手到底强在哪

很多人第一次接触大模型应用开发,做出来的东西本质上就是个"套壳聊天框"——用户问一句,模型答一句,仅此而已。这种形态在演示阶段看着挺唬人,一旦放到真实业务里就露馅了:你问它"我们内部文档里关于报销流程是怎么规定的",它要么一本正经地胡说八道,要么直接告诉你"我的知识截止到某年某月"。问题的根子在于,纯对话模型只有"记忆",没有"手脚"——它既不能去外部知识库里翻资料,也不能调用计算器、数据库查询这类外部能力。

我这次要拆解的这套方案,核心就是给对话助手装上两样东西:一双能翻资料的手(检索增强生成,也就是常说的 RAG)和一套能调外部工具的本事(工具调用,Tool Calling)。更关键的是第三个特性——流式输出。这三者凑在一起,才构成一个真正能落地的问答助手:用户提问后,助手先去知识库里检索相关片段,判断需不需要调用工具(比如算个数、查个实时状态),然后把最终答案一个字一个字地"吐"出来,而不是让用户对着转圈图标干等十几秒。

为什么流式这么重要?我做过一个粗略的体感测试:同样一段三百字的回答,非流式模式下用户平均要等 6 到 10 秒才看到第一个字,而流式模式下首字延迟通常能压到 1 秒以内。别小看这几秒,它直接决定了用户是觉得"这玩意儿真快"还是"这破系统卡死了"。人机交互里有个朴素的规律——反馈越早,耐心越足。流式输出本质上是在用"持续的小反馈"换取用户的等待容忍度。

这篇文章适合谁看?如果你已经用 LangChain 搭过最基础的对话链,想往上再走一步,把检索、工具、流式这三块拼成一个完整可用的助手,那这篇就是给你写的。我会把每个环节的选型理由、踩过的坑、以及那些官方文档里不会写的实操细节都摊开讲。全程不堆砌概念,重点放在"为什么这么设计"和"实际跑起来会遇到什么"。

需要先说明一点:下面涉及的所有代码结构和参数,都是基于我在多个模拟项目里反复验证过的常见实践总结出来的,你可以直接拿去改吧改吧用在类似场景里。文中出现的项目名、模块名均为虚构代称,不影响你理解整体思路。

2. 检索环节:为什么你的 RAG 总是"答非所问"

2.1 检索质量决定回答上限,而不是模型

先抛一个反直觉的结论:在一个 RAG 助手里,决定最终回答质量的关键往往不是那个生成答案的大模型,而是检索环节。我见过太多人把精力全花在换更大的模型上,结果检索回来的片段本身就是驴唇不对马嘴,再强的模型也只能"垃圾进垃圾出"。

检索的本质是什么?打个比方,它就像你去图书馆找资料。用户的问题是"我想了解 XX 政策对小微企业的影响",图书管理员(检索器)需要从几十万本书里挑出最相关的几页递给你。如果管理员眼神不好,递给你一本讲宏观经济的书,那你后面无论怎么研读都答不到点子上。所以检索环节要解决的核心问题是:如何把用户那句口语化的问题,精准匹配到知识库里语义最接近的文本块。

这里有个常见误区:很多人以为检索就是关键词匹配,用户问"报销",就去文档里找含"报销"两个字的段落。这在简单场景下能用,但一旦用户换个说法——比如问"出差花的钱怎么走流程"——关键词匹配就彻底歇菜了,因为文档里可能压根没有"出差"这个词,写的是"差旅"。语义检索就是为了解决这个"换个说法就找不到"的问题,它把问题和文档块都转成向量,在向量空间里比距离,只要意思相近,用词不同也能匹配上。

2.2 文本切分:切得好,检索就成功了一半

文本切分(Chunking)是检索环节里最容易被忽视、却最影响效果的一步。我踩过的坑是:早期图省事,直接按固定字数硬切,每 500 字一刀。结果切出来的块经常把一句话拦腰截断,检索回来的片段读起来前言不搭后语,模型拿到这种残缺上下文,回答自然也是支离破碎。

后来我调整了策略,核心原则是优先按语义边界切,其次才考虑长度。具体做法是:先按段落、标题这类天然分隔符切,如果某个段落太长超过阈值,再在句子边界处二次切分。同时给相邻的块之间留一点重叠区(overlap),通常是块大小的 10% 到 20%。这个重叠区的作用是防止关键信息正好卡在两块的交界处,被切得两边都不完整。

切分策略适用场景优点缺点
固定字数硬切快速原型验证实现简单易截断语义,检索质量差
按段落/标题切结构化文档(手册、规范)语义完整长段落仍需二次处理
递归字符切分通用场景兼顾语义与长度需调参
语义切分对质量要求极高效果最好计算开销大

我个人的默认选择是递归字符切分,它在通用性和效果之间平衡得最好。块大小我一般设在 500 到 800 字符之间,重叠区设 80 到 150 字符。这个数值不是拍脑袋来的:太小了单块信息量不够,模型没法据此作答;太大了检索精度下降,因为一块里混进了太多无关内容,向量表示会被"稀释"。

2.3 向量化与检索器选型:别盲目追新

向量化就是把文本转成一串数字(向量),让机器能算"语义距离"。这一步要用到嵌入模型。选嵌入模型时,我的经验是优先看它在中文语义相似度任务上的表现,而不是看它参数多大。有些模型参数不大,但在中文短文本匹配上非常能打,性价比极高。

检索器这块,最基础的用法是"相似度检索"——算问题向量和所有文档块向量的余弦相似度,取最高的前 K 个。但纯相似度检索有个毛病:它容易把"长得像但没用"的块也捞回来。比如你问"退款政策",它可能把"换货政策"也排进前列,因为两者用词高度重合。

改进办法是引入MMR(最大边际相关性)检索。它的思路是:不光看候选块和问题的相关性,还要看候选块彼此之间的差异性,避免捞回来一堆内容重复的块。用大白话说就是"既要相关,又要多样"。实测下来,MMR 在回答需要综合多个角度的问题时,效果明显好于纯相似度检索。

还有一个参数值得单独说:返回块的数量 K。K 太小,可能漏掉关键信息;K 太大,又会把噪声塞进上下文,还可能超出模型的上下文窗口。我一般从 K=4 起步,根据实际效果上下调整。如果发现回答经常缺信息,就往上加;如果发现回答里混进了不相关的内容,就往下减。

提示:检索环节的调优没有一劳永逸的参数,一定要拿真实问题去测。我习惯准备一组 20 到 30 个"标准问题+期望答案"的测试集,每次改完参数就跑一遍,看命中率有没有提升。

3. 工具调用:让助手学会"该出手时就出手"

3.1 什么该交给工具,什么该留给模型

工具调用的核心价值,是让模型在"自己不会"或"自己算不准"的时候,主动去求助外部能力。但这里有个关键判断:不是所有任务都适合做成工具。我见过有人把"把一段话翻译成英文"也做成工具,这就属于用力过猛了——模型本身就能干得不错的事,没必要绕一圈去调外部接口,反而增加了延迟和出错概率。

那什么该做成工具?我的判断标准有三条:

  • 模型知识覆盖不到的:比如查询某个内部系统的实时数据、读取数据库里的最新记录。
  • 模型算不准的:大模型做多位数乘法、复杂日期计算时经常出错,这类交给计算工具更靠谱。
  • 需要精确执行的:比如按固定格式生成报表、触发某个业务流程,这类容不得模型自由发挥。

拿一个模拟的差旅助手举例,我会给它配这几个工具:一个查天气的(回答"明天出差地天气如何")、一个算差旅补贴的(按天数和城市等级算钱)、一个查内部政策的(其实这个更适合走检索而不是工具)。注意最后这个——能用检索解决的,就别硬做成工具,因为检索本身就是一种"软性"的知识获取方式,比硬编码的工具更灵活。

3.2 工具描述写得好,模型才调得准

工具调用最容易翻车的地方,不是代码写错了,而是工具的描述没写清楚。模型决定调不调某个工具、传什么参数,全靠读你给的工具描述。描述写得含糊,模型就会乱调或者该调不调。

我总结的工具描述三要素:

  1. 这个工具是干什么的:一句话说清用途,别用行话。
  2. 什么时候该用它:给出触发场景,帮模型判断时机。
  3. 每个参数是什么意思、什么格式:参数名要自解释,格式要明确(比如日期是"YYYY-MM-DD"还是时间戳)。

举个例子,一个算差旅补贴的工具,描述我会这么写:"根据出差天数和城市等级计算补贴金额。当用户询问出差能拿多少补贴、差旅费怎么算时使用此工具。参数 days 为出差天数(整数),city_level 为城市等级(1 到 3 的整数,1 代表一线城市)。" 你看,这样写模型基本不会用错。

反过来,如果描述只写"计算补贴",模型就懵了:什么补贴?怎么算?参数传啥?结果就是要么不调,要么瞎传参数。工具描述的质量,直接决定了工具调用的准确率,这一点怎么强调都不过分。

3.3 多工具场景下的调度逻辑

当助手挂了多个工具时,调度逻辑就变得微妙了。模型需要先判断"这个问题要不要调工具",再判断"调哪个工具",最后"传什么参数"。这三步任何一步出错,结果都不对。

我的经验是,工具数量控制在 5 个以内比较稳妥。工具一多,模型的选择困难症就犯了,准确率会明显下降。如果业务确实需要很多工具,可以考虑分层:先让模型判断问题属于哪个大类,再在那个大类下的几个工具里选。这就像医院分诊——先分到科室,再由科室医生决定具体检查项目,比让一个医生面对全院所有检查项目要靠谱得多。

还有一个实操细节:工具执行失败要有兜底。外部工具可能超时、可能返回异常,这时候不能让整个对话崩掉。我的做法是给工具调用包一层异常处理,失败时返回一句"抱歉,查询XX时出了点问题,请稍后再试",同时把错误记进日志。用户看到的是友好的提示,而不是一串红色报错。

4. 流式输出:把"等待焦虑"变成"实时反馈"

4.1 流式到底流的是什么

很多人对流式有个误解,以为是把最终答案切成几段发出去。其实不是。流式输出的本质是模型在生成每一个 token(可以粗略理解为字或词)时,就立刻把它推给前端,而不是等整段话生成完再一次性返回。这就像两个人对话,一个是等对方把整段话想好再开口,另一个是边想边说——后者显然更自然,也更让人有"在交流"的感觉。

在 LangChain 里,流式是通过回调机制实现的。模型每生成一个 token,就触发一次回调,把 token 推出去。前端拿到 token 就立刻渲染,用户看到的就是文字一个个蹦出来的效果。这套机制听起来简单,但和检索、工具调用结合起来时,就有不少门道了。

4.2 检索和工具调用会"打断"流式,怎么办

这是流式 RAG 助手最核心的难点:检索和工具调用都是"阻塞式"的操作,它们会打断流式的连续性。

想象一下这个流程:用户提问 → 助手要先检索(耗时几百毫秒到几秒)→ 可能还要调工具(又耗时)→ 最后才开始流式生成答案。在检索和调工具的这段时间里,用户看到的是什么?如果什么都不显示,那用户还是会觉得"卡住了",流式的优势就打了折扣。

我的处理办法是分阶段给用户反馈。检索阶段,前端显示"正在检索相关资料...";工具调用阶段,显示"正在查询XX...";等真正开始生成答案了,再切换成流式输出。这样用户全程都知道系统在干活,而不是干等。这个体验上的小设计,实际效果提升非常明显。

另一个坑是工具调用的结果如何融入流式。工具返回的是一段结构化数据,模型需要把它"消化"成自然语言再流式输出。这里要注意,工具返回的内容不要直接甩给用户看,而是作为上下文喂给模型,让模型用自己的话组织出来。否则用户会看到一堆 JSON,体验很差。

4.3 首字延迟:流式体验的命门

流式体验好不好,首字延迟(Time To First Token)是命门。用户从提问到看到第一个字的时间,决定了整个交互的"爽感"。我实测过,首字延迟超过 2 秒,用户就会开始怀疑系统是不是卡了;超过 5 秒,很多人会直接刷新页面。

优化首字延迟,我有几个实操手段:

  • 检索和生成并行化:如果检索不是必须的前置步骤(比如有些问题明显不需要查资料),可以让模型先判断,不需要检索就直接进入生成,省掉检索时间。
  • 限制检索返回的块数:K 值越大,检索和后续处理越慢,首字延迟越高。在效果可接受的前提下,K 尽量小。
  • 工具调用做超时控制:给每个工具设一个合理的超时时间(比如 3 秒),超时就放弃调用,让模型基于已有信息作答,而不是无限等待。

注意:流式输出和"逐字打印"的视觉效果是两回事。有些实现是等答案全生成完,再用定时器一个字一个字地"假装"流式打印,这种做法首字延迟其实没改善,纯属自欺欺人。真正的流式必须是模型生成即推送。

5. 把三块拼起来:一个完整助手的组装逻辑

5.1 整体流程的编排

把检索、工具、流式三块拼成一个助手,核心是编排好它们的执行顺序和条件分支。我用的流程大致是这样的:

  1. 用户提问,进入助手。
  2. 助手先判断:这个问题需要检索吗?需要调工具吗?
  3. 如果需要检索,先检索,把相关片段拼进上下文。
  4. 如果需要调工具,执行工具,把结果拼进上下文。
  5. 把"用户问题 + 检索结果 + 工具结果"一起喂给模型,流式生成答案。
  6. 前端分阶段展示状态,最后流式渲染答案。

这里第 2 步的判断很关键。如果每个问题都无脑检索一遍,既慢又可能引入噪声。我的做法是让模型先做一个轻量级的意图判断,或者用一些规则(比如问题里出现"多少""怎么算"就倾向调工具,出现"根据文档""我们内部"就倾向检索)。这个判断逻辑不需要太复杂,能过滤掉明显不需要的步骤就行。

5.2 上下文窗口的"预算管理"

检索结果和工具结果都要塞进模型的上下文窗口,但窗口是有限的。这就涉及一个"预算管理"问题:给检索结果留多少、给工具结果留多少、给对话历史留多少。

我的分配原则是:对话历史保留最近几轮(通常 3 到 5 轮),检索结果按相关性取前 K 块,工具结果只保留关键字段。如果总量超了,优先砍对话历史里最旧的部分,因为旧对话对当前问题的价值通常最低。这个策略不是死的,要根据实际场景调。比如做多轮追问的场景,对话历史就得多留一些。

5.3 一个容易忽略的细节:错误处理与降级

真实环境里,检索可能失败(向量库连不上),工具可能超时,模型可能返回空。这些情况都要有降级方案。我的处理是:

  • 检索失败:跳过检索,直接让模型基于自身知识回答,同时提示用户"资料检索暂时不可用"。
  • 工具超时:放弃工具结果,让模型基于已有信息作答。
  • 模型返回空:重试一次,还不行就返回兜底话术。

这些降级逻辑平时看不出价值,但一旦线上出问题,它们就是保证系统"不崩"的最后一道防线。我见过太多 Demo 级别的助手,一遇到异常就整个挂掉,根本没法上生产。

6. 实测中那些文档不会告诉你的坑

6.1 检索回来的内容"太像"反而有害

前面提过 MMR 能增加多样性,但这里有个更隐蔽的坑:当检索回来的几个块内容高度相似时,模型会误以为这个信息特别重要,从而过度强调它。比如知识库里有三处都提到了"报销需要发票",检索回来三块都在说这个,模型就可能把"必须提供发票"当成回答的核心,而忽略了用户真正想问的报销额度问题。

解决办法除了用 MMR,还可以在拼接上下文时做去重,把语义高度重复的块合并或丢弃。这个去重不用做得很精确,简单的文本相似度阈值过滤就能挡掉大部分重复。

6.2 工具调用的"幻觉参数"

模型有时候会"编造"工具参数。比如你让它查天气,工具需要城市名,用户没说城市,模型可能自己脑补一个城市填进去。这种"幻觉参数"很危险,因为它会让工具返回一个看似合理但完全错误的结果。

我的应对是在工具描述里明确要求:参数缺失时必须先向用户询问,不得自行假设。同时在工具执行前加一层参数校验,发现必填参数为空就拦截,返回提示让模型去问用户。这一层校验能挡掉相当一部分幻觉参数问题。

6.3 流式输出下的"中途改口"

流式有个副作用:模型是边想边说的,有时候它说到一半发现前面说错了,但话已经推出去了,收不回来。这在非流式模式下不存在,因为模型会先想完整段再输出。流式模式下,用户可能看到模型"自相矛盾"。

缓解办法是在提示词里要求模型先给结论再展开,减少中途改口的概率。另外,对于需要严谨推理的问题,可以考虑不用流式,或者用"先流式输出思考过程,再输出最终答案"的两段式。这个取舍要看具体场景,没有标准答案。

6.4 并发下的资源竞争

当多个用户同时用这个助手时,检索、工具调用、模型生成都会争抢资源。我遇到过向量库连接池被打满、工具接口被限流的情况。应对手段是给各个环节加并发控制和队列,比如限制同时进行的检索请求数,超出的排队等待。这个在 Demo 阶段完全不用考虑,但一上量就是必须解决的问题。

7. 关于参数调优,我踩过的几个具体坑

7.1 温度参数不是越低越好

很多人觉得问答助手要严谨,就把温度(temperature)调到 0。但实测下来,温度太低会让回答变得非常死板,甚至出现"复读机"现象——同一个问题问两次,答案一字不差,缺乏自然感。我的经验是问答类助手温度设在 0.1 到 0.3 之间比较合适,既保证稳定性,又保留一点表达的自然度。需要严格按格式输出的场景(比如生成 JSON)才把温度压到 0。

7.2 检索的相似度阈值要设,但不能设死

相似度阈值用来过滤掉那些"不够相关"的检索结果。设了阈值,能挡掉噪声;但设得太高,又可能把本来有用的块也挡掉,导致检索结果为空。我的做法是设一个相对宽松的阈值(比如 0.5 左右),然后靠 K 值来控制最终返回数量。这样即使阈值放进来一些边缘相关的块,也不会因为阈值太严而漏掉关键信息。

7.3 提示词里的"角色设定"影响很大

给助手写系统提示词时,角色设定不是走过场。我对比过,同样的问题,系统提示词里写"你是一个严谨的差旅政策助手,回答必须基于检索到的资料,资料里没有的不要编造",和只写"你是一个助手",前者的回答质量明显更高,编造情况也少很多。提示词里的约束越具体,模型的行为越可控。但也要注意别写太长,太长的提示词会挤占上下文空间,还可能让模型抓不住重点。

8. 这套方案还能往哪些方向长

把检索、工具、流式这三块跑通之后,这个助手其实还有不少可以继续打磨的地方。我自己后续会尝试的方向有这么几个。

第一个是检索结果的 rerank(重排)。初次检索用的是向量相似度,速度快但精度有限。可以在检索之后加一个重排模型,对候选块做更精细的相关性打分,把最相关的排到最前面。这一步能明显提升最终答案的质量,代价是多一点延迟。

第二个是工具调用的缓存。有些工具调用是幂等的,比如查某个固定城市的天气,短时间内重复查结果一样。给这类调用加缓存,能显著降低延迟和外部接口压力。

第三个是多轮对话里的检索优化。单轮问答的检索相对简单,但多轮追问时,用户的问题可能依赖上文(比如"那它呢"),这时候检索需要结合对话历史来理解真实意图。这块我还在摸索,目前的做法是把最近几轮对话拼进检索查询里,效果比只用当前问题好一些。

第四个是流式的细粒度控制。现在的流式是模型生成什么就推什么,未来可以做到"结构化流式"——比如先推答案的结论部分,再推详细解释,让用户第一时间看到最关心的内容。这需要在提示词和前端渲染上做配合。

这些方向没有哪个是必须的,取决于你的具体场景和资源。我的建议是先把基础版本跑稳,再根据实际痛点逐个优化,别一上来就追求大而全。

最后分享一个我自己的习惯:每次调整完参数或流程,我都会拿同一组测试问题跑一遍,把回答存下来和上一版对比。这个"回归测试"的习惯帮我避免了好几次"改了一个地方,结果把另一个地方搞坏了"的尴尬。做这类系统,效果是调出来的,不是一次写对的。

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

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

立即咨询