【claude code 如何构建 agent 实践1】Agent Loop 永动机与工具扩展机制详解[S02]

Agent Loop 永动机与工具扩展机制详解

文章目录

  • [Agent Loop 永动机与工具扩展机制详解](#Agent Loop 永动机与工具扩展机制详解)

一、核心概念:Agent Loop是永动机

1.1 永动机的本质

Agent Loop是一个永不停止的循环,直到LLM决定停止为止。这个设计的精髓在于:

python 复制代码
# 核心永动机模式
while True:
    response = LLM(messages, tools)
    if response.stop_reason != "tool_use":
        return  # LLM决定停止,永动机停止
    execute tools
    append results  # 将结果反馈给LLM,继续循环

永动机的特点:

  • 自驱动: LLM自主决定是否继续
  • 🔄 闭环反馈: 工具结果反馈给LLM
  • 🛑 智能停止: LLM判断任务完成时主动停止
  • ♾️ 无限潜力: 理论上可以处理任意复杂任务

二、S01:基础永动机(单工具版本)

2.1 核心结构

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    S01: 基础Agent Loop                       │
│                   (Single Tool: bash)                        │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  用户输入: "创建一个test.txt文件并写入hello"                 │
└─────────────────────────────────────────────────────────────┘
                              ↓
                    ┌─────────────────┐
                    │   永动机启动    │
                    │ while True:     │
                    └─────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  第1轮迭代                                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ LLM调用: client.messages.create()                  │   │
│  │ Input: 用户请求 + SYSTEM提示                        │   │
│  │ Output: 决定调用bash工具                            │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  工具执行                                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ bash: echo "hello" > test.txt                       │   │
│  │ Output: "(no output)"                               │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  结果反馈                                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ messages.append({                                   │   │
│  │   "role": "user",                                   │   │
│  │   "content": [{"type": "tool_result", ...}]         │   │
│  │ })                                                  │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  第2轮迭代                                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ LLM调用: 收到工具结果,决定是否继续                  │   │
│  │ 判断: 任务已完成,不需要更多工具调用                  │   │
│  │ stop_reason: "end_turn" (不是"tool_use")            │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  永动机停止                                                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ if response.stop_reason != "tool_use":              │   │
│  │     return  # 退出循环,任务完成                    │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

2.2 S01代码分析

关键代码段:

python 复制代码
def agent_loop(messages: list):
    while True:  # ← 永动机核心
        response = client.messages.create(
            model=MODEL, system=SYSTEM, messages=messages,
            tools=TOOLS, max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})
        
        # 智能停止条件
        if response.stop_reason != "tool_use":
            return  # LLM决定停止
        
        # 工具执行(硬编码为bash)
        results = []
        for block in response.content:
            if block.type == "tool_use":
                print(f"\033[33m$ {block.input['command']}\033[0m")
                output = run_bash(block.input["command"])  # 硬编码bash
                print(output[:200])
                results.append({"type": "tool_result", "tool_use_id": block.id,
                                "content": output})
        messages.append({"role": "user", "content": results})

S01的限制:

  • ❌ 只有一个bash工具
  • ❌ 工具处理逻辑硬编码
  • ❌ 扩展性差,添加新工具需要修改核心循环

三、S02:工具扩展版(多工具版本)

3.1 核心演进:从硬编码到可扩展

关键变化:

python 复制代码
# S01: 硬编码工具处理
output = run_bash(block.input["command"])  # 只能处理bash

# S02: 可扩展工具分发
handler = TOOL_HANDLERS.get(block.name)  # 根据工具名称查找处理器
output = handler(**block.input) if handler else f"Unknown tool: {block.name}"

3.2 S02架构图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    S02: 扩展Agent Loop                       │
│              (Multiple Tools + Dispatch)                     │
└─────────────────────────────────────────────────────────────┘
                              ↓
                    ┌─────────────────┐
                    │   永动机启动    │
                    │ while True:     │
                    └─────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  工具定义层(可扩展)                                         │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ TOOLS = [                                          │   │
│  │   {"name": "bash", ...},                           │   │
│  │   {"name": "read_file", ...},                      │   │
│  │   {"name": "write_file", ...},                     │   │
│  │   {"name": "edit_file", ...},                      │   │
│  │ ]                                                  │   │
│  │                                                    │   │
│  │ TOOL_HANDLERS = {                                  │   │
│  │   "bash":       lambda **kw: run_bash(...),        │   │
│  │   "read_file":  lambda **kw: run_read(...),        │   │
│  │   "write_file": lambda **kw: run_write(...),       │   │
│  │   "edit_file":  lambda **kw: run_edit(...),        │   │
│  │ }                                                  │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  永动机核心(完全不变!)                                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ while True:                                         │   │
│  │     response = LLM(messages, tools)  ← 结构完全相同 │   │
│  │     if stop_reason != "tool_use":                   │   │
│  │         return                                      │   │
│  │     execute tools  ← 唯一变化点                    │   │
│  │     append results                                 │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│  工具分发层(新增)                                           │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ for block in response.content:                      │   │
│  │     if block.type == "tool_use":                    │   │
│  │         handler = TOOL_HANDLERS.get(block.name)     │   │
│  │         output = handler(**block.input)             │   │
│  │         results.append(tool_result)                 │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

3.3 S02详细流程图

复制代码
用户输入: "读取config.txt,将port改为8080,然后保存"
    ↓
┌─────────────────────────────────────────────────────────────┐
│  第1轮:LLM规划任务序列                                       │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ LLM分析: 需要三个步骤                                │   │
│  │ 1. read_file("config.txt") - 读取文件              │   │
│  │ 2. edit_file(...) - 修改port                       │   │
│  │ 3. (可能需要验证)                                   │   │
│  │                                                     │   │
│  │ 决定先调用: read_file                                │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│  工具分发                                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ block.name = "read_file"                           │   │
│  │ handler = TOOL_HANDLERS.get("read_file")            │   │
│  │    = lambda **kw: run_read(kw["path"], kw.get("limit"))│   │
│  │ output = handler(path="config.txt")                 │   │
│  │    = run_read("config.txt")                         │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│  read_file执行                                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ def run_read(path: str, limit: int = None) -> str: │   │
│  │     text = safe_path(path).read_text()              │   │
│  │     lines = text.splitlines()                       │   │
│  │     if limit and limit < len(lines):                │   │
│  │         lines = lines[:limit] + [...]               │   │
│  │     return "\n".join(lines)[:50000]                 │   │
│  │                                                     │   │
│  │ Output: "port=80\nhost=localhost\n..."             │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│  结果反馈给LLM                                               │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ messages.append({                                   │   │
│  │   "role": "user",                                   │   │
│  │   "content": [                                      │   │
│  │     {                                               │   │
│  │       "type": "tool_result",                        │   │
│  │       "tool_use_id": "toolu_01",                    │   │
│  │       "content": "port=80\nhost=localhost\n..."     │   │
│  │     }                                               │   │
│  │   ]                                                 │   │
│  │ })                                                  │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│  第2轮:LLM根据结果继续执行                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ LLM分析: 收到文件内容,发现port=80                   │   │
│  │ 目标: 改为8080                                      │   │
│  │ 决定调用: edit_file                                 │   │
│  │                                                     │   │
│  │ edit_file(                                          │   │
│  │   path="config.txt",                                │   │
│  │   old_text="port=80",                              │   │
│  │   new_text="port=8080"                             │   │
│  │ )                                                   │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│  工具分发                                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ block.name = "edit_file"                           │   │
│  │ handler = TOOL_HANDLERS.get("edit_file")            │   │
│  │    = lambda **kw: run_edit(...)                    │   │
│  │ output = handler(                                   │   │
│  │   path="config.txt",                                │   │
│  │   old_text="port=80",                              │   │
│  │   new_text="port=8080"                             │   │
│  │ )                                                   │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│  edit_file执行                                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ def run_edit(path: str, old_text: str, new_text: str):│  │
│  │     fp = safe_path(path)                            │   │
│  │     content = fp.read_text()                        │   │
│  │     if old_text not in content:                     │   │
│  │         return f"Error: Text not found"             │   │
│  │     fp.write_text(content.replace(old_text, new_text, 1))│  │
│  │     return f"Edited {path}"                         │   │
│  │                                                     │   │
│  │ Output: "Edited config.txt"                         │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│  第3轮:LLM判断任务完成                                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ LLM分析: 文件已成功修改                             │   │
│  │ 判断: 任务完成,无需更多工具调用                      │   │
│  │ stop_reason: "end_turn"                             │   │
│  │                                                     │   │
│  │ 返回文本响应: "已将config.txt中的port从80改为8080"  │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│  永动机停止                                                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ if response.stop_reason != "tool_use":              │   │
│  │     return  # 退出循环                              │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

四、核心要点:永动机与扩展的分离

4.1 永动机不变性

对比S01和S02的agent_loop:

python 复制代码
# S01的agent_loop
def agent_loop(messages: list):
    while True:                              # ← 相同
        response = client.messages.create(   # ← 相同
            model=MODEL, system=SYSTEM, messages=messages,
            tools=TOOLS, max_tokens=8000,
        )
        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"])  # S01硬编码
                # vs
                handler = TOOL_HANDLERS.get(block.name)     # S02可扩展
                output = handler(**block.input)            
                results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
        messages.append({"role": "user", "content": results})  # ← 相同

不变性统计:

  • ✅ 循环结构:100%相同
  • ✅ LLM调用:100%相同
  • ✅ 停止条件:100%相同
  • ✅ 消息管理:100%相同
  • ✅ 结果收集:100%相同
  • 🔄 工具执行:唯一变化点(从硬编码到可扩展)

4.2 扩展性实现

S02的扩展机制:

python 复制代码
# 1. 工具定义扩展
TOOLS = [
    {"name": "bash", "description": "Run a shell command.", ...},
    {"name": "read_file", "description": "Read file contents.", ...},
    {"name": "write_file", "description": "Write content to file.", ...},
    {"name": "edit_file", "description": "Replace exact text in file.", ...},
    # 添加新工具只需在这里添加定义
    # {"name": "new_tool", "description": "...", ...},
]

# 2. 工具处理器扩展
TOOL_HANDLERS = {
    "bash":       lambda **kw: run_bash(kw["command"]),
    "read_file":  lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":  lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
    # 添加新工具处理器只需在这里添加映射
    # "new_tool":  lambda **kw: run_new_tool(...),
}

# 3. 工具实现扩展
def run_new_tool(param1, param2):
    # 实现新工具的逻辑
    return result

4.3 扩展步骤演示

添加一个新工具的完整流程:

python 复制代码
# 步骤1: 定义工具实现
def run_search_files(pattern: str) -> str:
    """搜索匹配模式的文件"""
    try:
        matches = list(WORKDIR.rglob(pattern))
        return "\n".join(str(m.relative_to(WORKDIR)) for m in matches)
    except Exception as e:
        return f"Error: {e}"

# 步骤2: 添加工具处理器映射
TOOL_HANDLERS = {
    # ... 现有工具 ...
    "search_files": lambda **kw: run_search_files(kw["pattern"]),
}

# 步骤3: 添加工具定义
TOOLS = [
    # ... 现有工具 ...
    {
        "name": "search_files",
        "description": "Search for files matching a pattern.",
        "input_schema": {
            "type": "object",
            "properties": {
                "pattern": {"type": "string", "description": "File pattern to search for"}
            },
            "required": ["pattern"]
        }
    },
]

# 完成!永动机无需任何修改

五、架构层次图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                      应用层                                   │
│  用户交互、任务描述、结果展示                                 │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                   永动机层(不变)                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ while True:                                         │   │
│  │     response = LLM(messages, tools)                │   │
│  │     if stop_reason != "tool_use": return            │   │
│  │     execute tools via dispatch                     │   │
│  │     append results                                 │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  核心特点: 结构稳定,逻辑简单,无需修改                      │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                  工具分发层(扩展点)                        │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ TOOL_HANDLERS = {                                  │   │
│  │   "tool_name": handler_function,                   │   │
│  │ }                                                  │   │
│  │                                                    │   │
│  │ handler = TOOL_HANDLERS.get(tool_name)             │   │
│  │ output = handler(**tool_input)                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  核心特点: 动态分发,松耦合,易扩展                          │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                  工具实现层(可扩展)                        │
│  ┌──────────────────┐  ┌──────────────────┐                │
│  │   run_bash()     │  │  run_read()      │                │
│  ├──────────────────┤  ├──────────────────┤                │
│  │  run_write()     │  │  run_edit()      │                │
│  ├──────────────────┤  ├──────────────────┤                │
│  │  run_search()    │  │  run_...()       │                │
│  └──────────────────┘  └──────────────────┘                │
│                                                             │
│  核心特点: 独立实现,专注功能,易于测试                      │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                    系统层                                    │
│  文件系统、Shell执行、网络请求等                             │
└─────────────────────────────────────────────────────────────┘

六、设计模式分析

6.1 策略模式(Strategy Pattern)

工具分发就是策略模式的应用:

python 复制代码
# 策略接口
def handler(**kwargs) -> str:
    """所有工具处理器都遵循这个接口"""
    pass

# 具体策略
def run_bash(command: str) -> str:
    pass

def run_read(path: str, limit: int = None) -> str:
    pass

# 策略选择
handler = TOOL_HANDLERS.get(tool_name)
output = handler(**tool_input)

6.2 开闭原则(Open-Closed Principle)

对扩展开放,对修改封闭:

  • 开放扩展: 添加新工具无需修改永动机
  • 封闭修改: 永动机核心逻辑稳定不变
  • 低耦合: 工具与永动机通过接口解耦

6.3 单一职责原则(Single Responsibility Principle)

每层专注自己的职责:

  • 永动机层: 负责循环控制和消息管理
  • 分发层: 负责工具路由和参数传递
  • 实现层: 负责具体功能执行

七、实际应用场景

7.1 从S01到S02的能力提升

能力 S01 S02
Shell命令执行
文件读取
文件写入
文件编辑
代码分析 ✅ (组合使用)
项目重构 ✅ (组合使用)

7.2 典型任务对比

任务:分析一个Python项目并生成文档

S01的处理:

复制代码
用户: 分析这个Python项目并生成文档
LLM: 我只能用bash,让我试试...
bash: find . -name "*.py" | head -10
bash: cat file1.py | head -20
# ... 困难且有限

S02的处理:

复制代码
用户: 分析这个Python项目并生成文档
LLM: 我可以使用多个工具
1. read_file("main.py") - 读取主文件
2. read_file("utils.py") - 读取工具文件
3. write_file("docs.md", ...) - 生成文档
# ... 高效且完整

八、演进路径总结

复制代码
S01: 基础永动机
    ↓
    + 单工具 (bash)
    + 硬编码处理
    + 有限能力
    ↓
S02: 扩展永动机
    ↓
    + 多工具 (bash + read + write + edit)
    + 动态分发
    + 强大能力
    ↓
S03: 智能永动机 (todo_write)
    ↓
    + 自我跟踪 (TodoManager)
    + 进度管理
    + 状态感知
    ↓
S04: 子代理永动机 (subagent)
    ↓
    + 任务分解
    + 并行执行
    + 层次化

九、核心洞察

"The loop didn't change at all. I just added tools."

这个设计的精髓在于:

  1. 永动机是核心: Agent Loop是整个系统的心脏,保持稳定
  2. 工具是四肢: 通过工具扩展能力,不影响心脏
  3. 分发是神经: TOOL_HANDLERS连接心脏和四肢
  4. 扩展是进化: 添加新工具就像进化出新能力

9.1 设计哲学

python 复制代码
# 好的设计
永动机 = 稳定的核心循环
工具    = 灵活的能力扩展
分发    = 智能的路由机制

# 坏的设计
永动机 = 频繁修改的混乱代码
工具    = 硬编码的固定逻辑
分发    = 不存在的if-else地狱

9.2 实践建议

✅ 推荐做法:

  • 保持永动机简洁稳定
  • 通过工具扩展功能
  • 使用分发模式解耦
  • 每个工具职责单一

❌ 避免做法:

  • 在永动机中添加业务逻辑
  • 硬编码工具处理逻辑
  • 破坏分层架构
  • 让工具相互依赖

十、总结

S01到S02的演进展示了软件设计的核心原则:

  1. 发现稳定核心: Agent Loop就是那个稳定的核心
  2. 识别扩展点: 工具处理是自然的扩展点
  3. 设计扩展机制: TOOL_HANDLERS分发机制
  4. 保持核心不变: 永动机无需修改
  5. 通过扩展增强: 添加工具获得新能力

这种设计让系统既保持了简单性,又具备了强大的扩展性,是构建复杂系统的优秀范例。

关键要点:Agent Loop是永动机,工具是扩展。在永动机不改变的时候,只要实现扩展就能得到想要的结果。

下是您提供的相关文章的链接,可直接点击阅读:

相关推荐
布朗克1686 小时前
Claude Code 进阶玩法:MCP集成、自定义配置与团队协作
arcgis·claude code
qq_gpp1 天前
【Claude Code】Claude Code 的 Checkpoint 是怎么实现的
checkpoint·claude code·rewind
乾坤瞬间1 天前
【claude code agent 实践7】后台任务机制深度解析: 从S02到S08的演进
claude code·code agent
lunatic71 天前
Claude Code && Codex的安装方法
ai·codex·claude code
且去填词1 天前
Claude Code Skills 实践:从提示词到专业化 AI 开发工作流
人工智能·ai编程·开发·claude code·skills
dozenyaoyida1 天前
Claude Code Token 瓶颈破解:让 AI 编码永不中断的完整方案
token·上下文·claude code·ai记忆
乾坤瞬间2 天前
【claude code agent 实践3】Subagent子智能体机制深度解析:从S02到S04的演进
claude code·code agent
维元码簿2 天前
Claude Code 深度拆解:远程模式 4 — 无环境直连架构
ai·agent·claude code·ai coding