ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Cloudflare边缘操作系统实战:用Worker与Durable Object构建AI Agent

Cloudflare边缘操作系统实战:用Worker与Durable Object构建AI Agent 1. 从cloudflare-os这个标题说起它到底在指什么第一次看到cloudflare-os这个词我下意识以为是 Cloudflare 官方又出了什么新操作系统。翻了一圈资料才反应过来这更像是一个社区里自发形成的概念标签用来描述一种把 Cloudflare 全家桶Workers、Durable Objects、KV、R2、D1、Queues、AI Gateway 等当成操作系统来用的开发范式。换句话说它不是某个具体的产品而是一种架构思路把边缘网络当作内核把各类绑定binding当作系统调用把 Worker 当作进程把 Durable Object 当作有状态的守护进程。这个思路为什么最近被反复提起因为 AI Agent 这类应用对基础设施的要求和传统 Web 应用完全不一样。传统 CRUD 应用是请求-响应模型无状态、短生命周期、可预测。而 Agent 是长任务、多轮工具调用、需要记忆、需要并发协调、需要流式输出、需要按 token 计费。你拿一台固定区域的 VPS 去跑很快就会遇到三个问题一是冷启动和并发调度跟不上二是状态管理要自己造轮子三是全球用户访问延迟参差不齐。Cloudflare 这套东西恰好在这三点上都有对应的原语。Workers 负责无状态计算Durable Objects 负责单点强一致状态KV 负责最终一致的读多写少缓存R2 负责对象存储D1 负责关系型数据Queues 负责异步解耦AI Gateway 负责模型调用的可观测性和缓存。把它们组合起来确实有了一点操作系统的味道——你不再关心服务器在哪、怎么扩容、怎么容灾你只关心我要调度哪个能力。这篇文章我想聊的不是Cloudflare 有多好而是如果你真的打算用这套东西去搭一个 AI Agent 系统哪些地方是坑、哪些设计决策要想清楚、哪些能力被高估了、哪些被低估了。适合已经写过至少一个 Worker、对 Agent 有基本概念、准备把它推到生产环境的开发者。纯新手也能看但可能需要边看边查文档。2. 把边缘网络当内核这套操作系统的原语到底怎么分工2.1 Worker 是进程不是函数很多人第一次写 Worker脑子里还是云函数的模型一个请求进来跑一段代码返回结束。这个理解在简单场景下没错但一旦涉及 Agent就会出问题。Agent 的一次任务可能持续几十秒甚至几分钟中间要调用多次模型、多次工具、多次读写状态。如果你还按一个请求一个函数来设计就会被迫把整个 Agent 循环塞进一个请求里然后撞上 CPU 时间限制和请求超时。正确的做法是把 Worker 当成进程调度器而不是进程本身。一个请求进来Worker 只负责解析意图、创建或唤醒一个 Durable Object、把任务投递进去然后立刻返回一个任务 ID 或者建立一条流式连接。真正的 Agent 循环跑在 Durable Object 里它可以长时间存活、可以被多次唤醒、可以主动推送结果。这个分工想清楚了后面很多设计就顺了。我见过太多人把 Agent 逻辑直接写在 fetch handler 里然后抱怨为什么跑一半就断了。不是平台的问题是模型用错了。2.2 Durable Object 是这套架构里最被低估的原语如果说 Worker 是进程那 Durable Object后面简称 DO就是带状态的守护进程 单点锁。每个 DO 实例有全局唯一的 ID同一时刻只有一个实例在跑所有请求串行进入。这个特性听起来像限制实际上是 Agent 场景里最需要的东西。想象一个多轮对话 Agent用户发消息Agent 要读历史、调模型、调工具、写回历史。如果这些操作并发进来没有串行保证历史就会乱。用传统方案你得自己上分布式锁、自己处理竞态。用 DO天然就是串行的你只要把状态放在 DO 的 storage 里读写都是强一致的。但 DO 也有代价。它的单点特性意味着吞吐有上限一个 DO 实例大概每秒能处理几百到上千个请求取决于你的逻辑复杂度。所以设计时要考虑分片一个用户一个 DO还是一个会话一个 DO还是一个租户一个 DO我的经验是一个会话一个 DO最稳用户级别的聚合状态另开一个 DO 或者放 D1。这样单个会话的并发压力可控跨会话的查询走 D1。2.3 KV、R2、D1、Queues 各自的边界这四个存储原语经常被混用我列个表说清楚它们各自适合什么原语一致性适用场景不适用场景KV最终一致写入后全球生效有延迟配置、feature flag、读多写少的缓存需要立即读到刚写入的数据R2强一致单区域大文件、模型产物、日志归档高频小对象读写D1强一致单主库关系型数据、需要 SQL 查询高并发写入、跨区域低延迟读Queues至少一次投递异步任务、削峰、解耦需要严格顺序或精确一次Agent 场景里最常见的误用是拿 KV 存会话状态。KV 写入后全球生效有几十秒延迟你这边刚写完下一个请求打到另一个边缘节点读到旧值Agent 就失忆了。会话状态必须放 DO 或者 D1KV 只适合放那些变了也无所谓的东西。2.4 AI Gateway 不是必须的但强烈建议加上AI Gateway 的核心价值不是帮你调模型而是三件事缓存、限流、可观测。Agent 场景里模型调用是成本大头缓存能省的钱很可观——尤其是那些 system prompt 固定、用户问题重复率高的场景。限流能防止某个用户或某个 bug 把你的额度打爆。可观测能让你看到每次调用的延迟、token 数、失败率。我自己的做法是所有模型调用都走 AI Gateway哪怕只是加一层转发。多这一跳的延迟可以忽略但换来的是排查问题时的救命稻草。有一次线上 Agent 突然变慢就是靠 Gateway 的日志发现某个工具的返回体异常大把上下文撑爆了。3. 用 Worker DO 搭一个最小可用的 Agent 骨架3.1 为什么不用现成的 Agent 框架现在 Agent 框架满天飞LangChain、LlamaIndex、各种国产的、各种一行代码搭 Agent的。我的建议是如果你要做生产系统先别急着上框架。原因有三个。第一框架的抽象层会掩盖底层行为。Agent 出问题时你需要在模型返回了什么工具被调了几次状态在哪一步写坏了这些层面排查。框架把这些都包起来了你只能看它给你的日志很多时候不够用。第二框架的更新速度跟不上模型和平台的变化。你今天用的框架可能下个月就不维护了或者某个 API 变了它没跟上。生产系统最怕这种依赖。第三Agent 的核心逻辑其实不复杂。无非是读状态 → 调模型 → 解析工具调用 → 执行工具 → 写状态 → 循环。这个循环自己写一遍两百行代码比学一个框架的 API 快。所以我下面给的骨架是裸写的不依赖任何 Agent 框架。你可以后面按需引入但先理解底层。3.2 项目结构agent-worker/ ├── src/ │ ├── index.ts # Worker 入口路由分发 │ ├── session.ts # Durable Object会话状态与 Agent 循环 │ ├── tools/ │ │ ├── index.ts # 工具注册表 │ │ ├── search.ts # 示例工具搜索 │ │ └── calc.ts # 示例工具计算 │ └── llm.ts # 模型调用封装 ├── wrangler.toml └── package.json这个结构的关键是session.ts里的 DO 承担了 Agent 循环index.ts只做路由和鉴权。工具用注册表模式方便扩展。3.3 wrangler.toml 的关键配置name agent-worker main src/index.ts compatibility_date 2024-11-01 [[durable_objects.bindings]] name SESSION class_name SessionDO [[migrations]] tag v1 new_classes [SessionDO] [[kv_namespaces]] binding CACHE id your-kv-id [[d1_databases]] binding DB database_name agent-db database_id your-d1-id [ai] binding AI几个容易踩的点compatibility_date一定要写而且尽量用新的否则一些新 API 用不了。DO 的 migration 每次加新类都要加 tag不能改旧的。AI binding 是 Workers AI 的绑定如果你用外部模型比如 OpenAI、Anthropic不需要这个直接 fetch 就行。3.4 DO 里的 Agent 循环长什么样核心逻辑大概是这样export class SessionDO { state: DurableObjectState; env: Env; messages: Message[] []; constructor(state: DurableObjectState, env: Env) { this.state state; this.env env; this.state.blockConcurrencyWhile(async () { this.messages (await this.state.storage.get(messages)) || []; }); } async fetch(request: Request) { const { input } await request.json(); this.messages.push({ role: user, content: input }); let step 0; while (step 10) { const response await callLLM(this.env, this.messages); this.messages.push(response); if (response.toolCalls response.toolCalls.length 0) { for (const call of response.toolCalls) { const result await executeTool(call.name, call.args); this.messages.push({ role: tool, tool_call_id: call.id, content: JSON.stringify(result), }); } step; continue; } await this.state.storage.put(messages, this.messages); return new Response(JSON.stringify({ reply: response.content })); } return new Response(JSON.stringify({ error: max steps exceeded }), { status: 500 }); } }这段代码有几个设计决策值得说。blockConcurrencyWhile保证构造时状态加载完再处理请求避免竞态。step 10是防止 Agent 陷入死循环生产环境这个值要按业务调。每轮循环都写 storage 是浪费只在最终返回时写一次中间状态放内存——但如果 DO 被驱逐内存会丢所以关键节点还是要落盘。3.5 流式输出怎么接Agent 场景里流式输出几乎是必须的用户等 30 秒看不到任何反馈会以为卡死了。DO 支持返回 ReadableStream你可以把模型的流式响应直接透传出去。但要注意一旦开始流式输出就不能再改 HTTP 状态码了所以错误处理要在流开始前做完。我的做法是DO 先做一轮预检检查额度、检查输入合法性通过了再开始流式。流中间如果出错发一个特殊的 SSE 事件告诉前端出错了前端负责展示。这样用户体验最好。4. 状态、记忆与并发Agent 最容易翻车的地方4.1 短期记忆和长期记忆要分开设计Agent 的记忆这个词被用烂了实际上要拆成两层。短期记忆是当前会话的上下文就是那个 messages 数组放 DO 内存 storage。长期记忆是跨会话的知识比如这个用户偏好简洁回答这个用户是开发者放 D1 或者向量库。很多人一上来就搞向量数据库做长期记忆结果发现召回质量差、成本高、还慢。我的建议是先用结构化字段。用户偏好、常用工具、历史摘要这些用 D1 存几个字段就够了查询快、可控、可解释。真正需要语义召回的场景比如帮我找上次讨论过的那个方案再上向量。4.2 上下文窗口管理是个工程问题不是模型问题模型上下文再大也有上限而且越大越贵越慢。Agent 跑多轮之后messages 数组会膨胀。你必须主动管理。我常用的策略是三层最近 N 轮完整保留更早的轮次做摘要压缩再早的直接丢弃但保留关键实体。摘要压缩用便宜的小模型做比如把 10 轮对话压成 200 字。关键实体人名、项目名、数字单独抽出来放一个结构化字段不参与压缩。这里有个坑摘要会丢信息而且丢得不可预测。所以涉及金额、日期、ID 这类不能错的信息一定要单独存不能只靠摘要。4.3 并发请求下的状态一致性同一个会话用户可能快速发两条消息。如果两条都进 DODO 是串行处理的第二条会等第一条跑完。这听起来对但实际体验很差——用户发第二条时第一条可能还在跑 Agent 循环要等几十秒。我的处理方式是DO 内部维护一个任务队列新消息进来先入队返回已收到。Agent 循环处理完当前任务后检查队列有就继续。前端通过 WebSocket 或者轮询拿结果。这样用户感觉是即时响应实际是异步处理。另一个坑是 DO 的 storage 写入有频率限制。如果你每轮循环都写高频会话下会撞限流。批量写、延迟写、只写关键状态这三点要做到。4.4 工具调用的幂等性Agent 调工具可能失败重试如果工具不幂等就会重复执行。比如发邮件这个工具重试两次就发两封。解决办法是给每次工具调用生成一个唯一 ID工具内部用这个 ID 去重。对于有副作用的工具写数据库、发消息、扣款幂等设计是必须的不是可选的。我见过一个案例Agent 调支付工具网络抖动重试用户被扣了两次钱。这种事故在 Agent 系统里比传统系统更容易发生因为 Agent 会自作主张重试。5. 部署、观测与成本上线之后才是真正的开始5.1 本地开发和线上行为的差异wrangler dev本地跑和线上跑行为有差异。最明显的是 DO 的行为本地是单进程模拟线上是真正的分布式。本地测着没问题的并发逻辑线上可能出问题。所以关键路径一定要在 staging 环境用wrangler deploy --env staging跑一遍。另一个差异是 KV 的一致性。本地 KV 是立即一致的线上是最终一致。本地测着写完立刻读能读到线上可能读不到。这个坑我踩过排查了半天。5.2 观测要抓的三个指标Agent 系统的观测我重点关注三个每轮循环的 token 消耗、工具调用的成功率和延迟、端到端任务完成率。前两个决定成本第三个决定体验。Cloudflare 自带的 Analytics 能看到请求量、错误率、CPU 时间但看不到业务层面的指标。我的做法是在 Worker 里埋点把关键事件写到 Analytics Engine 或者直接推到外部比如自建的时序库。别嫌麻烦出问题时这些数据是唯一能救你的。5.3 成本控制的几个实操手段Agent 成本主要是模型调用。控制手段按性价比排序缓存 小模型分流 上下文压缩 限流。缓存用 AI Gateway 的缓存功能相同输入直接返回。小模型分流是指简单任务分类、抽取、格式化用便宜模型复杂推理才用大模型。上下文压缩前面说过。限流是兜底防止异常流量。我算过一笔账一个中等复杂度的 Agent不做任何优化每次任务大概 0.05 到 0.2 美元。做了缓存和分流之后能降到 0.02 到 0.08。量大了这个差距很可观。5.4 冷启动和 DO 驱逐DO 长时间不活动会被驱逐下次请求要重新加载状态。如果你的 DO 构造时要读大量数据冷启动会慢。解决办法是构造时只读必要的最小状态其余按需加载。另外 DO 的 storage 读取是异步的构造时用blockConcurrencyWhile包住避免请求进来时状态还没加载完。Worker 本身的冷启动已经很快了毫秒级基本不用管。真正要管的是 DO 的状态加载。6. 几个我踩过的坑和对应的解法6.1 坑一把 DO 当数据库用一开始我把所有会话数据都塞进 DO storage觉得方便。结果数据量一大DO 的 storage 读写变慢而且没法做跨会话查询。后来改成 DO 只存活跃会话的上下文历史归档到 D1查询走 D1。DO 的 storage 有大小限制别把它当无限仓库。6.2 坑二忽略 Worker 的 CPU 时间限制Worker 有 CPU 时间限制免费版 10ms付费版 30s 可配。注意是 CPU 时间不是墙钟时间。等模型响应的时间不算 CPU但 JSON 解析、字符串处理、加密这些算。我有一次在 Worker 里做大量 JSON 处理直接撞限制。解决办法是把重计算移到 DO 或者用流式处理别一次性加载大对象。6.3 坑三工具描述写得太随意Agent 靠工具描述来决定调哪个工具。描述写得模糊Agent 就会乱调。比如你有个工具叫search描述写搜索信息Agent 不知道搜什么、什么时候搜。正确的写法是写清楚这个工具做什么、什么时候用、输入格式、返回什么、有什么限制。工具描述是 prompt 工程的一部分要认真写。6.4 坑四没有超时和重试策略模型调用、工具调用、外部 API 调用都要有超时。没有超时一个卡住的调用会把整个 Agent 循环拖死。重试要有退避策略不能无脑重试。我的默认配置是模型调用超时 30s重试 2 次指数退避工具调用超时 10s重试 1 次外部 API 按对方文档来。6.5 坑五日志里泄露敏感信息Agent 的 messages 里可能有用户隐私、API key、内部数据。日志默认会记录请求体一不小心就泄露了。我的做法是日志只记元数据长度、hash、工具名不记内容确需记内容的先脱敏。这个在合规要求高的场景是硬性要求。7. 这套架构适合什么、不适合什么聊了这么多技术细节最后说点判断层面的东西。Cloudflare 这套边缘操作系统不是银弹它有明确的适用边界。适合的场景全球用户、对延迟敏感、流量波动大、团队小不想管服务器、Agent 任务以 IO 等待为主调模型、调 API。这些场景下它的弹性、全球分布、按量计费优势明显。不适合的场景需要长时间 CPU 密集计算比如本地跑推理、需要特定硬件GPU、需要复杂的事务跨多个数据源、团队已经有成熟的 K8s 体系且迁移成本高。这些场景下硬上 Cloudflare 会别扭。我自己的判断标准是如果我的 Agent 任务 90% 的时间在等外部响应那 Cloudflare 是优选如果 90% 的时间在算那要找别的地方。这个标准帮我省了很多纠结。另外提醒一句这套东西的生态还在快速变化。今天的最佳实践半年后可能就过时了。保持关注官方 changelog别把架构设计得太死留好替换空间。我一般会把模型调用、存储访问、工具执行都封装成接口底层实现可替换。这样平台变了、模型换了上层逻辑不用动。写到这里关于 cloudflare-os 这个概念的拆解基本覆盖了从架构选型到上线运维的主要环节。如果你正准备用这套东西搭 Agent建议先从最小骨架跑通再逐步加状态、加工具、加观测。别一上来就设计大而全的架构Agent 系统的复杂度是长出来的不是设计出来的。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表