Agents SDK 报 MaxTurnsExceeded,工具成功以后还有哪一步没结束
MaxTurnsExceeded 限制模型交互轮数。检查重复工具调用、返回信息和 handoff 的退出条件,分别设置轮数与时间预算;异常不会自动回滚已执行的外部操作。
Agents SDK 报 MaxTurnsExceeded,工具成功以后还有哪一步没结束
日志里工具已经返回成功,Runner 最后仍抛出:
Max turns (10) exceeded
先别把 max_turns 改成一百。工具调用完成之后,模型通常还要读到工具结果,再决定继续调用、转交其他 agent,还是给出最终答案。工具成功只证明其中一步完成;整个 agent run 可能仍在循环。
本文以 OpenAI Agents SDK 源码快照 125efa02 的 Runner 为依据,示例接口在 openai-agents 0.23.1 中核对。max_turns 限制的是模型交互轮数,不是单个工具执行了多少秒,也不是工具数量。一轮里可以出现多个工具调用。
从最后几轮找重复动作
先看三个事实:哪一个 agent 在运行、模型要求调用哪个工具、工具实际返回什么。只记录工具函数“执行成功”很容易漏掉返回给模型的内容。例如数据库查到了订单,但包装层只返回“OK”,模型拿不到订单状态,下一轮就又查一次。
接着看重复是否有进展。如果每次查询参数都相同,结果也相同,应检查提示词是否让模型必须获得一个实际上不存在的字段。多 agent 场景还要看 handoff:A 认为任务属于 B,B 又转回 A,也会消耗轮数。
修复应落在具体退出条件上。例如:查到状态后直接回答;订单不存在时明确告知,结束本次任务;工具返回暂不可用时报告失败,不再对同一条件无限重试。这些约束也应体现在工具说明和返回结果中。
给边界留在代码里
下面是一个只读工具示例。需要 openai-agents、已配置的 OPENAI_API_KEY,以及你有权限使用的 OPENAI_MODEL。运行会调用模型并可能产生费用;固定订单数据只是演示工具协议。
import asyncio
import os
from agents import Agent, Runner, MaxTurnsExceeded, RunConfig, function_tool
@function_tool
def lookup_order(order_id: str) -> dict[str, str]:
"""查询一次订单。found 时回答状态;not_found 时告知不存在,不重复查询同一编号。"""
if order_id == "demo-17":
return {"status": "found", "delivery": "delivered"}
return {"status": "not_found"}
async def main():
agent = Agent(
name="order-reader",
model=os.environ["OPENAI_MODEL"],
instructions="只查询用户指定的订单一次。根据工具结果回答后结束;不要猜测其他订单号。",
tools=[lookup_order],
)
try:
async with asyncio.timeout(30):
result = await Runner.run(
agent, "查询 demo-17", max_turns=3,
run_config=RunConfig(tracing_disabled=True),
)
except MaxTurnsExceeded:
print("轮数达到上限,请检查本次运行记录;不自动重新执行任务")
except TimeoutError:
print("执行超时,需要核查工具是否已经完成")
else:
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
这个示例的 30 秒只是应用侧时间预算,不能保证已经发出的外部操作被撤销。Python 的 asyncio.timeout 需要 Python 3.11 或更高版本。
什么时候才该增加 max_turns
把正常任务的步骤列出来。如果任务确实要多次取数、检查、汇总,并且每轮都推进了状态,增加上限有依据。验收时同时保留异常用例:让工具一直返回无法推进的结果,确认流程仍会在有限轮数内失败,而不是无限运行。
如果工具会发邮件、扣库存或创建订单,达到上限后尤其不能整段重跑。先根据业务 ID 查操作结果,再决定从哪里继续。MaxTurnsExceeded 不保证前面的工具没有执行,也不会自动回滚它们。


