• 让 FastAPI Agent 思考不阻塞:手把手教你实现异步任务与后台处理方案


    🚀 别让你的 Agent 变成“死机”的代名词

    你有没有遇到过这种尴尬:
    你写了一个超牛的 AI Agent,它能深度搜索、分析报告,甚至还能写代码。
    但当你兴致勃勃地在 FastAPI 里调用它时,前端小哥跑过来敲你桌子:“姐姐,你这接口报 504 超时了啊!用户盯着转圈圈盯了半分钟,早跑了!” 🎯

    说实话,这就是 Agent 开发最让人头疼的地方。
    LLM 思考需要时间,Agent 这种还要“反思、搜索、再反思”的多步任务,时间更是没个准。
    如果你在 FastAPI 的接口里直接 await agent.run() ,那基本上就是在玩火。

    今天我们就聊聊,怎么把这种“慢任务”优雅地甩到后台去,让 API 秒回,让 Agent 慢慢想。

    💡 核心摘要:我们要解决什么?

    - 解决长耗时 Agent 任务导致的接口超时问题

    - 搞清楚什么时候用自带的任务,什么时候上专业队列

    - 实现任务状态的实时查询和进度推送

    - 避开那些看似聪明实则坑人的并发陷阱

    🛠️ 第一部分:方案选择——杀鸡要用牛刀吗?

    在 FastAPI 里处理后台任务,主要有两种路子,大白话比喻:

    1. FastAPI BackgroundTasks(轻便的小推车)

    这是 FastAPI 内置的功能。它的原理是:在 HTTP 响应返回后,在同一个进程里顺便把任务做了。

    适用场景: 发个确认邮件、打个简单的日志。

    吐槽一句: 别拿它去跑重型 Agent!因为它和你的 Web 服务抢 CPU,任务一多,你整个 API 都会变得卡顿。

    2. Celery / ARQ(专业的运输车队)

    这是把任务彻底发到另一个地方(Worker)去执行,通常配合 Redis 或者 RabbitMQ。

    适用场景: 这种才是 Agent 的正解。不管 Agent 跑 1 分钟还是 1 小时,你的 API 都是瞬间回复一个 task_id

    👨‍💻 第二部分:实战演练——让 Agent “隐身”思考

    原理解释的差不多了,咱们直接上干货。假设我们要实现一个“批量生成行业分析报告”的 Agent。

    1. 提交任务接口

    这里重点来了:接口的任务不是“做报告”,而是“接订单”。

    @app.post("/analyze")
    async def start_analysis(topic: str):
        # 这里我们生成一个唯一ID
        task_id = str(uuid.uuid4())
        # 异步把任务塞进队列,千万别在这里 await agent.run()
        analyze_task.delay(task_id, topic)
        return {"status": "accepted", "task_id": task_id}  

    2. 状态查询接口

    前端拿到 task_id 后,可以每隔几秒来问问:“好了吗?好了吗?”

    @app.get("/task/{task_id}")
    async def get_status(task_id: str):
        result = analyze_task.AsyncResult(task_id)
        return {
            "task_id": task_id,
            "status": result.status, # PENDING, SUCCESS, etc.
            "result": result.result if result.ready() else None
        }  

    经验之谈: 很多同学在这儿容易翻车,记得在 Agent 运行过程中不断更新 Redis 里的“中间状态”。
    比如“正在搜索资料”、“正在起草文案”,这样前端显示进度条时,用户才不会觉得程序死了。

    ⚠️ 第三部分:避坑指南——那些我踩过的血泪坑

    接下来重点来了,这都是反复Debug换来的教训😭:

    🎯 危险的 asyncio.create_task:

    有些朋友喜欢在 FastAPI 里直接用这个函数来启动 Agent。看上去很美好,但它是“内存寄生型”。
    如果你的服务器突然重启,或者并发稍微高一点,内存会瞬间爆炸,而且任务丢了你都不知道在哪儿哭!

    🎯 数据库连接池耗尽:

    Agent 在跑异步任务时,如果每个步骤都去读写数据库,记得一定要在任务结束时手动释放连接。不然等你 Agent 跑完,你的 Web 服务也就挂了。

    🎯 实时进度建议用 WebSocket:

    如果你的 Agent 步骤非常多,轮询接口(Polling)会给服务器增加很多没必要的压力。这时候可以考虑用 FastAPI 的 WebSocket 建立长连接,让 Worker 主动把进度“推”给前端。

    这里多说一句:你的 Agent 通常跑在 Celery 的 Worker 进程里,而 WebSocket 连在 FastAPI 的进程里。Worker 里的 Agent 进度怎么传给 Web 进程?
    千万别尝试在 Worker 里直接调用 WebSocket 的 send 方法,那不在一个次元!最稳妥的办法是用
    Redis 的 Pub/Sub(发布/订阅) 机制。

    这里给个参考:

    # 搞个“连接管理器”,万一用户断网了、刷新页面了,你得能及时把废弃的连接清理掉,否则内存溢出分分钟的事
    class ConnectionManager:
        def __init__(self):
            # 存放活跃连接,key是task_id或者user_id
            self.active_connections: dict[str, WebSocket] = {}
    
        async def connect(self, task_id: str, websocket: WebSocket):
            await websocket.accept()
            self.active_connections[task_id] = websocket
    
        def disconnect(self, task_id: str):
            if task_id in self.active_connections:
                del self.active_connections[task_id]
    
        async def send_progress(self, task_id: str, message: dict):
            if task_id in self.active_connections:
                await self.active_connections[task_id].send_json(message)
    
    manager = ConnectionManager()
    
    # 在 Celery Task 内部(发布者)
    redis_client.publish(f"task_progress_{task_id}", json.dumps({"step": "正在搜索资料", "percent": 30}))
    
    # FastAPI 端(订阅者)
    @app.websocket("/ws/{task_id}")
    async def websocket_endpoint(websocket: WebSocket, task_id: str):
        await manager.connect(task_id, websocket)
        pubsub = redis_client.pubsub()
        await pubsub.subscribe(f"task_progress_{task_id}")
        
        try:
            while True:
                # 持续监听 Redis 的消息
                message = await pubsub.get_message(ignore_subscribe_init=True)
                if message:
                    data = json.loads(message['data'])
                    await manager.send_progress(task_id, data)
                    if data.get("percent") == 100: # 任务完成,关掉对讲机
                        break
                await asyncio.sleep(0.1) # 稍微喘口气,别空转太猛
        except WebSocketDisconnect:
            manager.disconnect(task_id)

    🌟 进阶思考:Agent 实例的并发管理

    你可能会问:如果我有 100 个用户同时点“生成报告”,我的 Worker 塞满了怎么办?

    这时候你需要给任务加 优先级队列 。VIP 客户的 Agent 优先跑,普通用户的慢慢排队。

    而且,针对 LLM 的 API 限流(Rate Limit),你也需要在 Celery 这一层做控制,别让你的 Agent 跑太快把 OpenAI 的 Key 给跑封了。

    最后啰嗦一句,后台任务工具的选择,不是功能越多越好,而是要看你的项目规模。如果是自用小 Demo, BackgroundTasks 够了;如果是要上线的产品,老老实实用 Celery 吧。


    嘿,看到这里的都是真爱!Agent 开发这块水很深,但我相信只要把基础的异步架构搭稳了,后面复杂的逻辑也就是往里塞肉的事儿。

    如果你在实战中遇到了什么诡异的 Bug,或者有更好的任务管理心得,评论区见呀!别忘了点赞、收藏加关注哦,咱们技术圈不迷路!✨

  • 相关阅读:
    php+sql后台实现从主表迁出至副表(数据超万条)
    PS图片背景透明(抠图)
    树莓派4b编译FFmpeg支持硬件编解码
    #每日一题合集#牛客JZ13-JZ22
    【ArcGIS微课1000例】0048:制图表达(3)---水立方效果实现
    低代码开发如何助力数字化企业管理系统平台构建
    感谢十二年的陪伴——分享回归,不忘初心(Eastmount博客总结及未来规划)
    C#报错 功能“结构字段初始化表达式“在C#7.3中不可用。请使用10.0或更高的语言版本。
    大数据可视化之医疗大数据平台
    OpenCV实现人脸关键点检测
  • 原文地址:https://www.cnblogs.com/ymtianyu/p/20041110