smolagents Agentic RAG 实战:用 CodeAgent 打造可推理、可迭代检索的知识库问答系统
【免费下载链接】smolagents🤗 smolagents: a barebones library for agents that think in code.项目地址: https://gitcode.com/gh_mirrors/smo/smolagents
导读
本文基于 smolagents 官方示例 docs/source/en/examples/rag.md 与仓库内完整可运行的 examples/rag.py,讲解如何把传统 RAG(检索增强生成)升级为Agentic RAG:即以 smolagents 的CodeAgent为核心,通过自定义Tool把 BM25 检索器包装成语义检索工具,让大模型自主优化查询、多轮检索、交叉验证并最终作答。读完本文,你将掌握在 smolagents 中定义检索工具、配置InferenceClientModel模型、驱动CodeAgent完成知识库问答的完整套路,并理解其底层工具校验与执行机制。
一、RAG 是什么:把答案建立在检索到的事实之上
Retrieval-Augmented Generation(检索增强生成,RAG)的核心思想可以概括为一句话:"用 LLM 回答用户问题,但回答的依据是从知识库中检索到的信息"。它把大语言模型的生成能力与外部知识检索能力结合起来,从而产出更准确、更有事实依据、更贴合上下文的回答。
1.1 为什么用 RAG
相比直接使用 vanilla 大模型或微调(fine-tuned)模型,RAG 有五个显著优势:
- 事实锚定(Factual Grounding):回答锚定在检索到的真实文档上,显著降低幻觉(hallucination)概率;
- 领域专精(Domain Specialization):无需重新训练模型,即可让通用模型回答特定领域的专业问题;
- 知识时效(Knowledge Recency):可以访问超出模型训练截止时间(training cutoff)的最新信息;
- 可追溯性(Transparency):生成内容可以引用来源文档,方便用户核对;
- 可控性(Control):可以精细控制模型能访问哪些信息、不能访问哪些信息。
1.2 传统 RAG 的局限
传统 RAG 虽然好用,但作为"一次检索 + 一次生成"的固定流水线,它面临四个典型挑战:
- 单次检索(Single Retrieval Step):如果第一轮检索结果质量差,最终生成的答案也会跟着遭殃,没有补救机会;
- 查询与文档不匹配(Query-Document Mismatch):用户的查询往往是疑问句,而包含答案的文档通常是陈述句,词面差异会让字面匹配失效;
- 推理能力有限(Limited Reasoning):简单的 RAG 流水线无法进行多步推理或查询修正;
- 上下文窗口约束(Context Window Constraints):检索到的文档必须能塞进模型的上下文窗口,限制了可用的信息量。
二、Agentic RAG:从固定流水线到可推理的检索 Agent
上述局限的根因在于:传统 RAG 是一条"单向、不可变"的流水线。而Agentic RAG的思路是:给 Agent 装备检索能力,把 RAG 变成"交互式、由推理驱动"的过程。
2.1 Agentic RAG 的关键能力
一个带检索工具的 Agent 可以做到:
- ✅生成优化查询(Formulate optimized queries):把用户的原始问题改写为更适合检索的查询形式;
- ✅多次检索(Perform multiple retrievals):按需迭代式地多次检索,逐步逼近答案;
- ✅对检索内容进行推理(Reason over retrieved content):对多个来源的信息进行分析、综合与结论提炼;
- ✅自我批判与修正(Self-critique and refine):评估检索结果质量,调整检索策略后再试。
2.2 天然实现的高级 RAG 技术
这种"思考-行动-观察"的循环天然就实现了两类高级 RAG 技术:
- HyDE(Hypothetical Document Embedding,假设性文档嵌入):不再直接用用户查询去检索,而是让 Agent 先生成一个"检索友好"的假设性文档或查询再检索(对应论文:2212.10496,HyDE 由 Gao 等人提出);
- Self-Query Refinement(自查询修正):Agent 先分析第一轮检索结果,发现信息不足或方向不对时,用修正后的查询发起第二轮检索。
在 smolagents 中,这两类能力不需要额外框架支持——它们就是CodeAgent在每一轮"写代码调用工具"中自然涌现的行为。
三、实战:构建一个 Transformers 文档问答 Agent
下面我们按步骤构建一个完整的 Agentic RAG 系统。目标:创建一个能回答Hugging Face Transformers 库相关问题的 Agent,其知识来源是 Transformers 官方文档。你可以跟着下面的代码片段逐步实现,也可以直接查看仓库中完整可运行的示例 examples/rag.py。
Step 1:安装依赖
首先安装所需依赖包:
pip install smolagents pandas langchain langchain-community sentence-transformers datasets python-dotenv rank_bm25 --upgrade如果你打算使用 Hugging Face 的 Inference API(通过InferenceClientModel调用云端推理),需要配置 API Token。推荐用python-dotenv从环境变量加载:
# 加载环境变量(包含 HF_TOKEN) from dotenv import load_dotenv load_dotenv()InferenceClientModel在初始化时会依次尝试显式传入的token、环境变量HF_TOKEN,最后回退到huggingface-cli login保存的本地凭据(详见 src/smolagents/models.py)。
Step 2:准备知识库
我们使用一个包含 Hugging Face 文档的数据集,过滤出 Transformers 部分,切分成适合检索的文档块:
import datasets from langchain.docstore.document import Document from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.retrievers import BM25Retriever # 加载 Hugging Face 文档数据集 knowledge_base = datasets.load_dataset("m-ric/huggingface_doc", split="train") # 只保留 Transformers 相关文档 knowledge_base = knowledge_base.filter(lambda row: row["source"].startswith("huggingface/transformers")) # 把数据集条目转换为带元数据的 Document 对象 source_docs = [ Document(page_content=doc["text"], metadata={"source": doc["source"].split("/")[1]}) for doc in knowledge_base ] # 将文档切分为更小的块,提升检索精度 text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, # 每个块的最大字符数 chunk_overlap=50, # 相邻块之间的重叠字符数,避免切断语义 add_start_index=True, strip_whitespace=True, separators=["\n\n", "\n", ".", " ", ""], # 切分优先级顺序 ) docs_processed = text_splitter.split_documents(source_docs) print(f"Knowledge base prepared with {len(docs_processed)} document chunks")这里的关键点在于切分参数:chunk_size=500保证块足够小以便精确检索,chunk_overlap=50让上下文在块边界处保持连贯,separators指定了从段落级到字符级的切分优先级。
Step 3:创建检索工具(Retriever Tool)
接下来定义一个自定义Tool,让 Agent 能用它从知识库中检索信息。这是整个 Agentic RAG 的关键桥梁:
from smolagents import Tool class RetrieverTool(Tool): name = "retriever" description = "Uses semantic search to retrieve the parts of transformers documentation that could be most relevant to answer your query." inputs = { "query": { "type": "string", "description": "The query to perform. This should be semantically close to your target documents. Use the affirmative form rather than a question.", } } output_type = "string" def __init__(self, docs, **kwargs): super().__init__(**kwargs) # 用处理后的文档初始化 BM25 检索器,返回 Top-10 相关文档 self.retriever = BM25Retriever.from_documents( docs, k=10 ) def forward(self, query: str) -> str: """执行检索并格式化返回结果。""" assert isinstance(query, str), "Your search query must be a string" # 执行检索 docs = self.retriever.invoke(query) # 格式化检索结果,便于 Agent 阅读 return "\nRetrieved documents:\n" + "".join( [ f"\n\n===== Document {str(i)} =====\n" + doc.page_content for i, doc in enumerate(docs) ] ) # 用处理好的文档初始化检索工具 retriever_tool = RetrieverTool(docs_processed)[!TIP] 这里选用BM25(词法检索方法)是为了简单和快速。生产环境可以换用基于 embedding 的语义检索以获得更好的检索质量,可参考 MTEB 排行榜挑选高质量的 embedding 模型。
源码视角:Tool基类到底做了什么
要理解RetrieverTool,需要看看它的基类实现(src/smolagents/tools.py):
- 必填类属性:子类必须声明
name(str)、description(str)、inputs(dict,每个输入项必须含type和description两个键)、output_type(str)。Tool.__init_subclass__会触发validate_after_init,即在__init__执行完毕后自动调用validate_arguments()做一次全量校验; - 校验规则(
validate_arguments):name必须是合法 Python 标识符且非保留字;inputs中的type必须是受支持类型,合法取值来自AUTHORIZED_TYPES = ["string", "boolean", "integer", "number", "image", "audio", "array", "object", "any", "null"];forward方法的参数名集合必须与inputs的键完全一致,否则直接抛异常; - 调用入口:
__call__会先检查is_initialized,若未初始化则调用可覆写的setup()(适合放置加载模型等昂贵操作);随后执行forward(*args, **kwargs)并返回结果; - 提示词注入:
to_code_prompt()会把工具的签名(如retriever(query: string) -> string)、描述与参数说明拼装成代码形式的函数文档,注入到 CodeAgent 的系统提示词中,让模型知道"可以调用retriever(query)这个函数"。
这也是为什么示例中inputs里只有query一个键,forward就只接收query一个参数——两者必须严格对应,代码里对工具的使用方式与给模型的提示词才一致。
Step 4:创建检索 Agent
现在创建能调用retriever_tool的CodeAgent:
from smolagents import InferenceClientModel, CodeAgent # 用我们的检索工具初始化 Agent agent = CodeAgent( tools=[retriever_tool], # 提供给 Agent 的工具列表 model=InferenceClientModel(), # 默认模型 "Qwen/Qwen3-Next-80B-A3B-Thinking" max_steps=4, # 限制推理步数 verbosity_level=2, # 输出详细的 Agent 推理过程 ) # 想指定特定模型时,可以这样写: # model=InferenceClientModel(model_id="meta-llama/Llama-3.3-70B-Instruct")[!TIP] Inference Providers 通过 serverless 推理合作伙伴提供数百个模型。不传
provider时默认走 "auto",即按用户在账户设置里的偏好顺序选择可用供应商;支持 Cerebras、Cohere、Fal、Fireworks、HF-Inference、Hyperbolic、Nebius、Novita、Replicate、SambaNova、Together 等多家供应商(详见 src/smolagents/models.py)。
源码视角:CodeAgent与InferenceClientModel的关键参数
CodeAgent(src/smolagents/agents.py)是"以代码形式表达工具调用"的 Agent:LLM 每次行动会生成一段 Python 代码(例如调用retriever(query=...)),解析后交给 Python 执行器运行。常用参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
tools | 必填 | Agent 可用的Tool列表 |
model | 必填 | 负责生成行动的Model实例 |
max_steps | 20 | 最大推理步数,MultiStepAgent._run_stream中循环条件为self.step_number <= max_steps,超限会进入_handle_max_steps_reached分支(src/smolagents/agents.py) |
verbosity_level | LogLevel.INFO | 日志详细程度,示例中2对应更详细的推理输出 |
stream_outputs | False | 是否流式输出;置True时要求模型实现generate_stream方法,否则抛ValueError(src/smolagents/agents.py) |
executor_type | "local" | 代码执行器类型,可选local、blaxel、e2b、modal、docker |
planning_interval | None | 每隔 N 步插入一次规划步骤,适合复杂长任务 |
additional_authorized_imports | [] | 额外允许 Agent 导入的包 |
仓库示例 examples/rag.py 中还额外开启了stream_outputs=True,可以边推理边流式输出。
InferenceClientModel(src/smolagents/models.py)是访问 Hugging Face Inference Providers 的模型封装:
model_id默认"Qwen/Qwen3-Next-80B-A3B-Thinking",也支持传入已部署的 Inference Endpoint URL;provider用于指定供应商(如"hyperbolic"),默认"auto"按用户偏好自动选择;传了base_url时provider不生效;token:需要被授权"调用 serverless Inference Providers";若模型是 gated(如 Llama-3 系列),token 还需有对应仓库的读取权限。不传时依次回退到HF_TOKEN环境变量和 HF CLI 登录凭据;timeout默认 120 秒;api_key是token的别名(与 OpenAI 客户端风格对齐),二者不能同时传入;- 若要本地私有部署,也可换用仓库中的
TransformersModel(src/smolagents/models.py)直接加载本地模型权重。
Step 5:运行 Agent 回答问题
最后,向 Agent 提出一个需要检索文档才能回答的问题:
# 提出一个需要检索信息才能回答的问题 question = "For a transformers model training, which is slower, the forward or the backward pass?" # 运行 Agent 获取答案 agent_output = agent.run(question) # 打印最终答案 print("\nFinal answer:") print(agent_output)agent.run()背后的执行流程是:生成系统提示词(包含retriever工具的代码签名)→ 进入"思考-写代码-执行-观察"循环 → 每步把结果写回 memory → 直到模型输出final_answer或达到max_steps上限(src/smolagents/agents.py)。针对"forward 与 backward 谁更慢"这类问题,Agent 会先改写为陈述式检索查询(如"backward pass is slower than forward pass"),调用retriever拿到相关文档片段,再综合多个文档块给出有依据的答案——这正是 HyDE 与多轮检索在实践中的体现。
四、Agentic RAG 的典型应用场景
掌握上述构建方式后,Agentic RAG 可以迁移到多种实际业务中:
- 技术文档助手:帮用户快速定位复杂技术文档中的关键信息;
- 科研论文分析:从多篇论文中抽取并综合结论;
- 法律文书审查:在海量判例与条款中查找相关先例;
- 智能客服:基于产品文档与知识库回答用户问题;
- 教育辅导:基于教材与学习资料提供定制化讲解。
替换知识库来源(如换成自己的内部 Wiki、PDF 语料或数据库)、换用语义检索器(如基于 embedding 的向量检索)即可适配上述场景,而 Agent 的"检索-推理"骨架无需改动。
五、结论
Agentic RAG 相对传统 RAG 流水线是一次显著升级:把 LLM Agent 的推理能力与检索系统的事实锚定能力结合起来,可以构建更强大、更灵活、更准确的信息系统。本文演示的方案:
- 用
RetrieverTool(继承Tool基类)把 BM25 检索器包装成 Agent 可调用的工具; - 用
CodeAgent+InferenceClientModel驱动"思考-检索-推理"循环; - 突破了单次检索的局限,让 Agent 与知识库之间形成更自然的交互;
- 通过自我批判与查询修正,为持续改进提供了框架。
当你构建自己的 Agentic RAG 系统时,建议在检索方法(BM25 vs 语义检索)、Agent 架构(CodeAgentvsToolCallingAgent,后者见 src/smolagents/agents.py)以及知识源三个维度上多做实验,找到最适合自己业务场景的组合。
【免费下载链接】smolagents🤗 smolagents: a barebones library for agents that think in code.项目地址: https://gitcode.com/gh_mirrors/smo/smolagents
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考