
上一篇我们拆开了 Agent 的原理,这一篇我们亲手造一个。
在上一篇文章中,我们把 Agent 彻底拆开了:Agent = 大模型 + 工具 + 运行逻辑。 大模型是大脑,工具是手脚,运行逻辑(ReAct 循环)让它知道什么时候该想、什么时候该做。
原理讲完了,你可能会想:说了这么多,代码到底长什么样?
今天我们就来回答这个问题。我们会用尽量少的 Python 代码——大约 50 行——从零实现一个能读文件、写文件、运行命令的 AI Agent。一个迷你版的 Claude Code。
不用任何框架,不用 LangChain,不用 CrewAI。纯 Python + API 调用,每一行代码都能看懂。
读完这篇文章,你不仅能理解 Agent 的代码骨架,还能真正看到上一篇讲的那些概念——ReAct 循环、系统提示词、工具调用——是怎么变成可运行的代码的。
在动手之前,我们先解决一个前置问题:代码怎么和大模型"对话"?
我们平时用 ChatGPT 或 DeepSeek,是在网页上打字聊天。但写程序的时候,总不能让代码自己去打开浏览器输入消息吧。
这就需要用到 API。
API 的全称是 Application Programming Interface(应用程序接口),本质很简单:是你的程序和大模型之间的一根"电话线"。
你的程序通过这根"电话线"把消息发过去,大模型想好之后再通过这根"电话线"把回复传回来。整个过程就是发送和接收数据,和你在网页上聊天的效果一样,只不过变成了代码自动完成。
市面上提供 API 的大模型有很多——OpenAI、Claude、DeepSeek、通义千问……它们的底层原理是一样的,区别主要在价格和调用方式上。
本文选择 DeepSeek API,原因很简单:
这个 API Key 就是你的"电话号码",程序凭它才能连上 DeepSeek 的大模型。
因为 DeepSeek 的 API 完全兼容 OpenAI 格式,所以我们直接用 OpenAI 的 Python 库:
pip install openai
就这一个库,不需要任何其他依赖。
准备工作做完了,我们来写第一段代码——让程序通过 API 调用大模型,发一条消息、收一条回复。
from openai import OpenAI
client = OpenAI(
api_key="你的 DeepSeek API Key",
base_url="https://api.deepseek.com"
)
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "user", "content": "你好,请用一句话介绍你自己"}
]
)
print(response.choices[0].message.content)
运行一下,你会看到模型的回复——比如"我是 DeepSeek,一个人工智能助手"之类的。
这段代码做了什么?拆开来看:
OpenAI(api_key=..., base_url=...) 就是拿起"电话",拨通 DeepSeek 的号messages 列表里放的就是你要说的话,role: "user" 表示这是用户发的消息response.choices[0].message.content 就是模型说回来的话和 OpenAI 的代码唯一的区别就是 api_key 和 base_url 两个参数——这也是为什么说"换模型只需改两行代码"。
到这里,我们的程序已经能和大模型对话了。但别忘了上一篇讲的——大模型只能"说",不能"做"。 它能给你回复一段文字,但它没法自己去读你的文件、写你的代码、运行你的命令。
要让它从"会说话"变成"能干活",我们得给它装上工具。
上一篇我们说过,Agent = 大脑 + 手脚。大脑有了(大模型),现在我们来造"手脚"。
我们给 Agent 装三个工具:
read_file | ||
write_file | ||
run_command |
有了这三个工具,Agent 就能自己读代码、写代码、运行程序——和 Claude Code 的核心能力一样。

先写三个 Python 函数,这就是工具的"身体":
import subprocess, os
defread_file(path):
return open(path).read()
defwrite_file(path, content):
os.makedirs(os.path.dirname(path) or".", exist_ok=True)
open(path, "w").write(content)
return"文件写入成功"
defrun_command(command):
result = subprocess.run(command, shell=True, capture_output=True, text=True)
return result.stdout + result.stderr
三个函数,每个两三行,做的事情很直白:读文件就 open().read(),写文件就 open("w").write(),运行命令就 subprocess.run()。
光有函数还不够。模型不知道你写了这些函数——你得把工具的"说明书"告诉它。
这就像招了一个新员工,你得告诉他:"公司有打印机、扫描仪、投影仪,打印机在三楼,用之前要先放纸。"你不说,他就不知道能用这些东西。
工具说明书长这样:
tools = [
{
"type": "function",
"function": {
"name": "read_file",
"description": "读取指定文件的内容",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "文件路径"}
},
"required": ["path"]
}
}
},
{
"type": "function",
"function": {
"name": "write_file",
"description": "写入内容到指定文件",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "文件路径"},
"content": {"type": "string", "description": "要写入的内容"}
},
"required": ["path", "content"]
}
}
},
{
"type": "function",
"function": {
"name": "run_command",
"description": "在终端中运行命令",
"parameters": {
"type": "object",
"properties": {
"command": {"type": "string", "description": "要执行的命令"}
},
"required": ["command"]
}
}
}
]
看起来有点长,但结构很简单:每个工具就三样东西——名字(name)、描述(description)、参数(parameters)。模型根据描述来判断什么时候该用哪个工具,根据参数来知道该传什么值。
现在,大脑有了,手脚也有了。但还差最关键的一步:谁来指挥大脑使用手脚?
这是全文最核心的部分。
还记得上一篇讲的 ReAct 模式吗?思考→行动→观察→再思考,循环往复,直到任务完成。
翻译成代码,就是一个 while 循环:
import json
defagent(task):
messages = [
{"role": "system", "content": "你是一个编程助手。使用工具来完成用户的任务。"},
{"role": "user", "content": task}
]
whileTrue:
# 1. 调用模型(思考)
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools
)
msg = response.choices[0].message
messages.append(msg)
# 2. 如果模型没有调用工具,说明任务完成
ifnot msg.tool_calls:
return msg.content
# 3. 执行模型要求的工具(行动)
for tool_call in msg.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f" [调用工具] {name}({args})")
# 调用对应的函数
if name == "read_file":
result = read_file(**args)
elif name == "write_file":
result = write_file(**args)
elif name == "run_command":
result = run_command(**args)
# 4. 把结果告诉模型(观察)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
# 回到 while True,继续下一轮循环

就这么多。 整个 Agent 的"运行引擎"就是这个 while 循环。
我们来对照上一篇的 ReAct 流程图,看看每一步对应什么:
| 思考(Thought) | client.chat.completions.create(...) | |
| 行动(Action) | read_file()write_file() / run_command() | |
| 观察(Observation) | messages.append({"role": "tool", ...}) | |
| 最终答案 | if not msg.tool_calls: return | |
| 循环 | while True |
看到了吗?上一篇讲了那么多的 ReAct 模式——思考、行动、观察、循环——落到代码里,就是一个 while 循环 + 一个 if 判断。
模型每次回复会带一个信号:要么返回 tool_calls(意思是"我要用工具"),要么返回纯文本(意思是"我说完了")。我们的代码只需要看这个信号:有工具调用就执行并继续循环,没有就结束。
这就是 Agent 的全部秘密。
上一篇我们讲过,系统提示词是 Agent 的"剧本"。前面的代码里我们只写了一句简单的系统提示词:
{"role": "system", "content": "你是一个编程助手。使用工具来完成用户的任务。"}
这够用,但不够好。就像只给新员工说了一句"你是程序员,干活吧",他能干,但可能干得不太对。
我们来加一个更完善的版本:
system_prompt = """你是一个专业的编程助手。你可以使用以下工具来完成用户的任务:
1. read_file:读取文件内容
2. write_file:写入文件内容
3. run_command:运行终端命令
工作流程:
- 仔细分析用户的任务需求
- 将任务分解为具体步骤
- 使用工具逐步完成每个步骤
- 每一步都要验证结果是否正确
- 所有步骤完成后,给用户一个简要总结
注意事项:
- 写代码前先想清楚文件结构
- 写完代码后用 run_command 验证是否能正常运行
- 如果运行出错,自己分析错误并修复
"""
对比一下效果:
同一个模型、同样的工具、同样的循环,换一段系统提示词,Agent 的表现就天差地别。 这就是上一篇说的——不同 Agent 产品表现差异大,很多时候不是模型不一样,而是"剧本"写得不一样。
还记得上一篇的贪吃蛇例子吗?我们说大模型只能给你输出代码文本,要自己复制保存。而 Agent 会自己创建文件。
现在我们亲手造的 Agent 就在眼前,让它来完成这个任务:
print(agent("写一个贪吃蛇游戏,用 HTML、CSS 和 JS 实现,代码分别放在不同的文件中。"))
运行后,你会在终端里看到这样的输出:
[调用工具] write_file({"path": "index.html", "content": "<!DOCTYPE html>..."})
[调用工具] write_file({"path": "style.css", "content": "body { margin: 0; ..."})
[调用工具] write_file({"path": "game.js", "content": "const canvas = ..."})
[调用工具] run_command({"command": "ls -la"})
贪吃蛇游戏已创建完毕。包含以下文件:
- index.html:游戏主页面
- style.css:样式文件
- game.js:游戏逻辑
用浏览器打开 index.html 即可开始游戏。

三个文件自动创建好了,直接打开就能玩。
上一篇讲的 ReAct 循环,在这里一步步走了出来: 模型先思考需要哪些文件,然后一个一个调用 write_file 写入,最后用 run_command 验证文件是否创建成功,确认无误后输出最终总结。
思考→行动→观察→再思考→再行动→再观察……直到任务完成。
这就是 Agent。50 行代码,从"只会说话"变成了"自己干活"。
上面的演示看起来很顺利,但真实情况没那么美好。我用同样的代码换了一个模型,结果翻车了——而且翻得很有教育意义。
我先后给 Agent 下了三个任务,结果一个比一个离谱:
index.html | |||
tetris.py | |||
index.html |
更离谱的是,有一次模型突然调用了 python3 -m http.server 8000——启动了一个永远不会结束的 HTTP 服务器。我们的 run_command 函数用的是 subprocess.run(),它会一直等命令结束。结果就是:整个程序卡死了,终端完全没反应。
注意,代码没有任何变化,变的只是模型。同样的工具、同样的循环、同样的提示词,换了一个"大脑",表现就天差地别。

这个翻车故事恰好印证了上一篇的核心观点:Agent 的上限,取决于模型的能力。 工具和循环是骨架,但骨架再好,大脑不行,Agent 就是个会犯傻的机器人。这也是为什么 Claude Code 用的是最强的 Claude 模型,而不是随便一个能跑的模型——大脑的质量,决定了 Agent 的质量。
顺便说一下,那个"卡死"的问题其实可以修复——给 run_command 加一个超时:
defrun_command(command):
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30)
return result.stdout + result.stderr
except subprocess.TimeoutExpired:
return"命令执行超时(30秒),已终止"
加了 timeout=30,任何命令超过 30 秒就自动终止,不会再卡死。但这只是兜底——真正的解决方案是用更聪明的模型,让它一开始就不会做出"启动服务器"这种傻事。
把前面所有步骤拼在一起,就是一个完整的迷你 Agent。全部代码如下:
import json, subprocess, os
from openai import OpenAI
# ---- 连接模型 ----
client = OpenAI(api_key="你的 API Key", base_url="https://api.deepseek.com")
# ---- 工具函数 ----
defread_file(path):
return open(path).read()
defwrite_file(path, content):
os.makedirs(os.path.dirname(path) or".", exist_ok=True)
open(path, "w").write(content)
return"文件写入成功"
defrun_command(command):
result = subprocess.run(command, shell=True, capture_output=True, text=True)
return result.stdout + result.stderr
# ---- 工具描述(告诉模型有哪些工具可用)----
tools = [
{"type": "function", "function": {"name": "read_file", "description": "读取文件内容",
"parameters": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}}},
{"type": "function", "function": {"name": "write_file", "description": "写入文件内容",
"parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}},
{"type": "function", "function": {"name": "run_command", "description": "运行终端命令",
"parameters": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}}}
]
TOOL_FUNCTIONS = {"read_file": read_file, "write_file": write_file, "run_command": run_command}
# ---- 系统提示词 ----
system_prompt = """你是一个专业的编程助手。你可以使用工具来完成用户的任务。
工作流程:分析需求 → 分解步骤 → 逐步执行 → 验证结果 → 总结。
写完代码后请用 run_command 验证。如果出错,自己分析并修复。"""
# ---- Agent 循环(核心)----
defagent(task):
messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": task}]
whileTrue:
response = client.chat.completions.create(model="deepseek-chat", messages=messages, tools=tools)
msg = response.choices[0].message
messages.append(msg)
ifnot msg.tool_calls:
return msg.content
for tc in msg.tool_calls:
args = json.loads(tc.function.arguments)
print(f" [调用工具] {tc.function.name}({args})")
result = TOOL_FUNCTIONS[tc.function.name](**args)
messages.append({"role": "tool", "tool_call_id": tc.id, "content": str(result)})
# ---- 启动 ----
print(agent(input("请输入任务: ")))
总共不到 50 行有效代码。
如果你想换成 Claude API,只需要改两个地方:把 openai 换成 anthropic,把 base_url 和认证方式改一下。工具定义和 Agent 循环的逻辑完全一样。
让我们回顾一下:我们用 4 步,从零造出了一个能自主干活的 AI Agent。

上一篇讲的每一个概念,在这篇都变成了可运行的代码:
Claude Code、Cursor、Codex 这些产品,当然比我们这 50 行代码复杂得多——它们有更多的工具、更精细的权限控制、更长的系统提示词、更好的错误处理。但核心骨架是一样的:模型 + 工具 + 循环。
就像一辆跑车和一辆自行车,结构复杂度天差地别,但本质都是"轮子 + 动力 + 方向控制"。理解了自行车,你就理解了跑车的基本原理。
Agent 没有魔法,就是模型 + 工具 + 循环。
如果这篇文章对你有帮助,欢迎点赞、收藏、转发。有任何问题,欢迎在评论区交流。