Phase 0 · Agent 核心循环

v1: 模型即Agent

原创教程·2026/7/22·14 阅读

v1: 模型即智能体

约 200 行代码。4 个工具。每个编码智能体的本质。

Claude Code 的秘密?没有秘密。

剥去 CLI 美化、进度条、权限系统。剩下的东西出奇地简单:一个让模型调用工具直到任务完成的循环。

核心洞察

传统助手:

用户 → 模型 → 文本响应

智能体系统:

用户 → 模型 → [工具 → 结果]* → 响应
                      ^________|

星号很重要。模型重复调用工具,直到它决定任务完成。这将聊天机器人转变为自主智能体。

关键洞察:模型是决策者。代码只提供工具并运行循环。

四个基本工具

Claude Code 有大约 20 个工具。但 4 个覆盖了 90% 的用例:

工具目的示例
bash运行命令npm install, git status
read_file读取内容查看 src/index.ts
write_file创建/覆盖创建 README.md
edit_file精确更改替换一个函数

有了这 4 个工具,模型可以:

  • 探索代码库(bash: find, grep, ls
  • 理解代码(read_file
  • 进行更改(write_file, edit_file
  • 运行任何东西(bash: python, npm, make

智能体循环

整个智能体在一个函数中:

def agent_loop(messages):
    while True:
        # 1. 询问模型
        response = client.messages.create(
            model=MODEL, system=SYSTEM,
            messages=messages, tools=TOOLS
        )

        # 2. 打印文本输出
        for block in response.content:
            if hasattr(block, "text"):
                print(block.text)

        # 3. 如果没有工具调用,完成
        if response.stop_reason != "tool_use":
            return messages

        # 4. 执行工具,继续
        results = []
        for tc in response.tool_calls:
            output = execute_tool(tc.name, tc.input)
            results.append({"type": "tool_result", "tool_use_id": tc.id, "content": output})

        messages.append({"role": "assistant", "content": response.content})
        messages.append({"role": "user", "content": results})

为什么这有效:

  1. 模型控制循环(持续调用工具直到 stop_reason != "tool_use"
  2. 结果成为上下文(作为"用户"消息反馈)
  3. 记忆是自动的(消息列表累积历史)

系统提示

唯一需要的"配置":

SYSTEM = f"""你是位于 {WORKDIR} 的编码智能体。

循环:简要思考 → 使用工具 → 报告结果。

规则:
- 优先使用工具而非文字。行动,不要只是解释。
- 永远不要编造文件路径。如果不确定,先使用 ls/find。
- 进行最小的更改。不要过度工程。
- 完成后,摘要更改了什么。"""

没有复杂的逻辑。只有清晰的指令。

为什么这个设计有效

1. 简单 没有状态机。没有规划模块。没有框架。

2. 模型做思考 模型决定使用哪些工具、按什么顺序、何时停止。

3. 透明 每个工具调用都可见。每个结果都在对话中。

4. 可扩展 添加一个工具 = 一个函数 + 一个 JSON 模式。

缺少什么

功能为什么省略添加于
待办事项跟踪不必要v2
子智能体复杂度v3
权限用于学习的信任模型生产环境

重点是:核心很小。其他一切都是细化。

更大的图景

Claude Code、Cursor Agent、Codex CLI、Devin——都共享这个模式:

while not done:
    response = model(conversation, tools)
    results = execute(response.tool_calls)
    conversation.append(results)

差异在于工具、显示、安全。但本质总是:给模型工具,让它工作


学习笔记

核心思想:智能体不是魔法。智能体基本上是 LLM + 工具 + 循环


1. 从聊天机器人到智能体

传统助手:

用户 → 模型 → 响应

智能体:

用户 → 模型 → [工具 → 结果]* → 响应
                  ^______________|

关键区别:

  • 聊天机器人只能回应。
  • 智能体可以采取行动。
  • 模型可以重复调用工具直到任务完成。

2. 智能体的本质

while True:
    response = model(messages, tools)

    if no_tool_calls:
        break

    result = execute_tool(...)
    messages.append(result)

核心循环:

观察
↓
思考
↓
行动
↓
观察

或者:

模型
↓
工具
↓
结果
↓
模型

3. 四个基本工具

bash

运行任意命令。

ls
find .
git status
pytest
npm install

目的:

  • 探索项目
  • 运行测试
  • 使用 Git
  • 使用 Docker
  • 执行脚本

在许多情况下:

智能体 ≈ LLM + Bash

readfile

读取文件内容。

read_file("src/main.py")

目的:

  • 理解代码
  • 检查配置
  • 读取日志

writefile

创建或覆盖文件。

write_file("README.md", content)

目的:

  • 生成代码
  • 创建文档
  • 创建配置文件

editfile

进行精确编辑。

edit_file(
    path="app.py",
    old_text="foo",
    new_text="bar"
)

目的:

  • 小修改
  • 错误修复
  • 重构

4. 模型就是智能体

传统软件:

if A:
    tool1()

if B:
    tool2()

程序员控制工作流。

智能体系统:

模型控制工作流。

应用程序只提供工具。

示例:

用户:

修复失败的测试。

可能的执行计划:

1. ls
2. pytest
3. 读取错误输出
4. 读取文件
5. 编辑文件
6. pytest
7. 重复...

模型动态创建这个工作流。


5. 为什么记忆有效

大多数简单的智能体使用:

messages = []

不断追加:

messages.append(user)
messages.append(model)
messages.append(tool_result)

产生的历史:

用户
助手
工具结果
助手
工具结果
...

整个历史被发送回模型。

那个历史变成了记忆。


6. 反应式智能体

最简单的智能体没有长期规划。

观察
↓
行动
↓
观察
↓
行动

示例:

ls
↓
read_file
↓
edit_file
↓
pytest
↓
edit_file
↓
pytest

Claude Code 的很大一部分以这种方式表现。


7. 规划

复杂的任务受益于规划。

示例:

将 Flask 应用程序迁移到 FastAPI。

生成待办事项列表:

1. 分析项目结构
2. 理解 API
3. 创建 FastAPI 骨架
4. 迁移路由
5. 更新测试
6. 验证执行

执行流程:

规划
↓
执行
↓
更新规划
↓
执行

概念上:

todo_list = [...]

8. 观察至关重要

智能体不是:

思考 → 行动

它们是:

思考
↓
行动
↓
观察
↓
再思考

示例:

pytest

输出:

FAILED: test_login

那个失败是一个观察。

下一个行动取决于它。


9. 工具结果必须返回上下文

错误的:

result = tool()
print(result)

正确的:

messages.append(tool_result)

否则模型不知道发生了什么。

智能体需要一个封闭的反馈循环:

模型
↓
工具
↓
结果
↓
模型

10. 自我修复来自哪里

模型写道:

foo()

运行测试:

pytest

错误:

NameError: foo not defined

错误进入上下文。

模型看到:

foo 不存在

它更新代码:

bar()

再次运行测试。

成功。

自我修复基本上是:

观察
↓
下一个预测

11. 为什么 Bash 如此强大

一个 Bash 工具覆盖:

Git
Docker
Python
Node
搜索
构建
部署

示例:

搜索代码:

grep -r login .

查找文件:

find . -name "*.py"

运行测试:

pytest

提交更改:

git commit

因此:

bash ≈ 许多专用工具的组合

12. 智能体框架的现实

许多框架看起来像:

规划智能体
↓
研究智能体
↓
编码智能体
↓
审查智能体
↓
测试智能体

但在底层:

while not done:
    think()
    use_tools()
    observe()

包装不同。

核心模式保持不变。


13. 演进路径

V1

LLM
+ 工具
+ 循环

V2

LLM
+ 工具
+ 循环
+ 规划

V3

LLM
+ 工具
+ 循环
+ 子智能体

Claude Code / Codex CLI

LLM
+ 工具
+ 循环
+ 规划
+ 记忆
+ 权限
+ Git 集成
+ 并行智能体

核心永远不变:

观察
→ 思考
→ 行动
→ 观察

接下来学什么

下一个主要主题是:

上下文工程

关键问题:

为什么智能体系统中最大的瓶颈
往往是上下文窗口而不是模型?

推荐路线图:

  1. 上下文窗口
  2. 上下文工程
  3. RAG
  4. 工具使用
  5. 规划系统
  6. 记忆系统
  7. 多智能体架构
  8. Claude Code 架构
  9. OpenHands 架构
  10. Codex CLI 架构
  11. 长期运行的智能体

一句话摘要

智能体 = LLM + 工具 + 循环

高级智能体 = LLM + 工具 + 循环 + 上下文工程

模型就是智能体。

完整源码

#!/usr/bin/env python3
"""
v1_basic_agent.py - Mini Claude Code:模型即智能体(约 200 行)

核心理念:"模型就是智能体"
=========================================
Claude Code、Cursor Agent、Codex CLI 的秘密?没有秘密。

剥去 CLI 美化、进度条、权限系统。剩下的
出奇地简单:一个让模型调用工具直到完成的循环。

传统助手:
    用户 → 模型 → 文本响应

智能体系统:
    用户 → 模型 → [工具 → 结果]* → 响应
                          ^________|

星号(*)很重要!模型重复调用工具,直到它决定
任务完成。这将聊天机器人转变为自主智能体。

关键洞察:模型是决策者。代码只提供工具并
运行循环。模型决定:
  - 调用哪些工具
  - 按什么顺序
  - 何时停止

四个基本工具:
------------------------
Claude Code 有大约 20 个工具。但这 4 个覆盖了 90% 的用例:

    | 工具         | 目的                 | 示例                       |
    |--------------|----------------------|----------------------------|
    | bash         | 运行任何命令          | npm install, git status    |
    | read_file    | 读取文件内容          | 查看 src/index.ts          |
    | write_file   | 创建/覆盖             | 创建 README.md             |
    | edit_file    | 精确更改              | 替换一个函数               |

只用这 4 个工具,模型就可以:
  - 探索代码库(bash: find, grep, ls)
  - 理解代码(read_file)
  - 进行更改(write_file, edit_file)
  - 运行任何东西(bash: python, npm, make)

用法:
    python v1_basic_agent.py
"""

import os
import subprocess
import sys
from pathlib import Path

from dotenv import load_dotenv

# 从 .env 文件加载配置
load_dotenv()

try:
    from anthropic import Anthropic
except ImportError:
    sys.exit("请安装:pip install anthropic python-dotenv")


# =============================================================================
# 配置
# =============================================================================

API_KEY = os.getenv("ANTHROPIC_API_KEY")
BASE_URL = os.getenv("ANTHROPIC_BASE_URL")
MODEL = os.getenv("MODEL_NAME", "claude-sonnet-4-20250514")
WORKDIR = Path.cwd()

# 初始化客户端 - 处理直接 Anthropic 和兼容的 API
client = Anthropic(api_key=API_KEY, base_url=BASE_URL) if BASE_URL else Anthropic(api_key=API_KEY)


# =============================================================================
# 系统提示 - 模型需要的唯一"配置"
# =============================================================================

SYSTEM = f"""你是位于 {WORKDIR} 的编码智能体。

循环:简要思考 → 使用工具 → 报告结果。

规则:
- 优先使用工具而非文字。行动,不要只是解释。
- 永远不要编造文件路径。如果不确定,先使用 bash ls/find。
- 进行最小的更改。不要过度工程。
- 完成后,摘要更改了什么。"""


# =============================================================================
# 工具定义 - 4 个工具覆盖 90% 的编码任务
# =============================================================================

TOOLS = [
    # 工具 1:Bash - 通往一切的网关
    # 可以运行任何命令:git, npm, python, curl 等
    {
        "name": "bash",
        "description": "运行 shell 命令。用于:ls, find, grep, git, npm, python 等",
        "input_schema": {
            "type": "object",
            "properties": {
                "command": {
                    "type": "string",
                    "description": "要执行的 shell 命令"
                }
            },
            "required": ["command"],
        },
    },

    # 工具 2:读取文件 - 用于理解现有代码
    # 返回文件内容,对大文件可选行限制
    {
        "name": "read_file",
        "description": "读取文件内容。返回 UTF-8 文本。",
        "input_schema": {
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "文件的相对路径"
                },
                "limit": {
                    "type": "integer",
                    "description": "最大读取行数(默认:全部)"
                },
            },
            "required": ["path"],
        },
    },

    # 工具 3:写入文件 - 用于创建新文件或完全覆盖
    # 自动创建父目录
    {
        "name": "write_file",
        "description": "写入内容到文件。如果需要,创建父目录。",
        "input_schema": {
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "文件的相对路径"
                },
                "content": {
                    "type": "string",
                    "description": "要写入的内容"
                },
            },
            "required": ["path", "content"],
        },
    },

    # 工具 4:编辑文件 - 用于对现有代码进行精确编辑
    # 使用精确字符串匹配进行手术式编辑
    {
        "name": "edit_file",
        "description": "替换文件中的精确文本。用于手术式编辑。",
        "input_schema": {
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "文件的相对路径"
                },
                "old_text": {
                    "type": "string",
                    "description": "要查找的精确文本(必须完全匹配)"
                },
                "new_text": {
                    "type": "string",
                    "description": "替换文本"
                },
            },
            "required": ["path", "old_text", "new_text"],
        },
    },
]


# =============================================================================
# 工具实现
# =============================================================================

def safe_path(p: str) -> Path:
    """
    确保路径保持在工作区内(安全措施)。

    防止模型访问项目目录之外的文件。
    解析相对路径并检查它们不会通过 '../' 逃脱。
    """
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"路径逃离工作区:{p}")
    return path


def run_bash(command: str) -> str:
    """
    执行带有安全检查的 shell 命令。

    安全性:阻止明显危险的命令。
    超时:60 秒以防止挂起。
    输出:截断到 50KB 以防止上下文溢出。
    """
    # 基本安全 - 阻止危险的模式
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "错误:危险命令已阻止"

    try:
        result = subprocess.run(
            command,
            shell=True,
            cwd=WORKDIR,
            capture_output=True,
            text=True,
            timeout=60
        )
        output = (result.stdout + result.stderr).strip()
        return output[:50000] if output else "(无输出)"

    except subprocess.TimeoutExpired:
        return "错误:命令超时(60秒)"
    except Exception as e:
        return f"错误:{e}"


def run_read(path: str, limit: int = None) -> str:
    """
    读取文件内容,可选行限制。

    对于大文件,使用 limit 只读取前 N 行。
    输出截断到 50KB 以防止上下文溢出。
    """
    try:
        text = safe_path(path).read_text()
        lines = text.splitlines()

        if limit and limit < len(lines):
            lines = lines[:limit]
            lines.append(f"...(还有 {len(text.splitlines()) - limit} 行)")

        return "\n".join(lines)[:50000]

    except Exception as e:
        return f"错误:{e}"


def run_write(path: str, content: str) -> str:
    """
    写入内容到文件,需要时创建父目录。

    这用于完全创建/覆盖文件。
    对于部分编辑,改用 edit_file。
    """
    try:
        fp = safe_path(path)
        fp.parent.mkdir(parents=True, exist_ok=True)
        fp.write_text(content)
        return f"写入 {len(content)} 字节到 {path}"

    except Exception as e:
        return f"错误:{e}"


def run_edit(path: str, old_text: str, new_text: str) -> str:
    """
    替换文件中的精确文本(手术式编辑)。

    使用精确字符串匹配 - old_text 必须逐字出现。
    只替换第一次出现,以防止意外的批量更改。
    """
    try:
        fp = safe_path(path)
        content = fp.read_text()

        if old_text not in content:
            return f"错误:在 {path} 中未找到文本"

        # 为安全起见只替换第一次出现
        new_content = content.replace(old_text, new_text, 1)
        fp.write_text(new_content)
        return f"已编辑 {path}"

    except Exception as e:
        return f"错误:{e}"


def execute_tool(name: str, args: dict) -> str:
    """
    将工具调用分派到适当的实现。

    这是模型工具调用和实际执行之间的桥梁。
    每个工具返回一个字符串结果,返回给模型。
    """
    if name == "bash":
        return run_bash(args["command"])
    if name == "read_file":
        return run_read(args["path"], args.get("limit"))
    if name == "write_file":
        return run_write(args["path"], args["content"])
    if name == "edit_file":
        return run_edit(args["path"], args["old_text"], args["new_text"])
    return f"未知工具:{name}"


# =============================================================================
# 智能体循环 - 这是一切的核心
# =============================================================================

def agent_loop(messages: list) -> list:
    """
    完整的智能体在一个函数中。

    这是所有编码智能体共享的模式:

        while True:
            response = model(messages, tools)
            if no tool calls: return
            execute tools, append results, continue

    模型控制循环:
      - 持续调用工具直到 stop_reason != "tool_use"
      - 结果成为上下文(作为"用户"消息反馈)
      - 记忆是自动的(消息列表累积历史)

    为什么这有效:
      1. 模型决定使用哪些工具、按什么顺序、何时停止
      2. 工具结果为下一个决策提供反馈
      3. 对话历史跨轮次保持上下文
    """
    while True:
        # 步骤 1:调用模型
        response = client.messages.create(
            model=MODEL,
            system=SYSTEM,
            messages=messages,
            tools=TOOLS,
            max_tokens=8000,
        )

        # 步骤 2:收集任何工具调用并打印文本输出
        tool_calls = []
        for block in response.content:
            if hasattr(block, "text"):
                print(block.text)
            if block.type == "tool_use":
                tool_calls.append(block)

        # 步骤 3:如果没有工具调用,任务完成
        if response.stop_reason != "tool_use":
            messages.append({"role": "assistant", "content": response.content})
            return messages

        # 步骤 4:执行每个工具并收集结果
        results = []
        for tc in tool_calls:
            # 显示正在执行什么
            print(f"\n> {tc.name}: {tc.input}")

            # 执行并显示结果预览
            output = execute_tool(tc.name, tc.input)
            preview = output[:200] + "..." if len(output) > 200 else output
            print(f"  {preview}")

            # 为模型收集结果
            results.append({
                "type": "tool_result",
                "tool_use_id": tc.id,
                "content": output,
            })

        # 步骤 5:追加到对话并继续
        # 注意:我们追加助手的响应,然后是用户的工具结果
        # 这保持了交替的用户/助手模式
        messages.append({"role": "assistant", "content": response.content})
        messages.append({"role": "user", "content": results})


# =============================================================================
# 主 REPL
# =============================================================================

def main():
    """
    用于交互使用的简单读取-求值-打印循环。

    历史列表跨轮次保持对话上下文,
    允许具有记忆的多轮对话。
    """
    print(f"Mini Claude Code v1 - {WORKDIR}")
    print("键入 'exit' 退出。\n")

    history = []

    while True:
        try:
            user_input = input("你:").strip()
        except (EOFError, KeyboardInterrupt):
            break

        if not user_input or user_input.lower() in ("exit", "quit", "q"):
            break

        # 将用户消息添加到历史
        history.append({"role": "user", "content": user_input})

        try:
            # 运行智能体循环
            agent_loop(history)
        except Exception as e:
            print(f"错误:{e}")

        print()  # 轮次之间的空行


if __name__ == "__main__":
    main()

模型即智能体。这就是全部秘密。

← 返回 README | 下一个:v2 →

评论 (0)

暂无评论,快来抢沙发吧!

91学AI

© 2026 91学AI · 按岗位学 AI 与大数据. All rights reserved.