资讯动态

Cloudflare Workers 边缘计算:AI Agent 运行底座与状态管理实践

发布时间:2026/10/26 15:46:03 来源:尧图企业网站定制
1. 从“cloudflare-os”这个标题说起它到底在指什么第一次看到“cloudflare-os”这个词我下意识以为 Cloudflare 出了一款操作系统。翻了一圈资料才反应过来它并不是一个真正意义上的 OS 发行版而是社区对“把 Cloudflare 全家桶当成一套运行环境来用”这件事的戏称。换句话说它指的是以Cloudflare Workers为计算核心配合 D1、R2、KV、Queues、Durable Objects、AI Gateway 等一整套边缘服务搭出一个能跑应用、能跑 Agent、能跑定时任务的“类操作系统”平台。这个说法之所以成立是因为 Workers 早就不是当年那个只能做请求转发的边缘函数了。它现在有持久化存储、有队列、有状态对象、有向量检索、有模型调用入口还有一套基于 V8 Isolate 的隔离机制。把这些拼起来你确实能得到一个“不用管服务器、全球分布、按量计费”的运行底座。对于想快速验证 AI Agent 想法的人来说这套东西的吸引力非常大——你不需要先买一台云主机也不需要配一堆运维脚本写完代码推上去就能跑。但“cloudflare-os”这个叫法也容易让人产生误解。它不是让你在上面装软件、跑任意二进制也不是一个通用 Linux 环境。它的边界很清晰你写的是 JavaScript/TypeScript或编译到 Wasm 的语言跑在 V8 Isolate 里受限于 CPU 时间、内存和 API 白名单。理解这一点比记住任何 API 都重要。我见过不少人兴冲冲地想把一个本地跑得好好的 Node 项目直接搬上去结果发现 fs、child_process、原生模块全用不了最后卡在原地。所以这篇内容我想聊的是把 Cloudflare 这套边缘能力当成“Agent 运行底座”来用时真正需要搞清楚的几件事它的运行模型是什么、Agent 在上面怎么落地、状态和记忆怎么存、多 Agent 协作怎么编排、以及那些文档里不会写但踩一次就记住的坑。适合已经写过一点 Workers、想往 AI Agent 方向走的人也适合正在选型、想知道这套方案到底适不适合自己项目的开发者。2. Workers 的运行模型为什么它和传统 Node 服务不是一回事2.1 V8 Isolate 带来的“冷启动幻觉”传统 Serverless 用容器冷启动动辄几百毫秒到几秒。Workers 用的是 V8 Isolate启动一个隔离环境只需要几毫秒甚至更短。这个差异直接改变了 Agent 的设计思路——你可以放心地把逻辑拆成很多个小函数让它们按需唤起而不用像以前那样为了“保温”而常驻一个进程。但这里有个反直觉的点Isolate 快不代表你的代码可以随便写。每个请求有 CPU 时间上限免费版和付费版额度不同付费版默认 30 秒 CPU 时间可申请提高超过就被掐断。注意是 CPU 时间不是墙钟时间。也就是说你在等网络 IO、等模型返回的时候CPU 是空闲的不计入。但如果你在做一个大循环、跑一个复杂的向量计算那 CPU 时间会蹭蹭往上涨。我踩过的第一个坑就在这里写了一个 Agent把用户输入做本地分词和关键词提取逻辑不复杂但循环很多本地测试没问题上线后偶尔报 “Worker exceeded CPU time limit”。后来把分词改成调用轻量模型或者预计算问题就没了。经验是把重计算尽量挪到模型侧或预计算阶段Worker 里只做编排和轻量处理。2.2 请求生命周期与全局状态的边界Workers 的请求模型是“一次请求一个执行上下文”。你在模块顶层声明的变量在同一个 Isolate 复用期间是共享的但 Isolate 什么时候被回收你控制不了。这意味着不能用模块级变量存用户会话状态它随时可能消失也可能被另一个请求看到。那 Agent 的对话状态存哪答案是外部存储。KV 适合读多写少的配置和缓存D1 适合结构化对话记录Durable Objects 适合需要强一致和串行化的场景。我一般这样分存储类型适合存什么一致性典型延迟KV系统提示词、工具描述、静态配置最终一致读快写慢D1对话历史、用户档案、任务记录强一致单区域中等Durable Objects会话锁、计数器、实时协作状态强一致单对象串行低R2大文件、模型产物、日志归档强一致中等Vectorize向量索引、语义检索最终一致低这张表是我实际用下来觉得最省心的分工。特别是 Durable Objects做多轮对话的“会话管家”非常合适——每个会话一个 DO 实例天然串行不用担心并发写冲突。2.3 绑定Bindings才是真正的“系统调用”在传统 OS 里程序通过系统调用访问文件、网络、进程。在 Workers 里对应的概念是Bindings。你在 wrangler.toml 里声明一个 KV 命名空间、一个 D1 数据库、一个 AI 绑定代码里就能通过 env 访问它们。这套设计的好处是权限清晰、类型安全坏处是没声明的能力你一点都用不了。我建议新手先把 wrangler.toml 当成“能力清单”来读。你想让 Agent 具备什么能力就先在这里加绑定。比如要调用模型加 AI 绑定要存对话加 D1 绑定要做异步任务加 Queues 绑定。加完再写代码思路会清晰很多。# wrangler.toml 片段 name agent-worker main src/index.ts compatibility_date 2024-11-01 [[d1_databases]] binding DB database_name agent-db database_id xxxx [[kv_namespaces]] binding CONFIG id xxxx [ai] binding AI [[durable_objects.bindings]] name SESSION class_name SessionDO [[migrations]] tag v1 new_classes [SessionDO]提示compatibility_date 不要随便改。它决定了运行时行为改日期可能引入不兼容变更。我一般只在需要新特性时才动它并且改完一定跑一遍回归。3. 把 Agent 跑在边缘架构怎么搭才不别扭3.1 单 Agent 的最小闭环一个能用的 Agent最小闭环是接收输入 → 组装上下文 → 调用模型 → 解析工具调用 → 执行工具 → 回填结果 → 再调模型 → 返回输出。在 Workers 里这个循环可以用一个 fetch handler 加一个 while 循环实现。但要注意模型调用是异步的工具执行也可能是异步的而 Worker 的 CPU 时间只算同步计算。所以只要你不做重计算这个循环跑几十轮都没问题。真正会卡住你的是模型 API 的延迟和 token 成本。我一般会设一个最大轮次比如 8 轮超过就强制返回当前结果并提示“任务较复杂已中断”。这个保护机制在线上非常必要否则一个死循环能把你的额度烧光。// 简化的 Agent 循环 async function runAgent(env, messages, maxTurns 8) { let turns 0; while (turns maxTurns) { const response await env.AI.run(cf/meta/llama-3.1-8b-instruct, { messages, tools: toolDefinitions, }); const choice response.choices[0]; messages.push(choice.message); if (!choice.message.tool_calls?.length) { return choice.message.content; } for (const call of choice.message.tool_calls) { const result await executeTool(env, call); messages.push({ role: tool, tool_call_id: call.id, content: JSON.stringify(result), }); } turns; } return 达到最大轮次已停止。; }3.2 工具Tools的设计决定 Agent 的上限Agent 聪不聪明很大程度上取决于你给它什么工具、工具描述写得好不好。我见过太多人把工具描述写成一句话然后抱怨模型不会用。工具描述要像写给一个新同事看的操作手册这个工具做什么、什么时候用、参数什么含义、返回什么格式、有什么限制。举个例子一个“查询订单”的工具描述应该包含输入是订单号字符串格式为 ORD- 开头加 8 位数字返回订单状态和金额如果订单不存在返回 null。这样模型才知道什么时候调用、怎么传参、怎么处理异常。另外工具数量不要太多。我实测下来单次暴露给模型的工具控制在 5 到 10 个效果最好。太多会让模型选择困难也浪费 token。如果确实有很多能力可以做一层“工具路由”先用一个轻量模型判断意图再动态加载相关工具。3.3 用 Queues 做异步任务别让用户干等有些 Agent 任务很耗时比如批量处理、长文档分析、多轮检索。这种场景不适合让用户 HTTP 请求一直挂着。我的做法是请求进来先写一条任务记录到 D1然后往 Queues 推一条消息立即返回任务 ID。后台消费者慢慢处理处理完更新状态。用户拿任务 ID 轮询或者用 WebSocket 接收通知。这套模式在 Workers 上跑非常顺因为 Queues 本身就是为边缘设计的消费者也是 Worker可以复用同一套工具代码。唯一要注意的是消息体大小有限制大内容要存 R2消息里只放引用。4. 记忆与状态Agent 的“长期记忆”到底怎么落地4.1 短期记忆对话窗口的管理多轮对话最直接的问题就是上下文越来越长。模型有 token 上限超了要么报错要么被截断。我的策略是分层最近 N 轮完整保留保证连贯性。更早的对话用模型做摘要压缩成一段话。关键事实抽取成结构化字段存 D1比如用户偏好、已确认的信息。摘要这一步可以异步做不阻塞主流程。我一般每 10 轮触发一次摘要把前 10 轮压成 200 字以内的要点。这样上下文长度能控制在一个稳定范围。4.2 长期记忆向量检索的正确打开方式想让 Agent “记住”跨会话的信息就得用向量检索。流程是把重要信息切片 → 生成 embedding → 存 Vectorize → 查询时先检索相关片段 → 拼进上下文。这里有个容易忽略的点不是所有信息都值得存向量库。我一开始把所有对话都塞进去结果检索出来的全是噪音。后来改成只存“结论性信息”和“用户明确表达的偏好”检索质量立刻上来了。另外embedding 模型的选择要和查询语言匹配。中文内容用支持中文的模型否则检索效果会差很多。Vectorize 支持多种 embedding 模型选之前先拿一批真实数据测一下召回率。4.3 Durable Objects 做会话锁如果同一个用户可能同时发多条消息就会出现并发写对话历史的问题。用 D1 的话两个请求同时读-改-写可能丢更新。这时候 Durable Objects 就派上用场了每个会话一个 DO所有写操作走 DO天然串行。DO 的另一个好处是可以存内存状态比如当前正在执行的任务、临时缓存。但记住DO 也会被驱逐内存状态不是持久的重要数据还是要落盘。export class SessionDO { state: DurableObjectState; messages: any[] []; constructor(state: DurableObjectState) { this.state state; this.state.blockConcurrencyWhile(async () { this.messages (await this.state.storage.get(messages)) || []; }); } async fetch(request: Request) { const { role, content } await request.json(); this.messages.push({ role, content }); await this.state.storage.put(messages, this.messages); return new Response(JSON.stringify({ ok: true, count: this.messages.length })); } }5. 多 Agent 协作与编排别一上来就搞复杂5.1 什么时候需要多 Agent单 Agent 能搞定的事就别拆成多 Agent。多 Agent 带来的复杂度是实打实的通信开销、状态同步、错误传播、调试困难。我一般只在两种情况下拆职责差异大比如一个负责检索、一个负责写作、一个负责审核各自的提示词和工具完全不同。需要并行多个子任务互不依赖可以同时跑最后汇总。如果只是任务步骤多用单 Agent 加多轮工具调用就够了没必要拆。5.2 编排模式路由、流水线、辩论常见的多 Agent 编排有三种模式路由模式一个调度 Agent 判断任务类型分发给对应的专家 Agent。适合任务类型明确的场景比如客服分流。流水线模式Agent A 的输出是 Agent B 的输入依次传递。适合有明确阶段的任务比如“检索 → 分析 → 成文”。辩论模式多个 Agent 对同一问题给出答案再由一个仲裁 Agent 综合。适合需要多视角的决策场景但成本高慎用。在 Workers 上实现这些模式可以用 Service Bindings 让 Worker 之间互相调用也可以用 Queues 做异步编排。Service Bindings 更简单适合同步场景Queues 适合长流程和需要重试的场景。5.3 通信协议结构化消息比自然语言可靠Agent 之间传消息我强烈建议用结构化格式JSON而不是自然语言。自然语言灵活但不可靠解析容易出错。定义一个简单的消息 schema比如{ from, to, type, payload, traceId }每个 Agent 按 schema 解析和生成。这样调试的时候一眼就能看出哪一步出了问题。traceId 特别重要。多 Agent 链路一长没有 traceId 根本没法追。我一般从入口请求生成一个 traceId一路透传下去日志里都带上排查问题时按 traceId 一搜就全出来了。6. 那些文档不写、踩一次就记住的坑6.1 环境变量和密钥的处理Workers 的密钥用wrangler secret put设置不要写进 wrangler.toml。本地开发用 .dev.vars 文件记得加进 .gitignore。我见过有人把 API key 直接写在代码里推上去了虽然 Workers 代码不公开但这是坏习惯一旦仓库泄露就麻烦。另外不同环境开发、预发、生产要用不同的密钥和数据库。wrangler 支持通过--env切换环境配置提前规划好能省很多事。6.2 日志和可观测性Workers 的日志用wrangler tail实时看但线上排查不能只靠这个。我一般会把关键事件写进 D1 或者发到外部分析服务。注意日志里不要打敏感信息用户输入、模型输出都可能包含隐私内容打日志前先脱敏。还有一个坑console.log在 Worker 里是异步的高频打日志可能影响性能。生产环境建议用采样比如 10% 的请求打详细日志。6.3 超时和重试模型调用、外部 API 调用都可能超时。Workers 里用AbortController设超时别让请求无限等。重试要加退避并且只对幂等操作重试。写操作重试可能导致重复执行要用幂等键去重。我踩过一个坑工具调用失败后自动重试结果同一个订单被创建了两次。后来加了幂等键每次工具调用带一个唯一 ID服务端根据 ID 去重问题才解决。6.4 成本控制边缘计算按量计费跑 Agent 很容易不知不觉烧钱。我的做法是设每日预算告警对模型调用做缓存相同输入直接返回缓存结果用轻量模型做意图判断重模型只处理复杂任务限制单用户每日调用次数这些措施加起来能把成本压到可控范围。特别是缓存很多 Agent 的查询是重复的缓存命中率能到 30% 以上。7. 从零搭一个最小可用 Agent 的实操路径如果你看到这里想动手我建议按这个顺序来别跳步装 wrangler初始化项目npm create cloudflarelatest选 TypeScript 模板。加 AI 绑定跑通一次模型调用先不管 Agent 逻辑就发一条消息拿回复。加 D1存对话历史建表写读写函数确认数据能落盘。加工具调用先做一个最简单的工具比如查时间跑通工具调用循环。加 Durable Objects 做会话管理把对话状态从 D1 挪到 DO体验串行写入。加 Queues 做异步任务把耗时操作挪到后台。加 Vectorize 做长期记忆最后再上向量检索前面几步稳了再加。每一步都跑通了再进下一步。我见过太多人一上来就想搭完整的多 Agent 系统结果卡在环境配置上就放弃了。小步快跑每步可验证是这个平台最舒服的用法。部署命令就一条wrangler deploy几秒钟后全球生效。这种即时反馈是本地开发很难体会到的。等你第一次看到自己的 Agent 在边缘跑起来、响应时间只有几十毫秒的时候大概就能理解为什么有人管它叫“cloudflare-os”了——它确实给了你一种“拥有一套轻量操作系统”的错觉只不过这套系统的边界需要你亲手摸一遍才清楚。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑