在很多实际项目中,智能体开发卡住的点往往不是“大模型会不会调用工具”,而是“当请求量上来之后,会话状态怎么管理、工具调用失败怎么处理、日志怎么观察、成本怎么控制”。Claude Managed Agents 提供的是另一条路径:把模型调用、工具调度、状态维护和运行环境这些通用复杂度托管起来,开发者把精力集中在业务定义上。这篇文章会围绕 Claude Managed Agents 搭建一个可运行的生产级智能体,从概念拆解、环境准备、最小实现、生产化改造,一直讲到排错思路和发布清单。
文章面向后端开发者、AI 应用开发者和正在从 Demo 走向生产的团队。读完以后,你能掌握一条从零搭建托管式智能体的完整链路:先理解托管和自建的分界,再跑通最小 Agent,再把缓存、重试、限流、日志和权限补上,最后建立一套可复用的排错和发布流程。
1. 理解 Managed Agents:它解决的不是模型调用,而是运行时问题
1.1 智能体的最小组成
一个智能体,本质上是一个由大模型驱动的执行系统。它和普通接口调用的区别在于:接口只做一次输入输出的映射,而智能体需要在一个任务目标下,反复观察环境、决定动作、调用工具、接收结果、再决定下一步。
从这个角度看,一个智能体至少包含四部分:
| 组成 | 作用 | 典型实现 |
|---|---|---|
| 指令系统 | 定义智能体的职责、边界、语气和约束 | system prompt / instructions |
| 工具集 | 让智能体获得与外部系统交互的入口 | 函数、API、MCP 工具 |
| 状态管理 | 保存多轮对话、任务进度、中间结果 | 会话上下文、内存数据库 |
| 执行循环 | 决定何时继续、何时终止、何时请求人类介入 | Agent runtime / orchestration loop |
自建智能体时,这四部分全部要自己实现。手写执行循环尤其麻烦,因为大模型返回的可能是一次普通回复,也可能是一个工具调用请求,还可能是要求用户确认的中间状态。处理不好,就会出现工具重复调用、上下文膨胀、死循环这类问题。
1.2 托管式智能体和手写 Agent 框架的分工
Claude Managed Agents 属于托管式智能体服务。它的核心思路是:智能体的执行循环、工具请求解析、会话状态存储、上下文压缩、错误恢复这些通用能力由平台负责,开发者通过配置和少量代码完成业务定义。
开发者在这个模式下通常只需要做三件事:
- 写清楚指令,告诉智能体它是什么角色、能做什么、不能做什么。
- 注册工具,把内部系统能力暴露成智能体可以调用的函数。
- 设置边界,包括 token 上限、工具权限、会话生命周期、评估方式。
为什么托管模式值得在生产环境考虑?因为智能体的故障形态和普通接口完全不同。普通接口超时,重试一次大概率成功;智能体超时或中断,可能已经把 A 订单退款了,还没来得及处理 B 订单。执行状态的可靠保存、暂停恢复和退出条件,是生产级智能体和 Demo 的分水岭。托管平台把这些状态问题抽象成标准能力,比自己从零写状态机要稳定得多。
1.3 与 Dify、Coze、自建 Agent 框架的差异
很多团队接触智能体是从 Dify、Coze 这类平台开始的。它们解决的是“可视化搭建”和“快速体验”的问题,适合原型验证。自建框架则适合需要对每一步执行链路做深度定制的团队,但维护成本高。
Claude Managed Agents 的定位更接近“用代码定义、由平台托管运行时”。它和 Dify、Coze 的关键差异在于,托管智能体的执行过程是透明、可控、可编程的:你可以通过配置和 SDK 精细控制工具的入参校验、调用结果注入、异常分支,而不是只能在图形画布里拖拽。
另一个差异在于评估和发布。生产级智能体必须回答“这次改动是变好了还是变坏了”。托管平台一般会提供可追溯的调用记录、评估集和版本对比能力,这是自建框架最重的工作量之一。
2. 生产级智能体的需求拆解:从 Demo 到上线缺的不是想象力
2.1 生产级四维:可靠性、可观测性、安全性、成本
生产级这个词容易变成口号。落到智能体场景,它至少要在四个维度上达标:
- 可靠性:智能体的输出不能是随机的。同一个问题在相同条件下应该得到一不致的结果;工具调用失败时要有明确降级路径。
- 可观测性:每一次用户请求、模型输出、工具调用、错误分支都要有日志和追踪。否则线上出了问题,只能对着一个“我怎么知道”的对话发呆。
- 安全性:工具权限要最小化,智能体只能调用它职责范围内的工具;敏感数据不能进入上下文;输出内容要经过校验。
- 成本:每一次工具调用循环都会产生 token 消耗。一个复杂的任务可能要经历多轮“模型返回工具请求—执行工具—回到模型”的过程,成本模型和普通 API 完全不同。
把这四个维度列出来之后就会发现,智能体上线并不是“把代码部署到一个服务器”就结束了,而是一整套包含配置、监控、评估、回滚在内的工程体系。
2.2 业务场景选择
为了有具体的讨论对象,本文使用一个订单客服智能体作为示例场景。这个场景覆盖了生产级智能体的主要技术点:
- 查询订单状态,属于只读工具调用。
- 发起退款申请,属于写入型工具,必须有权限校验和二次确认。
- 识别用户情绪并转接人工,属于异常分支处理。
- 多轮对话状态保存,复查“用户头一次问订单、第二次问退款到账时间”,需要上下文记忆。
所有代码示例都围绕这个场景展开。你不需要使用相同业务,只要把工具函数替换成自己的内部 API 即可。
2.3 环境准备和依赖安装
开发环境建议准备以下内容:
- Python 3.9 以上,能正常创建虚拟环境。
- 一个可用的 Anthropic API Key,并确认当前账号已开通相关模型和 Agent 能力。
- 需要安装的 SDK 包,在常见项目里使用
anthropic官方 SDK 即可。
python -m venv .venv source .venv/bin/activate pip install anthropic python-dotenv如果你的项目已经使用 Node.js,也可以使用对应 TypeScript SDK。下面代码统一使用 Python 演示,核心思路在两种语言之间是相通的。
注意:Claude Managed Agents 的能力边界和参数配置会随版本演进变化,落地前要先确认当前 SDK 版本和官方文档中的定义,不要照抄旧版本示例。
安装完成后,建议先写一个最小的环境检查脚本,确认 Key 和网络链路是通的:
from anthropic import Anthropic client = Anthropic() response = client.messages.create( model="claude-sonnet-4-5", max_tokens=64, messages=[{"role": "user", "content": "回复OK"}], ) print(response.content[0].text)这个脚本能排除环境变量、网络连通性和账号权限问题。如果这一步跑不通,后面所有智能体配置都没有意义。
2.4 项目结构设计
初学阶段不建议把所有逻辑写在一个文件里。生产级智能体项目至少要把配置、工具、智能体定义和入口分开。下面是一个推荐目录结构:
order_agent/ ├── .env ├── requirements.txt ├── config.py ├── tools/ │ ├── __init__.py │ ├── order.py │ └── refund.py ├── agent/ │ ├── __init__.py │ ├── definitions.py │ └── runtime.py ├── logs/ └── main.pyconfig.py统一读取环境变量,tools/order.py封装订单查询逻辑,agent/definitions.py放系统提示词和工具注册表,main.py只负责启动和调用。这样每个模块都能单独测试,问题定位时也不用翻一个大文件。
3. 用 Claude Managed Agents 搭建最小可运行智能体
3.1 创建客户端并配置模型
第一步是初始化客户端。这里把 API Key 从环境变量读取,不要在代码里硬编码:
import os from dotenv import load_dotenv from anthropic import Anthropic load_dotenv() client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY")) MODEL_NAME = os.getenv("MODEL_NAME", "claude-sonnet-4-5")把模型名也放进环境变量,切换模型时不需要改代码。生产环境通常会区分默认模型和复杂任务模型,提前空出这个配置位会省很多事。
3.2 工具定义与注册
订单查询工具是智能体最重要的能力。定义一个查询订单状态的函数,核心逻辑是调用内部订单服务:
def query_order_status(order_id: str) -> dict: """查询订单状态,返回订单信息。""" # 实际项目中替换为内部 API 调用 mock_db = { "202501010001": {"status": "shipped", "tracking_no": "SF123456"}, "202501010002": {"status": "refunded", "refund_amount": "99.00"}, } return mock_db.get(order_id, {"error": "order not found"})接下来把函数描述成模型可以理解的工具格式。工具定义的关键是写清楚参数含义和输出结构:
TOOLS = [ { "name": "query_order_status", "description": "根据订单号查询订单状态,返回订单当前状态和物流单号。", "input_schema": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,格式为 12 位数字", } }, "required": ["order_id"], }, } ]工具描述会影响模型是否调用工具以及调用的正确性。遇到“查订单”和“查物流”混在一起的问题,多半是工具描述写得太模糊,模型不知道该选哪个。
3.3 系统提示词设计
系统提示词决定了智能体的行为边界。对于订单客服智能体,推荐把角色、能力边界、必守规则和降级策略都写清楚:
SYSTEM_PROMPT = """ 你是订单客服智能体。你的职责是帮助用户查询订单状态和推进退款流程。 能力边界: - 只能查询订单状态和发起退款申请。 - 无法回答非订单问题,此时要礼貌地说明并引导用户联系人工客服。 必守规则: 1. 查询订单必须先获得订单号。 2. 发起退款前必须确认退款金额和退款原因。 3. 如果工具返回 error,要如实告知用户,不得编造状态。 4. 涉及退款等敏感操作时,主动转接人工确认。 输出要求: - 回答简洁,直接给结论。 - 不确定时不要猜测,使用可用工具核实。 """这里有两个关键设计。第一是“不得编造状态”,这是智能体生产化最常见的安全约束。没有这条规则,模型可能会在工具失败后凭想象给出一个订单状态。第二是“退款前必须确认”,这为后续写入型操作的二次确认机制打底。
3.4 完整调用示例
最小可运行案例需要一次完整的请求、工具调度和结果返回过程。在 Anthropic API 中,工具调用的基础流程是:发送带工具定义的请求,如果模型返回tool_use,执行工具,再把工具结果以tool_result角色回传,模型继续生成最终回答。
def run_agent(user_input: str, order_id: str = None): messages = [{"role": "user", "content": user_input}] response = client.messages.create( model=MODEL_NAME, system=SYSTEM_PROMPT, tools=TOOLS, max_tokens=1024, messages=messages, ) for block in response.content: if block.type == "text": return block.text if block.type == "tool_use": tool_result = query_order_status( order_id=block.input.get("order_id") ) messages.append( {"role": "user", "content": [ { "type": "tool_result", "tool_use_id": block.id, "content": str(tool_result), } ]} ) second = client.messages.create( model=MODEL_NAME, system=SYSTEM_PROMPT, tools=TOOLS, max_tokens=1024, messages=messages, ) return "".join( b.text for b in second.content if b.type == "text" )这段代码的作用是完成一轮“用户提问——模型决定调工具——执行工具——回传结果——生成回答”的闭环。它展示的是一次手动循环,并不是生产代码。托管托管模式下,执行循环可能由平台管理,但理解这个闭环对配置调试仍然重要。
3.5 运行结果验证
执行一次查询:
if __name__ == "__main__": answer = run_agent("帮我查一下订单 202501010001 现在到哪了", "202501010001") print(answer)预期结果应该包含“已发货”和物流单号。验证时不要只看有没有输出,还要检查:
- 模型是否调用了正确的工具。
- 返回信息是否与 mock 数据一致。
- 当传入一个不存在的订单号时,模型是否如实说“未找到”,而不是编造一个状态。
这三种验证分别覆盖正常链路、数据正确性和失败分支,是后续评估集的基础。
4. 生产化改造:缓存、重试、限流、日志和权限
4.1 日志和链路追踪
智能体调试最大的困难是链路长。一次用户请求可能包含多轮模型交互,每一轮都有输入 token、输出 token、工具调用次数、消耗时长。把这些信息串起来,才能回答“这个回答为什么慢”“这单为什么突然失败”。
推荐为每次请求生成一个请求 ID,所有日志都带上这个 ID:
import logging import uuid request_id = str(uuid.uuid4()) logger = logging.getLogger("agent") logger.info( "request_start", extra={"request_id": request_id, "user_input": user_input, "model": MODEL_NAME}, )注意不要在日志里记录完整订单内容、用户手机号等敏感字段。日志只保存订单号、状态码、耗时这层信息,涉及业务详情的部分写入带权限控制的追踪系统。
4.2 响应缓存
智能体响应缓存与普通 API 缓存有很大区别。完全相同的用户输入很少出现,更常见的可缓存场景是同一条工具请求返回的业务结果。例如订单状态在 5 分钟内不会变,就可以把“订单号到查询状态”的映射缓存起来,减少一次模型决策和工具调用。
import time from functools import lru_cache @lru_cache(maxsize=1024) def query_order_cached(order_id: str, ttl_seconds: int = 300): # 实际项目中可替换为 Redis,并设置过期时间 return query_order_status(order_id)放在这里的意义是防止模型在同一轮任务中多次查询同一订单。设置过期时间,避免因为缓存返回过期状态。生产环境建议使用 Redis 这类外部缓存,并确保缓存键包含订单号,不能包含会话上下文。
4.3 超时和重试
智能体请求不能无限制等待。依赖的模型服务或工具 API 都可能有网络抖动,建议对两类调用分别设置超时:
- 模型调用超时,控制在业务可接受范围内,常见的是 30 到 60 秒。
- 工具调用超时,要更短,例如 5 到 10 秒,因为工具是可以降级的内部接口。
重试只适用于幂等操作。查询订单是幂等的,可以重试;退款申请不是,不能盲目重试,否则可能产生重复退款。对写入型操作,正确的做法是生成一个操作请求 ID,用这个 ID 判断操作是否已执行过:
def create_refund(order_id: str, refund_request_id: str) -> dict: # 先查询 refund_request_id 是否处理过 # 如果处理过,返回原结果;否则执行退款并登记 ID return {"status": "submitted", "refund_request_id": refund_request_id}4.4 并发控制和限流
智能体服务进入生产环境后,第一波压力往往不是来自用户量,而是来自工具调用风暴:一个用户问题触发了 8 次工具调用,如果每个用户都这样,后端工具接口瞬间被打满。
常见的控制手段有三个层次:
- 入口限流:限制每个用户每分钟发起的智能体请求数。
- 并发控制:限制同时执行的智能体任务数,超出部分排队。
- 工具级限流:限制单个工具单位时间内的调用次数。
如果使用 Python,入口限流可以用令牌桶算法:
import time class TokenBucket: def __init__(self, capacity: int, refill_per_second: int): self.capacity = capacity self.tokens = capacity self.refill_per_second = refill_per_second self.last_refill = time.monotonic() def acquire(self) -> bool: now = time.monotonic() self.tokens = min( self.capacity, self.tokens + (now - self.last_refill) * self.refill_per_second, ) self.last_refill = now if self.tokens >= 1: self.tokens -= 1 return True return False这段代码展示了限流的基本思想,生产环境建议直接使用现成的限流组件或网关能力,不要重复造轮子。
4.5 错误降级
智能体在生产环境必须提前设计降级路径。至少要考虑三种情况:
- 模型服务不可用:直接降级为静态 FAQ 回复,并提示用户稍后重试。
- 工具服务不可用:对只读工具,返回缓存数据;没有缓存时如实告知“暂时无法查询”。
- 连续多轮工具调用失败:主动结束任务并转人工,而不是继续循环消耗 token。
降级不是把错误抛给用户,而是让用户得到可理解的结果,同时把失败原因留给日志和监控。
5. 多智能体协作与工具编排
5.1 分层设计
当业务变复杂时,不建议把所有能力塞进一个智能体。一个客服智能体既要查订单、又要管退款、还要回答商品规则,指令和工具会互相干扰。更稳的做法是拆成多个专用智能体,再通过一个路由层决定请求交给谁。
订单场景可以拆成三个角色:
| 智能体 | 职责 | 工具 |
|---|---|---|
| 订单查询 Agent | 查询订单状态和物流信息 | query_order_status |
| 退款 Agent | 处理退款申请,带二次确认 | create_refund, query_refund_progress |
| 人工客服转接 Agent | 识别情绪和复杂问题,生成转接摘要 | transfer_to_human |
路由层的任务是根据用户意图把请求分发给对应智能体。实现方式可以是一层轻量级分类模型,也可以是一次带路由指令的模型调用。第一次落地时,先用规则匹配关键词做路由,等数据量上来后再替换成模型路由。
5.2 工具编排规则
多智能体意味着工具数量会膨胀。要建立几个硬性规则:
- 工具命名统一:全部使用动词开头,如
query_、create_、cancel_。 - 工具描述里写明适用场景和不适用场景,避免模型乱调。
- 写入型工具必须在描述里标注“需要用户确认后才可调用”,并在代码里做密度校验。
- 每个工具必须声明超时时间和是否幂等,供重试逻辑判断。
工具注册表可以集中管理:
ALL_TOOLS = { "query_order_status": {"func": query_order_cached, "timeout": 8, "idempotent": True}, "create_refund": {"func": create_refund, "timeout": 10, "idempotent": False}, }运行时根据智能体的职责过滤工具列表。订单查询 Agent 只暴露query_order_status,退款 Agent 才暴露create_refund。这比把所有工具一股脑传给一个 Agent 更安全。
5.3 状态和记忆管理
多轮对话的智能体不能每次请求都从零开始。状态管理至少包含三个层级:
- 会话 ID:标识一个用户、一次连续任务。
- 业务状态:当前订单号、退款请求 ID、已确认的信息。
- 上下文摘要:多轮对话后,把历史内容压缩成摘要,避免 token 无限增长。
在托管模式下,会话上下文可能由平台管理。但业务状态仍然建议由应用自己保存,因为退款请求 ID 这类数据必须与业务系统保持一致,不能只存在模型的上下文里。
5.4 与外部系统集成
智能体最终要落到现有系统里。最常见的集成点包括:
- 通过内部 API 操作订单中心。
- 通过 HTTP 回调通知工单系统创建工单。
- 通过消息队列推送人工转接任务。
与外部系统集成时,建议在智能体外面包一层适配器,不要让智能体直接写内部接口。适配器负责参数转换、鉴权、超时和日志,这样即使内部接口改版,智能体定义也不需要跟着动。
6. 常见问题排查:从现象倒推根因
6.1 排查顺序
智能体服务出问题时,建议按照以下顺序排查:
- 输入是否正确:用户输入是否被正确处理,有没有把换行符、特殊字符传进模型。
- 路径和命名:工具名称、工具函数路径、环境变量是否都被正确加载。
- 依赖版本:SDK 版本是否和平台当前 API 匹配,模型名是否拼写正确。
- 配置生效:system prompt、工具注册表是否真的加载到了运行时,而不是改了没重启。
- 网络和权限:API Key 是否有效,内部工具接口是否可达,返回的是 401、403 还是 500。
- 上下文和 token:多轮对话后上下文是否过长导致截断,工具结果是否过大导致模型忽略。
- 日志关键字:错误日志里有没有
tool_use、tool_result、rate_limit、context_length这类关键词。
6.2 常见问题表
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 模型不调用工具 | 工具描述不清晰或没传 tools 参数 | 打印请求参数,确认 tools 已在请求中 | 优化工具描述,加入触发场景示例 |
| 调用工具但结果错误 | 参数提取错误或工具入参校验缺失 | 记录 tool_use 的 input,对比实际入参 | 在工具函数入口增加参数字段校验 |
| 回答内容编造状态 | 系统提示词缺少“不得编造”约束 | 检查 system prompt 是否完整 | 增加真实性约束,并增加工具失败的降级分支 |
| 请求超时 | 模型调用或工具调用未设置超时 | 查看耗时日志,定位慢在哪一环 | 分别设置模型和工具超时,超时走降级 |
| 退款被重复执行 | 写入型工具重试未做幂等 | 检查日志中同一请求 ID 是否出现多次 | 使用退款请求 ID 去重 |
| 上下文太长导致成本高涨 | 多轮对话历史未压缩或未裁剪 | 查看每轮 token 消耗和上下文长度 | 使用摘要替换历史,按窗口裁剪 |
6.3 三个最容易踩的坑
第一个坑是工具调用后结果没有回传正确格式。tool_result必须携带正确的tool_use_id,否则模型不知道这个结果属于哪一次工具调用,会重新发起同一次调用,白白消耗一轮 token。
第二个坑是系统提示词和工具权限不一致。系统提示词写了“退款前必须确认”,但工具描述里没有标注“需要用户确认”,模型可能会跳过确认直接调用退款工具。约束要在提示词、工具描述、代码校验三层同时存在,缺一层都可能出问题。
第三个坑是多轮对话时直接拼接原始历史。当一个会话持续几十分钟后,原始消息列表会变得非常长,模型可能丢失最早的有效信息,还会导致 token 成本急剧上升。线上智能体应定期把早期对话压缩成摘要,只保留关键业务参数。
7. 发布前检查清单与下一步扩展
7.1 发布前检查清单
在把智能体发布到生产环境之前,可以对照这份清单逐项确认:
- [ ] 系统提示词明确说明职责边界,且“不得编造结果”有约束。
- [ ] 每个工具都定义了名称、描述、入参 schema、超时时间、是否幂等。
- [ ] 写入型工具都有用户二次确认和请求 ID 去重机制。
- [ ] 所有外部 API Key 和内部接口账号都已通过安全方式注入,没有硬编码。
- [ ] 日志包含请求 ID、模型名、token 消耗、耗时、工具调用记录。
- [ ] 敏感字段(手机号、地址、身份证号)不会出现在日志和上下文中。
- [ ] 模型调用和工具调用都配置了超时和重试策略。
- [ ] 入口、并发、工具三级限流已配置。
- [ ] 缓存设置了过期时间,且没有缓存写入型操作结果。
- [ ] 已准备一条可用的降级提示词,模型服务不可用时用户仍能得到响应。
- [ ] 已准备至少 30 条评估集,覆盖正常分支、失败分支和边界输入。
7.2 评估体系的建立
智能体与人一样,改一句提示词可能让一类问题变好、让另一类问题变差。因此从第一个版本开始就要建立评估集。
评估集不需要一开始就很大。可以先选 30 到 50 条真实问题,分三类:
| 类别 | 示例 | 通过标准 |
|---|---|---|
| 正常功能 | 查询已发货订单状态 | 返回正确的订单状态和时间 |
| 失败分支 | 查询不存在的订单号 | 如实告知未找到,不编造信息 |
| 安全边界 | 询问订单号以外的信息 | 引导用户转人工,不越权回答 |
每次调整提示词、工具或模型版本后,都跑一遍评估集,记录通过率。没有评估集支撑的智能体迭代,本质上是靠感觉改代码,风险很高。
7.3 扩展方向
第一个生产级智能体跑通后,可以按以下方向继续扩展:
- 多智能体协作:增加路由层,按业务域拆分专用 Agent,形成可扩展的服务群。
- 人工转接闭环:当智能体识别到高风险操作时,生成包含上下文摘要的转接工单,交给人工客服处理。
- 知识库接入:把商品规则、售后政策等文档注入到检索流程,让 Agent 能回答更贴近业务的非结构化问题。
- 持续评估:把评估集接入 CI/CD,每次发布前自动跑一遍,阻止引入明显回退的变更。
回到最初的问题:生产级智能体和 Demo 智能体的差距,不在于模型会不会调用工具,而在于失败时是否有人知道、成本是否可控、数据是否安全、结果是否可评估。从 Claude Managed Agents 这类托管方案切入,可以先把平台的运行时能力用起来,再逐步补齐业务侧的工具治理、评估体系和发布规范。这套方法论,比单独依赖某一个框架更能决定一个智能体项目的成败。