第 4 章 · 配置、装配与三种运行器
本章讲清楚:mini 的行为为什么“几乎全在 YAML 里”;三个部件怎么从字符串名装配起来;以及三种把它跑起来的方式——交互式
miniCLI、Python 绑定、SWE-bench 批量跑分。最后给边界、横向对比。
4.1 Prompt 即配置
mini 一个很强的取舍:agent 的“行为”主要不写在代码里,写在 YAML 模板里。config/default.yaml、config/mini.yaml、config/benchmarks/swebench.yaml 各是一套“人格 + 规则”。
看 default.yaml:1-19 的 system_template,它教模型三件事:① 每次回复恰好一个 bash 代码块;② 命令前要写 THOUGHT 推理;③ 用 <format_example> 给出确切格式。整个“agent 怎么行动”的协议就是这段提示词。换个 YAML,就是换个 agent——不用动 Python。
这也解释了为什么 第 1 章的模板用 StrictUndefined:既然 prompt 是配置,模板里写错变量名就该立刻报错,而不是悄悄渲染成空。
注意一个细节: default.yaml 用的是纯文本动作格式(mswea_bash_command 围栏),而 mini.yaml 用的是工具调用格式(prompt 里说“至少一个 bash 工具调用”)、swebench.yaml 同样走工具调用。这对应 第 3 章的两条解析路线——配置和模型类要配套。
4.2 配置怎么合并:UNSET 哨兵 + 递归 merge
命令行参数、多个 YAML、键值对覆盖,全靠 recursive_merge(utils/serialize.py:6-29)按顺序深合并,后者覆盖前者。一个小巧思:用一个 UNSET = object() 哨兵表示“这个命令行选项用户没给”,merge 时跳过 UNSET(serialize.py:20-21),这样“没传 --model”不会用 None 把 YAML 里的值冲掉。mini.py:73-92 把 CLI 选项打包成一层 dict,凡未提供的都填 UNSET,再和配置文件 merge。
4.3 工厂:按字符串名 import 装配
三个 get_* 工厂(get_agent/get_model/get_environment)做同一件事:拿一个短名或全路径字符串,动态 import 出类,实例化。例如环境工厂(environments/__init__.py):
# 示意,非源码:get_environment_class 的核心
_ENVIRONMENT_MAPPING = {"docker": "minisweagent.environments.docker.DockerEnvironment", ...}
full_path = _ENVIRONMENT_MAPPING.get(spec, spec) # 短名→全路径,或直接当全路径
module_name, class_name = full_path.rsplit(".", 1)
return getattr(import_module(module_name), class_name)
好处: 你可以在配置里写 environment_class: docker(短名),也可以写 your_pkg.MyEnv(自己的类全路径)——不改 mini 源码就能插自定义环境/模型/agent。模型工厂还多一步:根据模型名自动选类、自动给 Anthropic 开缓存(见 第 3 章)。
4.4 运行器一:mini CLI + 交互式三模式
run/mini.py 是默认可执行文件 mini。它装配出的默认 agent 不是 DefaultAgent 而是 InteractiveAgent(mini.py:101,default_type="interactive")——把人放进循环。
交互式 agent(agents/interactive.py)在 DefaultAgent 上加了三种模式(InteractiveAgentConfig.mode,interactive.py:24-31):
| 模式 | 含义 |
|---|---|
human | 命令由你敲,立即执行(你亲自驾驶) |
confirm(默认) | 模型给命令,非白名单的要你按回车确认 |
yolo | 模型给命令,不确认直接执行 |
它主要靠覆盖三个方法实现,完美利用了 第 1 章的可扩展点:
- 覆盖
add_messages(interactive.py:43-56):每条消息漂亮地打印到终端。 - 覆盖
query(interactive.py:58-94):human模式下从你这儿读命令;还接住LimitsExceeded让你当场提高限额继续(但TimeExceeded不行——墙钟没法靠提限额绕过,interactive.py:75-79专门注释了这点)。 - 覆盖
execute_actions(interactive.py:124-139):执行前问确认;用 try/finally 保证即使被拒绝也把已有输出存进历史。
白名单(whitelist_actions,正则列表)让你把“ls、cat 这类只读命令”免确认,只对危险命令拦一道(interactive.py:162-163)。Ctrl-C 会被接住、让你打字给模型留言而不是直接崩(interactive.py:109-122)。这套就是在本地直接跑(无沙箱)时的人工安全闸。
4.5 运行器二:Python 绑定(最小例子)
run/hello_world.py 是“怎么用 Python 调”的最小样板:DefaultAgent(LitellmModel(...), LocalEnvironment(), **yaml里的agent段),然后 agent.run(task)。就这。库的设计目标之一就是这种“三行起步”的可嵌入性。
4.6 运行器三:SWE-bench 批量跑分
run/benchmarks/swebench.py 是“在 SWE-bench 上刷分”的批量器,把前三章的部件全用上了:
- 加载数据集:
DATASET_MAPPING(swebench.py:53-62)把verified/lite等短名映射到 HuggingFace 数据集;支持 filter/slice/shuffle。 - 每个实例一个 docker 环境:
get_sb_environment(swebench.py:79-94)按实例 id 推出对应的 SWE-bench 官方镜像名(get_swebench_docker_image_name,swebench.py:68-76,注意它把 docker 不允许的双下划线换成魔法 token_1776_)。 - 线程池并行:
ThreadPoolExecutor(max_workers=workers)(swebench.py:256-263)并行跑多个实例;每个实例落一份轨迹 + 把补丁写进共享的preds.json(用一把threading.Lock保护写入,swebench.py:65,97-108)。 - 提交物 = 哨兵后的 git diff:回看 第 2 章,模型
echo COMPLETE...; git diff产出的补丁就被环境捞成submission,这里写进preds.json的model_patch字段(swebench.py:97-108)。 - 断点续跑:已在
preds.json里的实例默认跳过(swebench.py:229-232),--redo-existing才重做。
这里也是 第 3 章那个全局成本限额真正发挥作用的地方——并行几十个实例时,只有进程级总闸能兜住预算。
4.7 配套:轨迹浏览器
因为历史是线性 JSON,回看很容易。run/utilities/inspector.py 是个 Textual TUI,把消息按“步”分组(_messages_to_steps,inspector.py:25-38:遇到带 actions 的或 assistant 消息就开新一步)来翻页浏览。这直接受益于“线性历史”这一设计——轨迹就是消息,无需额外重建。
4.8 巧妙之处汇总(可借鉴的技术)
- Prompt 即配置: 行为协议写在 YAML 模板里而非代码,换 agent = 换 YAML(
config/*.yaml)。 - UNSET 哨兵: 优雅区分“用户没给这个选项”和“用户显式设为 None/0”(
utils/serialize.py)。 - 字符串名工厂 + 鸭子类型 Protocol: 三个部件都能被外部类无侵入替换(
*/__init__.py的get_*)。 - 靠覆盖钩子做交互式 agent:
InteractiveAgent只覆盖add_messages/query/execute_actions,~200 行就加出 human/confirm/yolo + 限额续命 + Ctrl-C 留言(agents/interactive.py)。 - 哨兵字符串当“完成 + 提交”双信号: 同一条
echo COMPLETE...; git diff既宣告结束又交出补丁(见 第 2 章)。
4.9 边界与局限(诚实)
- 本地环境无沙箱:
LocalEnvironment直接在你机器上shell=True跑模型给的任意命令;安全完全靠 docker/singularity 等外层环境,或交互式confirm模式人工把关。 - 无内置历史压缩/裁剪: 线性历史只增不改,长任务可能撞上下文窗口上限(由模型层
ContextWindowExceededError中止,不会自动 summarize)。对比letta是反向选择——它把记忆分层、做 compaction(见docs/letta/)。 - 每次回复几个动作,取决于配置:
default.yaml/mini.yaml的 prompt 强制单动作,换来解析与执行的极度简单(execute_actions也是顺序跑、无并发,见 第 1 章 §1.9)。但这不是项目级硬约束:跑分用的swebench.yaml反而显式开了并行多 tool call——prompt 明说“可以在一次回复里发多个 bash tool call”(swebench.yaml:55,并给了多调用 示例swebench.yaml:63),且model_kwargs.parallel_tool_calls: true(swebench.yaml:183)。所以“单动作”只是默认配置的取舍,benchmark 配置为了搜索效率允许并行。 - 状态不跨命令:
cd/env 不持久(见 第 2 章),靠 prompt 提醒,偶尔仍绊住模型。 - 依赖模型自律: 因为没有“编辑工具”这类硬约束,改文件全靠模型自己用
sed/cat <<EOF(prompt 给了范例),弱模型可能把文件改坏。
4.10 横向对比
- vs
letta(同 shelf): letta 把复杂度押在有状态分层记忆 + compaction;mini 押在无状态线性极简。是“agent 复杂度放哪”的两端。参见docs/letta/02-agent-loop.md与docs/letta/04-compaction-and-internals.md。 - vs 自家 SWE-agent: mini 是 SWE-agent 的“100x 极简版”——SWE-agent 强调为 agent 定制工具与接口(ACI),mini 赌“现代模型 + 纯 bash 就够”。两者共享 SWE-bench 评测与轨迹浏览理念。
- 通用原理对照本库 guide:
docs/guide/agent-loop.md(agent 主循环模式)、docs/guide/execution-environments.md(执行/沙箱)、docs/guide/instruction-authority.md(prompt 作为行为权威)。
4.11 代码地图
| 主题 | 文件 | 符号 |
|---|---|---|
| 默认行为协议(纯文本格式) | src/minisweagent/config/default.yaml | system_template instance_template |
| mini CLI 配置(工具调用) | src/minisweagent/config/mini.yaml | system_template observation_template |
| SWE-bench 跑分配置 | src/minisweagent/config/benchmarks/swebench.yaml | instance_template |
| 递归合并 + UNSET 哨兵 | src/minisweagent/utils/serialize.py | recursive_merge UNSET |
| agent 工厂 | src/minisweagent/agents/__init__.py | get_agent get_agent_class |
| 模型工厂 | src/minisweagent/models/__init__.py | get_model get_model_class |
| 环境工厂 | src/minisweagent/environments/__init__.py | get_environment |
| mini CLI 入口 | src/minisweagent/run/mini.py | main |
| 交互式三模式 agent | src/minisweagent/agents/interactive.py | InteractiveAgent InteractiveAgentConfig |
| 确认/拒绝/白名单 | src/minisweagent/agents/interactive.py | _ask_confirmation_or_interrupt _should_ask_confirmation |
| 最小 Python 绑定 | src/minisweagent/run/hello_world.py | main |
| SWE-bench 批量主流程 | src/minisweagent/run/benchmarks/swebench.py | main process_instance |
| 实例 → docker 镜像名 | src/minisweagent/run/benchmarks/swebench.py | get_swebench_docker_image_name get_sb_environment |
| 写补丁(线程安全) | src/minisweagent/run/benchmarks/swebench.py | update_preds_file |
| 轨迹分步浏览 | src/minisweagent/run/utilities/inspector.py | _messages_to_steps |