
> 作者:李剑锋
导读:
在上节课 Advanced RAG 的学习中,我们对传统 RAG 的重点在于围绕固定流程不断做优化,让系统“检索更准、回答更稳”。
但面对真实世界中的复杂任务,仅靠一条预设好的检索流水线往往还不够。本篇文章将进一步带大家走进 Modular RAG 与 Agentic RAG 的核心思想,理解 RAG 系统如何从“固定流程执行”走向“模型自主决策”。
本文不仅系统讲解了 Agent 的基本原理、ReAct 的运行逻辑,以及工具调用在智能体中的关键作用,还结合 LangChain 框架,完整演示了一个简化版 Agentic RAG 系统的搭建过程。通过这篇文章,你将真正理解未来的大模型应用,重点不只是让模型知道更多,而是让模型学会在合适的时候,主动调用合适的能力来完成任务。
前言
课程回顾
在上一节课中,我们已经围绕 Advanced RAG(进阶检索增强生成),系统学习了一个知识库问答系统从“能检索、能回答”走向“检索更准、回答更稳”的核心优化思路。我们不仅回顾了 Naive RAG 的基本流程,也进一步拆解了向量数据库构建、检索前优化、检索后优化以及生成优化等关键环节,理解了如何通过工程化手段不断提升 RAG 系统的整体效果。换句话说,上节课我们主要解决的是这样一个问题:在“检索—生成”这条基本链路不变的前提下,如何把每一个环节做得更好。

但在真实场景中,很多任务并不会老老实实地沿着一条固定流程执行。有些问题只需要简单检索一次即可回答;有些问题需要先改写查询再去检索;还有一些复杂问题,可能需要多轮“检索—阅读—再检索—再阅读”,甚至在过程中结合搜索、计算、数据库查询等外部工具,才能逐步完成任务。
这就意味着仅仅优化固定流水线,虽然已经能够显著提升 RAG 效果,但还不足以应对更加复杂、动态和开放的任务场景。于是,RAG 系统开始从“固定流程优化”进一步走向“动态流程组织”,这也就引出了我们这节课要重点学习的内容 —— Modular RAG(模块化 RAG)。
所谓 Modular RAG,其核心思想就是不再把 RAG 理解为一条固定不变的流水线,而是把它拆解为一组可以灵活组合的功能模块。在这种思路下,查询改写、检索、重排序、阅读理解、结果融合、记忆管理、工具调用等能力,都可以被视为相对独立的模块。系统可以根据不同任务的需要,自由决定启用哪些模块、模块之间以什么顺序运行,甚至在必要时循环调用某些模块。
不过,需要进一步说明的是,模块化并不等于系统已经具备了真正的自主决策能力。Modular RAG 解决的是“流程可以被灵活搭建”的问题,但在很多情况下,这种流程仍然是由开发者提前设计好的。也就是说,虽然模块变多了、流程更灵活了,但“什么时候改写问题”“什么时候重新检索”“是否需要调用外部工具”“执行到哪一步该停止”,这些关键判断往往仍然依赖人工预设规则。
而随着大语言模型在推理、规划和工具调用能力上的持续增强,RAG 系统又进一步向前迈出了一步,不仅模块可以灵活组合,而且流程本身的控制权也可以逐步交给模型来完成。在这种模式下,大模型不再只是被动接收检索结果并负责生成答案的“输出器”,而是开始充当任务执行过程中的“决策中枢”,它能够根据当前问题和中间结果,自主判断下一步应该检索、改写、调用工具,还是直接生成最终答案。这也正是我们常说的 Agentic RAG(智能体化检索增强生成)。

严格来说,Agentic RAG 并不只是 Modular RAG 的简单别名,而更像是建立在模块化思想基础上的进一步发展形态。前者强调“模块可以自由组合”,后者则进一步强调“模型能够主动规划、动态选择并驱动这些模块完成任务”。
因此,在本节课中,我们将先从 Agent 的基本原理 讲起,帮助大家理解什么是“模型驱动的任务决策”;然后再结合实际代码,带大家亲手实现一个简化版的 Agentic RAG 系统。通过这个过程,大家将真正看到一个 RAG 系统如何从“按固定流程查资料”,升级为“能够自主判断、主动调用工具、逐步完成任务”的智能体化系统。那么事不宜迟,我们就正式开始本节课的学习吧!
Agent 原理
什么是 Agent
如果用一句话来概括的话,Agent 就是让大模型不再只是“回答问题”,而是能够围绕目标自主决定下一步该做什么。
我们可以结合下图来更深入地理解 Agent 的工作机制。Agent 并不是在接收到用户问题后立刻生成最终答案,而是会先通过 Perception(感知) 模块获取来自环境的 Observation(观察信息)。在最开始的第一轮交互中,这里的“环境”主要就是用户输入的问题本身;而在经过一轮或多轮执行之后,环境中还会进一步包含工具返回结果、外部系统状态以及前一步动作产生的新信息。因此,Agent 面对的并不是单一的输入,而是一个会随着执行过程不断变化的信息环境。

在获取到这些观察信息之后,模型通常不会立刻直接回复用户,而是会结合自身已有的 State(当前状态)、Memory / Experience / Reflection(记忆、经验与反思) 以及 World Knowledge(世界知识),进入 Planning / Decision(规划与决策) 环节。在这一过程中,大模型不再只是一个被动的文本生成器,而更像是整个系统中的“决策中枢”。它会基于当前掌握的所有信息,判断为了完成目标,接下来最合适的策略是什么。这里的策略也就是图中所说的 Policy。在实际系统中,这部分策略往往会通过系统提示词、流程约束或预设规则传递给模型,从而引导模型决定下一步应该采取什么 Action(行动),例如是直接作答、调用外部工具,还是继续补充信息、调整任务流程。
当 Agent 产生 Action(动作) 之后,往往会进一步触发 Tool Use(工具调用),并从环境中获得相应的 Feedback(反馈)。这些所谓的工具,听起来似乎很复杂,但本质上通常只是一些已经封装好的外部能力,例如搜索接口、数据库查询函数、天气查询 API、代码执行器等。Agent 调用这些工具后,工具返回的执行结果、成功或失败状态、甚至某种评价信号,都会构成图中的反馈信息。随后,这些反馈又会重新输入到 Agent 内部,帮助其更新当前状态,并据此判断下一步该如何行动。
因此,Agent 的本质并不是“一次性生成答案”,而是通过“感知—规划—行动—反馈”这一循环机制,不断向任务目标推进。也正因为如此,Agent 相较于传统的大模型问答系统更灵活,也更适合解决需要多步推理、工具调用与动态调整的复杂任务。
从经典智能体框架的角度来看,这种“先推理、再行动、再根据结果继续推理”的过程,实际上正是 ReAct 思想的典型体现。所谓 ReAct,就是 Reason(推理) 与 Act(行动) 的结合。它强调模型不应一次性直接给出最终答案,而是要在任务执行过程中,先分析当前信息与目标之间的关系,判断下一步该做什么;然后执行对应动作,例如调用工具;接着接收工具返回的 Observation(观察结果);最后再基于新的信息进入下一轮推理。也就是说,Agent 图中的 Planning / Decision → Action → Observation / Feedback,本质上就可以看作是 ReAct 在系统层面的具体展开。基本当前所有主流的智能体都是基于这一原理进行展开。

案例演示
我们可以通过一个简单例子来进一步理解。假设你想了解明天的天气情况,于是向一个 Agent 提问:“明天上海的天气如何?”。
首先,Agent 会根据当前的上下文信息,包括用户提问内容、已有记忆、系统提示词中的规则等,在 Planning / Decision(规划与决策) 阶段判断应该采取什么行动(Action & Tool Use)。对于这个现实问题,它通常不会直接“凭空回答”,而是会决定调用一个天气查询工具来获取上海明天的真实天气数据。与此同时,这一次决策过程以及工具调用记录,也会被写入到 State(当前状态) 中。这里的 State 可以理解为 Agent 在本轮执行过程中临时维护的运行上下文,它记录的是“当前任务做到哪一步了、已经获取了哪些信息、刚才执行了什么动作”。这对应的是图中虚线的部分。
在工具返回结果之后,例如返回“明天上海晴转多云,气温 18~26 摄氏度”,这些信息就会作为新的 Observation(观察信息) 回到 Agent 中,同时也会继续补充到当前的 State 里。接着,大模型会基于这些最新信息再次进入规划阶段,判断当前掌握的信息是否已经足以完成用户目标。如果认为信息已经充分,那么就可以直接组织自然语言输出最终答案;但如果模型判断还缺少一些关键信息,比如用户可能还需要空气质量、是否下雨、穿衣建议等内容,那么它也可以继续调用其他工具,或者对已有结果做进一步加工与整合。
当整个任务完成之后,系统通常还会把这次交互中比较重要的内容沉淀到 Memory(记忆) 中。不过,这里的记忆一般不会把所有中间推理步骤和临时状态都原封不动保存下来,而是会保留更适合长期使用的关键信息,例如用户问了什么、系统最终回答了什么、用户可能偏好的交互方式是什么等。这样一来,当你之后继续追问“那后天呢?”时,Agent 就能够结合前面的上下文,提供更加连贯、自然的回答,而不是把每一次交互都当作完全孤立的新问题。
从这个简单例子中我们就可以看出,智能体本质上并不是一个只会生成文本的模型,而是一个以大模型为核心、能够感知环境、调用工具、维护状态、接收反馈并持续调整策略的任务执行系统。 它最重要的特点,不在于“会不会回答问题”,而在于“能不能围绕目标自主组织解决过程”。换句话说,传统大模型更像是一个知识回答者,而 Agent 更像是一个能够边想边做、边做边调整的智能执行者。
进一步来说,RAG 更关注的是“给模型补充外部知识”,而 Agent 更关注的是“让模型学会在任务过程中判断什么时候需要知识、什么时候需要工具、什么时候应该继续、什么时候可以结束”。 因此,Agent 可以被看作是在大模型应用基础上的一次能力升级,它不再只是一个静态的问答模块,而是逐渐演变为一个具备一定自主决策能力的智能系统。
Agent 实现方式
底层实现
为了实现智能体系统的搭建,其实我们有很多种选择。比如,我们完全可以自己从底层出发,手动实现一个 ReAct Agent。关于这一点,我在前面的文章中其实已经专门讲过,介绍了如何使用 Python 从零“手搓”一个 ReAct Agent,相关链接我放在这里,大家感兴趣的话可以自行阅读了解:
框架实现
不过,在本节课中,我们依然沿用上节课已经使用过的 LangChain 框架来完成智能体的实现。尤其是在去年 10 月 LangChain 发布 V1.0 版本之后,其官方能力重心已经从早期的模型调用、提示词编排、记忆管理等内容,逐步转向了基于 LangGraph 的智能体开发。也正因为如此,现在我们并不需要像以前那样自己手动组织复杂的执行流程,而是只需要通过一行类似 create_agent(...) 的代码,就可以快速完成一个智能体的创建。
实际上,这部分内容我之前也在另一篇文章中做过较为详细的介绍,链接同样放在这里:
这里我们就快速过一下其基本原理和内容。在 LangChain 提供的 create_agent(...) 本质上就是一个最标准的 ReAct Agent。如下图所示,其主要分成四个组件:
input:传入的用户提示词及系统提示词model:所使用的大模型(比如最新的 qwen3.5 模型)tools:我们所设定的工具库(比如前面查询天气的工具)output:最终模型判定搜集好信息后返回的输出
这里面最体现 Reasoning + Action 的就是 model 组件和 tools 组件的循环交互。模型发出执行工具的指令(action),然后工具执行完的结果会返回给模型(observation)。最终在模型思考觉得收集到了足够的信息后,就会返回 finish 指令,输出最后的结果 output。

当然这个 output 是最终结果我们并不需要进行创建,但是前面三个内容都是创建 Agent 的必要条件,因此在下面的实战练习中,我们也将会来对这三部分内容的创建进行讲解。
Agentic RAG 实现方式
在了解完 Agent 的基本原理之后,接下来我们就可以进一步看看,如何基于这种思想实现一个更加智能化的 RAG 系统。其实从本质上来说,这件事并不复杂。按照前面介绍的 Agent 运行逻辑来看,要让 RAG 具备更强的灵活性,核心思路就是将原本固定流程中的“检索能力”封装成可供智能体调用的工具,当然除了检索工具本身以外,我们其实可以在检索的过程中就添加我们上节课提到的一些方法,这个就留给大家后面自己来进行实践了。
这里需要注意的是,所谓的 RAG 检索,并不一定只能指向量数据库检索。只要某种方式能够帮助系统获取与当前任务相关的外部信息,它都可以被视为一种“检索渠道”。例如,网页搜索可以作为工具,本地文件搜索可以作为工具,数据库查询(如 SQL 搜索)同样也可以作为工具。从这个角度来看,智能化 RAG 的重点并不在于具体使用哪一种检索方式,而在于如何把这些外部信息获取能力统一封装为工具,让大模型能够根据任务需要自主选择和调用。
当然,工具并不是简单封装出来就可以直接交给模型使用。在创建每个工具时,我们还需要为其提供清晰、准确的功能描述,说明这个工具的作用、适用场景以及输入输出特点。只有这样,大模型才能在执行过程中正确理解“这个工具是做什么的”,并进一步判断在当前场景下到底应该调用哪一个工具。否则,即使工具本身已经实现完成,模型也可能因为缺乏足够的工具语义信息,而无法做出合理的选择。
因此,在接下来的代码实战部分中,我将带大家一步步演示:如何在 LangChain 中完成大模型的调用,如何创建并注册工具,如何设计适合智能体运行的提示词,以及如何对智能体的整体行为进行调整与优化。通过这个过程,大家将能够更直观地理解,一个智能化的 RAG 系统究竟是如何从“固定检索流程”逐步演化为“能够自主决策与调用工具的 Agent 系统”的。
Agentic RAG 应用实战
环境准备
首先,我们需要提前安装好 VS Code 和 Anaconda,并配置好相关的系统环境变量。这个部分属于基础环境准备,网上教程很多,大家可以直接在 B 站搜索对应视频先完成安装与配置。
完成基础安装后,我们先创建并激活一个独立的 Conda 环境,方便后续管理依赖,避免与其他项目产生冲突:
conda create -n fine-tuning python=3.12 -y
conda activate fine-tuning
接着,建议为当前环境配置国内镜像源(这里使用清华源),这样后续安装 Python 包会更稳定、更快一些:
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
另外,对于大模型的推理、微调与部署这类任务来说,基本都离不开 NVIDIA GPU。本课程中的实操内容主要基于我本地的一台笔记本(R9000P,RTX 3060,6GB 显存)完成。6GB 显存虽然不算高,但通过合理设置参数,依然可以完成一些轻量级模型的训练与实验演示。当然,如果你的设备配置更高,会有更好的体验;如果是准备跟着课程完整实践,建议显存至少在 6GB 及以上。
对于希望使用 GPU 进行大模型开发的同学来说,CUDA 是绕不开的一环。虽然目前 CUDA 已经发展到 13.x 版本,但由于我这边使用的是较早期显卡,因此安装的是 CUDA 12.3。如果你还没有安装 CUDA,建议前往 NVIDIA 官网下载并安装 CUDA 12.3(例如 Windows 版本)。当然,从本课程后续依赖来看,安装 CUDA 12.1 及以上版本通常就已经够用,因为后面安装的 PyTorch 主要使用的是 cu121 对应版本。
在完成 CUDA 安装后,我们就可以在刚刚创建的 Conda 环境中安装课程所需的核心依赖库,包括 transformers、torch、huggingface_hub、accelerate 等。激活环境后,依次执行以下命令即可:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
pip install transformers==5.1.0 huggingface_hub accelerate bitsandbytes openai==2.11.0 dashscope langchain-classic==1.0.0 langchain==1.2.0 langchain-community==0.4.1 langchain-openai==1.1.6 beautifulsoup4==4.14.3 langchain_chroma==1.1.0 langchain_huggingface text-generation google-search-results numexpr langchainhub sentencepiece jinja2
安装完成后,我们可以通过以下代码检查一下 torch 是否能够识别到 CUDA:
import torch
print("torch:", torch.__version__)
print("cuda available:", torch.cuda.is_available())
print("gpu:", torch.cuda.get_device_name(0) if torch.cuda.is_available() else None)
如果 cuda available 输出 True,并且能打印出显卡名称,说明环境已经准备完成。以上就是本节课需要准备好的环境了,后面的课程将会在这个基础上进一步进行配置。
模型准备
LangChain 模型调用
在 LangChain 中,假如我们想调用 Huggingface 上的模型,其实有一个专门的库 langchain-huggingface 可以进行使用,上节课里我们其实也对此介绍过,这里就不过多赘述。但上节课我们还是通过 Transformers 库来进行的模型调用,这里我们看看怎么在 LangChain 中进行调用。
调用的方法其实很简单在官方文档中,假如我们要进行智能体开发的话,主要推荐的是使用 ChatHuggingFace 的方式来进行模型调用。我们可以用 pipeline 的模式进行调用,比如:
from langchain_huggingface import ChatHuggingFace, HuggingFacePipeline
llm = HuggingFacePipeline.from_model_id(
model_id=r"D:\微调与部署\qwen",
task="text-generation",
pipeline_kwargs={
"max_new_tokens": 2000,
"return_full_text": False,
},
)
chat_model = ChatHuggingFace(llm=llm)
messages = [{"role": "user", "content": "你是谁?"}]
print(chat_model.invoke(messages).content)
这里我们就是把本地的 qwen3-0.6B 模型通过 HuggingFacePipeline 这个模块进行了载入(包括返回的 token 数量和是否返回完整的提示词模版参数),然后通过 ChatHuggingFace 进行了对话封装,这样后续就可以正常的进行模版的解析了,此时返回的结果为:
<think>
好的,用户问我是谁。我需要先确认用户的问题意图,可能是在测试我的回答能力,或者想了解我的背景信息。首先,我应该礼貌地回应,并简要说明我的身份。用户可能对我的角色或功能感兴趣,或者想进一步了解我的知识库。因此,我需要保持回答的简洁明了,同时提供足够的信息,帮助用户更好地理解我的存在。此外,要确保回答符合用户的需求,避免提供不相关的信息。最后,保持友好和专业的语气,让用户感到被重视。
</think>
我是你的AI助手,专注于提供帮助和解答问题。如果您有任何问题或需要支持,请随时告诉我!
但是由于这个 ChatHuggingFace 封装的时候并没有设置好,导致我们没有办法通过传入参数去实现思考模式的关闭。假如我们希望关闭思考模型,其实有两个方案,一个方案是加入一个软提示 /no_think 以关闭思考模式:
from langchain_huggingface import ChatHuggingFace, HuggingFacePipeline
llm = HuggingFacePipeline.from_model_id(
model_id=r"D:\微调与部署\qwen",
task="text-generation",
pipeline_kwargs={
"max_new_tokens": 2000,
"return_full_text": False,
},
)
chat_model = ChatHuggingFace(llm=llm)
messages = [{"role": "user", "content": "你是谁?/no_think"}]
print(chat_model.invoke(messages).content)
此时返回的结果为:
<think>
</think>
我是一个AI助手,专门帮助您解答各种问题。如果您有任何疑问或需要支持,请随时告诉我!
虽然此时确实思考消失了,但是这两个标签还是在的。假如我们希望把这个标签也去掉的话,我们需要通过继承的方式获取底层的代码对其进行修改才能实现思考模式的关闭:
from langchain_huggingface import ChatHuggingFace, HuggingFacePipeline
class QwenChatHuggingFace(ChatHuggingFace):
thinking: bool = False
def _to_chat_prompt(self, messages):
ifnot messages:
raise ValueError("At least one message must be provided!")
messages_dicts = [self._to_chatml_format(m) for m in messages]
return self.tokenizer.apply_chat_template(
messages_dicts,
tokenize=False,
add_generation_prompt=True,
enable_thinking=self.thinking,
)
llm = HuggingFacePipeline.from_model_id(
model_id=r"D:\微调与部署\qwen",
task="text-generation",
pipeline_kwargs={
"max_new_tokens": 2000,
"temperature": 0.7,
"top_p": 0.8,
"top_k": 20,
"do_sample": True,
"return_full_text": False,
},
)
# 开启思考
chat_model = QwenChatHuggingFace(llm=llm, thinking=False)
messages = [("human", "请介绍一下你自己。")]
ai_msg = chat_model.invoke(messages)
print(ai_msg.content)
这样模型就能够正常的关闭思考模型进行回复了:
您好!我是AI助手,名叫“小智”。我是由阿里巴巴集团研发的智能语音助手,专为用户提供各种服务和支持。我可以回答问题、提供帮助,甚至根据您的需求生成文本或提供信息。
如果您有任何问题或需要帮助,请随时告诉我!😊
当然此时我们在新封装的 QwenChatHuggingFace 中传入 thinking=True 就能够开启了:
<think>
好的,用户让我介绍一下自己。首先,我需要确定用户的需求是什么。他们可能是在测试我的角色,或者想了解我的背景,或者只是好奇我的身份。我应该保持回答的自然和开放,避免过于生硬。
接下来,我需要考虑用户可能的背景。他们可能对人工智能或自我介绍感兴趣,也可能只是想确认我的存在。所以回答要涵盖基本的信息,同时保持友好和专业。
然后,我需要确保信息准确,不包含任何不实或误导的内容。同时,语言要简洁明了,避免使用复杂术语,让用户容易理解。
另外,还要注意语气,保持积极和欢迎的态度,让用户感到被重视。可能还需要提到我的功能或特点,比如提供帮助或信息,但不要过多展开。
最后,检查回答是否符合所有要求,没有遗漏任何关键点,并且语言流畅自然。
</think>
你好!我是AI助手,可以为你提供帮助、解答问题或分享知识。如果你有任何问题或需要支持,请随时告诉我!😊
工具调用(Function Calling)
在前面的学习中,我们已经知道,大模型本身最擅长的是根据输入生成自然语言文本。但在真实应用场景中,很多任务并不是仅靠“生成一段话”就能完成的。例如:
查询某个城市的实时天气 检索企业内部知识库 调用数据库查询订单信息 调用搜索引擎获取最新资讯 执行计算、发送邮件、操作业务系统
这些任务都有一个共同特点:模型不仅要会“回答”,还要会“借助外部能力完成任务”。而这正是工具调用(Tool Calling)的核心意义。
所谓工具调用,本质上就是让大模型在合适的时候,不直接输出最终答案,而是先输出一个结构化的调用请求,告诉系统自己想调用哪个工具、需要传入什么参数。随后,由外部程序真正执行该工具,并把执行结果返回给模型,模型再基于结果生成最终回答。
因此,工具调用并不是模型自己真的去运行 Python 函数、访问数据库或调用 API,而是模型先表达“我需要调用某个工具”,再由应用程序帮助它完成执行。可以说,在这个过程中:
模型负责理解任务与决策 程序负责执行工具 模型再基于结果组织最终回答
也正因为如此,工具调用是大模型从“会说话”走向“会做事”的关键一步,也是智能体系统中的核心能力之一。
为了让模型能够更规范地与外部工具配合,OpenAI 提出了 Function Calling 机制。它本质上是一种标准化的工具调用方案:开发者先把工具的名称、功能说明、参数结构告诉模型,模型在对话过程中如果判断需要使用某个工具,就会返回一个结构化的调用请求,而不是直接编造答案。

整体来说,这个过程主要分成五步:
开发者向模型声明可用工具:在模型开始回答用户问题之前,开发者需要先把工具的定义告诉模型。例如图中提供了一个
get_weather(location)工具,表示系统具备“查询天气”的能力。此时,模型并没有真的执行工具,只是知道:如果用户的问题涉及天气查询,那么它可以选择使用这个工具。模型根据用户问题决定是否调用工具:当用户提出 “What’s the weather in Paris?” 这样的问题后,模型会先判断:这个问题是否适合直接回答,还是应该借助工具。由于天气属于实时信息,不适合凭空生成,因此模型选择发出一个工具调用请求,例如
get_weather("paris")。这一步非常关键,因为它体现了模型是否具备“行动决策能力”。一个真正可用的智能体模型,不只是会输出答案,更要会判断何时不该硬答,而应该借助外部工具获取可靠信息。外部系统执行工具代码:模型发出的工具调用请求,本质上只是一个“调用指令”,真正执行的是外部程序。例如系统收到
get_weather("paris")后,会去调用后端函数、API 接口或某个服务模块,最终得到执行结果,例如:{"temperature":14}这说明工具调用的执行主体并不是模型本身,而是围绕模型构建的应用系统。模型在这里更像是一个“调度中心”或“决策中枢”。
工具结果返回给模型:当外部程序执行完成后,会把结果重新传回模型上下文中。此时模型不仅看到了用户最初的问题,也看到了工具执行后返回的数据。这一步的意义在于:模型的后续输出不再只是基于参数记忆或语言模式,而是基于“最新拿到的外部事实”。因此其回答会更准确,也更符合真实业务场景。
模型生成最终回答:在拿到工具结果之后,模型再组织自然语言输出,例如:
It’s currently 14°C in Paris.
至此,一个完整的工具调用闭环就完成了。可以看到,最终用户看到的仍然是一句自然流畅的回答,但在这句回答的背后,其实经历了“识别需求—选择工具—发起调用—执行函数—整合结果”这样一整套过程。
对于 qwen3-0.6B 模型而言,官方实际上已经明确说明其具备一定的工具调用能力。也就是说,该模型不仅能够进行普通的对话生成,还能够在特定场景下根据用户需求主动生成工具调用请求。我们可以通过一个简单的示例来验证这一点。
首先,定义一个简单的天气查询工具,并将其作为可用工具传递给模型,然后向模型提出一个需要借助外部工具才能更准确回答的问题:
from transformers import pipeline
# 1. 定义工具函数
def get_weather(location: str):
"""
获取指定城市的天气信息
Args:
location: 城市名称
"""
data = {
"paris": "14°C",
"beijing": "22°C",
"shanghai": "18°C"
}
return data.get(location.lower(), "未知")
# 2. 加载支持聊天的文本生成 pipeline
pipe = pipeline(
"text-generation",
model=r"D:\微调与部署\qwen",
trust_remote_code=True
)
# 3. 构造聊天消息
messages = [
{"role": "user", "content": "What's the weather in Paris?"}
]
# 4. 把工具传给模型
output = pipe(
messages,
tools=[get_weather],
max_new_tokens=256
)
print(output)
输出的结果如下:
[{'generated_text': [{'role': 'user', 'content': "What's the weather in Paris?"}, {'role': 'assistant', 'content': '<think>\nOkay, the user is asking about the weather in Paris. I need to use the get_weather function here. The function requires the location parameter, which in this case is Paris. Let me make sure to format the tool call correctly. The parameters should be a JSON object with the location key set to "Paris". I\'ll check if there are any other required fields, but since the function only needs the location, that\'s all. Alright, I\'ll generate the tool call with those details.\n</think>\n\n<tool_call>\n{"name": "get_weather", "arguments": {"location": "Paris"}}\n</tool_call>'}]}]
从该结果可以看出,模型在接收到用户问题后,并没有直接凭空生成天气答案,而是先结合当前可用工具进行了判断。在完成内部思考后,它给出了如下工具调用请求:
<tool_call>\n{"name": "get_weather", "arguments": {"location": "Paris"}}\n</tool_call>
这说明 qwen3-0.6B 已经能够根据问题内容判断当前任务需要借助外部工具完成,并进一步生成相应的工具名称与调用参数。换句话说,该模型已经具备了基础的工具调用意图生成能力。不过,从输出结果来看,它在参数构造上仍然存在一定偏差。例如这里传入的是大写形式的 Paris,而按照当前工具函数内部的实现方式,更理想的传参形式应当是小写的 paris。这也说明,模型虽然已经能够“知道该调用什么工具”,但在参数规范性与细节稳定性方面仍然还有进一步优化的空间。
当然,需要注意的是,这里模型只是发出了工具调用请求,并不意味着工具已经被真正执行。上述输出本质上仍然只是模型生成的一段结构化文本,后续还需要由外部程序对该请求进行解析,真正执行 get_weather 函数,并将工具返回结果重新传递给模型,模型才能基于这一结果生成最终的自然语言回答。
不过,尽管这一过程尚未形成完整闭环,但从该实验结果中仍然可以看出,qwen3-0.6B 并不是完全不具备工具调用能力,而是已经能够在给定工具定义的前提下,完成基本的工具识别、工具选择以及参数构造。这也表明,该模型在作为轻量级智能体大脑时,已经具备了一定的基础能力,只是在工具调用的准确性、稳定性以及后续执行闭环方面,仍然存在进一步完善的空间。
LangChain 工具调用
如果说 OpenAI 的 Function Calling 提供的是一种较底层的工具调用机制,那么在实际开发过程中,我们通常不会每次都手动处理工具 Schema、函数注册、消息拼接以及结果回传等细节。为了简化这一过程,往往需要借助更高层的开发框架,而 LangChain 正是其中最常见的选择之一。
在 LangChain 中,我们可以通过 bind_tools() 方法,将工具直接绑定到聊天模型上。这样一来,模型在回答问题时,就能够在合适的场景下选择调用外部工具。因此可以将二者理解为:
OpenAI Function Calling 是一种底层能力机制; LangChain 的 bind_tools()是一种更高层的工程封装方式。
它的价值在于,能够帮助开发者将“工具定义、模型绑定、工具调用以及结果处理”这一整套流程统一组织起来,从而更方便地构建智能体应用。实际上,我们后续使用的 create_agent(...) 方法,其底层也是建立在这一思路之上的。
不过,需要注意的是,bind_tools() 并不意味着在所有模型和框架组合下都能够开箱即用。以 ChatHuggingFace 为例,虽然它在形式上支持工具绑定,但在实际使用过程中,却未必能够真正完成完整、稳定的工具调用流程。换句话说,它“看起来绑定成功了”,并不代表模型就一定能够正确发出工具调用指令。
例如,下面这段代码尝试为本地 Hugging Face 模型绑定一个简单的天气查询工具,并要求模型在必要时调用该工具:
from langchain_huggingface import ChatHuggingFace, HuggingFacePipeline
from langchain_core.tools import tool
# 1. 定义一个简单工具
@tool
def get_weather(city: str) -> str:
"""查询指定城市的天气。"""
fake_db = {
"上海": "上海今天晴,18~26摄氏度。",
"广州": "广州今天多云,22~29摄氏度。",
}
return fake_db.get(city, f"未找到 {city} 的天气信息。")
# 2. 本地模型
llm = HuggingFacePipeline.from_model_id(
model_id=r"D:\微调与部署\qwen",
task="text-generation",
pipeline_kwargs={
"max_new_tokens": 512,
"temperature": 0.0,
"do_sample": False,
"return_full_text": False,
},
)
chat_model = ChatHuggingFace(llm=llm)
# 3. 绑定工具
model_with_tools = chat_model.bind_tools([get_weather])
# 4. 发起请求
msg = model_with_tools.invoke("请帮我查询上海的天气。不要直接猜测,如果需要请调用工具。")
print("=== 原始内容 ===")
print(msg.content)
print("\n=== tool_calls ===")
print(msg.tool_calls)
从代码逻辑上看,我们已经完成了工具定义、工具绑定以及模型调用,整个流程似乎没有问题。然而,实际返回结果却发现,模型并没有按照预期发出工具调用请求,而是直接生成了一段普通的自然语言回答:
=== 原始内容 ===
<think>
Okay, the user is asking about the weather in Paris. I need to provide a comprehensive answer. First, I should mention the main cities in France, like Paris, Lyon, and Marseille. Then, I can talk about the climate. Paris is known for its warm weather, especially in the summer, but it's also quite cold in winter. I should note that the weather varies by season. Maybe include some specific details like the temperature ranges, maybe even mention the seasons. Also, maybe add a note about the weather in different parts of the city, like the city center versus the suburbs. I should keep the answer clear and helpful, making sure to cover the key points without being too technical.
</think>
Paris, France, is a city known for its vibrant culture and diverse weather. Here's a general overview:
- **Climate**: Paris experiences a **tropical climate** with warm summers and mild winters. The weather varies by season, with:
- **Summer (June to August)**: Warm, sunny, and comfortable, often with temperatures ranging from15°C to 28°C.
- **Winter (December to February)**: Cold, with temperatures dropping to around 0°C to 5°C, and occasional snow in winter months.
- **Weather Variations**:
- **City Center**: Typically milder and more comfortable.
- **Suburbs**: May experience more extreme weather, with higher temperatures and potential for rain or snow.
- **Notable Weather Patterns**:
- In spring, the weather can be dry and cool.
- In autumn, it can be rainy or cloudy.
If you're planning a trip, check the weather forecast for the specific time and location you're interested in. For the most accurate information, consult a weather service or use a travel app.
=== tool_calls ===
[]
从这一结果可以看出,虽然模型表面上已经绑定了工具,但在真正回答问题时,它并没有输出类似 <tool_call>...</tool_call> 这样的结构化调用指令,tool_calls 字段也仍然为空。也就是说,在这一组合方式下,工具调用流程并没有真正被触发。
造成这一现象的原因其实是在 ChatHuggingFace + HuggingFacePipeline 这一组合下,LangChain 的 bind_tools() 行为,往往并不完全等价于 Hugging Face 原生 pipeline 中通过 tools= 参数直接传入工具的方式。虽然从开发者视角来看,工具似乎已经“绑定成功”,但从模型实际接收到的上下文来看,这些工具定义未必真的被正确注入到底层的聊天模板中。
基于上述分析,这里我们就不再继续对 ChatHuggingFace 的底层实现进行额外改造,而是改为使用 Qwen 团队与 LangChain 协作适配的 ChatTongyi 模块来完成模型调用。相较于前面的方式,ChatTongyi 已经对工具调用相关能力进行了较完善的封装,因此在实际使用时能够更方便地支持工具绑定与调用流程。
当然,这种方式也有一个前提,那就是它需要我们先前往阿里云百炼大模型平台申请并获取对应的 API Key,之后才能正常完成模型访问与调用。下面,我们就来一步步完成这一过程。
百炼大模型平台 API_KEY 获取
首先我们需要前往阿里云百炼大模型的官网(大模型服务平台百炼控制台),然后点击右上角注册或登录账号:

登录后点击上方模型标签:

然后找到左下方 API Key 按钮:

然后我们就可以创建一个 API Key:

最后点击复制按钮即可获取到对应的 API_KEY 内容。这样我们就可以拿到 API KEY 信息了。接下来我们可以把这个信息写入到系统变量中,比如在 Anaconda Prompt 中写入:
set DASHSCOPE_API_KEY=sk-xxxxxxxx
又比如在代码中直接写入:
import os
os.environ["DASHSCOPE_API_KEY"] = "sk-yyyyyyyy"
当然对于本地电脑我们可以存入系统环境变量中,具体可以参考下面两张图完成:


ChatTongyi 模型调用
准备好了 API_KEY 后并将其存入系统变量后,我们就可以开始对模型进行调用了。调用的方法非常简单,我们只需要选择模型即可进行调用,具体模型的内容我们可以在模型广场中找到:

这里我们就使用综合能力比较好的 qwen-plus 来进行演示。一般情况下 qwen 官方会给我们 100 万 token 让我们免费试用(有过期时间),我们只需要填写一下个人信息或者充值一毛钱就好:

接下来我们就可以回到 LangChain 中使用官方合作的 ChatTongyi 进行模型调用:
from langchain_community.chat_models import ChatTongyi
chat_model = ChatTongyi(model="qwen-plus")
messages = [{"role": "user", "content": "请介绍一下你自己。"}]
ai_msg = chat_model.invoke(messages)
print(ai_msg.content)
返回的结果为:
你好!我是通义千问(Qwen),阿里巴巴集团旗下的超大规模语言模型。我能够回答问题、创作文字,比如写故事、写公文、写邮件、写剧本、逻辑推理、编程等等,还能表达观点,玩游戏等。
我的训练数据截止于2024年10月,因此对之前发生的事件、已有的知识有较全面的掌握,但无法获取实时信息(如当前天气、最新新闻、股票行情等)。不过,我支持多语言,包括中文、英文、法语、西班牙语、葡萄牙语、俄语、阿拉伯语、日语、韩语、越南语、泰语、印尼语等,可以满足国际化的使用需求。
此外,我具备较强的对话理解能力,能准确把握上下文,提供连贯、自然的交互体验;也支持长文本输入(最长支持32768个token),适合处理复杂的任务或分析长文档。
如果你有任何问题、需要帮助写作、学习辅导、技术解答,或者只是想聊聊天——我都很乐意陪伴和协助!😊
你想从哪里开始呢?
那我们也可以来测试一下其工具调用的能力:
from langchain_community.chat_models import ChatTongyi
from langchain_core.tools import tool
# 1. 定义一个简单工具
@tool
def get_weather(location: str):
"""
获取指定城市的天气信息
Args:
location: 城市名称
"""
data = {
"paris": "14°C",
"beijing": "22°C",
"shanghai": "18°C"
}
return data.get(location.lower(), "未知")
# 2. 闭源模型
chat_model = ChatTongyi(model="qwen-max")
# 3. 绑定工具
model_with_tools = chat_model.bind_tools([get_weather])
messages = [{"role": "user", "content": "What's the weather in Paris?"}]
# 4. 发起请求
msg = model_with_tools.invoke(messages)
print("=== 原始内容 ===")
print(msg.content)
print("\n=== tool_calls ===")
print(msg.tool_calls)
此时就可以看到,模型并没有输出任何内容,而是直接进行了工具调用请求:
=== 原始内容 ===
=== tool_calls ===
[{'name': 'get_weather', 'args': {'location': 'Paris'}, 'id': 'call_32cc0c6afb1a4ebbbfc677', 'type': 'tool_call'}]
所以接下来我们就可以使用该模型进行工具的调用了。
工具准备
工具构建原理
在准备好模型之后,接下来就需要开始构建智能体可用的工具了。实际上,在前面介绍工具调用时,我们已经创建过一个简单的天气查询工具 get_weather,其代码如下:
from langchain_core.tools import tool
@tool
def get_weather(location: str):
"""
获取指定城市的天气信息
Args:
location: 城市名称
"""
data = {
"paris": "14°C",
"beijing": "22°C",
"shanghai": "18°C"
}
return data.get(location.lower(), "未知")
从本质上来看,工具其实就是一个普通的 Python 函数,只不过在定义时需要补充一些额外的信息,以便大模型能够理解并正确使用它。对于 LangChain 而言,一个函数如果想要被当作工具使用,通常需要具备两个关键要素。
首先,是在函数上方添加 @tool 装饰器。这个装饰器的作用是将普通函数包装成 LangChain 可以识别和管理的工具对象,使其能够参与后续的工具绑定与调用流程。
其次,是为函数编写清晰的文档字符串,也就是下面这一部分内容:
"""
获取指定城市的天气信息
Args:
location: 城市名称
"""
这部分内容并不仅仅是给程序员阅读的说明文字,更重要的是,它会作为工具描述信息提供给大模型。模型会借助这些描述来理解:这个工具是什么、具备什么功能、接收哪些参数,以及在什么场景下应该调用它。因此,文档字符串写得是否清晰、准确,往往会直接影响模型选择工具和构造参数的效果。
也就是说,构建 LangChain 工具并不复杂。我们通常只需要在原本普通函数的基础上,补充 @tool 装饰器以及规范的文档字符串,就可以将其转换为一个能够被大模型识别和调用的工具。下面我们就来看看怎么把我们上节课构建的 RAG 检索函数转换成一个 LangChain 的工具吧!
RAG 工具
在上节 RAG 实战课程中,我们实际上已经编写过两个与知识库检索相关的函数。其中一个是用于创建向量数据库的 vectorstore 函数,其作用是将网页内容加载、切分、向量化,并最终存入本地向量数据库中:
from langchain_huggingface.embeddings import HuggingFaceEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import WebBaseLoader
from langchain_chroma import Chroma
def vectorstore(url):
loader = WebBaseLoader(url)
docs = loader.load()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size = 500,
chunk_overlap = 150)
splits = text_splitter.split_documents(docs)
embeddings = HuggingFaceEmbeddings(model_name=r"D:\微调与部署\qwen-embedding")
vectordb = Chroma.from_documents(
documents=splits,
embedding=embeddings,
persist_directory='./chroma5')
print(vectordb._collection.count())
vectorstore(url="https://zh.d2l.ai/chapter_multilayer-perceptrons/underfit-overfit.html")
如果本地尚未创建对应的向量数据库,那么首先需要运行这段代码,将知识库内容预处理并保存到本地。只有完成这一步,后续的检索功能才能正常使用。
除了向量数据库的创建之外,我们还编写了另一个函数,即用于从向量数据库中检索相关内容的 retriever 函数。它的作用是根据用户提出的问题,从向量数据库中找到最相关的文本片段,并将其返回。当然我们可以基于上一节课学习到的内容进行改造,为其添加重排序、总结等能力,这里我们就沿用最基础的内容。具体代码如下:
from langchain_huggingface.embeddings import HuggingFaceEmbeddings
from langchain_chroma import Chroma
def retriever(question):
# 使用当前创建的向量数据库
embeddings = HuggingFaceEmbeddings(model_name=r"D:\微调与部署\qwen-embedding")
vectordb = Chroma(persist_directory="./chroma5", embedding_function=embeddings)
# 利用相似度搜索检索与问题最相关的1个切片
retriever = vectordb.as_retriever(
search_type="similarity",
search_kwargs={"k": 1}
)
docs = retriever.invoke(question)
context = docs[0].page_content
return context
不过,很显然,这里定义的 retriever 还只是一个普通的 Python 函数,它本身并不能直接作为 LangChain 智能体的工具来使用。如果希望模型在对话过程中能够自主判断“是否需要查询知识库”,并在必要时主动调用它,那么我们就需要按照前面介绍的方法,将其改造成一个 LangChain 工具。
实际上,改造的过程并不复杂,本质上就是在原函数的基础上,补充 @tool 装饰器以及清晰的文档字符串。例如可以改写为下面这种形式:
from langchain.tools import tool
from langchain_huggingface.embeddings import HuggingFaceEmbeddings
from langchain_chroma import Chroma
# 建议放在函数外,避免每次调用工具都重复加载
embeddings = HuggingFaceEmbeddings(model_name=r"D:\微调与部署\qwen-embedding")
vectordb = Chroma(
persist_directory="./chroma5",
embedding_function=embeddings
)
@tool
def retrieve(query: str) -> str:
"""
检索机器学习知识库中的相关内容。
当用户问题涉及课程资料、知识库内容、专业细节、较复杂概念、
需要更准确表述、或模型对答案不够确定时,应优先调用本工具。
对于非常基础且可以确定的常识性问题,可以直接回答。
"""
# 利用相似度搜索检索与问题最相关的1个切片
retriever = vectordb.as_retriever(
search_type="similarity",
search_kwargs={"k": 1}
)
docs = retriever.invoke(query)
context = docs[0].page_content
return context
这样改造之后,这个函数就不再只是一个普通的检索函数,而是成为了一个可以被 LangChain 智能体识别和调用的工具。在上面的描述中,我们不仅说明了这个工具的用途是“检索机器学习知识库中的相关内容”,还进一步告诉模型:当问题涉及课程资料、专业细节、复杂概念,或者模型本身对答案不够确定时,应优先调用该工具;而对于那些非常基础、能够直接确定的常识性问题,则不一定需要额外检索。
经过这样的定义之后,模型就能够更好地理解该工具的使用边界,从而在合适的时机发出工具调用请求。换句话说,工具描述写得越清晰,模型就越容易知道何时该调用工具、何时不该调用工具。 这也是为什么在构建智能体工具时,我们不仅要关注函数本身能不能运行,还要特别重视工具说明是否准确、具体、易于模型理解。
最后我们需要将这个工具放到一个列表里,等会将会传入到 create_agent() 中:
tools = [retrieve]
其他工具
除了前面实现的 RAG 检索工具之外,我们实际上还可以接入许多其他工具来进一步增强智能体的能力。从本质上来说,一个智能体是否足够强大,除了取决于模型本身的推理与生成能力之外,更重要的往往在于其背后的工具体系设计是否合理、丰富且可用。
例如,我们完全可以构建多个不同的向量数据库,并将它们分别封装成独立工具。这样一来,智能体在执行任务时,就能够根据当前问题所属的领域、知识来源或应用场景,自主选择更合适的知识库进行检索,而不必始终依赖单一的数据源。
除此之外,我们还可以进一步加入 SQL 检索能力。LangChain 官方就提供了一个专门面向关系型数据库的工具包——SQLDatabaseToolkit。它能够结合大语言模型的自然语言理解能力,将用户输入的自然语言问题自动转换为对应的 SQL 查询语句,并执行查询,从而显著简化人与数据库之间的交互过程。开发者无需手动编写 SQL,只需要将语言模型(LLM)和数据库实例(DB)传入工具包中,即可完成初始化并获得一整套可供智能体调用的数据库工具。例如:
from langchain_community.agent_toolkits import SQLDatabaseToolkit
# 假设 db 已在上一步创建
from langchain_community.utilities import SQLDatabase
db = SQLDatabase.from_uri("sqlite:///Chinook.db")
from langchain_community.chat_models import ChatTongyi
model = ChatTongyi(model="qwen-max")
# 创建工具包
toolkit = SQLDatabaseToolkit(db=db, llm=model)
tools = toolkit.get_tools()
# 输出可用工具
for tool in tools:
print(f"🔧 工具名: {tool.name}")
print(f"📘 说明: {tool.description}\n")
我们可以通过打印这些工具对象,查看 LangChain 自动为我们准备好的 SQL 工具及其说明信息:
🔧 工具名: sql_db_query
📘 说明: Input to this tool is a detailed and correct SQL query, output is a result from the database. If the query is not correct, an error message will be returned. If an error is returned, rewrite the query, check the query, and try again. If you encounter an issue with Unknown column 'xxxx' in 'field list', use sql_db_schema to query the correct table fields.
🔧 工具名: sql_db_schema
📘 说明: Input to this tool is a comma-separated list of tables, output is the schema and sample rows for those tables. Be sure that the tables actually exist by calling sql_db_list_tables first! Example Input: table1, table2, table3
🔧 工具名: sql_db_list_tables
📘 说明: Input is an empty string, output is a comma-separated list of tables in the database.
🔧 工具名: sql_db_query_checker
📘 说明: Use this tool to double check if your query is correct before executing it. Always use this tool before executing a query with sql_db_query!
从这里也可以看出,LangChain 并不是只提供一个简单的“查询工具”,而是围绕数据库访问过程构建了一整套相互配合的工具体系。比如,智能体在正式执行 SQL 查询之前,可以先查看当前数据库中有哪些表、进一步检查表结构,甚至先对生成的 SQL 语句进行校验。这种分步骤的工具设计方式,能够有效提高查询的准确性与安全性,也更符合真实智能体系统的执行逻辑。
当然,除了从本地数据库中获取信息之外,还有一个非常重要的信息来源,就是互联网中的实时信息。实际上,几乎所有主流大模型厂商,如今都已经为自己的产品加入了联网搜索能力:

而这种能力同样也可以在 LangChain 中以工具的形式接入,并交由智能体在合适的场景下自主调用,从而增强系统在开放域问答、实时信息查询、外部资料补充等任务中的实际表现。
在 LangChain 内置工具中,其实已经提供了不少与网络搜索相关的能力,例如:

不过需要注意的是,LangChain 中相当一部分网络搜索工具依赖国外服务,很多在国内环境下无法直接稳定使用。目前相对容易使用的,通常只有 arxiv 和 wikipedia 这类面向单一数据源的检索工具。至于更通用的网页搜索能力,往往还是需要借助国内可访问的搜索 API 来自行封装,例如博查、百度 AI 搜索、Jina 等。
下面我们就以 博查(Bocha) 为例,简单演示一下如何将网页搜索能力封装为一个智能体工具。大家可以自行前往博查官网注册账号并申请对应的 API_KEY。如果只是用于学习和测试,也可以先在资源包页面领取“免费试用”资源,其他活动信息则可以参考其官方文档。

在博查中提供四个 API,那其实我们只是为了去检索一些内容的话,用基础的 Web Search API 就已经足够了:

在博查官方文档中,实际上也提供了与 LangChain 结合使用的示例。我们只需要拿到 API_KEY 后,就可以将其封装为一个 LangChain 工具,供智能体在需要时直接调用。例如:
BOCHA_API_KEY = "YOUR-BOCHA-API-KEY"
@tool
def bocha_websearch_tool(query: str, count: int = 10) -> str:
"""
使用Bocha Web Search API 进行搜索互联网网页,输入应为搜索查询字符串,输出将返回搜索结果的详细信息,包括网页标题、网页URL、网页摘要、网站名称、网站Icon、网页发布时间等。
"""
url = 'https://api.bochaai.com/v1/web-search'
headers = {'Authorization': f'Bearer {BOCHA_API_KEY}', # 请替换为你的API密钥
'Content-Type': 'application/json'}
data = {"query": query,
"freshness": "noLimit", # 搜索的时间范围,例如 "oneDay", "oneWeek", "oneMonth", "oneYear", "noLimit"
"summary": True, # 是否返回长文本摘要
"count": count}
response = requests.post(url, headers=headers, json=data)
if response.status_code == 200:
json_response = response.json()
try:
if json_response["code"] != 200ornot json_response["data"]:
returnf"搜索API请求失败,原因是: {response.msg or '未知错误'}"
webpages = json_response["data"]["webPages"]["value"]
ifnot webpages:
return"未找到相关结果。"
formatted_results = ""
for idx, page in enumerate(webpages, start=1):
formatted_results += (f"引用: {idx}\n"
f"标题: {page['name']}\n"
f"URL: {page['url']}\n"
f"摘要: {page['summary']}\n"
f"网站名称: {page['siteName']}\n"
f"网站图标: {page['siteIcon']}\n"
f"发布时间: {page['dateLastCrawled']}\n\n")
return formatted_results.strip()
except Exception as e:
returnf"搜索API请求失败,原因是:搜索结果解析失败 {str(e)}"
else:
returnf"搜索API请求失败,状态码: {response.status_code}, 错误信息: {response.text}"
通过这种方式,我们就将一个原本需要手动访问网页接口的外部搜索能力,封装成了一个可以被 LangChain 智能体直接调用的工具。这样一来,当用户提出的问题涉及最新资讯、实时动态、网页资料或开放域知识时,智能体就可以根据需要主动调用该工具,而不是仅依赖自身参数知识或本地知识库内容进行回答。
从这个角度来看,Agentic RAG 的核心并不只是“把向量检索接进来”这么简单,而是逐步为智能体构建一个完整的外部能力系统。 向量数据库检索、关系型数据库查询、互联网搜索、代码执行、业务接口调用等,都可以被统一封装为工具,交由大模型在合适的场景下自主选择和调用。也正因为如此,工具体系的丰富程度与设计质量,往往会直接决定一个智能体系统在真实场景中的上限。
提示词设计
在完成模型与工具的部署后,真正决定智能体“怎么行动”的,往往不是模型本身,而是系统提示词(System Prompt)。它的作用并不只是简单地告诉模型“你是谁”,更重要的是为智能体建立一套明确的行为规则,即什么时候可以直接回答,什么时候需要调用检索工具,什么时候应当继续尝试,什么时候必须停止并如实说明信息不足。换句话说,系统提示词本质上承担的是一种“策略控制器”的角色。对于 Agentic RAG 而言,它不只是影响回答风格,更直接影响整个任务流程的组织方式。
从这个角度来看,我们可以参考论文中的 ADAPTIVE(Adaptive Retrieval-Augmented Generation) 框架,来设计智能体的提示词逻辑。该框架的核心思想,并不是让系统“逢问必检索”,而是让模型根据当前任务状态,动态决定是否检索、如何检索、检索后是否足以回答,以及是否需要继续迭代。
从图中可以看到,ADAPTIVE 的整体流程大致可以概括为以下几个阶段:
用户输入问题(Query)后,系统首先进入第一次 Judge(判断)。 在这一步中,模型需要先判断当前问题是否真的需要检索:
如果问题较简单,或者模型凭借已有知识就能较为确定地回答,那么可以直接进入 Generate,随后输出 Response; 如果问题较复杂、依赖外部知识,或者模型自身把握不足,那么就进入 Retrieve 阶段,按需触发检索。
如果证据已经充分,就可以直接输出最终结果; 如果证据仍然不足,或者返回内容相关性较弱,那么系统就需要进入 Query Transformation / Decomposition(查询改写 / 查询拆解) 模块,对问题进行重新组织,再次发起检索,形成一个可循环迭代的闭环过程。
也就是说,ADAPTIVE 的关键并不只是“检索”本身,而是通过两次关键判断,把“是否检索”和“检索结果是否可用”这两个原本容易被忽略的问题,显式纳入到了智能体的决策流程中。

基于这样的思路,我们就可以把智能体在 RAG 场景中的行为规则进一步总结为以下四个核心约束。
首先,如果问题本身较为简单,且模型凭借已有知识就能够较为准确地作答,那么应当直接回答,而不是机械地调用检索工具。 这样做的意义在于减少不必要的检索开销,也避免把原本可以直接完成的任务复杂化。
其次,如果确实需要检索,那么每一次检索都应尽量聚焦一个清晰、独立的检索意图。 当模型发现问题中包含多个方向、多个子任务时,不应把多个关键词生硬拼接成一次查询,而应当拆分为多次独立检索。这样更符合图中“查询改写 / 查询拆解”模块的思想,也更有利于提高召回结果的可控性与相关性。
再次,检索完成后,模型还必须对返回内容做一次“可回答性判断”。 如果结果中包含了定义、解释、结论、例子或其他足以支撑回答的实质性内容,那么可以基于这些证据进行总结回复;但如果结果只是目录、标题、章节索引、关键词列表,或者虽然主题接近却没有真正回答问题,那么这些内容就不能被直接当作充分证据使用。此时,模型应优先尝试更换查询表述,而不是勉强生成一个看似完整、实则缺乏依据的答案。
最后,如果经过多轮检索之后,仍然没有找到足够相关、足以支撑回答的内容,那么模型必须诚实地说明当前数据库中没有检索到可靠信息,而不是继续编造或脑补。 当然,如果数据库没有找到答案,但模型本身确实掌握该问题的通用知识,也可以在明确标注信息来源的前提下进行补充说明。
基于以上这些考虑,我们就可以为当前这个 Agentic RAG 系统设计如下系统提示词:
system_prompt = """
你现在是一个 RAG 智能助手,主要负责回答与机器学习相关的问题。你拥有一个名为 retrieve 的检索工具,可以在需要时获取额外信息。
请严格遵守以下规则:
1. 如果问题很简单,且你凭借自身知识就可以准确回答,那么直接回答,不要调用工具。
2. 如果问题较复杂,或者你无法保证答案足够准确完整,可以调用 retrieve 工具进行检索。
3. 调用 retrieve 工具时,每次调用的 query 只能是以下两种形式之一:
- 一个单独的关键词或短语
- 一句完整、简短、自然的检索语句
4. 严禁在一次工具调用中输入多个并列关键词、多个空格拼接词、多个子问题,或多个检索意图。
例如,不允许使用“过拟合 正则化 关系”这类关键词堆叠形式。
5. 如果你需要尝试多个检索方向,必须发起多次 retrieve 工具调用。每次调用只允许一个独立检索内容。
6. 在使用工具后,你必须先判断检索结果是否真的能够支持回答用户问题。
只有当返回内容中包含与问题直接相关的定义、解释、论述、结论、例子或实质性知识内容时,才可以基于该结果作答。
7. 如果检索结果只是目录、标题、章节名、列表、导航信息、主题名称、章节索引、关键词罗列,或者虽然主题相近但没有直接回答问题,
那么这些结果不能视为充分证据,不能据此自行扩展、推测或脑补出详细答案。
8. 如果检索结果相关性不足,你应当优先更换一个更合适、更具体的关键词、短语或检索语句再次检索。
你可以多次调用工具,但每次都必须是一个独立检索内容。
9. 如果多次检索后仍然无法获得与问题直接相关、足以支撑回答的内容,
那么你应明确告诉用户:
- 数据库中没有检索到足够相关的信息,或
- 当前检索结果不足以支持可靠回答。
不要把不充分的检索结果强行扩展成完整答案,不要编造内容。
10. 如果数据库没有找到足够相关的信息,但你自己确实知道这个问题的通用知识答案,
你可以补充基于自身知识的回答,但必须明确说明:
“以下内容不是直接来自数据库检索结果,而是基于通用知识的补充说明。”
如果你自己也不确定,就直接说明不懂或暂时无法确定。
请始终优先保证回答的准确性、相关性与诚实性。
宁可明确说明“数据库中没有足够相关信息”,也不要根据很弱的检索结果进行过度发挥。
"""
智能体组建
在准备好模型、工具和系统提示词之后,我们就可以将这三部分组合起来,正式构建一个智能体,并对其进行调用。
在 LangChain 中,可以通过如下方式快速完成一个基础智能体的创建:
from langchain.agents import create_agent
agent = create_agent(model=chat_model, tools=tools, system_prompt=system_prompt)
通过这段代码,我们就完成了一个智能体的基本搭建。这里传入的 model、tools 和 system_prompt 是构建智能体最核心的三个部分,分别对应:
模型:决定智能体的推理与生成能力; 工具:决定智能体是否能够访问外部能力,例如检索数据库、调用接口等; 系统提示词:决定智能体的整体行为规则,包括何时直接回答、何时调用工具、调用后如何组织回复等。
当然,create_agent() 并不只支持这三个参数。在实际开发中,我们还可以进一步加入一些增强组件,例如 middleware、checkpointer 等,用于实现更复杂的流程控制、状态持久化或中间处理逻辑。这些扩展内容大家可以后续再深入了解,在我的 LangChain 课程中也都有比较系统的讲解:
https://github.com/Jianfeng777/langchain_material_for_baidu_ai-studio
智能体调用
普通调用
首先来看一个最简单的调用方式。比如我们给智能体传入一个较为基础的问题:“什么是过拟合?”:
query = "什么是过拟合?"
print(agent.invoke({"messages": [{"role": "user", "content": query}]}))
可以看到,由于这个问题本身较为基础,模型凭借自身已有知识就能够完成回答,因此不会触发任何工具调用,而是直接生成回复。
返回结果如下:
{'messages': [HumanMessage(content='什么是过拟合?', additional_kwargs={}, response_metadata={}, id='39da65c3-a46f-4f2b-bb7a-ab3ec63cb875'), AIMessage(content='过拟合是指机器学习模型在训练数据上表现得过于优秀,以至于它学到了训练数据中的噪声和细节,而这些并不能很好地泛化到未见过的数据(测试数据或新数据)上。结果是,这样的模型在训练集上的误差很小,但在新的、未见过的数据上预测时,其性能会显著下降。\n\n简单来说,过拟合就是模型对训练数据“记忆”得太好,失去了对新数据的适应能力。这通常发生在模型过于复杂或者训练数据量不够多的情况下。解决过拟合的一些常见方法包括增加更多的训练数据、使用正则化技术、提前停止训练以及采用 dropout 等技术。', additional_kwargs={}, response_metadata={'model_name': 'qwen-max', 'finish_reason': 'stop', 'request_id': '9cacc14c-d453-4ded-bf32-64ab12c1cebc', 'token_usage': {'input_tokens': 868, 'output_tokens': 143, 'prompt_tokens_details': {'cached_tokens': 0}, 'total_tokens': 1011}}, id='lc_run--019d390e-620d-7a21-9357-fb148b3487b1-0', tool_calls=[], invalid_tool_calls=[])]}
从返回结果中也可以观察到,最终生成的 AIMessage 中 tool_calls=[],说明这次回答完全依赖模型自身知识完成,没有使用任何外部工具。
这也正符合我们前面在系统提示词中设定的行为逻辑,即如果模型认为自己已有足够知识回答当前问题,就直接回答,而不是无意义地调用工具。
单次数据库调用
接下来,我们来看一种需要借助外部知识库的情况。例如我们向智能体提出这样一个问题:
数据库里有没有提到过拟合和正则化的关系?
这个问题明显更偏向于“基于数据库内容进行回答”,因此模型通常会优先尝试调用数据库检索工具。为了更清楚地观察智能体内部的执行过程,这里我们使用流式输出的方式来查看每一步中间结果:
query = "数据库里有没有提到过拟合和正则化的关系?"
for chunk in agent.stream(
{"messages": [{"role": "user", "content": query}]},
stream_mode="updates",
):
print(chunk)
print("-----")
从输出过程可以看到,整个执行流程大致分为三个阶段:
模型先分析问题,并决定调用工具; 工具根据查询词从数据库中检索相关内容并返回结果; 模型再根据工具返回的信息组织最终回复。
对应的输出如下:
{'model': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'arguments': '{"query": "过拟合和正则化的关系"}', 'name': 'retrieve'}, 'id': 'call_e2f514912e6d4e3fb7ef88', 'index': 0, 'type': 'function'}]}, response_metadata={'model_name': 'qwen-max', 'finish_reason': 'tool_calls', 'request_id': '99bfa7e8-1fa1-48cb-81c0-cc69c5236f92', 'token_usage': {'input_tokens': 876, 'output_tokens': 23, 'prompt_tokens_details': {'cached_tokens': 0}, 'total_tokens': 899}}, id='lc_run--019d3910-03ad-7b63-8af3-7365e2bfd7ef-0', tool_calls=[{'name': 'retrieve', 'args': {'query': '过拟合和正则化的关系'}, 'id': 'call_e2f514912e6d4e3fb7ef88', 'type': 'tool_call'}], invalid_tool_calls=[])]}}
-----
{'tools': {'messages': [ToolMessage(content='困难在于,当我们训练模型时,我们只能访问数据中的小部分样本。\n最大的公开图像数据集包含大约一百万张图像。\n而在大部分时候,我们只能从数千或数万个数据样本中学习。\n在大型医院系统中,我们可能会访问数十万份医疗记录。\n当我们使用有限的样本时,可能会遇到这样的问题:\n当收集到更多的数据时,会发现之前找到的明显关系并不成立。\n将模型在训练数据上拟合的比在潜在分布中更接近的现象称为过拟合(overfitting),\n用于对抗过拟合的技术称为正则化(regularization)。\n在前面的章节中,有些读者可能在用Fashion-MNIST数据集做实验时已经观察到了这种过拟合现象。\n在实验中调整模型架构或超参数时会发现:\n如果有足够多的神经元、层数和训练迭代周期,\n模型最终可以在训练集上达到完美的精度,此时测试集的准确性却下降了。', name='retrieve', id='6cf2f144-6650-4999-aea4-e80ca8d915cc', tool_call_id='call_e2f514912e6d4e3fb7ef88')]}}
-----
{'model': {'messages': [AIMessage(content='过拟合指的是模型在训练数据上拟合得比在潜在分布中更接近的现象。简单来说,就是模型学习到了训练数据中的噪声和细节,以至于影响了它对新数据的泛化能力。正则化是一种用来对抗过拟合的技术,通过向损失函数添加额外项来限制模型的复杂度,从而使得模型更加倾向于选择简单的假设,即使得模型不会过于复杂而过度拟合训练数据。\n\n数据库中提到,当您使用有限的数据样本进行训练时,可能会遇到过拟合的问题。例如,在实验中调整模型架构或超参数时会发现:如果有足够多的神经元、层数和训练迭代周期,模型最终可以在训练集上达到完美的精度,但此时测试集上的准确性却可能下降了。这正是过拟合的表现。采用正则化技术可以帮助缓解这个问题,提高模型对未见数据的预测性能。', additional_kwargs={}, response_metadata={'model_name': 'qwen-max', 'finish_reason': 'stop', 'request_id': 'a7747e01-0b28-4232-8155-dae95d578162', 'token_usage': {'input_tokens': 1114, 'output_tokens': 192, 'prompt_tokens_details': {'cached_tokens': 0}, 'total_tokens': 1306}}, id='lc_run--019d3910-0c4c-7be3-a42c-9fb14db2e270-0', tool_calls=[], invalid_tool_calls=[])]}}
-----
从这一过程可以很直观地看出,智能体并不是“一上来就回答”,而是会先判断当前问题是否需要借助外部知识。如果需要,它就会先发起工具调用,在拿到检索结果之后,再基于结果生成最终答案。
多次数据库调用
看完了单次工具调用之后,我们再进一步测试一个更复杂的问题:智能体是否能够在一次回答过程中,针对不同子问题进行多次检索?
例如,我们提出这样一个复合问题:
数据库里有没有提到过拟合和正则化的关系?然后也根据数据库里的内容给我说一下欠拟合和过拟合的关系。
这个问题实际上包含了两个不同的检索目标:
过拟合和正则化的关系 欠拟合和过拟合的关系
因此,比较理想的行为应该是:模型将这个复合问题拆分为两个子问题,并分别调用两次数据库检索工具,然后再综合两部分结果生成最终回复。具体测试代码如下:
query = "数据库里有没有提到过拟合和正则化的关系?然后也根据数据库里的内容给我说一下欠拟合和过拟合的关系。"
for chunk in agent.stream(
{"messages": [{"role": "user", "content": query}]},
stream_mode="updates",
):
print(chunk)
print("-----")
从输出结果可以看到,模型确实在一开始就发起了两次工具调用请求,分别对应两个检索问题。随后,工具分别返回两部分内容,最后模型再将它们整合起来完成回答。
{'model': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'arguments': '{"query": "过拟合和正则化的关系"}', 'name': 'retrieve'}, 'id': 'call_23d4aaa8f4e84399933829', 'index': 0, 'type': 'function'}, {'function': {'arguments': '{"query": "欠拟合和过拟合的关系"}', 'name': 'retrieve'}, 'id': 'call_218e5f55f79b4e7093973d', 'index': 1, 'type': 'function'}]}, response_metadata={'model_name': 'qwen-max', 'finish_reason': 'tool_calls', 'request_id': '5620a14a-ebfa-454f-a589-087fee242a9a', 'token_usage': {'input_tokens': 894, 'output_tokens': 46, 'prompt_tokens_details': {'cached_tokens': 0}, 'total_tokens': 940}}, id='lc_run--019d3913-16c2-73f2-bdda-347a7dda0876-0', tool_calls=[{'name': 'retrieve', 'args': {'query': '过拟合和正则化的关系'}, 'id': 'call_23d4aaa8f4e84399933829', 'type': 'tool_call'}, {'name': 'retrieve', 'args': {'query': '欠拟合和过拟合的关系'}, 'id': 'call_218e5f55f79b4e7093973d', 'type': 'tool_call'}], invalid_tool_calls=[])]}}
-----
{'tools': {'messages': [ToolMessage(content='4.4.3. 欠拟合还是过拟合?¶\n当我们比较训练和验证误差时,我们要注意两种常见的情况。\n首先,我们要注意这样的情况:训练误差和验证误差都很严重,\n但它们之间仅有一点差距。\n如果模型不能降低训练误差,这可能意味着模型过于简单(即表达能力不足),\n无法捕获试图学习的模式。\n此外,由于我们的训练和验证误差之间的泛化误差很小,\n我们有理由相信可以用一个更复杂的模型降低训练误差。\n这种现象被称为欠拟合(underfitting)。\n另一方面,当我们的训练误差明显低于验证误差时要小心,\n这表明严重的过拟合(overfitting)。\n注意,过拟合并不总是一件坏事。 特别是在深度学习领域,众所周知,\n最好的预测模型在训练数据上的表现往往比在保留(验证)数据上好得多。\n最终,我们通常更关心验证误差,而不是训练误差和验证误差之间的差距。\n是否过拟合或欠拟合可能取决于模型复杂性和可用训练数据集的大小,\n这两个点将在下面进行讨论。', name='retrieve', id='4490dc80-6474-4acd-906a-f147be21d17f', tool_call_id='call_218e5f55f79b4e7093973d')]}}
-----
{'tools': {'messages': [ToolMessage(content='困难在于,当我们训练模型时,我们只能访问数据中的小部分样本。\n最大的公开图像数据集包含大约一百万张图像。\n而在大部分时候,我们只能从数千或数万个数据样本中学习。\n在大型医院系统中,我们可能会访问数十万份医疗记录。\n当我们使用有限的样本时,可能会遇到这样的问题:\n当收集到更多的数据时,会发现之前找到的明显关系并不成立。\n将模型在训练数据上拟合的比在潜在分布中更接近的现象称为过拟合(overfitting),\n用于对抗过拟合的技术称为正则化(regularization)。\n在前面的章节中,有些读者可能在用Fashion-MNIST数据集做实验时已经观察到了这种过拟合现象。\n在实验中调整模型架构或超参数时会发现:\n如果有足够多的神经元、层数和训练迭代周期,\n模型最终可以在训练集上达到完美的精度,此时测试集的准确性却下降了。', name='retrieve', id='f0ab0e96-2d40-45d2-8d28-eda7091b0be1', tool_call_id='call_23d4aaa8f4e84399933829')]}}
-----
{'model': {'messages': [AIMessage(content='数据库中有关于过拟合和正则化的关系,以及欠拟合和过拟合之间的关系的描述。\n\n### 过拟合和正则化的关系\n- 当我们使用有限的数据样本进行模型训练时,可能会遇到过拟合的问题。过拟合指的是模型在训练数据上表现得比在潜在分布中更接近的现象。\n- 为了对抗过拟合,可以采用的技术称为正则化。通过正则化方法,我们可以减少模型对于训练数据的过度适应,从而提高其泛化能力。\n\n### 欠拟合和过拟合的关系\n- **欠拟合**:当模型过于简单,无法充分捕捉数据中的模式时,会导致训练误差和验证误差都很高,但两者之间的差距很小。这种现象被称为欠拟合。解决欠拟合的方法通常是增加模型复杂度或训练更多的迭代次数。\n- **过拟合**:相反,如果模型在训练集上的性能非常好(训练误差低),但在未见过的数据(如验证集)上表现不佳(验证误差较高),则表明模型出现了过拟合。这意味着模型学习到了训练数据中的噪声而非真正的规律。应对过拟合的一种方式就是采用正则化技术来约束模型复杂度。\n- 值得注意的是,在深度学习领域,轻微的过拟合有时并不一定是坏事,因为优秀的预测模型往往在训练数据上的表现会优于验证数据。重要的是关注验证误差的表现,而不仅仅是训练与验证误差之间的差异。模型是否倾向于过拟合或欠拟合通常取决于模型本身的复杂性和可用训练数据量这两个因素。', additional_kwargs={}, response_metadata={'model_name': 'qwen-max', 'finish_reason': 'stop', 'request_id': 'bc8ee0cc-abe9-4b12-95dc-3cae1f836ea1', 'token_usage': {'input_tokens': 1392, 'output_tokens': 340, 'prompt_tokens_details': {'cached_tokens': 0}, 'total_tokens': 1732}}, id='lc_run--019d3913-36c3-7850-b864-63b85accae3f-0', tool_calls=[], invalid_tool_calls=[])]}}
-----
这个例子说明,智能体不仅能够调用工具,还能够根据问题复杂度自动拆解需求,并针对不同子任务进行多次查询。
这也是智能体相较于传统“单轮问答”系统更强的一点,因为它不只是回答问题,而是在一定程度上具备了任务分解与执行能力。
提问数据库里没有的内容
除了检索“数据库中存在的内容”之外,我们还需要关注另一种情况。如果用户提问的内容在数据库中根本没有明确答案,智能体会如何处理?
例如,我们提出下面这个问题:
数据库里有没有提到计算机视觉的概念?里面具体说了啥?
测试代码如下:
query = "数据库里有没有提到计算机视觉的概念?里面具体说了啥?"
for chunk in agent.stream(
{"messages": [{"role": "user", "content": query}]},
stream_mode="updates",
):
print(chunk)
print("-----")
从执行结果来看,模型依然会先尝试调用数据库检索工具,查询“计算机视觉的概念”相关内容。但是工具返回的结果中,实际上只有“计算机视觉”这一章节目录及相关主题列表,并没有直接给出“计算机视觉是什么”的定义性描述。对应输出如下:
{'model': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'arguments': '{"query": "计算机视觉的概念"}', 'name': 'retrieve'}, 'id': 'call_c52c33331c974371a51395', 'index': 0, 'type': 'function'}]}, response_metadata={'model_name': 'qwen-max', 'finish_reason': 'tool_calls', 'request_id': 'baebdd43-50ca-4a65-ba25-1eab2e90247d', 'token_usage': {'input_tokens': 875, 'output_tokens': 18, 'prompt_tokens_details': {'cached_tokens': 512}, 'total_tokens': 893}}, id='lc_run--019d3917-641a-7ab3-bd4a-889bc924ad61-0', tool_calls=[{'name': 'retrieve', 'args': {'query': '计算机视觉的概念'}, 'id': 'call_c52c33331c974371a51395', 'type': 'tool_call'}], invalid_tool_calls=[])]}}
-----
{'tools': {'messages': [ToolMessage(content='12. 计算性能\n12.1. 编译器和解释器\n12.2. 异步计算\n12.3. 自动并行\n12.4. 硬件\n12.5. 多GPU训练\n12.6. 多GPU的简洁实现\n12.7. 参数服务器\n\n\n13. 计算机视觉\n13.1. 图像增广\n13.2. 微调\n13.3. 目标检测和边界框\n13.4. 锚框\n13.5. 多尺度目标检测\n13.6. 目标检测数据集\n13.7. 单发多框检测(SSD)\n13.8. 区域卷积神经网络(R-CNN)系列\n13.9. 语义分割和数据集\n13.10. 转置卷积\n13.11. 全卷积网络\n13.12. 风格迁移\n13.13. 实战 Kaggle 比赛:图像分类 (CIFAR-10)\n13.14. 实战Kaggle比赛:狗的品种识别(ImageNet Dogs)', name='retrieve', id='c00af0da-3d95-4c85-8b1c-c5483f195703', tool_call_id='call_c52c33331c974371a51395')]}}
-----
{'model': {'messages': [AIMessage(content='数据库中的内容主要列举了计算机视觉相关的多个主题和技术,但没有直接提供计算机视觉的基本概念。不过,基于我的知识,我可以告诉你计算机视觉的基本定义:它是一个跨学科的科学领域,旨在让计算机能够理解和解析数字图像或视频中的信息,并据此做出决策或者采取行动。这包括了一系列任务,如图像分类、目标检测、图像分割等。\n\n数据库中提到的具体内容则涵盖了计算机视觉领域的一些高级技术和应用,比如图像增广、微调、目标检测(包括边界框和锚框的概念)、语义分割以及风格迁移等技术。这些技术都是为了提高计算机处理和理解图像的能力而设计的。\n\n如果你对上述任何一个具体技术感兴趣,想要了解更详细的信息,请告诉我!', additional_kwargs={}, response_metadata={'model_name': 'qwen-max', 'finish_reason': 'stop', 'request_id': 'd1b191bb-8a76-4c72-9ac7-7ee112a7a636', 'token_usage': {'input_tokens': 1186, 'output_tokens': 155, 'prompt_tokens_details': {'cached_tokens': 0}, 'total_tokens': 1341}}, id='lc_run--019d3917-7700-7bb1-927f-e83df2d1d35f-0', tool_calls=[], invalid_tool_calls=[])]}}
-----
从这里可以看出,智能体的表现还是比较合理的:
它先通过工具确认数据库中是否存在相关内容; 当发现数据库里没有直接给出概念定义时,它并没有胡乱声称“数据库中有明确定义”; 而是如实说明:数据库主要包含相关主题目录,没有直接解释概念本身; 同时,根据系统提示词中“检索不到时可结合自身知识补充回答”的设定,再利用模型自身知识对“计算机视觉”进行了基础解释。
这种行为其实非常符合一个较为理想的智能体设计目标,即优先尊重外部知识库内容,在知识库不足时,再谨慎地使用模型自身知识进行补充,而不是混淆两者的来源。
总结
本节课中,我们在上一节 Advanced RAG 的基础上,进一步将视角从“如何优化固定流程”扩展到了“如何让系统具备动态决策能力”。首先,我们回顾了课程的整体演进脉络:如果说 Naive RAG 解决的是最基础的“检索 + 生成”问题,Advanced RAG 关注的是如何围绕固定链路进行工程化优化,那么本节课所学习的 Modular RAG 与 Agentic RAG,则进一步把重点放在了“流程如何灵活组织”以及“流程由谁来决定”这两个更高层次的问题上。
在此基础上,我们首先系统梳理了 Agent 的基本原理。通过对感知、规划、行动、反馈、状态、记忆等概念的讲解,我们看到,Agent 并不是一个只会接收输入并直接生成答案的大模型,而是一个能够围绕任务目标,持续感知环境、调用工具、接收反馈并动态调整策略的执行系统。从这一点来看,Agent 的关键能力不在于“会不会回答”,而在于“能不能自主组织解决过程”。也正因为如此,ReAct 所强调的 Reason + Act,也就成为了当前大多数智能体系统的核心思想基础。
随后,我们进一步从实现层面说明了 Agentic RAG 的本质:它并不是简单地把 RAG 接进大模型,而是将原本固定流程中的检索能力、搜索能力、数据库访问能力等外部信息获取方式,统一封装为可供智能体自主调用的工具。这样一来,RAG 就不再是一条开发者预先写死的流水线,而是变成了一个可以被模型按需选择、动态调用的能力组件。换句话说,在 Agentic RAG 中,真正发生变化的并不是“有没有检索”,而是“检索何时发生、如何发生、是否需要继续发生”,这些判断开始逐步由模型来完成。
在代码实战部分,我们基于 LangChain 框架完成了一个简化版 Agentic RAG 系统的搭建。围绕这一目标,我们依次准备了模型、工具与系统提示词三项核心内容,其中模型作为智能体的大脑,负责推理、判断与生成;工具作为智能体的外部能力接口,负责执行知识库检索、天气查询、网页搜索等动作;而系统提示词则负责为模型建立清晰的行为约束,告诉它什么时候可以直接回答,什么时候需要调用工具,什么时候应该继续检索,什么时候应当停止并诚实说明信息不足。
通过后续的调用实验,我们也直观地观察到了智能体系统与传统问答流程之间的差异。有些问题较为简单时,模型可以直接依靠自身知识作答;而当问题依赖数据库内容时,模型会主动发起工具调用;面对一个包含多个子目标的复杂问题时,模型甚至能够自动拆分任务,并进行多次数据库检索;而当数据库中不存在明确答案时,智能体也能够先识别出检索结果的不足,再结合自身知识进行谨慎补充,而不是直接混淆信息来源或凭空编造结果。这些现象都表明,Agentic RAG 的核心价值并不只是“能调工具”,而是能够围绕任务目标,在不同阶段自主做出更合理的决策。
总体来看,Agentic RAG 的真正意义,在于它让 RAG 从“固定流程的知识增强模块”,逐步演化为“具备动态规划与工具调用能力的智能执行系统”。 在这种范式下,模型不再只是检索结果的被动使用者,而是开始成为整个任务流程的组织者和调度者。它既可以判断何时需要知识,何时需要工具,也可以在信息不足时继续行动,在信息充分时及时结束,这使得整个系统在面对真实世界中的复杂问题时,具备了更高的灵活性、鲁棒性和扩展潜力。
当然,本节课中我们所实现的仍然只是一个相对简化的 Agentic RAG 原型系统。它已经能够帮助大家建立起从“传统 RAG”走向“智能体化 RAG”的整体认识,但在真实应用场景中,一个更成熟的系统往往还会进一步加入更复杂的能力,例如:多工具协同调度、更完善的查询改写与检索优化机制、更精细的状态管理、更长期的记忆能力,以及基于 LangGraph 的流程控制与中间件扩展等。这些内容,也正是智能体系统后续继续深入的重要方向。
因此,希望大家通过本节课能够真正建立这样一个认识,未来的大模型应用,重点不再只是“让模型知道更多”,而是“让模型学会在合适的时候,以合适的方式,调用合适的能力来完成任务”。 而 Agentic RAG,正是这一趋势在知识增强系统中的重要体现。
从下节课开始我们将正式从提示词调优进入到模型参数调优的部分,那我们下节课再见啦!
-- 完 --
关注机智流并加入 AI 技术交流群,不仅能和来自大厂名校的 AI 开发者、爱好者一起进行技术交流,同时还有与、、、、等。
cc | 大模型技术交流群 hf | HuggingFace 高赞论文分享群 lc|LangChain 技术交流群 code | AI Coding 交流群 具身 | 具身智能交流群 硬件 | AI 硬件交流群 推理 | AI 推理框架交流群 智能体 | Agent 技术交流群