基于Cloudflare Workers构建AI Agent运行时:架构设计与工程实践 1. 从“cloudflare-os”这个标题说起它到底在讲什么第一次看到“cloudflare-os”这个标题很多人会误以为Cloudflare要发布一款操作系统类似某个Linux发行版或者桌面环境。实际上结合Cloudflare Workers、AI Agent、开源这几个关键词来看它指向的是一个更具体、也更有意思的方向用Cloudflare Workers作为运行时底座搭建一套面向AI Agent的轻量级“操作系统层”。这里的“OS”不是传统意义上管理硬件资源的操作系统而是管理Agent生命周期、工具调用、记忆存储、任务编排的一层软件抽象。我最早接触这个思路是在做一个多Agent协作的小项目时。当时面临的核心问题是Agent的部署和调度太碎了。每个Agent要么跑在本地脚本里要么塞进某个容器状态管理、工具注册、消息路由全靠手写胶水代码。后来发现Cloudflare Workers天然适合干这件事——它边缘部署、冷启动极快、有Durable Objects做有状态协调、有KV和R2做存储、有Queues做异步任务、有AI Gateway做模型调用统一入口。把这些能力组合起来就形成了一个事实上的“Agent运行环境”也就是大家口口相传的cloudflare-os。这篇文章适合谁看如果你正在做AI Agent开发手头有一堆工具函数和提示词需要管理或者你想找一个低成本、低运维的Agent托管方案那这篇内容会对你有直接帮助。如果你只是听说过Cloudflare Workers但没实际用过我也会把关键概念拆开讲清楚。全文基于我在实际项目中的选型、踩坑和调优经验结合社区里常见的实践方案尽量把“为什么这么设计”讲透。2. 整体架构设计为什么用Workers来承载Agent2.1 核心思路把Agent当成一个“有状态的服务”传统做法里一个Agent往往是一个长时间运行的进程内部维护对话历史、工具注册表、任务队列。这种模式在本地开发时很舒服但一旦要部署到线上、要支持多用户、要保证可用性问题就来了进程挂了状态就丢了扩容时状态同步麻烦冷启动慢导致响应延迟高。cloudflare-os的核心思路是把Agent拆成“无状态的计算”加“有状态的存储”两部分。计算部分跑在Workers里每次请求来了就执行执行完就释放不需要常驻进程。状态部分交给Durable Objects每个Agent实例对应一个Durable Object它是有状态的、单线程执行的天然适合做对话历史、任务队列、锁管理这类需要串行化的操作。这个设计的好处很明显。第一部署简单一个wrangler deploy就上去了不需要管服务器、不需要配负载均衡。第二成本低Workers按请求计费空闲时不花钱对于个人项目或者中小规模应用非常友好。第三边缘节点多用户请求就近处理延迟低。第四和Cloudflare生态里的AI Gateway、Vectorize、R2无缝集成模型调用、向量检索、文件存储都不用自己搭。当然也有代价。Durable Objects有单点特性虽然Cloudflare做了容灾但设计上还是要避免把全局热点放在一个Object里。另外Workers有CPU时间限制复杂推理任务不适合直接在里面跑得拆成异步任务用Queues或者Workflows来处理。这些限制在架构设计时就要考虑进去。2.2 模块划分一个Agent OS需要哪些组件我把cloudflare-os的组件分成五层来看这样理解起来比较清晰。接入层负责接收外部请求可以是HTTP API、WebSocket、或者定时触发器。Workers本身就能处理这些WebSocket用Durable Objects做长连接管理。编排层是核心负责解析用户意图、决定调用哪个Agent、协调多个Agent之间的协作。这一层可以用Workers里的JavaScript逻辑实现也可以用Workers AI做意图分类。多Agent协作时可以用Durable Objects做协调器每个子Agent是一个独立的Object协调器负责分发任务和汇总结果。能力层是Agent可以调用的工具集合。每个工具就是一个函数注册在工具注册表里。工具可以是纯计算、可以调外部API、可以查数据库、可以调模型。Cloudflare的AI Gateway在这里很有用它统一了模型调用的接口支持缓存、限流、回退不用在每个工具里重复写模型调用的样板代码。记忆层分短期和长期。短期记忆就是当前对话的上下文存在Durable Object的存储里。长期记忆用Vectorize做向量检索把历史对话、知识库文档嵌入后存进去需要时检索相关片段注入上下文。R2可以用来存原始文件比如用户上传的PDF、图片。可观测层包括日志、指标、追踪。Workers有内置的日志和分析但Agent场景下还需要记录每次工具调用的输入输出、模型调用的token消耗、任务执行时长。这些可以写到Analytics Engine或者外部可观测平台。这五层不是必须全部实现起步阶段可以只做接入层、编排层和最简单的记忆层后面按需扩展。关键是分层清晰每层职责单一方便替换和测试。2.3 选型对比为什么不是其他方案有人会问为什么不用LangChain加一个普通服务器或者用Vercel的Edge Functions我实际对比过几种方案。LangChain加服务器的方式灵活度最高但运维成本也最高。你得管服务器、管依赖、管扩容对于个人开发者或者小团队来说精力消耗大。而且LangChain的抽象层比较厚出问题时排查链路长。Vercel Edge Functions和Workers类似但生态上Cloudflare的Durable Objects、Vectorize、AI Gateway组合更完整特别是Durable Objects做有状态协调Vercel那边没有直接对应的产品。纯用AWS Lambda加Step Functions也能做但配置复杂冷启动问题更明显而且边缘部署能力不如Cloudflare。所以cloudflare-os这个方向本质上是利用Cloudflare现有产品矩阵拼出一个对Agent场景友好的运行环境。它不是要替代完整的操作系统而是在Serverless的约束下找到一套能跑通Agent工作流的模式。3. 核心细节解析Agent运行时到底怎么实现3.1 Durable ObjectsAgent的“单线程大脑”Durable Objects是cloudflare-os里最关键的组件理解它才能理解整个架构。简单说每个Durable Object是一个有唯一ID的、单线程执行的、有持久化存储的对象。你可以把它想象成一个永远在线的微型服务器但不需要你管部署和扩容。在Agent场景里我通常这样用每个用户会话对应一个Durable ObjectObject的ID用用户ID加会话ID生成。这个Object里维护对话历史数组、当前任务状态、工具调用记录。所有对会话的读写都经过这个Object天然保证了顺序性不会出现两个请求同时改历史导致覆盖的问题。Durable Objects的存储API分两种。一种是storage.put/get类似KV适合存小量结构化数据。另一种是storage.list可以列出所有键。对于对话历史我一般存成一个数组每次追加新消息后整体写回。如果历史很长可以分页存储比如每50条一个键避免单次读写太大。注意Durable Objects的存储有大小限制单个值不能超过128KB。对话历史如果太长要么截断要么分片存储。我踩过这个坑一次存了太多消息导致写入失败后来改成滑动窗口加摘要的方式解决。另一个关键点是Durable Objects的唤醒机制。当请求到达时如果Object不在内存中Cloudflare会自动唤醒它从存储里恢复状态。这个过程通常很快但首次唤醒会有一定延迟。对于延迟敏感的场景可以定期发心跳请求保持Object活跃但这样会增加成本需要权衡。3.2 工具注册与调用让Agent知道它能做什么Agent的能力边界由工具决定。在cloudflare-os里工具注册表是一个核心数据结构我一般用一个JSON对象来维护键是工具名值是工具的描述、参数schema、执行函数。工具描述要写清楚因为模型需要根据描述来决定调用哪个工具。描述里要包含这个工具做什么、什么时候用、参数是什么意思、返回值是什么格式。我见过很多项目工具描述写得太简略导致模型乱调用或者不调用。参数schema用JSON Schema格式这样可以直接喂给模型做function calling。Cloudflare Workers AI支持function callingOpenAI兼容的接口也支持所以schema写一次可以复用。执行函数就是普通的JavaScript异步函数接收参数对象返回结果。这里要注意错误处理工具执行失败时要返回结构化的错误信息而不是抛异常否则整个Agent流程会中断。我通常返回{ success: false, error: ... }这样的格式让模型知道调用失败了可以决定重试还是换工具。工具注册表的加载方式有两种。一种是静态注册在代码里写死部署时一起打包。另一种是动态注册从KV或D1里读取配置运行时组装。静态注册简单直接适合工具集固定的场景。动态注册灵活可以在不重新部署的情况下增减工具但需要处理版本兼容和权限控制。我自己的项目里用的是混合方式核心工具静态注册保证可用性扩展工具动态注册方便迭代。动态工具加载时要做校验确保函数签名和schema一致避免运行时出错。3.3 记忆管理短期上下文与长期检索的配合Agent的记忆分两层短期和长期管理策略完全不同。短期记忆就是当前对话的上下文窗口。模型有token限制不能无限塞历史。我的做法是维护一个滑动窗口保留最近N轮对话同时生成一个摘要。摘要用模型生成把更早的对话压缩成一段话放在上下文开头。这样既保留了长期信息又控制了token消耗。具体实现时每轮对话结束后检查历史长度。如果超过阈值比如20轮就把最早的10轮拿出来让模型生成摘要摘要追加到系统提示里然后从历史里删掉这10轮。摘要也要控制长度一般200字以内。长期记忆用Vectorize。把知识库文档、历史对话的重要片段、用户偏好等信息嵌入成向量存进去。检索时用当前用户输入做查询返回最相关的K个片段注入到上下文里。嵌入模型可以用Workers AI的bge模型也可以用外部API。实操心得Vectorize的检索质量很依赖嵌入模型和分块策略。我试过按固定长度分块效果一般。后来改成按语义分块比如按段落或按标题分检索准确率明显提升。另外检索回来的片段要加来源标注让模型知道信息来自哪里方便追溯。记忆的更新策略也要考虑。不是所有对话都值得存长期记忆。我一般只存用户明确表达偏好、重要事实、任务结论这几类。存的时候打上标签检索时可以按标签过滤。比如用户说“我喜欢简洁的回答”这条就存成偏好标签以后每次对话都检索出来注入。3.4 多Agent协作编排器与子Agent的分工单Agent能做的事有限复杂任务需要多个Agent协作。cloudflare-os里我用一个编排器Agent加多个子Agent的模式。编排器负责理解用户请求拆解成子任务分发给对应的子Agent然后汇总结果。子Agent各自有专长比如一个负责查资料一个负责写代码一个负责审核。编排器和子Agent之间通过Durable Objects通信每个子Agent是一个独立的Object编排器通过RPC调用它们。任务分发时编排器要决定是串行还是并行。串行适合有依赖关系的任务比如先查资料再写总结。并行适合独立任务比如同时查多个数据源。并行时用Promise.all等待所有子Agent返回然后合并结果。子Agent的返回格式要统一我一般用{ agent: name, status: success/failure, result: ..., error: ... }。编排器根据status决定下一步失败时可以重试或者降级。注意多Agent协作时容易出现死循环或者无限递归。比如Agent A调用Agent BB又调用A。我一般在编排器里设置最大调用深度和超时时间超过就强制终止并返回错误。这个保护机制很有必要我遇到过因为提示词问题导致两个Agent互相调用几十次的情况浪费了大量token。4. 实操过程从零搭建一个最小可用Agent OS4.1 环境准备与项目初始化开始之前需要准备几样东西。一个Cloudflare账号开通Workers、Durable Objects、KV、Vectorize、AI Gateway这几个服务。Node.js环境版本18以上。wrangler CLI这是Cloudflare的部署工具。初始化项目用npm create cloudflarelatest选择TypeScript模板。然后安装依赖npm install honoHono是一个轻量Web框架在Workers上跑很合适。再装zod做参数校验ai和cloudflare/ai做模型调用。wrangler.toml是核心配置文件要声明Durable Objects绑定、KV命名空间、Vectorize索引、AI Gateway。我一般把配置分成开发和生产两套用环境变量区分。开发时用本地模拟生产时连真实资源。name cloudflare-os main src/index.ts compatibility_date 2024-01-01 [[durable_objects.bindings]] name AGENT class_name AgentDurableObject [[kv_namespaces]] binding KV id your-kv-id [[vectorize]] binding VECTORIZE index_name agent-memory [ai] binding AIDurable Object的类要单独定义用export class AgentDurableObject然后在index.ts里导出。Workers会自动识别并绑定。4.2 实现一个带工具调用的Agent循环Agent的核心循环是接收用户输入调用模型模型返回工具调用请求执行工具把结果返回给模型模型生成最终回复。这个循环要支持多轮工具调用直到模型不再请求工具为止。先定义工具注册表。我用一个Map来存键是工具名值是{ description, parameters, execute }。parameters用zod schema定义然后转成JSON Schema给模型。const tools new Map(); tools.set(get_weather, { description: 查询指定城市的天气, parameters: z.object({ city: z.string().describe(城市名称) }), execute: async ({ city }) { const res await fetch(https://api.weather.com/${city}); return await res.json(); } });Agent循环的实现我用一个while循环最多迭代10次防止无限调用。每次调用模型时把工具列表转成模型能识别的格式传进去。模型返回后检查是否有tool_calls有就执行对应工具把结果追加到消息历史继续循环。没有就返回最终回复。async function runAgent(messages, tools) { let iterations 0; while (iterations 10) { const response await ai.run(cf/meta/llama-3-8b-instruct, { messages, tools: formatTools(tools) }); if (!response.tool_calls) { return response.response; } for (const call of response.tool_calls) { const tool tools.get(call.name); const result await tool.execute(call.arguments); messages.push({ role: tool, tool_call_id: call.id, content: JSON.stringify(result) }); } iterations; } throw new Error(Agent循环超过最大迭代次数); }这个循环看起来简单但有几个细节要注意。消息历史里工具调用的格式要符合模型要求不同模型格式略有差异。工具执行失败时要返回错误信息而不是抛异常否则循环会中断。迭代次数限制要设置防止模型陷入死循环。4.3 接入Durable Object做会话管理把Agent循环放进Durable Object里每个会话一个Object。Object的构造函数里初始化存储从storage里读取历史消息。fetch方法处理请求调用Agent循环然后把新消息写回storage。export class AgentDurableObject { state: DurableObjectState; messages: Message[]; constructor(state: DurableObjectState) { this.state state; 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 }); const reply await runAgent(this.messages, tools); this.messages.push({ role: assistant, content: reply }); await this.state.storage.put(messages, this.messages); return new Response(JSON.stringify({ reply })); } }blockConcurrencyWhile确保初始化完成前不处理请求避免状态不一致。每次请求结束后写回storage保证持久化。如果消息太长可以在写回前做截断或摘要。路由层用Hono根据用户ID和会话ID生成Durable Object的ID然后调用。这样同一个会话的请求总是打到同一个Object状态一致。app.post(/chat, async (c) { const { userId, sessionId, input } await c.req.json(); const id c.env.AGENT.idFromName(${userId}:${sessionId}); const stub c.env.AGENT.get(id); return stub.fetch(c.req.raw); });4.4 用Vectorize实现长期记忆检索长期记忆的写入和检索分两步。写入时把需要记住的内容嵌入成向量存到Vectorize里同时把原文存到KV或D1用向量ID关联。检索时把用户输入嵌入查询Vectorize拿到最相似的K个向量ID再从KV里取原文。嵌入用Workers AI的cf/baai/bge-base-en-v1.5模型输出768维向量。写入时批量操作一次嵌入多条减少调用次数。async function storeMemory(env, text, metadata) { const embedding await env.AI.run(cf/baai/bge-base-en-v1.5, { text: [text] }); const id crypto.randomUUID(); await env.VECTORIZE.insert([{ id, values: embedding.data[0], metadata }]); await env.KV.put(memory:${id}, text); return id; }检索时先嵌入查询然后env.VECTORIZE.query(embedding, { topK: 5 })拿到结果后从KV取原文拼成上下文注入。实操心得Vectorize的topK不要设太大3到5个就够太多会稀释上下文。metadata里可以存时间戳、来源、标签检索时可以加过滤条件。比如只检索最近30天的记忆或者只检索某个标签的记忆。4.5 部署与本地调试本地调试用wrangler dev它会启动本地模拟环境Durable Objects、KV、Vectorize都有本地实现。不过本地Vectorize是简化版检索效果和线上有差异最终测试还是要部署到线上。部署用wrangler deploy几秒钟就完成。部署后可以用wrangler tail看实时日志排查问题很方便。环境变量和密钥用wrangler secret put设置不要写在配置文件里。AI Gateway的配置在Cloudflare控制台做设置好模型路由、缓存策略、限流规则然后在代码里通过Gateway ID调用。成本方面Workers免费版每天10万请求Durable Objects免费版有100万次读和100万次写。个人项目基本够用。Vectorize按查询次数和存储量计费也不贵。AI Gateway本身不额外收费模型调用按token计费。5. 常见问题与排查技巧实录5.1 Agent不调用工具或乱调用工具这是最常见的问题原因通常出在工具描述上。模型根据描述决定是否调用描述不清楚它就不知道什么时候该用。排查步骤先看工具描述是否说明了使用场景。比如“查询天气”不如“当用户询问某个城市的天气情况时使用输入城市名称返回温度和天气状况”。再检查参数schema是否有描述每个参数都要有describe。最后看模型是否支持function calling有些小模型不支持得换模型。如果工具太多导致模型选择困难可以分组。把工具按领域分成几组先让模型选组再选具体工具。或者用检索的方式根据用户输入先检索相关工具只把相关的几个传给模型。5.2 Durable Object状态丢失或冲突状态丢失一般是没写回storage或者写回失败没处理。每次修改状态后都要await写回并且检查返回值。如果写回失败要重试或者记录错误。状态冲突发生在多个请求同时到达同一个Object时。Durable Objects是单线程的请求会排队但如果代码里有await排队期间可能处理其他请求。解决办法是用blockConcurrencyWhile包裹关键操作或者用事务API。注意Durable Objects的存储操作有速率限制频繁写入会触发限流。我一般做批量写入攒几条一起写减少操作次数。另外storage.put是异步的不await的话可能丢失。5.3 模型调用超时或token超限Workers有CPU时间限制免费版10毫秒付费版30秒。模型调用本身不占CPU时间但等待响应算wall timewall time限制是30秒。如果模型响应慢可能超时。解决办法是用流式响应边生成边返回减少等待。或者把长任务拆成异步任务用Queues排队处理前端轮询结果。AI Gateway可以设置超时和重试配置合理的超时时间失败自动重试。token超限的话要控制上下文长度。滑动窗口加摘要的方式能有效控制。另外工具返回的结果也要控制大小不要把整个网页内容塞进去提取关键信息就好。5.4 常见问题速查表问题现象可能原因排查方法解决方案Agent不调用工具工具描述不清检查description和参数schema补充使用场景和参数说明状态丢失未写回storage检查await和返回值每次修改后写回加重试请求超时模型响应慢看wall time日志流式响应或异步任务token超限上下文太长统计token数滑动窗口加摘要检索不准分块策略差检查检索结果按语义分块加metadata过滤多Agent死循环调用深度无限制看调用链日志设最大深度和超时5.5 几个容易被忽略的细节第一个是错误处理。Agent流程里任何一步都可能失败模型调用、工具执行、存储读写。每个环节都要有try-catch失败时返回结构化错误让上层决定怎么处理。不要让它直接抛到最外层那样用户看到的就是500错误。第二个是日志。Agent的日志要记录关键信息用户输入、模型返回、工具调用、执行结果、耗时。这些日志对调试和优化至关重要。我一般用结构化日志JSON格式方便后续分析。第三个是版本管理。工具和提示词会迭代要记录版本。每次变更后旧版本保留一段时间方便回滚。提示词可以存在KV里带版本号运行时读取最新版本。第四个是安全。Agent能调用工具工具能访问外部资源这里要有权限控制。不是所有用户都能调用所有工具要根据用户身份过滤工具列表。工具执行时也要校验参数防止注入攻击。6. 扩展方向这个Agent OS还能怎么玩6.1 接入更多模型和AI Gateway的高级功能目前主要用Workers AI的模型但通过AI Gateway可以接入外部模型比如OpenAI、Anthropic、Google的模型。AI Gateway统一了接口切换模型只需要改配置不用改代码。AI Gateway还支持缓存相同请求直接返回缓存结果省钱省时间。对于常见问题缓存命中率很高。限流功能可以防止滥用设置每个用户的请求配额。回退功能在主模型不可用时自动切到备用模型保证可用性。6.2 用Workflows做长任务编排Workers适合短请求长任务用Workflows更合适。Workflows是Cloudflare的持久化执行引擎支持多步骤任务、重试、超时、人工审批。把复杂的Agent任务拆成多个步骤每步是一个Workflow step失败自动重试状态自动持久化。比如一个研究报告生成任务第一步检索资料第二步分析资料第三步生成大纲第四步写正文第五步审核。每步都可能失败用Workflows管理整个流程比在Workers里写循环可靠得多。6.3 多Agent协作的进阶模式除了编排器加子Agent的模式还可以做Agent市场。每个Agent是一个独立的Worker注册到中心目录其他Agent按需调用。这样Agent可以独立部署、独立升级互不影响。Agent之间通过标准协议通信比如HTTP或者消息队列。调用时带上认证信息中心目录做权限校验。这种模式适合大型系统Agent数量多、团队多的时候解耦很重要。6.4 可观测性与持续优化Agent上线后要持续监控效果。关键指标包括任务完成率、平均耗时、token消耗、工具调用成功率、用户满意度。这些指标写到Analytics Engine用Grafana或者Cloudflare dashboard看板展示。根据指标做优化。如果某个工具调用失败率高检查工具实现。如果token消耗大优化提示词和上下文管理。如果完成率低分析失败案例改进Agent逻辑。这是一个持续迭代的过程没有一劳永逸的方案。我个人在实际操作中的体会是cloudflare-os这个方向最大的价值在于它把Agent开发的门槛降到了很低。你不需要懂容器编排、不需要管服务器、不需要配数据库用JavaScript写逻辑用Cloudflare的服务做支撑几天就能搭出一个可用的Agent系统。当然它也不是万能的复杂推理、大规模并发、精细控制这些场景还是需要更重的方案。但对于大多数个人项目和小团队来说这套组合已经足够能打了。