🏠 总目录📚 本教程 01 · 10分钟造个Agent
📑 本页目录(点开跳转)

01 · 第一天:10 分钟造出你的第一个 Agent

10 分钟 | ⭐ 核心 | 🔨 必须动手 | 不需要 API Key


🎯 一句话

Agent = 大模型 + 工具 + 一个 while 循环。 就这三样。这一节你徒手把它写出来,看清里面没有任何魔法。

① 想看当前状态,决定下一步② 做调用工具③ 看读工具返回的结果④ 判断够了吗?没够就再来一轮Agent循环Agent 和「一次调用」的本质区别,就是这个圈⭐ 它能看到自己行动的结果,并据此决定下一步 —— 这就是「反馈循环」⚠️ 也正因为有圈,必须有终止条件:步数上限 / 成本上限 / 无进展检测
Agent 和「一次调用」的本质区别就是这个圈:它能看到自己行动的结果,并据此决定下一步。⚠️ 也正因为有圈,必须有终止条件 —— 否则它会在死循环里反复尝试同样的失败操作。

🧠 先看清那个循环

所有 Agent 产品——Claude Code、Cursor、Manus——剥掉皮之后都是这个循环:

① 把对话历史发给模型
② 模型回复两种东西之一
  • A:「我要调用工具 X,参数是 Y」
  • B:「任务完成,答案是 Z」
③-A 如果是工具调用 → 回到 ①
  • 执行工具
  • 把结果塞回对话历史
  • ↩︎ 回到第 ① 步,循环继续
③-B 如果是最终答案 → 结束
  • 退出循环,把答案返回给用户
💡 整个循环只有这一个出口 —— 模型说"完成了",程序才停。

💡 人话:模型自己决定「接下来干什么」,程序只负责跑腿(执行工具)和记账(维护对话历史)。决策权在模型,这就是 Agent 和普通程序的区别。


🔨 动手:完整的 Agent(能跑,不用 API Key)

下面的代码里有一个「假模型」——它按剧本演出一个真模型的行为,让你零成本看清循环的每一步。复制运行:

# -*- coding: utf-8 -*-
"""一个最小但完整的 Agent。MockModel 让你不用 API Key 就能跑通全部机制。"""
import json, os

# ============ 第 1 步:定义工具 ============
# 工具 = 一个函数 + 一段给模型看的说明书
def calculator(expression: str) -> str:
    """计算数学表达式"""
    try:
        return str(eval(expression, {"__builtins__": {}}, {}))
    except Exception as e:
        return f"计算出错:{e}。请检查表达式,只支持 + - * / ** ()"

def list_files(path: str = ".") -> str:
    """列出目录下的文件"""
    try:
        items = os.listdir(path)[:20]
        return "\n".join(items) if items else "(空目录)"
    except Exception as e:
        return f"读取失败:{e}"

TOOLS = {"calculator": calculator, "list_files": list_files}

TOOL_DESCRIPTIONS = """你可以使用以下工具:
- calculator(expression): 计算数学表达式,如 "23*7+1"
- list_files(path): 列出目录下的文件

需要用工具时,只输出一行 JSON:{"tool": "工具名", "args": {参数}}
不需要工具时,直接输出答案。"""

# ============ 第 2 步:模型(先用假的) ============
class MockModel:
    """按剧本扮演一个真模型:先查文件、再算数、最后总结。
    换成真模型时,只需要替换这个类(见文末)。"""
    def __init__(self):
        self.script = [
            '{"tool": "list_files", "args": {"path": "."}}',
            '{"tool": "calculator", "args": {"expression": "365 * 24"}}',
            "根据工具结果:当前目录的文件已列出,一年有 8760 小时。任务完成。",
        ]
        self.step = 0

    def chat(self, messages):
        reply = self.script[min(self.step, len(self.script) - 1)]
        self.step += 1
        return reply

# ============ 第 3 步:那个循环 ============
def run_agent(model, user_task, max_steps=10):
    messages = [
        {"role": "system", "content": TOOL_DESCRIPTIONS},
        {"role": "user", "content": user_task},
    ]
    for step in range(max_steps):
        reply = model.chat(messages)
        messages.append({"role": "assistant", "content": reply})
        print(f"\n【第 {step+1} 轮】模型说:{reply}")

        # 判断:是工具调用,还是最终答案?
        try:
            call = json.loads(reply)
            assert "tool" in call
        except (json.JSONDecodeError, AssertionError):
            print("\n✅ 模型给出最终答案,循环结束。")
            return reply

        # 执行工具,把结果塞回对话
        tool_fn = TOOLS.get(call["tool"])
        result = tool_fn(**call["args"]) if tool_fn else f"错误:没有 {call['tool']} 这个工具"
        print(f"       工具返回:{result[:80]}")
        messages.append({"role": "user", "content": f"工具返回结果:\n{result}"})

    return "达到最大步数,强制停止。"  # ⭐ 护栏:永远要有最大步数

# ============ 跑起来 ============
run_agent(MockModel(), "看看当前目录有什么文件,然后告诉我一年有多少小时")

你会看到:模型(假的)第 1 轮要求列文件 → 程序执行并回填 → 第 2 轮要求算数 → 回填 → 第 3 轮给出答案退出。

恭喜,这就是一个 Agent 的全部骨架。 大约 60 行。


🔍 五个值得盯着看的细节

  1. messages 是唯一的状态。模型没有记忆,每轮都把完整历史发过去。这就是为什么「上下文」是一切的核心约束(第 4 节)
  2. 工具结果以 user 角色回填。模型看到的世界 = 对话历史里的文字。工具返回什么文字,模型就「感知」到什么
  3. max_steps 护栏。没有它,一个犯轴的模型能无限循环烧钱。真实系统还会加超时、预算上限
  4. 工具的错误信息写给模型看calculator 出错时返回的是「请检查表达式,只支持…」——这是在教模型怎么改,不是给人看的报错(第 7 节展开)
  5. TOOL_DESCRIPTIONS 是提示词。工具说明书写得好不好,直接决定模型用不用得对(第 7 节的核心命题)

🔌 换成真模型(有 API Key 再做,没有就跳过)

只需替换 MockModel

# pip install anthropic
import anthropic

class ClaudeModel:
    def __init__(self):
        self.client = anthropic.Anthropic()  # 读环境变量 ANTHROPIC_API_KEY

    def chat(self, messages):
        system = next(m["content"] for m in messages if m["role"] == "system")
        rest = [m for m in messages if m["role"] != "system"]
        resp = self.client.messages.create(
            model="claude-sonnet-5",
            max_tokens=1024,
            system=system,
            messages=rest,
        )
        return resp.content[0].text

run_agent(ClaudeModel(), "当前目录里有几个 py 文件?")

📌 真实开发中你会用官方的 tool use API(模型原生输出结构化工具调用,不用自己解析 JSON)或 Claude Agent SDK(循环都帮你写好了)。但你现在徒手写过一遍,用那些工具时就知道皮下是什么。


🔗 这一章连到哪里

去哪 为什么
ML基础 01 同样的「10 分钟先跑通再问为什么」,那边是监督学习版
全景导论 09 ⭐ 你刚写的那个循环有名字 —— ReAct 就是它的理论原型(那一章第 ③ 节,而且会回指本章)。那一章还把 CoT / Self-Consistency / Reflexion / ToT / 长思考排在一起,告诉你什么时候值得让模型"多想"
13 · 多 Agent 协作 ⭐ 同一个循环的两个变体不在导论里,在本教程:Plan-and-Execute(开头一次性出完整计划)和 ReWOO(工具返回根本不回模型)。⚠️ 判据是「计划是谁做的、什么时候做的、还改不改」——附录 A 有一句话版
全景导论 02 循环里被反复调用的那个「模型」,内部到底在做什么

✅ 检查点

  1. Agent 的三要素是什么?
  2. 模型是怎么「记住」上一轮工具结果的?
  3. 为什么必须有 max_steps
  4. Agent 和普通程序的本质区别是什么?
👀 答案
  1. 大模型 + 工具 + 循环。
  2. 它根本没记住——程序把工具结果追加进 messages,每轮把完整历史重新发给它。
  3. 防止无限循环烧钱/卡死。这是最基本的护栏。
  4. 决策权在模型:下一步做什么由模型的输出决定,而不是程序里预写的分支。

🛑 可以停在这里

走神救援

Agent = 模型 + 工具 + while 循环,就这三样,里面没有任何魔法。循环是这么转的:①把对话历史发给模型 → ②模型只回两种东西之一,A「我要调用工具 X,参数是 Y」或 B「任务完成,答案是 Z」 → ③-A 是工具调用就执行、把结果塞回历史、回到 ①;③-B 是最终答案就退出。⭐整个循环只有这一个出口——模型说"完成了",程序才停。这套骨架总共约 60 行,配一个按剧本演出的假模型(先列文件、再算 365×24、报出一年 8760 小时)就能零成本跑通,不用 API Key。五个值得盯的细节:⭐messages 是唯一的状态——模型根本没有记忆,是程序把工具结果追加进去、每轮再把完整历史重发一遍,这就是为什么上下文是一切的核心约束(第 4 节);工具结果以 user 角色回填,模型看到的世界就等于对话历史里的文字,工具返回什么它才感知到什么;⚠️max_steps 是最低限度的护栏(示例里是 10),没有它一个犯轴的模型能无限循环烧钱,真实系统还要加超时和预算上限;工具的错误信息是写给模型看的——出错时返回「请检查表达式,只支持…」是在教它怎么改,不是给人看的报错(第 7 节);TOOL_DESCRIPTIONS 本质就是提示词,说明书写得好不好直接决定模型用不用得对。⭐决策权在模型,这就是 Agent 和普通程序的本质区别:下一步做什么由模型的输出决定,不是代码里预写的分支。

下一节 👉 02-模型与effort-选对大脑.md

打卡记录保存在你的浏览器里,首页能看到总进度