DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

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 的同步与异步入口。