Pydantic AI 调 Gemini 偶发 different event loop,先看客户端活了多久
Pydantic AI 调 Gemini 出现 different event loop 时,检查异步客户端是否被跨循环复用。核对包含已确认修复的兼容版本,并验证真实 worker 的连续、并发和取消场景。
Pydantic AI 调 Gemini 偶发 different event loop,先看客户端活了多久
单次调用 Gemini 正常,Celery worker 连续处理任务后偶尔出现:
is bound to a different event loop
堆栈如果经过 google-genai、httpx、httpcore,最后停在 asyncio 的 Event 或 Lock,值得先检查异步 HTTP 客户端是否跨了事件循环。加重试不一定有用:重试仍然可能拿到同一个已绑定旧 loop 的对象。
Pydantic AI 官方 issue #3240 的初始报告涉及从 1.1.0 升级到 1.4.0,后续有 Celery 并发场景反馈。维护者在结帖说明中将一类根因指向全局缓存的 AsyncClient,并确认 #4421 随 v1.100.0 移除了该缓存。这个版本结论只覆盖库里已确认的那条路径,不能据此判断所有同名异常都是同一个 bug。
先在实际 worker 里看版本
不要只在本地终端运行 pip show。记录发生错误的 worker 镜像中 pydantic-ai 或 pydantic-ai-slim、google-genai、httpx 的版本。如果仍在受影响的旧组合上,选择包含修复且与项目兼容的版本,在测试环境更新锁文件并回归。
源码升级之后,还要搜应用里的全局对象:模块级 AsyncClient、长期存活的 provider、封装后缓存的 agent,以及每个任务里新建 loop 的同步包装。应用自己复用的客户端,不会因为库删除了自己的缓存就自动变安全。
让一次任务拥有自己的异步资源
下方接口按 v1.100.0 的源码核对,采用 GoogleModel + GoogleProvider,用 agent 的异步上下文管理资源。适合一个同步任务入口拥有一次完整异步生命周期的场景;需安装带 Google 支持的 Pydantic AI,并配置 GOOGLE_API_KEY 和 GOOGLE_MODEL。运行会请求 Gemini,并可能产生费用。
import asyncio
import os
from pydantic_ai import Agent
from pydantic_ai.models.google import GoogleModel
from pydantic_ai.providers.google import GoogleProvider
async def run_one(prompt: str) -> str:
provider = GoogleProvider(api_key=os.environ["GOOGLE_API_KEY"])
model = GoogleModel(os.environ["GOOGLE_MODEL"], provider=provider)
agent = Agent(model)
async with agent:
result = await agent.run(prompt)
return result.output
def run_task(prompt: str) -> str:
# 用于没有正在运行的事件循环的同步任务入口。
return asyncio.run(run_one(prompt))
if __name__ == "__main__":
print(run_task("只回答 OK"))
print(run_task("只回答 OK"))
两个连续调用会分别创建和关闭 loop,也分别创建 provider。把 provider 提到模块顶部缓存起来,会破坏示例要验证的隔离关系。若自行注入 httpx.AsyncClient,同样要在该 loop 内创建,并在 loop 结束前关闭。
这个写法有连接重建成本。长期异步服务可以在一个稳定的 loop 里用应用生命周期管理客户端,没必要每次请求都重建。Celery 的进程、线程或其他并发池行为也不相同,应按实际部署方式决定对象的归属,避免在父进程预先创建连接再由子进程沿用。
怎样证明偶发错误真消失了
至少覆盖三个场景:同一 worker 连续处理多个任务;按照线上并发池运行并发任务;任务取消或失败后再处理新任务。关注未关闭客户端、连接池关闭后复用等警告。
本地先跑两个独立 loop 能暴露一部分生命周期问题,但不能代替真实 Celery 并发回归。若升级和隔离后仍失败,记录进程 ID、线程 ID、loop 标识及对象创建位置,再缩小到最小复现;不要把密钥或完整业务提示词写进诊断日志。


