☰
多Agent工作流实战:基于OpenAI Agents SDK搭建客服分流系统
2026/10/17 3:36:03 网站建设 项目流程

做多Agent应用有段时间了。最开始我习惯手写循环去调模型、拼工具调用、自己管多轮上下文,结果接口调用、重试、流式、JSON解析这些琐碎逻辑缠在一起,维护成本非常高。后来我把这类活儿迁移到 OpenAI Agents SDK(下面简称Agents SDK)上,代码量减少一大截,该有的工具调用、Agent交接、输入校验、链路追踪也都齐了。这篇文章就围绕这个轻量级多Agent工作流框架展开,聊聊它的核心概念、实际怎么搭一个“AI客服团队”,以及我在模拟项目X里踩过的坑。

如果你是做AI应用开发的,准备在业务里引入多Agent协作,或者只是想知道手写循环和用SDK的区别在哪里,这篇都适合先读一遍。我不打算写繁琐的框架全景,只聚焦最常用的东西:Agent、Handoff、Guardrail、Runner、Sessions,外加一套可以直接模仿的客服分流示例。

1. 这个SDK到底解决了什么问题

1.1 先说说手搓多Agent的痛

多Agent应用听上去很美好,但真正落地时你会发现,大量的复杂度都不在“调用大模型”这一步,而在编排层。所谓编排层,就是你要回答几个核心问题:模型这次该调用哪个工具?工具返回结果后要不要再回传模型?当前这个Agent回答到一半,能不能把用户转交给另一个Agent?多轮对话的上下文要存哪里?如果模型调用工具超时或者连续走错分支,怎么兜底?

我最早在模拟项目X里做过一个客服机器人,需求不算复杂:用户进来后,先判断是订单问题、退款问题还是技术问题,然后分别交给不同“角色”处理。一开始的方案是手写一个while循环,循环里做这么几件事:组装消息列表、调用模型、检查返回结果里有没有工具调用、有则执行工具并把结果追加进对话、然后再来一轮。看似简单,真写起来你会发现每一步都有很多边界情况:模型返回的工具名拼错了怎么办?工具参数是JSON字符串,解析失败了怎么办?多轮循环次数到了,但任务还没完成怎么办?不同角色的上下文怎么切换?

这些逻辑不是不能写,但写出来的代码大多跟业务无关,纯属为框架“填坑”。Agents SDK做的就是把这一层标准化:它把每次模型交互封装成一个Agent;把Agent之间的切换封装成Handoff;把输入输出的安全校验封装成Guardrail;把循环执行逻辑封装进Runner。你只管声明“谁是谁、会什么、可以转给谁”,剩下的底层循环、上下文传递、工具调用结果回填,框架帮你处理。

1.2 Agents SDK的核心设计思路

这套SDK的核心设计思路,是“用少量抽象覆盖大多数真实场景”。

它主要有几个关键抽象:Agent是基本工作单元,里面包含模型、指令、工具列表、交接目标和输出格式约束;Handoff负责把当前会话转交给另一个Agent;Guardrail负责在输入进入Agent之前、或者输出返回给用户之前做一次拦截校验;Sessions负责维持多轮对话的记忆;Runner负责把Agent跑起来并控制总轮数。

为什么说它轻量?因为它没有搞一整套可视化流程引擎,也没有把节点和边这些概念强加给你。它更像是一个围绕模型调用协议的“薄封装”,你不需要理解几十个组件之间复杂的血缘关系。对比我之前用过的其他编排框架,Agents SDK的上手成本确实低很多。大多数场景下,你只需要会定义普通函数、会写一点Pydantic模型,就能在几分钟内跑通一个多Agent的协作任务。

轻量的另一点体现在代码结构上。整个框架没有把Agent变成某种需要注册到中央注册表的高层实体,Agent就是一个普通对象。你可以把它存进列表,可以按条件动态生成,甚至可以在一个循环里创建多个临时Agent来处理不同子任务。这种设计很符合Python开发者的直觉。

1.3 适合谁用,不适合谁用

从我个人的使用经验看,这框架特别适合两类场景。

第一类是工具型Agent,也就是模型需要频繁调用业务API来完成任务的场景。比如查订单、查库存、查天气、操作数据库、发通知。SDK统一封装了工具定义和结果回填,效果立竿见影。

第二类是客服分流。一个前台Agent判断问题类型,通过Handoff把用户转给退款、技术、售后等不同专家Agent。每个专家Agent只带自己的工具和知识,上下文更干净,模型跑起来也更不容易被无关信息干扰。

但也不要盲目套用。如果你要构建的更像一个严格的业务流程引擎,比如包含人工审批节点、条件分支状态机、定时任务触发,或者需要保存非常复杂的中间状态,那这框架就不太合适。Agents SDK更适合“模型主导的协作”,也就是让模型来推动流程往前走;而真正的流程确定性控制,你还是应该交给专门的业务编排系统。

2. 核心概念拆解:Agent、Handoff、Guardrail

2.1 Agent:说白了就是一个“带工具的模型对话”

Agent在Agents SDK里不是一个玄乎的概念,它本质上是把“模型实例 + 系统指令 + 工具集合 + 输出格式约束”打包在一起的对象。你定义一个Agent时,只是告诉框架:这个角色负责什么,能调用哪些工具,输出应该长成什么样。

from agents import Agent, function_tool @function_tool def query_order_status(order_id: str) -> str: """根据订单号查询订单当前状态,返回一段给用户看的文字。""" # 真实项目中这里会查数据库或调用订单服务 if order_id.startswith("A"): return "订单存在,状态:已支付,允许退款" return "订单不存在或已关闭" agent = Agent( name="订单查询助手", instructions="你负责查询订单状态。用户问订单情况时,调用query_order_status工具。", tools=[query_order_status], )

这里最需要注意的就是工具的docstring。模型不知道你的函数内部怎么实现,它只能靠函数名和docstring来判断“这个工具是干嘛的、什么时候该用”。所以docstring里要写清楚触发条件和参数含义。比如同样是一个订单查询工具,docstring写“查订单”,模型可能在用户问退款时也想调用它;docstring写“根据订单号查询订单当前状态,用户询问订单支付/发货/退款状态时调用”,模型的选择就会准得多。

Agent还有一个很好用的参数是output_type。以前要模型输出JSON,我们总得在提示词里反复强调“必须输出JSON格式”。但手写JSON很痛苦,模型偶尔还会把大括号写漏。使用output_type可以让框架把JSON Schema传给模型,并在返回后做一次结构校验。

from pydantic import BaseModel class OrderInfo(BaseModel): order_id: str status: str can_refund: bool agent = Agent( name="订单查询助手", instructions="查询订单后用订单信息回答用户。", tools=[query_order_status], output_type=OrderInfo, )

这样模型就会尽力输出符合OrderInfo结构的JSON,框架再做校验和转换,从根上解决非法JSON问题。

2.2 Handoff:Agent之间的“交接棒”

Handoff是这套SDK里最出彩的设计。它的原理很巧妙:每一个Handoff,本质上会被框架注册成一个工具,这个工具对模型可见。模型阅读用户输入后,如果判断“这问题该退款专员处理”,它就会调用那个交接工具,比如transfer_to_refund_agent。框架检测到这个工具被调用,就暂停当前Agent,把会话转交给目标Agent继续处理。

from agents import Agent refund_agent = Agent( name="退款专员", instructions="你只负责处理退款相关业务。", ) tech_agent = Agent( name="技术专员", instructions="你只负责处理技术故障和报错问题。", ) triage_agent = Agent( name="前台分流", instructions=( "你是客服系统的第一入口。" "用户涉及退款、订单金额、退款状态时,转交给退款专员。" "用户涉及报错、接口异常、页面打不开时,转交给技术专员。" "其他简单问题直接回答。" ), handoffs=[refund_agent, tech_agent], )

你觉得奇怪吗,一个叫“前台分流”的Agent没有配任何工具,它怎么工作?实际上Handoff就是它的工具。模型看到用户说“我要退款”,就会根据指令里的交接规则产生调用一个交接工具的概率。框架允许模型自己决定要不要交接、交接给谁,这比我们用if/else硬编码要灵活得多。

默认情况下,框架生成的交接工具名是transfer_to_Agent名这样的格式,通常够用。但如果你想给模型看的工具名更短、更明确,可以用handoff()函数来做覆盖。

from agents import handoff refund_handoff = handoff( refund_agent, tool_name_override="transfer_to_after_sale", tool_description_override="当用户需要退款、投诉或咨询售后问题时,调用此工具。", ) triage_agent = Agent( name="前台分流", instructions="你是客服系统的第一入口。", handoffs=[refund_handoff], )

我试下来,给Handoff写清楚tool_description_override比只挂Agent对象要好。因为描述就是模型决定是否交接的唯一依据,写得太泛,模型容易乱转;写得准确,分流准确率会明显提升。

2.3 Guardrail:给Agent团队上“安检”

多Agent协作最怕的不是模型答错,而是模型一本正经地去执行不该执行的操作。比如用户随便编一个订单号,退款流程照样往下走;比如用户试图诱导Agent泄露另一个用户的订单信息。在真实业务里,这种风险不能只靠模型自觉。

Agents SDK提供了Guardrail机制,分为输入护栏和输出护栏。以输入护栏为例,你可以自定义一个校验函数,在用户输入真正进入Agent之前先检查一遍。GuardrailFunctionOutput里有一个tripwire_triggered字段,一旦设为True,整个运行会立刻中断,抛出一个异常。这样框架不会让模型在非法输入上继续“自由发挥”。

下面是一个很简单的退款合法性校验护栏:

from agents import GuardrailFunctionOutput, InputGuardrail from pydantic import BaseModel class RefundGuardrailResult(BaseModel): is_allowed: bool reason: str async def refund_request_guardrail(ctx, agent, input_data): # input_data是用户的原始输入,这里也可以调用业务服务判断 if "A123" in input_data: return GuardrailFunctionOutput( output_info=RefundGuardrailResult(is_allowed=True, reason="订单存在,允许退款"), tripwire_triggered=False, ) return GuardrailFunctionOutput( output_info=RefundGuardrailResult(is_allowed=False, reason="缺少有效订单号,禁止进入退款流程"), tripwire_triggered=True, ) refund_agent = Agent( name="退款专员", instructions="你只负责处理退款相关业务。", guardrails=[InputGuardrail(guardrail_function=refund_request_guardrail)], )

有的场景下,你可能会觉得“让模型自己判断处理就好了,为什么要额外写Guardrail”。我的体会是,Guardrail用于守住边界,模型用于完成任务。一个负责任的信息系统,不能让模型既是运动员又是裁判员。尤其涉及资金、隐私、删除、写操作这一类高风险动作,坚决建议设置护栏。别把它写得过于复杂,只需要拦住那些百分之百不合法的请求就行。

2.4 Sessions与Tracing:记忆和监控同样重要

多轮对话是客服类Agent逃不开的需求。Agents SDK里,一次Runner.run()会生成一个会话ID,如果你保存这个ID,并在下一次运行时传进去,整个对话记忆就能续上。

import asyncio from agents import Runner async def main(): user_input = "我买的东西还没到,要退款,订单号A123456" result = await Runner.run(triage_agent, user_input) session_id = result.session_id print("第一轮:", result.final_output) # 下一轮携带session_id,模型能记住刚才的上下文 next_result = await Runner.run( triage_agent, "好的,那帮我查退款进度", session_id=session_id, ) print("第二轮:", next_result.final_output) asyncio.run(main())

在多轮场景里,session_id一般会随业务请求存储在后端数据库。用户下次进来,带上这个ID,Agent就能无缝衔接。否则同一用户每次进来都是“失忆”状态,体验打折扣。

Tracing则负责告诉你刚才那几轮到底发生了什么。运行结束后,框架会生成包含一次完整运行的链路数据,包括每一步模型调用、工具调用、交接、耗时、token消耗。线上排查“模型为什么不调用工具”时,追踪日志几乎是第一手证据。不过日志里可能包含用户输入等敏感数据,接入生产环境前要做好脱敏。

3. 实战:5分钟搭一个客服分流Agent团队

3.1 先交代场景

我们要模拟一个工单系统,用户在对话窗口里描述问题,客服团队负责分流和解决。我用三个Agent来组队:前台分流、退款专员、技术专员。两个业务工具:查订单状态、查退款状态。再加一个安全护栏:进入退款处理前必须校验订单合法性。

这个结构的价值在于,每个Agent保持职责单一。前台分流只负责判断问题和交接,退款专员只处理退款相关,技术专员只处理报错相关。职责越单一,模型越不容易被无关指令干扰。

3.2 环境准备

在开始之前,先把Python版本确认一下,建议3.9以上。然后安装依赖:

pip install openai-agents

接着配置模型接口的访问密钥。不同朋友用的接入方式可能不同,最简单的是设置环境变量,让SDK默认读取。

export OPENAI_API_KEY="sk-your-key"

密钥一定不要提交到代码仓库,更不要写死在业务代码里。如果是团队开发,建议通过密钥管理系统注入环境变量。配置完成后再检查一下版本,Agents SDK目前迭代得很快,接口有些微调,如果后续版本有变化,以官方更新日志为准。

3.3 定义工具与Guardrail

工具函数和普通Python函数几乎一样,只是加了一个@function_tool装饰器。这里我自定义两个工具,分别返回订单状态和退款状态。真实开发中,你需要让工具去查询底层数据库或者调用内部服务。

from agents import function_tool @function_tool def query_order_status(order_id: str) -> str: """根据订单号查询订单当前状态。订单号以A开头表示有效订单,否则视为无效订单。""" if order_id.startswith("A"): return "订单存在,状态:已支付,允许退款" return "订单不存在或已关闭" @function_tool def query_refund_status(refund_id: str) -> str: """根据退款单号查询退款处理进度。退款单号以R开头表示已受理。""" if refund_id.startswith("R"): return "退款处理中,预计3个工作日到账" return "未查询到该退款单"

工具返回的字符串,就是直接给模型看的内容。所以返回结果也要写得像“一段人能看懂的文字”,不要只丢一个字典对象。模型会把这些文本包装成最终回复。

然后定义退款专用的Guardrail,这个护栏会在退款Agent真正处理用户输入前执行一次校验。这里我用了最简单的方式:输入里没有有效订单号就触发中断。生产环境里,这里应该调用一个真实的订单校验服务。

from agents import GuardrailFunctionOutput, InputGuardrail from pydantic import BaseModel class RefundGuardrailResult(BaseModel): is_allowed: bool reason: str async def refund_request_guardrail(ctx, agent, input_data): if "A123" in input_data: return GuardrailFunctionOutput( output_info=RefundGuardrailResult(is_allowed=True, reason="订单存在,允许退款"), tripwire_triggered=False, ) return GuardrailFunctionOutput( output_info=RefundGuardrailResult(is_allowed=False, reason="缺少有效订单号,禁止进入退款流程"), tripwire_triggered=True, ) refund_guardrail = InputGuardrail(guardrail_function=refund_request_guardrail)

3.4 定义三个Agent

定义Agent的时候,不要急着堆指令。每个Agent的instructions只需要包含三层信息:这个角色负责什么、可以调用哪些工具、什么时候应该交接给谁。

from agents import Agent refund_agent = Agent( name="退款专员", instructions=( "你是退款专员。只处理退款申请、退款状态查询、订单状态确认等业务。" "查询订单状态请调用query_order_status,查询退款进度请调用query_refund_status。" "没有明确证据表明可以退款时,不要向用户承诺退款。" ), tools=[query_order_status, query_refund_status], guardrails=[refund_guardrail], ) tech_agent = Agent( name="技术专员", instructions=( "你是技术专员。只处理报错、接口异常、页面无法访问等技术类问题。" "不要处理退款和订单金额问题。" ), ) triage_agent = Agent( name="前台分流", instructions=( "你是客服系统的入口。先判断用户问题类型。" "涉及退款、订单金额、退款状态时,转交给退款专员。" "涉及报错、接口异常、技术故障时,转交给技术专员。" "其他问题直接礼貌回复即可。" ), handoffs=[refund_agent, tech_agent], )

你可能会发现,前台分流Agent没有一个额外的判断字段,也没有任何工具。它的“工具”就是两个handoff引用。模型会自动把它们视作两个交接工具。这种写法让整个团队的可读性很高,任何人扫一眼代码,就知道客服团队由哪些角色组成、各自的边界是什么。

3.5 用Runner把团队跑起来

万事俱备,只差Runner。Runner.run接收一个起始Agent和用户输入,然后内部开始循环:调用模型、处理工具调用、必要时完成交接、继续跑,直到得到最终回复。

import asyncio from agents import Runner async def main(): user_input = "我买的东西还没到,我要退款,订单号A123456" result = await Runner.run( triage_agent, user_input, max_turns=10, ) print("最终回复:", result.final_output) print("最后处理Agent:", result.last_agent.name) asyncio.run(main())

这里max_turns=10是务实的做法。多Agent流程里最常见的问题就是模型陷入反复调用工具或反复交接的循环。如果不加限制,请求可能卡很久。设置一个最大轮数,流程就能快速失败,至少不会让用户一直等。

如果你想观察刚才发生了什么,可以遍历result.new_items,里面包含了消息、工具调用、工具结果、交接记录等事件。我建议在联调阶段打印这些信息,能帮你快速建立对一次运行的直觉。

3.6 多轮会话怎么维持

上面的例子只是一次单轮查询。真实客服场景中,用户会追问“那退款状态呢”“现在怎么样了”,这都需要Agent记住之前提到的订单号。刚才已经演示过,Runner.run会返回session_id,把它保持下来,下一轮带上就行。

session_id = result.session_id # 用户后续消息 next_result = await Runner.run( triage_agent, "好的,那帮我查退款进度", session_id=session_id, )

需要提醒的是,Session不是无限备忘录。对话太长,token消耗和延迟都会跟着上涨。Agents SDK目前没有内置自动摘要机制。如果你想对超长对话做压缩,一种做法是把历史消息做个摘要,再作为新的上下文带入Session,或者定期清空Session,只保留关键业务信息。

4. 关键参数与API细节

4.1 Agent参数逐项解析

写代码时,大部分精力会花在Agent对象的参数配置上。我把最常用的几个参数整理成了一张表,方便快速对照。

参数作用使用注意
nameAgent展示名,用于日志与追踪建议用中文或简短英文,避免纯数字
instructions系统指令,相当于角色设定越短越聚焦,不要堆长篇背景故事
model指定模型名称不同模型工具调用能力差异很大
tools可调用工具列表工具docstring要写清楚触发场景
handoffs交接的Agent列表数量别太多,控制在3-5个以内
guardrails输入/输出安全护栏只放强约束,不放复杂规则
output_type结构化输出类型需要JSON时强烈建议设置

这里想格外强调model参数。很多人习惯全局用一个最强模型,其实根据任务复杂度区分模型是很划算的。前台分流这类简单分类任务,可以挂一个小参数模型,速度快还省钱;退款专员涉及业务工具调用和语义理解,可以挂更强的模型。每个Agent是独立对象,可以在不同Agent上指定不同模型。

output_type值得单独拿出来讲。使用它以后,框架会将输出Schema注入给模型,并对最终回复做校验。这样模型几乎不可能再返回残缺的JSON。如果你之前还在用“请以JSON格式回答”这种提示词,建议改成output_type。

4.2 Handoff进阶:自定义交接工具名与回调

默认的交接工具名是transfer_to_Agent名,基本能满足需求。但有些场景下这个名字太长了,或者你有多个Agent名字相似,导致模型分辨困难。这时可以用handoff()函数覆盖工具名。

from agents import Agent, handoff refund_agent = Agent(name="退款专员", instructions="你只处理退款相关业务。") after_sale_handoff = handoff( refund_agent, tool_name_override="transfer_to_after_sale", tool_description_override="当用户需要处理退款、投诉或售后问题时调用此工具。", ) triage_agent = Agent( name="前台分流", instructions="根据问题类型决定是否交接。", handoffs=[after_sale_handoff], )

handoff()还支持on_handoff回调参数,在交接发生的那一瞬间执行。常用在埋点统计、日志记录、清理无用上下文等场景。注意这个回调是异步的。

async def after_sale_callback(ctx): print("用户已转接售后") after_sale_handoff = handoff( refund_agent, tool_name_override="transfer_to_after_sale", on_handoff=after_sale_callback, )

Handoff还有一个容易踩的坑:如果你在一个Agent里既挂了tools又挂了handoffs,模型可能会混淆。它既可能调用业务工具,也可能调用交接工具。正常情况下没问题,但如果业务工具描述和交接触发条件写得很相似,模型就会选错。解决方法是把两类工具的触发场景写得泾渭分明。

4.3 Guardrail的返回结构

在编写护栏函数时,你总是需要返回一个GuardrailFunctionOutput对象。它有两个关键字段:output_info和tripwire_triggered。output_info可以是一个Pydantic模型,框架会把它保留在运行上下文里,方便后续业务读取;tripwire_triggered一但为True,框架会抛出InputGuardrailTripwireTriggered异常。

所以如果你的业务逻辑是“这个输入有点可疑,但还不到终止的程度”,就不要把tripwire_triggered设为True。可以返回False,让Agent继续处理,但你可以在业务层记录一条风险日志。只有遇到“百分之百不能继续”的情况,才触发护栏中断。

比如退款场景中,用户输入的订单号在系统里查无此单,这就属于不能继续的强约束。触发中断后,你的业务代码可以捕获异常,给用户一个标准话术:

from agents import InputGuardrailTripwireTriggered try: result = await Runner.run(triage_agent, "我要退款,单号不存在BBBB") except InputGuardrailTripwireTriggered: print("抱歉,未查询到有效订单,无法进入退款流程。")

4.4 Runner的运行控制与结果读取

Runner.run是最常用的异步入口。如果你的业务代码不太想引入async,可以看当前版本是否提供同步封装。不过我更建议直接用async,因为这框架本身对异步支持得很好,遇到并发请求时更省资源。

Runner.run的返回值是一个RunResult对象,里面有几样东西经常用:

  • final_output:最后一个Agent输出的文本。
  • last_agent:最后一个处理消息的Agent对象。
  • session_id:本次运行的会话ID,用于多轮记忆。
  • new_items:本次运行产生的事件列表。

事件列表里包含MessageOutputItem、FunctionCallItem、FunctionCallOutputItem、HandoffOutputItem等。联调时把它打印出来,能清楚看到模型哪一步调用了哪个工具,哪一步发生了交接。

for item in result.new_items: print(item.type, item)

还有一个context参数可以使用,它允许你传入自定义的业务上下文,供工具函数和护栏读取。不要把业务上下文硬塞进用户对话文本里,那样会污染模型对用户意图的判断。放context里更干净。

5. 常见问题排查与避坑实录

5.1 常见问题速查表

我把实际使用中遇到的高频问题整理成表格,先看现象,再看解法。

问题现象可能原因推荐解法
Agent不调用工具,直接凭空编答案工具描述不够具体,模型感知不到触发条件重写docstring,写明什么时候调用、参数含义
一直触发同一种工具,停不下来max_turns设置过小,或工具返回结果不满足模型预期提高max_turns,精简工具返回文案
Handoff不生效,用户被问了一堆问题指令没有写清楚交接触发条件在instructions里增加明确交接规则和例子
两个Agent互相交接,形成死循环两个Agent指令互相矛盾设置max_turns为固定值,调整交接描述
Guardrail误杀,正常输入也被拦护栏规则过于宽泛只保留强约束条件,业务规则放工具层
输出JSON字段对不上没设置output_type,或模型能力偏弱使用output_type,且字段名语义化
新的一轮忘记上文session_id丢失或未传入持久化session_id,下一轮显式传入
线上超时/吞吐不足单个Agent承担太多上下文,turns过多精简指令,给不同Agent分配不同模型

这张表不追求覆盖所有边界情况,但它覆盖了我踩过的绝大多数坑。下面挑几个典型问题展开说说排查思路。

5.2 模型不调用工具,怎么排查

“模型不调用工具”是新手最先遇到的高频问题。但你打开追踪日志,通常会发现模型其实收到了工具定义,也理解了用户话,可它偏选择不看工具、直接回答。这通常不是模型不智能,而是工具描述不够“指向性”。

比如,一个工具叫get_info,docstring写“获取信息”。模型看到用户说“我想退款”,它根本不知道该不该调用你。比较好的做法是,把工具写成refund_eligibility_check,说明写“判断当前订单是否满足退款条件,用户咨询退款时优先调用此工具查询真实状态”。模型看到这个描述,调用概率会明显提高。

另一个小技巧是在指令里加一句“当用户询问某类信息时,必须先调用工具,再根据工具结果回答”。这相当于给模型设了个行为规则,能减少“凭常识自由发挥”的情况。

5.3 Handoff死循环,要设置上限

多Agent协作中最尴尬的一幕:前台分流把用户交给退款专员,退款专员认为“这问题应该问售前”,又转回前台。转来转去,用户在一旁干等。出现这种情况,除了调整指令,最务实的防御就是设置max_turns。这是Runner.run的参数,我一般会设成10-15。一旦循环达到上限,框架会主动返回当前输出,至少不会卡死接口。

如果你在日志里发现某两个Agent反复交接,很可能是它们的职责边界不清晰,或者两条指令里存在“该问题不属于我”的相互推诿。把边界说死,比如退款专员明确“只处理退款,不得把用户交给其他Agent”,技术专员明确“只处理报错,不得要求用户去退款专员”。这种显式的禁止性描述,比“你负责处理退款”更有效。

5.4 Guardrail误杀和漏放,如何平衡

Guardrail写成什么样,非常考验设计能力。写得太宽松,拦不住危险行为;写得太严格,正常用户也会被误杀,严重影响体验。

我的原则是:Guardrail只处理那些“逻辑上完全确定”的拦截规则。比如订单号在系统里不存在,那就必须拦;用户输入包含明显恶意内容,那就必须拦。而像“这个用户是不是情绪激动”“这个退款请求是否合理”这类需要业务判断的内容,不应该进Guardrail,放进工具函数或者业务服务里处理。

另外要记住,Guardrail本身也是一个模型调用或者一次外部服务请求,它会带来额外延迟和成本。一个Agent没必要挂十个护栏,通常挂一个输入护栏、一个输出护栏就够了。更多的校验应该发生在底层业务服务里,而不是挤在Agent边界。

5.5 几项独家心得

这段算是我个人在模拟项目X里反复试错后沉淀下来的习惯。

第一,职责分离要明确。模型负责做决策,工具负责做执行,Guardrail负责做边界,业务代码负责做持久化。不要让模型大包大揽,也不要把所有校验逻辑都堆到提示词里。这样系统出了问题,你一眼就能定位是哪一层的锅。

第二,Handoff是一种模块化手段。把一个大而全的Agent拆成几个小Agent后,每个小Agent的上下文只含自己的工具和指令。模型不需要在几十个工具里去大海捞针,准确率和稳定性都会好一些。但要舍得拆分成本,Agent不是拆得越多越好,每多一次交接就是多一次模型往返。

第三,从成本和延迟角度做减法。分流Agent这种简单任务,用便宜模型;退款专员这种复杂任务,用强模型。Handoff数量控制在3-5个,工具列表如果不常用就暂时不挂,先让主线跑通,再逐步加需求。

第四,线上出问题先看Tracing。不要靠猜。Agents SDK自带的追踪数据里记录了完整的工具调用和交接过程。你只要打开一次运行轨迹,就能看到模型在哪个节点产生了幻觉,在哪一步多走了弯路。调试多Agent应用,这个习惯会省掉大量沟通成本。

6. 结尾:一点真实体会

最后分享一个我在多次实际项目中得到的感受:Agents SDK不是万能的,但它确实把多Agent协作里最啰嗦、最容易出错的那段循环逻辑收走了。它更像一个“Agent协作层”,而不是业务流程引擎。如果你只是想用大模型做一次单轮问答,杀鸡用不上牛刀;但如果你要做工具调用、Agent之间互相交接、再带上安全护栏和链路追踪,这套东西能帮你节省大量时间。

我个人体会最深的是,接入这个SDK之后,我的注意力终于从“怎么拼上下文、怎么写工具调用循环”转移到了“怎么设计每个Agent的职责边界、怎么定义工具描述、怎么设护栏”。后者才是一个多Agent应用真正值钱的地方。工具调用循环谁都能写,但把这套协作逻辑设计得清晰、稳定、可维护,需要反复迭代。

你在试的时候,如果也遇到模型不调用工具、Handoff乱串、JSON输出不稳定这类问题,别急着怀疑框架。大概率是Agent指令描述不够细,或者工具触发条件不够明确。先从一次追踪日志入手,看清模型到底在想什么,再逐步调整。按这个思路走,5分钟跑通Demo不难,难的是把它打磨成能上生产的稳定系统。这正是下一篇文章想聊的话题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询