SWE-agent Tool Bundle 配置详解:从 BundleConfig、Command 到 Argument 的完整实践指南
【免费下载链接】SWE-agentSWE-agent takes a GitHub issue and tries to automatically fix it, using your LM of choice. It can also be employed for offensive cybersecurity or competitive coding challenges. [NeurIPS 2024]项目地址: https://gitcode.com/GitHub_Trending/sw/SWE-agent
导读
在 SWE-agent 中,Agent 的能力边界由一组可执行工具(tools)决定,而这组工具的组织与管理正是通过Tool Bundle(工具包)机制实现的。本文以仓库文档 docs/reference/bundle_config.md 为主体,结合源码 sweagent/tools/bundle.py、sweagent/tools/commands.py 及仓库内的真实 bundle 配置,系统讲解 Tool Bundle 的配置模型:顶层BundleConfig如何声明工具与状态命令,Command如何定义可执行命令及其签名,Argument如何描述命令参数。读完本文,你将掌握如何阅读、编写、校验和扩展一个 SWE-agent 工具包,为 Agent 定制专属工具集。
一、概念澄清:Tool Bundle 配置 ≠ 工具配置
在深入配置字段之前,必须先厘清一个容易混淆的概念边界。仓库文档在开头就明确提示:
这是用于配置tool bundle的页面,而不是配置 Agent 正在使用的工具的页面。后者请参见 tools configuration。
也就是说,SWE-agent 中"工具"相关的配置分两层:
| 层级 | 对应文档 | 作用对象 | 核心内容 |
|---|---|---|---|
| 工具配置(ToolConfig) | docs/reference/tools_config.md | Agent 运行时 | 启用哪些 bundle、环境变量、超时、解析函数、过滤规则等 |
| 工具包配置(BundleConfig) | docs/reference/bundle_config.md(本文) | bundle 目录内的config.yaml | 声明工具命令集合与状态命令 |
从源码看,二者的关系非常清晰:ToolConfig中有一个bundles: list[Bundle]字段(见 sweagent/tools/tools.py),而Bundle对象在加载时会读取其所在目录下的config.yaml并解析为BundleConfig。因此可以这样理解:bundle 配置是"生产工具"的配方,工具配置是"装配工具"的清单。
二、BundleConfig:bundle 配置的顶层模型
BundleConfig定义在 sweagent/tools/bundle.py 中,是整个 bundle 配置文件(config.yaml)对应的 Pydantic 数据模型:
class BundleConfig(BaseModel): tools: dict[str, dict] state_command: str | None = None它只有两个字段:
tools(必填):一个字典,key 是工具(命令)名,value 是对应的Command配置字典。例如searchbundle 的 tools/search/config.yaml 中定义了find_file、search_dir、search_file三个工具。state_command(可选):一个特殊的命令名,在每次 Agent 动作之后执行,用于输出环境状态(详见下文第四部分)。
2.1 Bundle 的加载与校验流程
BundleConfig通常不是直接被使用者手动构造的,而是由Bundle模型在加载时自动解析。Bundle的validate_tools校验器(sweagent/tools/bundle.py#L22-L41)完成了如下步骤:
- 将
path转换为绝对路径; - 校验 bundle 目录存在;
- 校验目录下存在
config.yaml; - 读取并
yaml.safe_load该文件,构造BundleConfig; - 校验
hidden_tools(隐藏工具列表)中的每一项都真实存在于tools键集合中,否则抛出ValueError。
2.2 hidden_tools:动态隐藏工具
Bundle还支持hidden_tools: list[str]字段,用于在不修改 bundle 配置文件的情况下,屏蔽其中的某些工具。其实现位于Bundle.commands属性(sweagent/tools/bundle.py#L52-L57):
@property def commands(self) -> list[Command]: return [ Command(name=tool, **tool_config.model_dump() if isinstance(tool_config, Command) else tool_config) for tool, tool_config in self.config.tools.items() if tool not in self.hidden_tools ]注意这里的关键细节:tools中的每个配置字典会被展开为Command(name=tool, **tool_config)构造为命令对象,同时过滤掉hidden_tools中列出的名称。
hidden_tools的用法在测试 tests/test_run_single.py#L48-L63 中有直接体现:Bundle(path=TOOLS_DIR / "windowed", hidden_tools=["scroll_up"])—— 加载windowedbundle 但隐藏其scroll_up工具。这种机制特别适合在不改动公共 bundle 的前提下,为不同任务场景裁剪工具面。
2.3 state_command 属性透传
Bundle通过state_command属性直接透传config.state_command(sweagent/tools/bundle.py#L43-L45),而ToolConfig.state_commands(sweagent/tools/tools.py#L158-L164)会聚合所有 bundle 的 state_command 并在每个动作后依次执行。
三、Command:定义一个可执行命令
Command类是 bundle 中每个工具的核心模型,定义在 sweagent/tools/commands.py#L79-L205。其字段如下:
| 字段 | 类型 | 默认值 | 含义 |
|---|---|---|---|
name | str | 必填 | 命令名,Agent 在回复中以此调用工具 |
docstring | str | None | 必填 | 命令的人类可读描述,会注入模型提示词 |
signature | str | None | None | 自定义调用签名,覆盖默认的name arg1 arg2 ... |
end_name | str | None | None | 多行命令的终止标记;一旦设置即表示这是多行命令 |
arguments | list[Argument] | [] | 命令接受的参数列表 |
3.1 单行命令与多行命令
Command的end_name字段是区分两类命令的关键:
- 单行命令:
end_name为None,命令在一行内完成,如find_file、scroll_up; - 多行命令:
end_name非空,命令体可以跨越多个行,并以end_name作为终止标记。这类命令的完整定义可以在 sweagent/tools/parsing.py 与 sweagent/tools/utils.py 的解析逻辑中看到:ToolHandler.guard_multiline_input会利用end_name以 heredoc 形式包裹多行参数发送到 bash(sweagent/tools/tools.py#L382-L409)。
ToolConfig.model_post_init会收集所有带end_name的命令,构造multi_line_command_endings字典供解析器使用(sweagent/tools/tools.py#L201-L213)。
3.2 invoke_format:调用格式的生成
Command.invoke_format(sweagent/tools/commands.py#L102-L131)决定了"如何把参数拼进命令字符串"。其逻辑分两种情况:
- 提供
signature时:先校验每个参数名确实以<name>、[<name>]、{name}或--name中的某一种形式出现在签名中,然后通过正则re.sub(rf"\[?<({ARGUMENT_NAME_PATTERN})>\]?", r"{\1}", self.signature)把尖括号占位符替换为 Python format 占位符; - 未提供
signature时:按默认格式"name {arg1} {arg2} ..."拼接。
以 tools/search/config.yaml 为例:
find_file: signature: "find_file <file_name> [<dir>]" arguments: - name: file_name type: string required: true - name: dir type: string required: false这里<file_name>(必填)与[<dir>](可选)展示了签名中两种占位符的写法,invoke_format会将其转换为"find_file {file_name} {dir} "。
3.3 validate_arguments:编写命令时的自检约束
Command.validate_arguments(sweagent/tools/commands.py#L167-L205)在模型构造时执行多项校验,编写自定义命令时需注意:
- 必填参数必须在可选参数之前,否则报错
Required argument ... cannot come after optional arguments; - 参数名不得重复;
- 参数名必须匹配正则
[a-zA-Z_][a-zA-Z0-9_-]*(sweagent/tools/commands.py#L30); - 签名/调用格式中的占位符集合必须与
arguments的参数名集合完全一致,否则报错(_extract_keys通过string.Formatter解析出格式字符串中的所有字段名)。
3.4 get_function_calling_tool:与 Function Calling 的桥接
当parse_function配置为 function calling 解析器时,Command.get_function_calling_tool(sweagent/tools/commands.py#L133-L165)会把命令转换为 OpenAI 风格的 function schema:
- 工具名 =
command.name; - 描述 =
docstring; - 每个
Argument映射为一个 JSON Schema 属性(含type、description、可选的items、enum); required: true的参数进入required列表。
这正是ToolConfig.tools属性(sweagent/tools/tools.py#L193-L195)的实现基础:[command.get_function_calling_tool() for command in self.commands]。
四、Argument:参数的类型化描述
Argument类定义在 sweagent/tools/commands.py#L52-L76,为命令参数提供类型化描述,字段如下:
| 字段 | 类型 | 默认值 | 含义 |
|---|---|---|---|
name | str | 必填 | 参数名,需匹配[a-zA-Z_][a-zA-Z0-9_-]* |
type | str | 必填 | 参数类型,如"string"、"integer" |
items | dict[str, str] | None | None | 数组元素类型的描述(如{"type": "string"}) |
description | str | 必填 | 参数的人类可读描述 |
required | bool | 必填 | 是否必填 |
enum | list[str] | None | None | 可枚举的取值白名单 |
argument_format | str | "{{value}}" | 参数在命令中的渲染格式,必须使用 Jinja 语法{{value}}而非{value} |
argument_format有一个专门的校验器validate_argument_format(sweagent/tools/commands.py#L73-L76),调用 sweagent/utils/jinja_warnings.py 中的_warn_probably_wrong_jinja_syntax来提醒开发者避免误用单花括号。
在实际配置中,enum常用于约束参数取值范围。从源码注释可见其设计意图:它既会进入 function calling schema 的enum字段,帮助模型在结构化输出时只选择合法值。
五、实战:完整的 bundle 配置范例
结合上述三个模型,一个真实且完整的 bundle 配置应同时具备tools与(可选的)state_command。下面以仓库中的 tools/windowed/config.yaml 为例展示全貌:
tools: goto: signature: "goto <line_number>" docstring: "moves the window to show <line_number>" arguments: - name: line_number type: integer description: "the line number to move the window to" required: true open: signature: 'open "<path>" [<line_number>]' docstring: "opens the file at the given path in the editor. If line_number is provided, the window will be move to include that line" arguments: - name: path type: string description: "the path to the file to open" required: true - name: line_number type: integer description: "the line number to move the window to (if not provided, the window will start at the top of the file)" required: false create: signature: "create <filename>" docstring: "creates and opens a new file with the given name" arguments: - name: filename type: string description: "the name of the file to create" required: true scroll_up: signature: "scroll_up" docstring: "moves the window up {WINDOW} lines" arguments: [] scroll_down: signature: "scroll_down" docstring: "moves the window down {WINDOW} lines" arguments: [] state_command: "_state"这个范例展示了几个常见模式:
- 无参数命令:
scroll_up、scroll_down的arguments为空列表,其docstring中的{WINDOW}由 sweagent/tools/utils.py 的文档生成逻辑结合env_variables渲染(generate_command_docs会把环境变量传入文档模板); - 必填/可选参数组合:
open的path必填、line_number可选,对应签名中<path>与[<line_number>]的写法差异; - 整数参数:
line_number的类型是integer,在 function calling schema 中会生成{"type": "integer"}。
再对比 tools/diff_state/config.yaml:
tools: {} state_command: "_state_diff_state"该 bundle 不定义任何工具,只注册一个state_command,用于在每个动作后输出 diff 相关状态——这证明了tools与state_command是两个正交的维度。
六、state_command:动作后的状态采集机制
state_command是 bundle 配置中一个极易被忽视但作用关键的字段。其工作流程在 sweagent/tools/tools.py#L337-L348 的ToolHandler.get_state中体现:
- 依次执行所有 bundle 的
state_command; - 读取环境中的
/root/state.json; - 解析 JSON 并返回状态字典,供提示词模板格式化使用。
在 docs/config/tools.md 中给出了经典 SWE-agent 窗口工具的状态命令实现(_state脚本):它通过 registry 读取当前打开的文件,输出{"open_file": ..., "working_dir": ...}这样的 JSON。该状态字典随后可用于模板中的占位符(例如在提示词里展示"当前工作目录"与"当前打开文件")。
state_command也可以叠加多个 bundle 使用:ToolConfig.state_commands会把所有 bundle 的state_command收集起来依次执行,最终合并读取/root/state.json的内容(sweagent/tools/tools.py#L158-L164)。
七、将 bundle 接入 Agent:在 ToolConfig 中装配
了解了 bundle 自身的配置模型后,最后一步是把它接入 Agent。在 Agent 配置(如 config/default.yaml)的tools.bundles中按路径引用即可:
tools: bundles: - path: tools/registry - path: tools/edit_anthropic - path: tools/review_on_submit_m enable_bash_tool: true parse_function: type: function_calling装配后ToolConfig.commands(sweagent/tools/tools.py#L167-L191)会:
- 若
enable_bash_tool为 true,先加入内置的BASH_COMMAND(定义在 sweagent/tools/commands.py#L209-L223,即Command(name="bash", signature="<command>", ...)); - 遍历所有 bundle 的命令;
- 检测重名工具:同一命令名在不同 bundle 中重复定义会直接抛错,错误信息会指出首次定义与重复定义的来源路径。
ToolHandler._install_commands(sweagent/tools/tools.py#L292-L312)则负责把 bundle 上传到容器,执行install.sh,并逐一校验命令在容器中可用(which <command>)。
八、小结
围绕 docs/reference/bundle_config.md 的三个核心类,本文完成了从理论到实践的完整梳理:
BundleConfig(sweagent/tools/bundle.py#L12-L15):bundle 配置文件的顶层模型,tools必填、state_command可选;Command(sweagent/tools/commands.py#L79-L205):定义命令名、文档、签名、多行终止标记与参数,并通过invoke_format生成调用格式、get_function_calling_tool桥接结构化输出,构造时还有严格的参数顺序与签名一致性校验;Argument(sweagent/tools/commands.py#L52-L76):类型化参数描述,支持type、required、enum、items与 Jinja 风格的argument_format。
三个类共同构成了 SWE-agent 工具扩展的"最小可编程单元"。如果你希望新增一个自定义工具,推荐的路径是:先阅读 docs/config/tools.md 了解 bundle 目录结构与装配方式,再参照本文的字段规范编写config.yaml,最后参考 docs/usage/adding_custom_tools.md 的教程完成落地。每一个字段的合法性都由 Pydantic 校验器在加载时守护,出错信息会直接指出问题所在,这让自定义工具的开发过程既灵活又可控。
【免费下载链接】SWE-agentSWE-agent takes a GitHub issue and tries to automatically fix it, using your LM of choice. It can also be employed for offensive cybersecurity or competitive coding challenges. [NeurIPS 2024]项目地址: https://gitcode.com/GitHub_Trending/sw/SWE-agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考