API / REST 层
平台对外的门面。今天读 api/rest_api.py + api/features/v1.py:FastAPI 应用结构、启动钩子、异常映射、主要端点、付费墙、Store 市场。
👶 小白:后端出错时那一大串吓人的报错堆栈,用户会不会直接看到?
👨🏫 老师:不会,也绝不能让他看到(既不友好又泄露内部实现)。平台有统一异常映射这层"翻译官":后厨真出了什么岔子(数据库异常、余额不足……),它统一翻译成干净的 HTTP 状态码 + 一句人话(比如余额不足 → 402 "请充值")。用户只看到礼貌的提示,混乱的内部细节留在日志里给工程师看。
FastAPI app 结构
主 REST app 在 api/rest_api.py:208 创建(app = fastapi.FastAPI(...)),随后挂中间件 SecurityHeadersMiddleware(:220)、GZipMiddleware(:223),并用大量 include_router(...)(从 :328 起)组装模块化路由。它是整个平台对外的 HTTP 门面——前端和外部集成都通过它操作平台。特点:lifespan 启动钩子、几个中间件(Day 04 讲过 CORS/GZip/安全头)、统一异常映射、大量 include_router(模块化路由)。
ws="none"(WebSocket 交给独立进程,Day 15)。lifespan 启动钩子
rest_api.py:103 的 lifespan:应用启动时连 DB/Redis、配线程池、注册所有 Block(Day 02 的 load_all_blocks + initialize_blocks)、注册托管凭证 provider、跑数据迁移。
统一异常映射
try...except...return 404,那是几百份重复代码——而且总有人漏写,导致某个接口把内部异常原样吐给前端(泄漏细节、前端也无法统一处理)。NotFound、UserPaywalledError…),至于"这对应哪个 HTTP 码"由一处统一的异常处理器决定。改一处,全站生效;前端也能靠稳定的 HTTP 码统一反应(收到 402 就弹充值框)。这就是"协议转换"与"业务逻辑"的解耦。rest_api.py:309 把各种内部异常映射成 HTTP 状态码:
| 内部异常 | → HTTP 码 |
|---|---|
| Prisma 错误 | 500 |
| NotFound | 404 |
| NotAuthorized | 403 |
UserPaywalledError | 402(付费墙) |
| PreconditionFailed | 428 |
UserPaywalledError),映射层自动转成正确的 HTTP 响应。这样前端能靠 HTTP 码统一处理(收到 402 就弹充值框)。关注点分离:业务抛语义异常、映射层管协议转换。主要 REST 端点
核心 v1 路由在 api/features/v1.py(前缀 /api):
| 功能 | 端点 |
|---|---|
| 列出所有 Block | GET /api/blocks(缓存 + GZip) |
| 直接执行单个 Block | POST /api/blocks/{id}/execute(先扣费) |
| 创建 Agent(图) | POST /api/graphs |
| 执行 Agent | POST /api/graphs/{id}/execute/{version} |
| 停止/查状态/查成本 | .../stop、.../executions、.../cost_summary |
| 查余额/充值 | GET /api/credits、.../request_top_up |
GET /api/blocks 返回 300+ 块,响应很大 → 用缓存 + GZip 压缩(Day 04)。POST /api/graphs/{id}/execute 就是 Day 05 追踪的执行入口。这张端点表 = 平台对外能力的清单。前端的每个功能都对应这里的某个端点。付费墙 402
执行/直跑 Block 的路由挂了 Depends(enforce_payment_paywall)(v1.py:1848)——余额 ≤0 就返回 402 Payment Required。
POST /api/graphs/abc/execute/3(你的余额 = 0)。因为该路由挂了
Depends(enforce_payment_paywall),请求还没进业务逻辑就被前置依赖拦下 →响应:
402 Payment Required,body {"detail":"Insufficient balance"}。前端一看到 402,不显示报错,而是弹出"余额不足,去充值"对话框(Day 16 的 Stripe 充值)。充值后重试即可。
Depends(enforce_payment_paywall) 是 FastAPI 的依赖注入——把"检查余额"作为这些端点的前置依赖,统一拦截,不用每个端点手写检查。用依赖注入做横切的"付费墙"检查——优雅。Store 市场
api/features/store/routes.py 是 Agent 市场:列出商店 Agent(:141)、看单个 Agent、下载、创作者列表、提交上架(:424)、上传媒体/生成封面图。审核走 admin 路由。
路由模块化
rest_api.py:328 用大量 include_router 按功能拆分:/api/integrations(Day 17)、/api/analytics、/api/store、/api/builder、/api/library、/api/chat、/api/mcp、/api/oauth、一批 /api/admin/*,还 mount 了外部 API /external-api(给 API-key 调用方)。
include_router 组装——每个模块管自己的端点,清晰、易维护、易协作(不同团队管不同模块)。和 OpenHands 的 v1_router 汇总、CrewAI 的模块化一样。/external-api(给外部程序用 API key 调)体现平台不只服务前端,也能被程序化调用——真正的开放平台。今日小结 + 动手
🧠 今天你应该能回答
- REST 层是平台的什么角色?为什么 ws="none"?
- lifespan 启动钩子做什么?为什么在启动时做?
- 统一异常映射的好处?402 是什么?
- 主要端点有哪些?付费墙怎么用依赖注入实现?
- Store 市场的意义?为什么路由要模块化?
✋ 动手
P=autogpt_platform/backend/backend
grep -n 'lifespan\|include_router\|UserPaywalledError\|402' $P/api/rest_api.py | head
grep -n 'blocks\|graphs.*execute\|enforce_payment_paywall' $P/api/features/v1.py | head
grep -n 'store/agents\|create.*submission' $P/api/features/store/routes.py | head
classic/ 的 think→plan→act 循环和 Forge 框架。理解 Agent 领域的来路。