惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
J
Java Code Geeks
F
Fortinet All Blogs
Blog — PlanetScale
Blog — PlanetScale
P
Proofpoint News Feed
U
Unit 42
B
Blog
宝玉的分享
宝玉的分享
腾讯CDC
Microsoft Azure Blog
Microsoft Azure Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Last Week in AI
Last Week in AI
博客园 - Franky
博客园 - 三生石上(FineUI控件)
人人都是产品经理
人人都是产品经理
Martin Fowler
Martin Fowler
博客园_首页
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
云风的 BLOG
云风的 BLOG
L
LangChain Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Y
Y Combinator Blog
The GitHub Blog
The GitHub Blog
博客园 - 叶小钗

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解
让 FastAPI Agent 思考不阻塞:手把手教你实现异步任务与后台...
一名程序媛呀 · 2026-05-14 · via 博客园_首页

🚀 别让你的 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,或者有更好的任务管理心得,评论区见呀!别忘了点赞、收藏加关注哦,咱们技术圈不迷路!✨