Claude Agent 实战:多步骤工具调用,就是一个 while 循环

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

“AI Agent"听着挺唬人,但拆开看,核心就是一个 while 循环:发消息 → Claude 说"我要调这个工具” → 你执行 → 把结果发回去 → 重复,直到 Claude 说"我完事了"。这篇文章把这层窗户纸捅破。

在这里插入图片描述

一、先说结论:Agent 没什么神秘的

我在香港做 FinTech,最近在把一个"查数据、算指标、出报告"的活儿交给 Claude 自动跑。研究了一圈所谓的"Agent 框架",最后发现——Agent 的底层,就是一个 while 循环加一个 stop_reason 信号。

拆开看,就三步:

  1. 发消息给 Claude,带上工具定义
  2. stop_reason:如果是 tool_use,说明 Claude 要调用工具,你执行并把结果发回去
  3. 重复,直到 stop_reason 变成 end_turn(Claude 说"完成了")

没有魔法,没有黑箱。这篇文章我把这个循环、stop_reason 信号、以及我实际踩过的三个坑,一次性讲清楚。核心结论先放这:Agent 的价值不在循环本身,而在你给它的工具和循环里那些不起眼的细节(比如漏加一条消息就能让整个 Agent 抽风)。

收藏提示①:文末的 Agent Loop 模板可以直接复用,换上你的工具就能跑。

二、环境信息

版本
Python3.13.12
anthropic SDK最新版
模型claude-sonnet-4-6(示例用)
核心概念stop_reasontool_use / end_turn)+ tool_use 循环

三、stop_reason 是唯一的"信号灯"

整个 Agent 循环,全靠 stop_reason 这个字段驱动:

stop_reason含义你该做什么
tool_useClaude 想调用工具执行工具,把结果发回去,继续循环
end_turnClaude 完成了返回最终文本,结束循环
其他(max_tokens 等)意外情况记录并处理,别当完成

关键点在于:判断"Claude 是不是做完了",只能看 stop_reason,不能看返回内容是不是文本。 这个坑下面会重点讲。

四、完整的 Agent Loop

下面是核心模式,while True + stop_reason 判断:

import anthropic
import json

client = anthropic.Anthropic()

# 工具定义:让 Claude 能查产品信息
tools = [
    {
        "name": "search_products",
        "description": "按关键词搜索产品库",
        "input_schema": {
            "type": "object",
            "properties": {"query": {"type": "string"}},
            "required": ["query"],
        },
    },
    {
        "name": "get_product_detail",
        "description": "按 ID 获取产品详情",
        "input_schema": {
            "type": "object",
            "properties": {"product_id": {"type": "string"}},
            "required": ["product_id"],
        },
    },
]

def execute_tool(name: str, args: dict) -> str:
    """执行工具,返回结果字符串"""
    if name == "search_products":
        return json.dumps([{"id": "P001", "name": "Widget Pro", "price": 29.99}])
    elif name == "get_product_detail":
        return json.dumps({"id": args["product_id"], "name": "Widget Pro", "stock": 142})
    return json.dumps({"error": "unknown tool"})

def run_agent(user_input: str, max_steps: int = 10) -> str:
    messages = [{"role": "user", "content": user_input}]

    for _ in range(max_steps):          # 安全上限,不是终止信号
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=4096,
            tools=tools,
            system="你是产品查询助手,用工具回答用户问题。",
            messages=messages,
        )

        # 关键:先 append Claude 的响应,再 append 工具结果
        messages.append({"role": "assistant", "content": response.content})

        if response.stop_reason == "end_turn":
            return "".join(b.text for b in response.content if hasattr(b, "text"))

        if response.stop_reason == "tool_use":
            tool_results = []
            for block in response.content:
                if block.type == "tool_use":
                    result = execute_tool(block.name, block.input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,   # 关键:把结果链接回工具调用
                        "content": result,
                    })
            messages.append({"role": "user", "content": tool_results})

    return "达到最大步数,未完成"   # 安全兜底

print(run_agent("帮我查一下 Widget Pro 的库存"))

这套代码跑起来,Claude 会先调 search_products 找到产品 ID,再调 get_product_detail 查库存,最后用文本回答你——这就是一个多步骤 Agent 的完整闭环

收藏提示②:整个循环就记住三件事——stop_reason 判断状态、tool_use_id 链接结果、先 append assistant 响应再 append 工具结果。这三件事对了,Agent 就稳了。

在这里插入图片描述

上图:发消息 → 判断 stop_reason → 左边 tool_use(执行工具、发回结果、回到顶部循环)→ 右边 end_turn(返回文本、结束)。这就是 Agent 的全部骨架。

五、三个坑:踩过才知道有多隐蔽

这三个坑,每个都能让你的 Agent 悄悄抽风,而且报错还看不出来。

坑 1:漏加 assistant message

很多人写循环时,只 append 工具结果,忘了先 append Claude 自己的响应:

# ❌ 错误:直接 append 工具结果
messages.append({"role": "user", "content": tool_results})

# ✅ 正确:先 append Claude 的响应,再 append 工具结果
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})

Claude 需要看到自己上一步的响应,才能把工具结果链接回它之前发起的工具调用。漏了这条,会出现"答非所问"甚至死循环。

坑 2:用内容类型当终止条件

# ❌ 错误:检查返回内容是不是文本
if response.content[0].type == "text":
    return response.content[0].text

# ✅ 正确:看 stop_reason
if response.stop_reason == "end_turn":
    return ...

为什么错?因为 Claude 可能在返回 tool_use同时也带了一段文本(比如"我来帮你查一下")。如果你检查 content[0].type == "text",就会在工具还没执行时就提前终止——Agent 干一半就停了。

坑 3:把迭代上限当主要终止条件

for _ in range(max_steps)安全网,不是完成信号。把上限当终止条件,要么任务干到一半被砍(上限设太小),要么死循环烧钱(上限设太大)。真正的完成信号只有一个:stop_reason == "end_turn"

收藏提示③:这三个坑的根子都是同一件事——Agent 的状态判断只信 stop_reason,不信内容、不信计数。把这个原则刻进脑子,能少踩一堆坑。

在这里插入图片描述

上图:三个坑的"错误 vs 正确"一目了然——红色是坑,绿色是正确做法。

六、安全措施:别让 Agent 脱缰

Agent 会犯错的,而且犯错方式很朴素:选错工具、死循环、误读结果。三条护栏:

  1. 硬性上限:设 max_steps 和 token 预算,触发就停,标记人工复核。
  2. 工具入参校验:执行工具前检查输入是否合理,空 URL、非法 ID 直接挡下。
  3. 人机检查点:凡是写数据库、发邮件、调外部 API 的,加一道人工确认,Agent 只负责"提案"。

这三条不性感,但决定了 Agent 是"跑起来就崩"还是"能稳定服役"。

七、写在最后

写完这个循环,我对"Agent"这个词彻底祛魅了——它就是一个会调用工具的循环,技术上没有半点神秘。真正的难点从来不在循环,而在:你给 Claude 什么工具、工具描述写得清不清楚、循环里那些细节对不对。

这也是为什么那些动辄吹"通用 Agent"的东西容易翻车,而一个"只干查数据这一件事、但工具定义精确、循环细节到位"的小 Agent 反而能稳定出活。

需要说明的是,stop_reason 的取值、SDK 接口这些会随版本演进,本文以写作时的官方文档为准。如果你正想把重复的活儿交给 Claude 自动跑,这个循环是你绕不开的第一课。

收藏提示④:如果这篇帮你把 Agent 祛魅了,收藏 + 点赞,下次写 Agent 时直接回来抄这个循环模板。

在这里插入图片描述

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值