R2R RAG Agent 自定义工具(Custom Tools)开发指南:从 Tool 基类到配置注册的完整实践
【免费下载链接】R2RSoTA production-ready AI retrieval system. Agentic Retrieval-Augmented Generation (RAG) with a RESTful API.项目地址: https://gitcode.com/GitHub_Trending/r2/R2R
本指南以 R2R 官方 Cookbook 文档为基础,系统讲解如何为 R2R 的 RAG Agent 定义、注册并调用自定义工具(Custom Tools)。读完本文,你将掌握工具文件的存放位置与自动发现机制、Tool基类的字段语义、一个完整工具从编写到配置再到运行验证的端到端流程,并理解 Agent 底层是如何把工具描述注入 LLM 并执行工具调用的。
为什么需要自定义工具
R2R 的 Agent 在回答问题时并非只能依赖检索到的文档片段,它还具备"工具调用"(Tool/Function Calling)能力:LLM 可以根据用户问题决定是否调用某个工具,工具执行结果再回传给模型,形成多轮推理。仓库默认内置了一批与文档检索相关的工具(如search_file_descriptions、search_file_knowledge、get_file_content,见 agent.py 中RAGAgentConfig.rag_tools的默认值),但实际业务中往往需要查询数据库、调用外部 API、执行专有算法等能力——这正是自定义工具的用武之地。
R2R 允许用户定义自己的工具,并在服务器启动时把这些工具定义传入 Agent。本文即围绕这条链路展开。
工具文件存放位置与自动发现机制
R2R 仓库中专门为用户工具预留了目录:/docker/user_tools(仓库内对应 docker/user_tools)。该目录会被挂载到 R2R Docker 容器内部,所有放在这里的工具文件在容器启动后即可被应用访问。
挂载关系在编排文件中可以看到:
- docker/compose.yaml:
./user_tools:/app/user_tools - docker/compose.full.yaml:
./user_tools:/app/user_tools
容器内工具目录的绝对路径通过环境变量R2R_USER_TOOLS_PATH指定:
- docker/env/r2r.env 与 docker/env/r2r-full.env 中均为
R2R_USER_TOOLS_PATH=/app/user_tools
从源码看,工具目录的定位与加载由ToolRegistry完成,见 registry.py:
self.user_tools_path = ( user_tools_path or os.getenv("R2R_USER_TOOLS_PATH") or "../docker/user_tools" )也就是说,ToolRegistry的解析优先级为:构造参数显式传入 > 环境变量R2R_USER_TOOLS_PATH> 默认相对路径../docker/user_tools。目录不存在时会打印 warning 日志,不会导致启动失败。
ToolRegistry._discover_user_tools(见 registry.py)会扫描该目录下所有.py文件(跳过_或.开头的文件),动态导入模块,并用inspect找出其中继承自Tool且定义于本模块的类,实例化后以tool_instance.name为键注册到_user_tools字典中。同理,内置工具由_discover_built_in_tools从 py/core/base/agent/tools/built_in 目录自动发现(当前内置有get_file_content、search_file_descriptions、search_file_knowledge、tavily_extract、tavily_search、web_scrape、web_search等模块)。
因此,你要做的第一步就是把自定义工具文件放入docker/user_tools目录。
Tool 基类:自定义工具的抽象契约
在动手写工具前,先理解Tool基类。它定义在 py/shared/abstractions/tool.py,核心字段如下:
| 字段 | 类型 | 含义 |
|---|---|---|
name | str | 工具名,Agent 与 LLM 以此识别并调用该工具 |
description | str | 自然语言描述,会展示给 Agent(LLM),用于决定何时调用 |
parameters | dict | JSON Schema 格式的参数定义(type/properties/required) |
results_function | Callable | 实际执行逻辑,即execute方法 |
llm_format_function | Optional[Callable] | 可选的格式化函数,把原始结果转换为更适合 LLM 阅读的文本 |
stream_function | Optional[Callable] | 可选的流式输出函数 |
context | Optional[Any] | 工具上下文,由 Agent 在实例化时注入 |
基类还提供了两个重要方法:
set_context(context):为工具实例注入上下文(见 tool.py);execute(*args, **kwargs):包装results_function的入口,会校验context非空,再以context=self.context调用实际实现(见 tool.py)。
官方工具模板逐字段解读
在 docker/user_tools/README.md 中提供了工具模板,其结构如下:
from core.base.agent.tools.base import Tool class ToolNameTool(Tool): """ A user defined tool. """ def __init__(self): super().__init__( name="tool_name", description="A natural language tool description that is shown to the agent.", parameters={ "type": "object", "properties": { "input_parameter": { "type": "string", "description": "Define any input parameters by their name and type", }, }, "required": ["input_parameter"], }, results_function=self.execute, llm_format_function=None, ) async def execute(self, input_parameter: str, *args, **kwargs): """ Implementation of the tool. """ # Any custom tool logic can go here output_response = some_method(input_parameter) result = AggregateSearchResult( generic_tool_result=[web_response], ) # Add to results collector if context is provided if context and hasattr(context, "search_results_collector"): context.search_results_collector.add_aggregate_result(result) return result这个模板包含两个核心方法:
__init__:在这里"定义"工具。name、description、parameters共同构成了给 LLM 看到的工具声明;其中description尤为关键,它决定了模型在什么场景下会选择该工具,应写得自然、具体。execute:在这里"实现"工具逻辑,负责处理输入参数、执行自定义逻辑并返回结果。注意execute是async异步方法。
模板中AggregateSearchResult用于封装检索类结果(可通过context.search_results_collector.add_aggregate_result(result)把结果收集起来,供引用链使用);如果你的工具不涉及检索,可以直接返回普通文本/字符串结果,如下一节示例所示。
编写你的第一个工具:SecretMethodTool 完整示例
下面是一个官方文档中的玩具示例:工具接收一个整数和一个字符串参数,返回一条俏皮消息给 Agent。若你的工具需要额外依赖,请把它们追加到 docker/user_tools/user_requirements.txt(该文件当前为空,正是留给用户声明的)。
from r2r import Tool, AggregateSearchResult class SecretMethodTool(Tool): """ A user defined tool. """ def __init__(self): super().__init__( name="secret_method", description="Performs a secret method.", parameters={ "type": "object", "properties": { "number": { "type": "string", "description": "An integer input for the secret method.", }, "string": { "type": "string", "description": "A string input for the secret method.", }, }, "required": ["number", "string"], }, results_function=self.execute, llm_format_function=None, ) async def execute(self, number: int, string: str, *args, **kwargs): """ Implementation of the tool. """ output_response = f"Your order for {number} dancing flamingos has been received. They will arrive by unicycle courier within 3-5 business dreams. Please prepare {string} for them." result = AggregateSearchResult( generic_tool_result=output_response, ) context = self.context # Add to results collector if context is provided if context and hasattr(context, "search_results_collector"): context.search_results_collector.add_aggregate_result(result) return result几个值得注意的实践点:
- 导入路径:模板使用
from core.base.agent.tools.base import Tool,而示例使用from r2r import Tool, AggregateSearchResult——两者本质是同一类,r2r包(见 py/r2r/init.py)对其做了再导出,便于用户导入。文档说明r2r是公共 SDK 导出接口。 parameters采用 JSON Schema 形式:properties中每个参数声明type与description,required列出必填参数。这些信息会随工具声明一起发给 LLM。execute的参数签名要与parameters中的属性一一对应(number、string),LLM 解析出的实参会以关键字参数形式传入。- 通过
self.context获取 Agent 注入的上下文,若有search_results_collector属性则把聚合结果登记进去,方便 Agent 在最终回答中引用工具执行结果。
在配置中注册工具
工具文件就绪后,需要修改配置文件的agent段,把工具名加入rag_tools列表:
[agent] rag_tools = ["secret_method"]对应到仓库的默认配置 py/r2r/r2r.toml,其[agent]段默认值为:
[agent] rag_tools = ["search_file_descriptions", "search_file_knowledge", "get_file_content"] # can add "web_search" | "web_scrape"rag_tools是RAGAgentConfig的字段(见 agent.py),默认注册三个文件检索类内置工具,注释提示可追加web_search/web_scrape等。自定义工具名(如secret_method)直接追加即可。
从实现看,rag_tools中的每个名字会在 Agent 启动时通过ToolRegistry解析并实例化。见 rag.py:
for tool_name in set(self.config.rag_tools): ... if tool_instance := self.tool_registry.create_tool_instance(create_tool_instance(见 registry.py)会先到_user_tools中按名字查找用户工具,找不到再回退到_built_in_tools;实例化后注入llm_format_function与上下文,最终加入 Agent 的self.tools列表。
运行验证:Agent 如何调用你的工具
配置完成后启动服务,即可通过 Python SDK 触发一次工具调用,验证 Agent 是否理解了新工具:
client.retrieval.agent( message={"role": "user", "content": "Can you run the secret method tool? Feel free to use any parameters you want. I just want to see the output."}, )官方文档给出的实际运行结果为:
results=AgentResponse(messages=[Message(role='assistant', content='The secret method tool produced the following output:\n\n"Your order for 42 dancing flamingos has been received. They will arrive by unicycle courier within 3-5 business dreams. Please prepare Hello, World! for them."\n\nThis whimsical response seems to be a playful and humorous output generated by the tool.', name=None, function_call=None, tool_calls=None, tool_call_id=None, metadata={'citations': [], 'tool_calls': [{'name': 'secret_method', 'args': '{"number":"42","string":"Hello, World!"}'}], 'aggregated_search_result': '[]'}, structured_content=None, image_url=None, image_data=None)], conversation_id='12ad2d6b-1429-48ea-9077-711726d8cfde')从响应中可以确认三件事:Agent 识别到了secret_method工具(metadata.tool_calls中记录了工具名与参数{"number":"42","string":"Hello, World!"})、为必填参数自动生成了合理的取值、并且正确理解并复述了工具返回的消息——说明description与工具输出对模型是清晰可读的。
如果你想了解这条链路在源码中是如何走通的,可以参考 Agent 的三个关键环节:
- 工具声明注入:
get_generation_config会把self.tools中的每个工具序列化为{"function": {"name", "description", "parameters"}, "type": "function"}结构随请求发送给 LLM(见 agent.py),这正是模型"知道"存在secret_method的原因。 - 工具调用分发:模型返回函数调用意图后,
handle_function_or_tool_call负责解析 JSON 参数并执行await tool.execute(*args, **merged_kwargs)(见 agent.py)。异常时(如 JSON 解析失败)会生成错误信息回传给模型继续推理。 - 结果回传:执行结果经
llm_format_function(此处为None,即原样)格式化为 tool 消息加入会话,供模型生成最终回答;同时记录进self.tool_calls。
依赖管理与进阶要点
- 第三方依赖:如果自定义工具需要引入新的 Python 依赖,请把它们写入 docker/user_tools/user_requirements.txt。该文件位于
docker目录下,随镜像/启动流程安装,从而保证容器内工具可正常 import。 llm_format_function:Tool支持自定义"LLM 格式化函数"。当工具的原始返回值结构复杂(例如包含大量内部字段)时,可提供一个把raw_result精简为一段自然语言摘要的函数,既能减少 token 消耗,也能让模型更容易理解结果(参见 tool.py 的字段定义)。stream_function:如需流式输出工具结果,可补充实现该可选函数。- 上下文(context):
Tool.execute会强制校验context非空(见 tool.py),因此自定义工具的execute中应像示例那样通过self.context访问 Agent 注入的上下文;带search_results_collector时调用add_aggregate_result登记检索结果,能让最终回答携带可追踪的引用。
小结
为 R2R RAG Agent 添加自定义工具只需四步:把工具文件放入 docker/user_tools 目录 → 继承Tool基类并在__init__声明工具、在execute实现逻辑 → 在配置的[agent].rag_tools中加入工具名 → 重启服务验证。底层由ToolRegistry自动发现用户工具并注入 Agent,Tool基类则定义了模型可见的工具契约。掌握了这套机制,你就可以把任意业务能力(数据库查询、外部 API、专有算法)以工具形式开放给 RAG Agent,显著扩展其能力边界。
【免费下载链接】R2RSoTA production-ready AI retrieval system. Agentic Retrieval-Augmented Generation (RAG) with a RESTful API.项目地址: https://gitcode.com/GitHub_Trending/r2/R2R
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考