教学目标
看懂一个最小 SWE Agent 是怎么跑起来的
这不是生产级框架,而是一台透明的教学机器。你要掌握的是逻辑: 模型负责判断下一步,程序负责执行动作,history 负责把结果带回下一轮。
“列出目录并总结”
规则 + 历史 + 工具
要不要调用工具
读文件 / 写文件 / 跑命令
让模型看到真实结果
一句话
Agent 就是一个循环:模型给计划,程序执行,结果回填,模型再计划。
当前代码
主文件是 minimal_swe_agent.py, 另有英文和中文 Markdown 讲义作为源材料。
安全边界
所有文件操作锁在 ~/agent-lab/workspace/;shell 默认需要 y/n 确认。
01
先建立心智模型
你可以把这个系统想成“小项目经理 + 执行员”。模型像项目经理,读任务、判断下一步、 提出动作;Python 程序像执行员,检查动作是否合法,然后真的去读文件、写文件、跑命令。
模型负责决策
- 理解用户目标
- 判断是否需要工具
- 填出工具名和参数
- 根据结果继续规划
程序负责执行
- 解析结构化工具调用
- 检查 workspace 边界
- 运行本地函数
- 把结果写回 history
02
一轮完整运行
- 用户输入任务
run_agent()把任务放进 history。 - 拼 prompt
build_prompt()合并 system prompt、history、tools。 - 调用模型
call_llm()根据 provider 发给 OpenAI 或 DeepSeek。 - 解析工具调用
parser 把模型输出变成
ToolCall(name, arguments, call_id)。 - 执行工具
execute_tool()调度read_file/write_file/run_shell。 - 结果回填
工具输出追加到 history,下一轮模型就能看到真实世界反馈。
- 判断停止
如果模型不再调用工具,就打印最终回答并结束。
核心
五件套地图
03
主循环 loop:agent 的心跳
普通聊天是“问一次、答一次”。Agent 是“问模型、执行工具、把结果给模型、再问模型”。 没有 loop,模型最多只能提出动作,没法看到动作结果,也没法修正下一步。
for turn in range(1, args.max_turns + 1):
request = build_prompt(...)
response = call_llm(...)
calls = extract_function_calls(response)
if not calls:
print(final_answer)
return
for call in calls:
output = execute_tool(call)
history.append(tool_result)04
上下文 context:给失忆模型带工作日志
每次 API 调用,模型本质上只看到你这次传过去的内容。history 就是我们替它维护的工作日志: 用户任务、模型消息、工具调用、工具结果都放进去。上下文太长时,程序优先丢旧工具结果。
| history 里有什么 | 为什么需要 |
|---|---|
| 用户任务 | 让模型记住目标是什么。 |
| 模型输出 | 让下一轮知道自己刚才提出过什么。 |
| 工具结果 | 让模型基于真实结果继续判断。 |
| 压缩策略 | 防止请求无限变长,保留最近且重要的信息。 |
05
prompt 拼接:把“任务现场”打包给模型
build_prompt() 是最值得反复看的函数。它说明 agent 没有玄学:
一次模型请求就是 system prompt、history、tools 三块材料的组合。
python3 minimal_swe_agent.py \
--provider deepseek \
--dump-prompt \
--dry-run \
"List files and summarize."06
output parser:把“我想做”变成结构化工单
模型如果只输出“我想运行 ls”,程序很难可靠执行。Function calling 的价值是让模型输出结构化数据: 工具名是什么,参数是什么,call_id 是什么。
脆弱文本
好的,我来运行 ls 看一下目录。结构化调用
{
"name": "run_shell",
"arguments": {"command": "ls"}
}07
executor:真正碰电脑的地方
executor 是信任边界。模型提出动作,executor 决定是否执行、怎么执行、执行范围在哪里。 这个项目故意只给三个工具,够理解核心,不被工程复杂度淹没。
/Users/karasuakamatsu/agent-lab/workspace
路径解析后必须还在这个目录里,否则拒绝。
08
OpenAI / DeepSeek:同一内核,两种 API 方言
DeepSeek 可以做这个教学实验。它和 OpenAI 的差别不在 agent 思想,而在请求/响应格式。 代码用 provider boundary 把差异隔离起来。
client.responses.create(
model="gpt-5.4",
instructions=SYSTEM_PROMPT,
input=history,
tools=TOOLS
)工具结果回填形态:{"type": "function_call_output", "call_id": "...", "output": "..."}
client.chat.completions.create(
model="deepseek-v4-flash",
messages=[system_message] + history,
tools=chat_tools(),
tool_choice="auto"
)工具结果回填形态:{"role": "tool", "tool_call_id": "...", "content": "..."}
09
本地运行方式
先安装依赖,再选择 provider。不要把 API key 写进文件;用环境变量。
只看 prompt,不调用模型
cd /Users/karasuakamatsu/agent-lab
python3 minimal_swe_agent.py \
--provider deepseek \
--dump-prompt \
--dry-run \
"List files."真实调用 DeepSeek
python3 -m pip install --upgrade -r requirements.txt
read -s DEEPSEEK_API_KEY
export DEEPSEEK_API_KEY
python3 minimal_swe_agent.py \
--provider deepseek \
"List the current directory files and summarize them."10
推荐读代码路线
不要从第一行硬啃。按下面顺序读,脑子里会先有地图,再看局部细节。
- SYSTEM_PROMPT先看模型被要求遵守什么规则。
- TOOLS再看模型可以调用哪些动作。
- run_agent()看主循环如何把模型和工具串起来。
- build_prompt()看一次请求如何被组装出来。
- call_llm()看 OpenAI / DeepSeek 差异被隔离在哪里。
- parser看模型输出如何变成工具调用。
- executor看真实动作和安全边界在哪里。