【与我学 ClaudeCode】工具与执行篇:从 0 到 1 拆解 Agent Loop 与 Tool Use 的极简设计哲学

作者:逆境不可逃

技术永无止境

希望我的内容可以帮助到你!!!!!


大家吼 ! 我是 逆境不可逃 今天给大家带来文章《【与我学 ClaudeCode】工具与执行篇:从 0 到 1 拆解 Agent Loop 与 Tool Use 的极简设计哲学》.

Learn-Claude-Code 官方地址 : https://github.com/shareAI-lab/learn-claude-code


开篇:为什么 ClaudeCode 的 Agent 能跑起来?

很多人聊 AI Agent,张口就是 "规划框架""状态机""多智能体编排",仿佛不堆砌复杂组件就做不出能干活的 Agent。但当你扒开 ClaudeCode 的底层设计会发现:它的核心逻辑,用不到 30 行代码就能跑通;它的核心能力,只靠一个循环 + 几个工具就实现了。

这篇我们就从 ClaudeCode 的v0版本设计出发,拆解它最底层的两个核心模块 ------Agent Loop(智能体循环)Tool Use(工具使用),搞懂为什么 "一个循环 + Bash = 一个可用的 Agent",以及它是如何一步步扩展到 4 个核心工具,支撑起完整的编程任务的。


一、Agent Loop:模型与真实世界的第一道桥梁

1. 问题:为什么 LLM 不能直接 "干活"?

语言模型天生擅长推理代码、理解需求,但它有个致命缺陷 ------它碰不到真实世界

  • 它没法直接读本地文件、修改代码;
  • 没法运行测试、查看报错日志;
  • 更没法根据执行结果调整后续动作。

没有循环之前,这些操作都得你手动完成:模型输出命令→你复制到终端执行→把结果粘回对话里→模型再根据结果继续推理。你,就是那个人肉循环。

2. 核心解法:极简 Agent 循环,30 行代码跑通端到端

ClaudeCode 的 Agent 循环,核心逻辑就是 "模型思考→工具执行→结果反馈→再思考" 的闭环,直到模型不再调用工具为止。

我们直接看它的完整实现(和官方设计完全对齐):

复制代码
def agent_loop(query):
    # 1. 初始化对话上下文,用户prompt作为第一条消息
    messages = [{"role": "user", "content": query}]
    while True:
        # 2. 调用LLM,传入对话历史、系统提示和工具定义
        response = client.messages.create(
            model=MODEL, system=SYSTEM, messages=messages,
            tools=TOOLS, max_tokens=8000,
        )
        # 3. 追加模型的响应到对话历史
        messages.append({"role": "assistant", "content": response.content})

        # 4. 关键退出条件:如果模型不再调用工具,直接结束循环
        if response.stop_reason != "tool_use":
            return

        # 5. 执行所有工具调用,收集结果
        results = []
        for block in response.content:
            if block.type == "tool_use":
                # 执行Bash命令(后续会扩展为工具分发)
                output = run_bash(block.input["command"])
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": output,
                })
        # 6. 把工具执行结果作为用户消息,追加到对话历史,开启下一轮循环
        messages.append({"role": "user", "content": results})

这个循环的设计,完美契合了 ClaudeCode 的核心哲学:最小可行 Agent,一个工具 + 一个循环

3. 设计决策:为什么仅靠 Bash 就够了?

v0版本中,ClaudeCode 只提供了 Bash 这一个工具,背后的设计思考非常值得学习:

  • 能力全覆盖 :Bash 能读写文件、运行任意程序、在进程间传递数据、管理文件系统,任何额外的read_file/write_file工具,都只是 Bash 能力的子集;
  • 降低模型负担:模型只需要学习一个工具的 Schema,不用在多个工具中做选择,出错概率更低;
  • 聚焦核心逻辑:循环本身是 Agent 的灵魂,过早引入复杂工具集,会掩盖 "带 Shell 的 LLM 本身就是通用 Agent" 这个核心洞察。

4.Agent Loop 阶段完整代码

复制代码
#!/usr/bin/env python3
# Harness: the loop -- the model's first connection to the real world.
"""
s01_agent_loop.py - The Agent Loop
The entire secret of an AI coding agent in one pattern:
    while stop_reason == "tool_use":
        response = LLM(messages, tools)
        execute tools
        append results
    +----------+      +-------+      +---------+
    |   User   | ---> |  LLM  | ---> |  Tool   |
    |  prompt  |      |       |      | execute |
    +----------+      +---+---+      +----+----+
                          ^               |
                          |   tool_result |
                          +---------------+
                          (loop continues)
This is the core loop: feed tool results back to the model
until the model decides to stop. Production agents layer
policy, hooks, and lifecycle controls on top.
"""
# ==================== 导入依赖库 ====================
import os
import subprocess  # 用于执行系统命令(bash)
try:
    # 处理终端输入,修复macOS中文/退格问题
    import readline
    readline.parse_and_bind('set bind-tty-special-chars off')
    readline.parse_and_bind('set input-meta on')
    readline.parse_and_bind('set output-meta on')
    readline.parse_and_bind('set convert-meta off')
    readline.parse_and_bind('set enable-meta-keybindings on')
except ImportError:
    pass

# 接入Anthropic Claude官方SDK
from anthropic import Anthropic
# 加载.env环境变量(API KEY、模型ID等)
from dotenv import load_dotenv
load_dotenv(override=True)

# ==================== 初始化Claude客户端 ====================
# 如果配置了自定义base_url,清除多余的认证变量(兼容代理/本地部署)
if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

# 创建LLM客户端
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))

# 从环境变量读取模型ID(如claude-3-5-sonnet-20241022)
MODEL = os.environ["MODEL_ID"]

# 系统提示词:告诉模型它是一个编码智能体,只做不说
SYSTEM = f"You are a coding agent at {os.getcwd()}. Use bash to solve tasks. Act, don't explain."

# ==================== 定义工具:仅Bash(s01版本核心) ====================
TOOLS = [{
    "name": "bash",
    "description": "Run a shell command.",
    # 工具入参格式:必须传入command字符串
    "input_schema": {
        "type": "object",
        "properties": {"command": {"type": "string"}},
        "required": ["command"],
    },
}]

# ==================== Bash执行函数:安全+封装 ====================
def run_bash(command: str) -> str:
    """
    安全执行bash命令,防止危险操作,捕获输出和错误
    """
    # 黑名单:禁止执行高危命令
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    
    try:
        # 执行shell命令,捕获标准输出+标准错误
        r = subprocess.run(
            command,
            shell=True,
            cwd=os.getcwd(),        # 在当前目录执行
            capture_output=True,    # 捕获输出
            text=True,              # 以字符串返回
            timeout=120             # 超时2分钟
        )
        # 合并输出并裁剪长度,防止token爆炸
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"
    
    # 异常处理
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"
    except (FileNotFoundError, OSError) as e:
        return f"Error: {e}"

# ==================== 核心:Agent Loop(智能体循环) ====================
def agent_loop(messages: list):
    """
    整个Agent的灵魂:循环调用LLM → 执行工具 → 返回结果 → 继续循环
    直到模型停止调用工具
    """
    while True:
        # 1. 调用Claude模型,传入对话历史+工具
        response = client.messages.create(
            model=MODEL,
            system=SYSTEM,
            messages=messages,
            tools=TOOLS,
            max_tokens=8000,
        )

        # 2. 把模型的回复加入对话历史
        messages.append({"role": "assistant", "content": response.content})

        # 3. 退出条件:如果模型没有调用工具,直接结束循环
        if response.stop_reason != "tool_use":
            return

        # 4. 模型调用了工具 → 逐条执行
        results = []
        for block in response.content:
            if block.type == "tool_use":
                # 黄色打印执行的命令(终端显示)
                print(f"\033[33m$ {block.input['command']}\033[0m")
                # 执行bash命令
                output = run_bash(block.input["command"])
                # 打印前200字符输出
                print(output[:200])
                # 组装工具执行结果(必须带tool_use_id)
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": output
                })

        # 5. 把工具结果作为用户消息传回LLM,进入下一轮循环
        messages.append({"role": "user", "content": results})

# ==================== 主程序:用户交互入口 ====================
if __name__ == "__main__":
    # 全局对话历史
    history = []
    while True:
        try:
            # 蓝色提示符
            query = input("\033[36ms01 >> \033[0m")
        except (EOFError, KeyboardInterrupt):
            break
        
        # 输入q/exit退出程序
        if query.strip().lower() in ("q", "exit", ""):
            break
        
        # 把用户问题加入历史
        history.append({"role": "user", "content": query})
        
        # 启动Agent循环
        agent_loop(history)
        
        # 打印模型最终回答
        response_content = history[-1]["content"]
        if isinstance(response_content, list):
            for block in response_content:
                if hasattr(block, "text"):
                    print(block.text)
        print()
  • Agent Loop 只有一个 while True,是整个智能体的大脑
  • 退出条件只有一个stop_reason != "tool_use"
  • 对话历史不断累积:用户提问 → 模型思考 → 工具结果 → 模型再思考
  • 工具只有 bash ,完美体现 One loop & Bash is all you need
  • 安全机制:命令黑名单、超时限制、输出截断
  • 完全可扩展:后面加工具只需要加 handler,循环完全不变

二、Tool Use:不修改循环,优雅扩展模型能力

1. 问题:为什么后来又从 1 个工具扩展到 4 个?

只用 Bash 虽然能跑,但实际用下来有很多痛点:

  • cat读取大文件时截断不可预测;
  • sed处理特殊字符时极易出错;
  • 所有操作都走 Shell,安全边界不可控,容易出现路径逃逸问题。

这时候就需要引入专用工具,但 ClaudeCode 的设计原则是:加工具,不改循环

2. 核心解法:工具分发器,用字典替代硬编码

ClaudeCode 用一个TOOL_HANDLERS字典实现了工具的优雅扩展,循环本身完全不用修改:

复制代码
# 工具分发映射:工具名 → 处理函数
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"]),
}

# 循环中执行工具的逻辑(和v0版本相比,只改了工具调用部分)
for block in response.content:
    if block.type == "tool_use":
        # 按工具名查找对应的处理函数
        handler = TOOL_HANDLERS.get(block.name)
        output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
        results.append({
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": output,
        })

3. 四个核心工具:覆盖 95% 编程任务的极简组合

ClaudeCode 最终选择了 4 个工具,而非更多,背后的设计逻辑非常清晰:

工具 核心能力 解决的问题
bash 执行任意命令、运行程序、管理进程 通用操作,处理复杂命令和程序运行
read_file 带行号的精确文件读取 避免cat截断问题,精准读取文件内容
write_file 创建 / 覆盖文件内容 避免 Bash 重定向的编码、权限问题
edit_file 精确字符串替换 避免sed/awk处理特殊字符的错误

为什么是这四个?

  • 覆盖了约 95% 的编程任务,没有冗余工具;
  • 工具越少,模型的认知负担越轻,选错工具的概率越低;
  • 减少了需要维护的 Schema 和边界情况,系统更稳定。

4. 关键安全设计:路径沙箱,防止工作区逃逸

在专用工具中,ClaudeCode 引入了safe_path机制,彻底解决了 Bash 的安全隐患:

复制代码
def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    # 检查路径是否在工作区内,防止../等路径逃逸
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

# 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]

5. 设计决策:为什么不用更复杂的编排框架?

ClaudeCode 的工具设计,和市面上很多 Agent 框架形成了鲜明对比:

  • 其他框架 :依赖ReAct循环、Thought/Action/Observation解析、LangChain 式链式编排,假设模型需要框架 "脚手架" 才能当 Agent;
  • ClaudeCode:认为模型本身已经知道如何当 Agent,它只需要工具来和世界交互,不需要框架定义它的行为。

这也是为什么 ClaudeCode 的工具系统没有引入规划器、任务队列、状态机 ------模型的思维链本身就是计划,循环只是不断询问模型下一步做什么

6.Tool Use 阶段完整代码

复制代码
#!/usr/bin/env python3
# Harness: tool dispatch -- expanding what the model can reach.
"""
s02_tool_use.py - Tools
The agent loop from s01 didn't change. We just added tools to the array
and a dispatch map to route calls.
    +----------+      +-------+      +------------------+
    |   User   | ---> |  LLM  | ---> | Tool Dispatch    |
    |  prompt  |      |       |      | {                |
    +----------+      +---+---+      |   bash: run_bash |
                          ^          |   read: run_read |
                          |          |   write: run_wr  |
                          +----------+   edit: run_edit |
                          tool_result| }                |
                                     +------------------+
Key insight: "The loop didn't change at all. I just added tools."
"""
# ==================== 1. 导入依赖库 ====================
import os
import subprocess  # 执行bash命令
from pathlib import Path  # 处理文件路径(安全路径核心)
from anthropic import Anthropic  # Claude官方SDK
from dotenv import load_dotenv  # 加载环境变量

# 加载.env配置文件
load_dotenv(override=True)

# 如果配置了自定义接口地址,清除冲突的认证变量
if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

# ==================== 2. 全局配置初始化 ====================
# 定义工作目录:所有文件操作都限制在这个目录内(安全沙箱)
WORKDIR = Path.cwd()
# 初始化Claude客户端
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
# 从环境变量获取模型ID
MODEL = os.environ["MODEL_ID"]
# 系统提示词:定义Agent身份,只执行不解释
SYSTEM = f"You are a coding agent at {WORKDIR}. Use tools to solve tasks. Act, don't explain."

# ==================== 3. 核心安全机制:路径沙箱 ====================
def safe_path(p: str) -> Path:
    """
    安全路径校验:防止模型通过 ../ 等方式逃逸到工作目录外
    这是Agent的核心安全防线
    """
    # 拼接绝对路径并标准化
    path = (WORKDIR / p).resolve()
    # 校验路径是否在允许的工作目录内
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

# ==================== 4. 工具执行函数(4大核心工具) ====================
def run_bash(command: str) -> str:
    """
    工具1:执行bash命令
    带安全黑名单、超时、输出限制
    """
    # 高危命令黑名单
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"

    try:
        # 执行命令,捕获输出和错误
        r = subprocess.run(
            command, shell=True, cwd=WORKDIR,
            capture_output=True, text=True, timeout=120
        )
        out = (r.stdout + r.stderr).strip()
        # 限制输出长度,防止token爆炸
        return out[:50000] if out else "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"

def run_read(path: str, limit: int = None) -> str:
    """
    工具2:读取文件内容
    支持行数限制,自动截断超长文件,比cat更稳定
    """
    try:
        text = safe_path(path).read_text()
        lines = text.splitlines()
        # 如果设置了行数限制,进行截断
        if limit and limit < len(lines):
            lines = lines[:limit] + [f"... ({len(lines) - limit} more lines)"]
        return "\n".join(lines)[:50000]
    except Exception as e:
        return f"Error: {e}"

def run_write(path: str, content: str) -> str:
    """
    工具3:写入文件(新建/覆盖)
    自动创建父目录,安全路径校验
    """
    try:
        fp = safe_path(path)
        fp.parent.mkdir(parents=True, exist_ok=True)  # 自动创建文件夹
        fp.write_text(content)
        return f"Wrote {len(content)} bytes to {path}"
    except Exception as e:
        return f"Error: {e}"

def run_edit(path: str, old_text: str, new_text: str) -> str:
    """
    工具4:精确编辑文件(替换指定文本)
    必须精准匹配原文,避免sed乱改文件
    """
    try:
        fp = safe_path(path)
        content = fp.read_text()
        # 必须找到原文才允许替换,防止误修改
        if old_text not in content:
            return f"Error: Text not found in {path}"
        fp.write_text(content.replace(old_text, new_text, 1))  # 只替换第一次出现的内容
        return f"Edited {path}"
    except Exception as e:
        return f"Error: {e}"

# ==================== 5. 核心设计:工具分发字典 ====================
# 工具名 -> 执行函数 的映射表
# 【关键】加新工具只需要在这里加一行,循环完全不用改!
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"]),
}

# ==================== 6. 工具定义(给LLM看的Schema) ====================
# 告诉Claude模型:有哪些工具、参数是什么
TOOLS = [
    {
        "name": "bash",
        "description": "Run a shell command.",
        "input_schema": {
            "type": "object",
            "properties": {"command": {"type": "string"}},
            "required": ["command"]
        }
    },
    {
        "name": "read_file",
        "description": "Read file contents.",
        "input_schema": {
            "type": "object",
            "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}},
            "required": ["path"]
        }
    },
    {
        "name": "write_file",
        "description": "Write content to file.",
        "input_schema": {
            "type": "object",
            "properties": {"path": {"type": "string"}, "content": {"type": "string"}},
            "required": ["path", "content"]
        }
    },
    {
        "name": "edit_file",
        "description": "Replace exact text in file.",
        "input_schema": {
            "type": "object",
            "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}},
            "required": ["path", "old_text", "new_text"]
        }
    },
]

# ==================== 7. Agent Loop(和s01完全一样!) ====================
def agent_loop(messages: list):
    """
    核心循环:和s01没有任何区别!
    这就是ClaudeCode的设计精髓:循环不变,只扩展工具
    """
    while True:
        # 1. 调用LLM
        response = client.messages.create(
            model=MODEL, system=SYSTEM, messages=messages,
            tools=TOOLS, max_tokens=8000,
        )
        # 2. 保存模型回复到对话历史
        messages.append({"role": "assistant", "content": response.content})

        # 3. 退出条件:模型不再调用工具
        if response.stop_reason != "tool_use":
            return

        # 4. 执行工具:通过分发字典查找对应函数
        results = []
        for block in response.content:
            if block.type == "tool_use":
                # 从字典中获取工具处理函数(核心改动点)
                handler = TOOL_HANDLERS.get(block.name)
                # 执行函数并传参
                output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
                
                # 打印执行日志
                print(f"> {block.name}:")
                print(output[:200])
                
                # 组装结果返回给LLM
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": output
                })
        # 5. 把工具结果加入对话历史,开启下一轮循环
        messages.append({"role": "user", "content": results})

# ==================== 8. 主程序:用户交互入口 ====================
if __name__ == "__main__":
    history = []  # 全局对话历史
    while True:
        try:
            query = input("\033[36ms02 >> \033[0m")  # 终端蓝色提示符
        except (EOFError, KeyboardInterrupt):
            break
        
        # 退出指令
        if query.strip().lower() in ("q", "exit", ""):
            break
        
        # 存入用户提问并启动Agent
        history.append({"role": "user", "content": query})
        agent_loop(history)
        
        # 打印模型最终回答
        response_content = history[-1]["content"]
        if isinstance(response_content, list):
            for block in response_content:
                if hasattr(block, "text"):
                    print(block.text)
        print()
  • 循环完全没变 agent_loop 函数和 Agent Loop 版 一模一样,证明循环是 Agent 的灵魂,永远不变

  • 工具分发字典(TOOL_HANDLERS) 用字典替代了硬编码 if-else加新工具 = 加一行代码,极致优雅。

  • 路径沙箱(safe_path) 所有文件操作都必须通过校验,彻底防止模型逃逸到系统目录,生产级安全设计。


三、Let's see 核心设计对比:从 v0 到 v1,我们到底改了什么?

组件 v0(仅 Bash) v1(四工具扩展) 核心优势
Agent Loop while True + stop_reason 完全不变 核心逻辑稳定,后续所有迭代都基于这个循环
工具数量 1 个(仅 Bash) 4 个(bash/read/write/edit) 覆盖更多场景,同时控制模型负担
工具调用 硬编码 Bash 调用 TOOL_HANDLERS字典分发 新增工具无需修改循环,扩展性极强
路径安全 safe_path()沙箱控制 防止工作区逃逸,提升安全性
模型交互 纯 Bash 命令 结构化 JSON Schema 调用 消除解析歧义,提升工具调用成功率

四、In the end:极简设计才是工程之美

ClaudeCode 的 Agent Loop 和 Tool Use 设计,给我们的最大启发,从来不是 "怎么写一个 Agent 循环",而是如何用极简的核心设计,支撑起复杂的能力扩展

  1. 循环是灵魂,永远不变:后续的子代理、规划功能,都是在这个循环之上叠加,而不是修改它;
  2. 工具是手脚,可插拔扩展:新增工具只需要加一个处理函数和 Schema,循环完全不用动;
  3. 模型是大脑,不被框架绑架:让模型自己决定做什么、怎么做,框架只提供和世界交互的能力。
相关推荐
red_redemption4 小时前
自由学习记录(188)
学习
cd_949217214 小时前
星思半导体:深耕芯片研发,助力卫星互联网产业高质量发展
网络·人工智能
我想我不够好。4 小时前
2025.5.18 2.5hour
学习
xiaoxiaoxiaolll4 小时前
Light首次发表:动量空间穆勒矩阵偏振测量,破解纳米手性结构表征难题
人工智能·算法
kishu_iOS&AI4 小时前
NLP —— Transformer底层源码剖析(解码器部分+输出)
人工智能·自然语言处理·transformer
迦南的迦 亚索的索4 小时前
机器学习_01_基础
人工智能·机器学习
百珏4 小时前
AI 应用技术演进串讲大纲
人工智能·后端·架构
工业机器人销售服务4 小时前
泡沫箱码垛(易碎),伯朗特机器人宽幅吸盘+低真空,吸气泡沫箱无压痕
人工智能
lally.4 小时前
CVE-2026-45727:CloakBrowser `cloakserve` 中由 `fingerprint` 引出的路径穿越与目录删除
人工智能·安全架构