Jupyter、FastAPI 里 Runner.run_sync 报事件循环正在运行
已有事件循环的 Notebook 或异步路由应 await Runner.run,避免再调用 run_sync。回到原失败入口验证,并把异步客户端的创建、使用和关闭留在同一生命周期。
Jupyter、FastAPI 里 Runner.run_sync 报事件循环正在运行
同一段 Agents SDK 代码,在命令行脚本里能跑,放进 notebook 或 FastAPI 的 async 路由后立即报错:
AgentRunner.run_sync() cannot be called when an event loop is already running.
这条错误直接指出了调用环境:当前线程已经在运行事件循环,又进入了同步 runner。请求还没必要送到模型,换 API key 或重试都碰不到根因。
本文对照 OpenAI Agents SDK 源码快照 125efa02;示例入口在 openai-agents 0.23.1 中核对。
原来那一行应该怎么换
异步函数里,把 Runner.run_sync(agent, question) 改为 await Runner.run(agent, question)。不要漏掉 await,否则得到的是协程对象,问题会变成“没有真正执行”或序列化协程失败。
下面可以直接作为 FastAPI 应用。需安装 fastapi、uvicorn、openai-agents,配置 OPENAI_API_KEY 和可用模型名 OPENAI_MODEL;调用路由会发生真实模型请求。
import os
from fastapi import FastAPI
from pydantic import BaseModel
from agents import Agent, Runner, RunConfig
app = FastAPI()
agent = Agent(name="assistant", model=os.environ["OPENAI_MODEL"])
class Question(BaseModel):
text: str
@app.post("/ask")
async def ask(body: Question):
result = await Runner.run(
agent,
body.text,
max_turns=3,
run_config=RunConfig(tracing_disabled=True),
)
return {"answer": result.final_output}
这里没有自定义 HTTP 客户端;如果项目自己创建了异步客户端,还要把它的创建、使用和关闭放进同一个应用生命周期。把入口改成 await 只能解决同步 runner 嵌套事件循环,不能顺便修复另一个 loop 上创建的连接池。
Notebook 支持顶层 await 时,直接在单元格里写 result = await Runner.run(agent, "你好")。不要再用 asyncio.run 包一层。普通命令行脚本的顶层没有正在运行的 loop,可以保留 run_sync;如果脚本本来就有 async main,也统一用 await Runner.run。
先做一个不联网的反例测试
下面只验证同步入口的保护逻辑,不调用模型,适合在迁移前确认遇到的是这类错误:
import asyncio
from agents import Agent, Runner, RunConfig
async def check_guard():
try:
Runner.run_sync(
Agent(name="loop-probe"), "unused",
run_config=RunConfig(tracing_disabled=True),
)
except RuntimeError as exc:
assert "event loop" in str(exc)
print("sync entry rejected inside an active loop")
else:
raise AssertionError("expected an active-loop error")
if __name__ == "__main__":
asyncio.run(check_guard())
修复后的验收仍要在原先失败的入口做:notebook 就在 notebook 内 await,Web 服务就在 async 路由里请求。只回到一个新的同步脚本测试成功,无法证明原来的嵌套已经消失。
别为了赶进度给整个应用打事件循环补丁,或在请求中随意新建一个 loop。这样的改动会改变取消、关闭和客户端复用的行为,故障可能被挪到下一次请求。入口本来已经提供异步 API,沿着它接下去通常更容易维护。
官方依据:Runner 的同步与异步入口。


