从 0 构建一个 AI Agent:本质就是一个循环
市面上的 Agent 框架层出不穷,容易让人以为里面有什么高深的东西。其实没有。
Agent 的本质,就是一个循环:
调用 API 问模型 → 执行工具 → 回喂结果,如此往复,直到拿到答案。
下面是一段完整、可跑的最小 Agent——模型用 DeepSeek,示例工具用最朴素的 cat(读文件)。看完你就会发现:所有框架,都只是把这个循环包得更顺手。
完整代码
import json, subprocess
from openai import OpenAI
# DeepSeek 提供 OpenAI 兼容接口,直接用它
client = OpenAI(
base_url="https://api.deepseek.com",
api_key="sk-你的DeepSeek密钥",
)
# 1) 工具定义:告诉模型"有哪些函数、参数是什么"
TOOLS = [{
"type": "function",
"function": {
"name": "cat",
"description": "读取一个文件的全部内容",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "文件路径"},
},
"required": ["path"],
},
},
}]
# 2) 工具实现:真正干活的代码(模型不执行,你的代码执行)
def run_tool(name, args):
if name == "cat":
return subprocess.run(
["cat", args["path"]], capture_output=True, text=True
).stdout
return f"unknown tool: {name}"
# 3) Agent 主循环
def agent(user_input):
messages = [{"role": "user", "content": user_input}]
while True:
# ① 调用 API 问模型
reply = client.chat.completions.create(
model="deepseek-chat", messages=messages, tools=TOOLS
)
msg = reply.choices[0].message
messages.append(msg)
if not msg.tool_calls: # 模型不再要工具 -> 给出答案
return msg.content
# ② 执行工具 ③ 回喂结果
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = run_tool(call.function.name, args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": result,
})
print(agent("读一下 /etc/hostname 里的内容"))
每一轮循环只做三件事:
① 调用 API 问模型 → ② 执行工具 → ③ 回喂结果,然后回到 ①,直到模型给出最终答案。
所有"框架"——LangChain、AutoGen、你见过的任何一个——都只是把这几十行包得更顺手:加日志、加记忆、加并发、加 UI。循环没变。
理解了这一点,下面几个概念就都好懂了。
一、工具调用:模型决定,你的代码执行
看上面的 cat:模型本身只会"说话",不会"做事"。我们做的是——
- 把
cat的名字和参数告诉模型(那段TOOLSschema); - 模型决定要不要调、传什么路径;
run_tool去真正执行,把结果回喂给下一轮。
关键在这句:模型不执行任何东西,它只输出"我想 cat 一下 /etc/hostname"。真正动手的永远是你的代码——这既是安全边界(你能拦、能审),也是为什么"给模型一双干净的手"比"给它一百个工具"更重要。
别贪多。先给 3 个真用得上的工具,就够它干很多事。
二、记忆:短期靠拼,长期靠检索
模型没有记忆——每次调用都是"新人"。上面代码里,Agent 的"记忆"其实就是那个不断增长的 messages 列表,是我们在回喂结果时一并带回去的上下文:
- 短期:把对话历史拼进
messages。简单,但越拼越长、越贵。 - 长期:把要点存进向量库,需要时检索回来。省 token,且能无限扩展。
先做短期,等到量大或要跨会话回忆,再上长期。
记住一句话:记忆不是"存下来",而是"下次能取出来"。
三、可观测:Agent 的调试就是"看轨迹"
模型是不确定的:同样的输入,可能走完全不同的路径。所以你看不到它每一步在干嘛,就根本没法调。
至少要能看见:每一轮模型的决定、每一次工具调用的参数与结果(run_tool 那里就是最好的埋点位置)、以及时间和失败卡在哪里。
Agent 调试的本质,是读它的执行轨迹。
四、换模型只要一行
上面代码用的是 DeepSeek(OpenAI 兼容接口)。想换别的模型——GPT、Claude、任意推理服务——只要改 base_url、api_key 和 model 这几处,agent() 的循环一个字都不用动。
这就是接口标准化的好处:模型是可替换的零件,你这个循环才是资产。
一句话收尾:Agent 的本质,就是一个循环——调用 API 问模型 → 执行工具 → 回喂结果。 框架会变,这个循环不会。
📦 完整可运行代码:github.com/zishuowang696/agent-from-scratch
💬 有问题或建议?在下方评论,或到 GitHub 提 Issue。
(本文中英双语;本系列记录从 0 构建 Agent 的过程。)
评论 0
还没有评论,来抢沙发~
登录 登录后即可参与评论