从大模型到 Agent

这篇文章讨论一个问题:大模型是怎么走到 Agent 的。

“AI Agent”、“Claude Code” 这些词越来越常见,但很多介绍停留在概念层。本文按实现路径展开:从语言模型的基本能力出发,到可以在真实环境中执行任务的 Agent 系统。

大模型到 Agent 的关键发展

语言模型的本质

语言模型,本质上是一个函数。

输入是一段文字,输出是”下一个词的概率分布”。把这个过程反复执行,就得到连贯文本。GPT、Claude、Llama 的底层形式本质一致。

它能理解代码、能推理逻辑——但有一个根本性的局限:

它只能处理文字,碰不到真实世界。

不能读文件,不能跑测试,不能搜网络,不知道今天几号。它活在一个纯文字的沙盒里。

还有另一个问题——它是无状态的。每次 API 调用,它都不会自动保留上一次的上下文。只有把历史对话再次传入,它才会”记得”。没有外部机制,它无法持续执行任务。

语言模型 = 一个函数

  f(tokens) → 下一个词的概率分布

强项:理解、推理、生成
局限:
  × 碰不到真实世界
  × 无状态(每次调用都是新开始)
  × 无法主动持续做事

关键里程碑

要理解 Agent 怎么来的,需要沿着时间线看几个关键节点。

2017,Transformer。注意力机制让模型能关注序列中任意位置的信息。今天所有大模型都建立在这个基础上。

2020,GPT-3 与 In-Context Learning。不用微调,只要在 prompt 里给几个例子,模型就能学新任务。这打开了”用自然语言控制模型行为”的大门。

2022,Chain-of-Thought。Google 发现,让模型一步一步思考而不是直接给答案,推理准确率会明显提升。这让研究者意识到:推理过程本身可以被引导。

时间线(上半段)

2017  Transformer
      └─ 注意力机制,所有大模型的基础

2020  GPT-3 + In-Context Learning
      └─ 自然语言控制模型行为
         prompt engineering 的起点

2022  Chain-of-Thought
      └─ 引导推理过程
         "让模型先想,再答"

ReAct:推理与行动的结合

2022 年,ReAct 被提出。

论文叫 ReAct: Synergizing Reasoning and Acting in Language Models

核心思想是让模型在推理行动之间交替进行。每一步推理后都可以调用外部工具,再把结果作为新的观察继续推理。

在 ReAct 之前,模型主要停留在文本生成。ReAct 之后,模型可以通过工具调用参与任务执行。

这个框架直接影响了今天所有 Agent 的设计。

ReAct 模式(2022)

Thought:     我需要知道今天的天气
Action:      search("北京今天天气")
Observation: 晴,22°C
Thought:     用户问要不要带伞,不用带
Action:      finish("今天晴天,不需要带伞")

推理(Reasoning) + 行动(Acting)交替
→ Agent 的雏形

Tool Use 标准化:从演示到生产

ReAct 证明了推理 + 行动可行,但工程上还有一个问题。

当时工具调用是非结构化的——模型用普通文字输出 Action: search(...),再用字符串解析来执行。一旦模型输出格式略有偏差,就全崩了。这很脆弱。

2023 年,OpenAI 推出 Function Calling,Anthropic 推出 Tool Use。工程化改进主要在这里:

模型不再用文字描述操作,而是输出结构化的调用块。调用方精确解析、执行工具、返回结果。

有了这个机制,可靠的 Agent 循环才成为可能。Agent 也因此从实验演示逐步走向可落地系统。

模型本身也针对 Tool Use 做了专门训练——它知道什么时候该调工具,怎么构造合法参数,工具返回后怎么继续推理。

2023  Function Calling / Tool Use

ReAct 时代(脆弱):
  模型输出普通文字 → 字符串解析 → 容易崩

Tool Use 时代(可靠):
  模型输出结构化调用块 ↓

  {
    "type": "tool_use",
    "name": "search",
    "id":   "tool_abc123",
    "input": { "query": "北京今天天气" }
  }

  → 精确解析 → 执行 → 返回结果

推理模型与长上下文

2024 年之后,两件事让 Agent 的可用性继续提升。

一是推理模型——o1、o3、Claude 3.7 这类,把”思考过程”内化到模型本身,在生成答案前进行更深的内部推理。

二是长上下文——窗口从 4K 扩展到 200K token,Agent 能处理更长任务,减少频繁压缩历史的次数。

2024+  推理模型 + 长上下文

推理模型(o1 / o3 / Claude 3.7)
  └─ "思考"内化,不只是 prompt 引导

长上下文
  └─ 4K → 200K token
     更长任务,更少压缩

─────────────────────────────────────
完整时间线

2017  Transformer        基础架构
2020  GPT-3 + ICL        自然语言控制
2022  Chain-of-Thought   引导推理
2022  ReAct              推理+行动(雏形)
2023  Tool Use           结构化调用(可靠化)
2024+ 推理模型/长上下文  能力大幅提升

从模型到 Agent 需要什么

从实现角度看,语言模型要成为 Agent,需要同时具备以下能力:

推理——CoT、ReAct 给了它; 行动——Tool Use 标准化给了它; 循环——Agent 框架给了它,把”感知→思考→行动”自动循环,直到任务完成。

下面进入实现细节,先看这个”循环”如何落地。

LM → Agent 的关键能力

① 推理(Reasoning)
   CoT + ReAct 给的

② 行动(Action)
   Tool Use 标准化给的
   结构化调用,可靠执行

③ 循环(Loop)
   Agent 框架给的
   感知 → 思考 → 行动 → 感知 → …
   直到任务完成

Agent 是怎么搭起来的

最小的 Agent

先看代码。最小的 Agent 可以写成下面这样。

核心是一个 while True 循环。每轮三步:把用户输入发给模型,检查 stop_reason;如果模型要调工具,就执行并回填结果;如果模型输出完成,就退出。

不到 30 行,就可以构成一个完整 Agent。

关键点有三个:messages[] 承载全部状态,每次调用都带完整历史;stop_reason 是决策点;while True 负责持续推进任务。

没有这个循环,开发者就得手动充当循环体:每次工具调用后都要把结果拼回上下文,再次请求模型。

def agent_loop(query):
    messages = [{"role": "user", "content": query}]

    while True:                           # ← 循环
        response = client.messages.create(
            model=MODEL, system=SYSTEM,
            messages=messages, tools=TOOLS,
        )
        messages.append({
            "role": "assistant",
            "content": response.content,
        })

        if response.stop_reason != "tool_use":
            return                        # ← 退出条件

        results = []
        for block in response.content:
            if block.type == "tool_use":
                output = run_bash(block.input["command"])
                results.append({
                    "type":        "tool_result",
                    "tool_use_id": block.id,
                    "content":     output,
                })
        messages.append({"role": "user", "content": results})

工具分发

工具分发机制并不复杂。

Tool Use API 返回结构化调用块,Agent 做一次字典查找,执行对应函数,再把结果返回给模型。

关键点是:加新工具只需要加一个 handler 和一份 schema 定义,循环本身一行不改。

Claude Code 有 20 多个工具,OpenClaw 也类似,但底层的 dispatch 逻辑完全一样。

# 工具分发表
TOOL_HANDLERS = {
    "bash":       lambda **kw: run_bash(kw["command"]),
    "read_file":  lambda **kw: run_read(kw["path"]),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":  lambda **kw: run_edit(kw["path"],
                                        kw["old"], kw["new"]),
}

# 分发逻辑(循环内)
handler = TOOL_HANDLERS.get(block.name)
output  = handler(**block.input)

# 加新工具 = 加一行 handler + 一份 schema
# 循环本身一行不改

会话持久化

模型是无状态的,但 Agent 需要跨会话记忆。常见做法如下。

用 JSONL 文件——每条消息追加一行 JSON 记录。每次会话开始,把文件重放一遍,重建 messages[]。对模型来说,就好像对话从没中断过。

上下文窗口是有限的,对话越长越接近上限。因此通常需要三阶段溢出恢复:先正常调用;溢出后截断工具结果;仍溢出就让 LLM 压缩旧消息为摘要;再溢出才报错。

会话持久化:JSONL 文件

每行一条 JSON 记录,追加写入:

{"type": "user",        "content": "帮我重构这个函数", "ts": ...}
{"type": "assistant",   "content": [...],               "ts": ...}
{"type": "tool_result", "content": "Done.",             "ts": ...}

重放 → 重建 messages[] → 模型感知不到中断

─────────────────────────────────────
上下文溢出:三阶段恢复

① 正常调用
      ↓ 溢出
② 截断工具结果(保留摘要)
      ↓ 还溢出
③ LLM 生成历史摘要,替换旧消息
      ↓ 还溢出
   报错

系统提示词的分层组装

在实际系统里,系统提示词通常不是硬编码字符串,而是每轮对话前动态组装。

层级越靠前,对模型行为的影响越强。例如把 SOUL.md 放在第 2 层,可以稳定控制输出风格。

一个常见设计是 Skills 两层注入:系统提示词只放技能名称,成本更低;模型需要时再调用 load_skill 工具加载完整内容,按需消耗 token。不把所有领域知识都放进系统提示词,有助于控制上下文成本。

系统提示词 8 层动态组装(每轮重建)

Layer 1  Identity    "你是谁"
Layer 2  Soul        性格、行为风格         ← 越靠前影响越强
Layer 3  Tools       工具使用指南
Layer 4  Skills      按需加载的领域知识
Layer 5  Memory      长期记忆 + 本轮召回
Layer 6  Bootstrap   启动上下文文件
Layer 7  Runtime     当前时间、模型 ID
Layer 8  Channel     "你正在通过 Telegram 回复"

─────────────────────────────────────
Skills 两层注入

Layer 4(便宜):技能名称列表  ~100 tokens/个
工具调用(按需):完整内容     ~2000 tokens/个

load_skill("pdf") → 返回完整文档

记忆系统

Agent 还需要长期记忆,例如用户偏好和过往对话中的关键信息。

OpenClaw 的做法:记忆存两层,常驻文件加每日日志;每次用户发消息,用 TF-IDF 搜索相关记忆,把召回结果注入系统提示词第 5 层。

这套方案可以用纯 Python 实现,不需要向量数据库。对很多场景来说,TF-IDF 已经够用,引入向量数据库反而会增加系统复杂度。

记忆系统

存储层:
  MEMORY.md        常驻记忆(用户偏好、重要事实)
  YYYY-MM-DD.jsonl 每日记忆日志

召回:
  用户消息 → TF-IDF 搜索 → top-k 记忆片段
           → 注入 Layer 5

特点:
  ✓ 纯 Python,无外部依赖
  ✓ 不需要向量数据库
  ✓ 够用就好,不过度工程

主流 Agent 的实现

Claude Code 和 OpenClaw

下面对比两个常见实现。

Claude Code 是 Anthropic 的命令行编程 Agent。核心就是前面说的 while True 循环加工具分发,工具集有 20 多个,覆盖文件读写、bash 执行、搜索、网页抓取。一个亮点是 str_replace 编辑模式——不让模型输出整个文件,而是输出”把哪段替换成哪段”,精确可靠。系统提示词里定义了详细的行为约束:先读再写、验证结果、路径沙箱保护。

OpenClaw 是一个更通用的 Agent 网关框架,可以接 Telegram、飞书、CLI 等多个通道,支持多 Agent 路由和后台定时任务。核心机制完全一样,只是外面包了更多层:持久化、路由、可靠性重试、并发调度。

两者的核心都可以概括为:while True 循环 + 工具分发表,差异主要在外层工程系统。

Claude Code
  用途:命令行编程 Agent
  工具:~20 个(文件/bash/搜索/网页)
  亮点:str_replace 编辑,路径沙箱
  本质:while True + 工具分发

OpenClaw
  用途:多通道 Agent 网关
  通道:Telegram / 飞书 / CLI
  亮点:多 Agent 路由,后台心跳/Cron
  本质:while True + 工具分发
        + 持久化 / 路由 / 重试 / 并发

─────────────────────────────────────
两者共同的内核

while True:
    response = LLM(messages, tools)
    if stop_reason != "tool_use": break
    results = dispatch(response)
    messages.append(results)