mini-swe-agent
这个项目是 SWE-agent 团队推出的一个极简但功能强大的 AI 软件工程代理(Agent)。它旨在用极简的代码实现核心的智能体循环,同时保持强大的代码修复能力。
一、核心实现架构与技术细节
mini-swe-agent的实现非常精炼,核心就是一个 while 循环和一套基于异常的信号传递机制
1.1 控制流:异常驱动的循环
- 主循环:用一个 while 不断调用 LLM 获取下一个动作,然后执行该动作并获取观察结果。
- 终止信号:循环的终止不是通过复杂的条件判断,而是通过抛出和捕获特定的异常来实现。定义了
InterruptAgentFlow异常层级,包含:submitted成功提交LimitsExceeded达到操作次数或时间限制FormatError模型输出格式无法解析
- 消息传递:这些异常不仅用于控制流,还能携带消息有效载荷。
run()方法会捕获这些异常,将消息注入对话历史,然后根据异常类型决定是继续循环(如格式错误是可以重试的)还是终止循环(如成功提交就可以正常终止)。这种设计将控制逻辑和业务逻辑清晰分离。
1.2 工具系统:YAML+BASH
- 工具定义:工具签名、文档字符串和参数说明(通过一个 YAML 配置文件定义)。这使其能渲染为函数调用的JSON Schema 或系统提示词中的工具文档
- 工具实现:每个工具就是一个独立的 BASH 脚本(
bin/目录)。这样有三大好处:- 通用可移植性:Bash 脚本可以在 Linux 容器中运行
- 安装简单:只需将工具目录复制到沙箱环境并运行一个
install.sh脚本 - 文档自动生成:工具的文档直接源自 YAML 定义,无需手动维护
- 状态共享:工具之间不共享 Python 对象状态,而是通过环境变量、文件系统和_
state命令的 JSON 输出进行通信
一个工具包的最小规范示例
根据其设计协议,一个自定义工具包至少需要包含一下文件
tools/my_bundle/ ├── config.yaml # 工具定义 ├── bin/ │ └── my_command # 工具实现(Bash脚本) └── install.sh # 依赖安装脚本(可选)config.yaml
tools:my_command:signature:"my_command <arg1> [<arg2>]"docstring:"这个工具的功能描述,给 LLM 看"arguments:-{name:arg1,type:string,required:true,description:"第一个参数的说明"}-{name:arg2,type:integer,required:false,description:"第二个参数的说明(可选)"}state_command:"_my_state"# 可选,每次动作后运行的命令,用于输出状态 JSONenv_variables:# 可选,注入到运行时的环境变量MY_VAR:"default_value"bin/my_command
#!/usr/bin/env_basharg1="$1"arg2="${2:-}"# 这里实现工具的具体逻辑echo"执行 my_command,参数1:$arg1, 参数2:$arg2"1.3 提示词工程:结构化与过程化引导
精心设计的提示词模板
- 极简系统提示词:“You are a helpful assistant that can interact with a computer to solve tasks.”。所有与具体任务相关的上下文(如问题描述、仓库路径)都放在实例提示词中。这样能让系统提示词被 LLM KV 缓存。
- 过程化指令:明确引导 LLM 遵循一个类似人类工程师的五步工作流:
- 阅读与理解:首先阅读和代码库中与问题描述相关的代码
- 复现问题:创建一个脚本来复现错误,并用
python <filename.py>执行它 - 编辑修复:编辑仓库的源代码来解决问题
- 验证修复:重新运行复现的脚本,并确认错误已被修复
- 考虑边缘情况:思考边缘情况,确保你的修复也能处理他们
- 无演示(Zero-shot)
二、项目拆解分析
2.1 生命周期
- 初始化:系统首先从配置文件和命令行参数中加载和合并配置,随后,根据配置实例化模型、环境和智能体本身
- 运行:Agent 的
run()方法被调用,启动核心的智能体循环。循环内部不断进行"思考-行动-观察"(ReAct) 的迭代,直到完成或达到某个限制 - 终止:循环通过异常机制优雅推出,整个执行轨迹(消息、动作、成本等)序列化为一个 JSON 文件,保存下来,便于分析和回放。
2.2 Agent Loop
# 伪代码,源于default.py的run方法defrun(self,task):# 初始化消息历史self.messages=[system_message,instance_message]whileTrue:try:# 1. 调用LLM获取下一步动作response=self.model.query(self.messages)# 2. 解析动作并执行action,observation=self.env.execute(response)# 3. 将结果添加到历史self.messages.append({"role":"assistant","content":response})self.messages.append({"role":"user","content":observation})exceptInterruptAgentFlowase:# 4. 捕获特定异常来控制流程ifisinstance(e,Submitted):break# 成功完成,退出循环elifisinstance(e,(LimitsExceeded,TimeExceeded)):break# 达到限制,退出循环elifisinstance(e,FormatError):# 处理格式错误,可能重试self.n_consecutive_format_errors+=1ifself.n_consecutive_format_errors>self.config.max_consecutive_format_errors:break2.3 Tool
- 执行逻辑:在环境中通过命令行调用实现。环境负责接收 LLM 的工具调用指令,并将其映射为具体的系统命令执行,然后捕获输出返回给 Agent
2.4 Environment
- 核心类:
local.py实现了在本地文件系统上执行命令的LocalEnvironment,这是最常用的环境。此外,singularity.py等提供了在容器(如 Singularity)中执行的环境,用于隔离和安全性。 - 职责:环境的职责时执行动作并返回观察结果,它接收来自 Agent 的字符串执行(如 Bash 命令或一个编辑指令),在目标工作目录中执行,并将命令的输出、执行状态等信息格式化后返回给 Agent,作为下一轮循环的输入。
2.5 Prompt
通过模板+变量的方式,分离了结构和内容
- 模板定义:提示词的骨架模板定义在配置文件(如config/mini.yaml)中。
- system_template:系统提示,定义 Agent 的角色、能力和行为规范。
- instance_template:示例提示,包含具体的任务描述。
- 变量填充与渲染:在运行时,Agent 的
get_template_vars()方法会收集来自配置、环境、模型以及当前状态的所有变量,合并成一个dict,用Jinja2模板引擎进行渲染,生成最终发送给 LLM 的完整提示词。 - 动态上下文:模板可以引用动态变量,例如
{{working_dir}}当前工作目录{{problem_statement}}问题描述
2.6 Memory
非常直接,用完整的消息历史列表
- 存储位置:记忆保存在
DefualtAgent实例的self.messages属性中,这是一个list,按顺序存储完整的对话历史 - 内容:遵循 OpenAI 的消息格式
[{"role":"system", "content":"系统提示"}] - 持久化:运行结束后,这个完整的 messages 列表会被序列化为 JSON。
2.7 LLM 调用
models模块中
- 模型抽象:该目录定义了模型接口,调用各种 LLM API (基于 LiteLLM)
- 调用点:在 Agent Loop 的每一次迭代中,
self.model.query(self.messages)会被调用,query 方法接收完整历史消息,返回模型响应。 - 配置:模型名称、API 密钥、参数(如温度,Top-p),并在模型初始化时传入
2.8 各个模块之间如何通信
模块间通信遵循“依赖注入”和“显式方法调用”的原则,清晰且低耦合
- Agent 初始化时,接收实例化的
Model和Environment对象。 - Agent 调用 Model:通过
self.model.query()。 - Agent 调用 Environment:Agent 将 LLM 的响应传递给
self.env.execute()。Environment 执行命令后,将观察结果字符串返回给 Agent。 - 配置作为 “胶水”,YMAL 配置指定了要使用的模型类、环境类及其初始化参数。
get_model和get_enviroment等工厂函数,根据配置动态创建相应实例