首页 / 资讯中心 / 文章详情

基于Cloudflare Workers的AI Agent部署实战:pnpm与wrangler避坑指南

基于Cloudflare Workers的AI Agent部署实战:pnpm与wrangler避坑指南 ★ FEATURED ARTICLE
1. 从“cloudflare-os”这个名字说起它到底想解决什么问题第一次看到cloudflare-os这个仓库名我下意识以为 Cloudflare 官方出了个操作系统。点进去才发现它跟内核、驱动、桌面环境一点关系都没有——这是一个把 Cloudflare Workers 当作“运行时底座”在上面搭出一整套 AI Agent 编排能力的开源项目。名字里的 “os” 更接近“面向 Agent 的操作层”这个含义而不是传统意义上的操作系统。我之所以对这个项目感兴趣是因为过去大半年我一直在折腾 AI Agent 的部署问题。本地跑一套 Agent 框架最烦的从来不是模型调用本身而是三件事进程要一直活着、状态要能持久化、对外要有个稳定的入口。用一台小服务器扛着电费、运维、证书续期、被扫描全是隐性成本。而 Cloudflare Workers 这套东西恰好把这三点都吃掉了它是事件驱动的、天然全球分布、自带 KV / D1 / R2 / Durable Objects 这些存储原语还有wrangler这条顺手的命令行工具链。cloudflare-os的核心思路就是把 Agent 的“大脑”和“手脚”拆开大脑模型推理、任务规划可以放在任意兼容 OpenAI 接口的服务上手脚工具调用、状态读写、定时任务、对外 HTTP 接口全部落在 Workers 里。这样一来你不需要一台 7x24 开机的机器Agent 也能在有人访问、有定时器触发、有队列消息到达的时候被“唤醒”干活干完就休眠。对个人开发者和小团队来说这个成本模型非常友好。这篇文章我会围绕这个项目拆几件事它背后的运行时逻辑是什么、环境怎么搭、pnpm和wrangler这两个工具在实操里最容易卡在哪、Agent 的状态和工具怎么组织、以及我在部署过程中踩过的那些坑。适合已经会一点 JavaScript/TypeScript、想把自己的 AI Agent 从“本地玩具”变成“随时可访问的服务”的人看。如果你连pnpm都还没装明白也别急第二部分我会把这块讲透。2. 为什么是 Workers 而不是一台常驻服务器2.1 传统 Agent 部署的三个隐性成本大部分人做 AI Agent 的第一反应是买台云主机装个 Node 环境npm run start挂上去。跑通 Demo 没问题但真要用起来问题会一个个冒出来。第一个是常驻成本。Agent 大部分时间其实在等——等用户发消息、等定时任务、等外部 webhook。可你的服务器不管有没有请求都在计费一个月几十块看着不多但如果你只是想做个自用的小助手这笔钱花得有点冤。第二个是状态管理。Agent 需要记住对话历史、任务进度、工具调用结果。本地可以用 SQLite 文件但一旦部署到多实例或者容器重启文件就没了。你得额外接一个数据库又要考虑连接池、迁移、备份。第三个是入口和证书。想让 Agent 能被外部调用你得配域名、搞 HTTPS 证书、处理跨域、防滥用。这些活儿不难但琐碎而且每次换机器都要重来一遍。2.2 Workers 的运行时模型恰好对上 Agent 的需求Cloudflare Workers 的执行模型是请求驱动 事件驱动的。没有请求的时候它不占任何常驻资源有请求进来它在离用户最近的节点上冷启动一个隔离环境执行你的代码。这个模型对 Agent 来说有几个直接好处。按需唤醒。你的 Agent 可以挂在一个 HTTP 路由上用户发消息就触发一次推理也可以挂在 Cron Trigger 上每天定点跑一次任务还可以消费 Queue 里的消息做异步处理。不跑的时候成本几乎为零。存储原语齐全。KV 适合存配置和低频读写的键值数据D1 是基于 SQLite 的关系库适合存对话记录和任务表R2 是对象存储适合放文件Durable Objects 则提供了有状态的单实例对象这是做 Agent 会话状态最关键的组件——同一个会话 ID 永远路由到同一个对象实例天然解决了并发写冲突。全球分布。你的 Agent 接口天然就在全球边缘节点上用户在哪都能就近响应不需要自己搞 CDN。下面这张表是我自己对比下来觉得最能说明问题的几个维度维度常驻服务器方案Workers 方案空闲成本持续计费接近零冷启动无但机器可能挂有通常几十毫秒级状态存储需自建数据库KV / D1 / R2 / Durable ObjectsHTTPS 与域名自行配置平台托管扩缩容手动或脚本自动长时任务随意受执行时长限制需拆分2.3 一个必须提前想清楚的限制Workers 不是没有代价的。单次请求的执行时长有上限免费版和付费版额度不同但都不适合跑那种要几分钟甚至几十分钟的长任务。所以cloudflare-os这类项目的设计里长任务一定要拆成“触发—排队—分片执行—汇总”的模式用 Queue 或者 Durable Objects 的 alarm 机制来接力。我一开始没注意这点写了个 Agent 让它一次性把一整篇长文档总结完结果请求直接超时。后来改成入口请求只负责把任务写进 D1 并投递一条 Queue 消息消费者每次只处理一个分片处理完再投递下一个分片最后汇总。这个改动之后再长的任务也能稳稳跑完。这个经验我觉得比任何文档都值钱——在 Workers 上做 Agent第一原则是“把长任务切成短任务”。3. 环境搭建pnpm 与 wrangler 的正确打开方式3.1 为什么这个项目用 pnpm 而不是 npmcloudflare-os用的是pnpm作为包管理器。很多人习惯npm会问为什么不直接用 npm。原因很实际Workers 项目依赖树往往不深但依赖体积直接影响冷启动和打包产物大小。pnpm用内容寻址的存储加硬链接多个项目共享同一份依赖磁盘占用小安装快而且它的严格依赖隔离能避免“幽灵依赖”——就是你代码里 import 了一个没写进package.json的包npm 因为扁平化恰好能解析到pnpm 会直接报错。这在部署到 Workers 时特别重要因为打包器对幽灵依赖的处理和本地不一致很容易出现“本地能跑、线上报模块找不到”。3.2 pnpm 安装失败的几种典型情况和处理热词里pnpm下载失败、pnpm 不是内部或外部命令、pnpm : 无法将“pnpm”项识别为 cmdlet出现频率极高说明这是真痛点。我按平台把常见情况理一遍。Windows 上提示“不是内部或外部命令”绝大多数是装完之后没有重开终端。npm install -g pnpm会把可执行文件放到 npm 的全局 bin 目录这个目录在安装时可能还没进当前终端的 PATH。解决办法很简单关掉所有终端窗口重新开一个再执行pnpm -v。如果还不行用npm config get prefix看看全局目录在哪手动把它加进系统环境变量。PowerShell 里报“无法将 pnpm 项识别为 cmdlet”除了 PATH 问题还有一种情况是执行策略限制。可以先跑Get-Command pnpm确认到底找没找到如果找到了但执行被拦检查一下$env:Path里有没有 npm 全局目录。离线安装。有些内网环境没法直连包源热词里pnpm离线安装、pnpm离线安装包也是高频。思路是在一台能联网的机器上用pnpm store path找到存储目录把整个 store 打包拷过去然后在目标机器上设置pnpm config set store-dir 路径再执行安装。或者更省事直接用pnpm install --offline配合预先准备好的 store。想删掉重装。热词里有删除pnpm一般是想彻底清理。npm uninstall -g pnpm卸载后还要手动删掉 store 目录和缓存目录否则残留的元数据可能让重装后的行为诡异。提示装完 pnpm 之后建议顺手执行pnpm setup它会帮你把 pnpm 的全局 bin 目录写进 shell 配置比手动改 PATH 省心。3.3 wrangler 的定位和初始化流程wrangler是 Cloudflare 官方的命令行工具负责本地开发、构建、部署、管理密钥。cloudflare-os这类项目基本都靠它驱动。典型流程是这样# 安装依赖项目根目录 pnpm install # 登录账号会打开浏览器授权 pnpm wrangler login # 本地开发起一个模拟 Workers 运行时的服务 pnpm wrangler dev # 部署到线上 pnpm wrangler deploywrangler dev默认在本地模拟运行时包括 KV、D1、Durable Objects 这些绑定方便你调试。但要注意本地模拟和线上真实环境在行为上并非 100% 一致尤其是 Durable Objects 的 alarm 和 Queue 的投递时机。我的习惯是本地只验证逻辑涉及定时和队列的功能一定要在预发环境再跑一遍。wrangler.toml或wrangler.jsonc是核心配置文件绑定、路由、环境变量都在这里声明。一个最小化的结构大概长这样name cloudflare-os main src/index.ts compatibility_date 2024-09-23 [[kv_namespaces]] binding AGENT_KV id xxxxxxxx [[d1_databases]] binding AGENT_DB database_name agent-db database_id yyyyyyyy [durable_objects] bindings [ { name AGENT_SESSION, class_name AgentSession } ]这里有个容易忽略的点compatibility_date不要随便写未来日期。它决定了运行时启用哪些行为特性写太新可能引入你还没适配的变更写太旧又用不上新 API。稳妥做法是跟着项目模板走或者用官方文档推荐的当前稳定日期。4. Agent 的状态、工具与调度怎么组织4.1 用 Durable Objects 承载会话状态Agent 最核心的状态是会话上下文。用 KV 存会有并发写覆盖的问题用 D1 存每次读写都要走一次 SQL延迟和成本都不理想。Durable Objects 的设计正好对症每个会话一个对象实例所有对该会话的读写都串行经过这个实例天然没有竞态。在cloudflare-os里通常会定义一个AgentSession类内部维护消息数组、当前任务、工具调用记录。外部通过一个稳定的 ID比如用户 ID 加会话 ID 拼出来的字符串去get这个对象然后调用它的方法。代码骨架大致是export class AgentSession { state: DurableObjectState; messages: Message[] []; constructor(state: DurableObjectState) { this.state state; this.state.blockConcurrencyWhile(async () { this.messages (await this.state.storage.get(messages)) || []; }); } async addMessage(msg: Message) { this.messages.push(msg); await this.state.storage.put(messages, this.messages); } async getHistory() { return this.messages; } }blockConcurrencyWhile这个调用很关键它保证对象在从存储加载完状态之前不会处理其他请求。少了它高并发下可能读到空状态。4.2 工具调用的注册与分发Agent 要干活就得调工具。cloudflare-os的思路是把工具做成一张注册表每个工具声明名称、参数 schema 和执行函数模型返回工具调用请求后由一个分发器按名字找到对应函数执行。const tools { get_weather: { description: 查询指定城市天气, parameters: { city: string }, handler: async ({ city }: { city: string }) { // 实际调用外部 API return { city, temp: 22 }; }, }, save_note: { description: 保存一条笔记, parameters: { content: string }, handler: async ({ content }: { content: string }) { await env.AGENT_DB.prepare( INSERT INTO notes (content, created_at) VALUES (?, ?) ).bind(content, Date.now()).run(); return { ok: true }; }, }, };分发的时候把工具的名称和描述转成模型能理解的格式传进去模型决定调哪个你再根据返回的名字执行。这里有个实操心得工具描述要写得像给新人看的说明书别只写“查询天气”要写清楚参数含义、返回结构、什么情况下该用。模型选错工具十有八九是描述太含糊。4.3 定时任务与队列接力前面说过长任务要拆。cloudflare-os里通常用两种机制配合Cron Trigger 负责周期性唤醒Queue 负责分片接力。Cron 在wrangler.toml里声明[triggers] crons [0 */6 * * *]这表示每 6 小时触发一次。触发后Worker 的scheduled处理器会被调用你可以在里面检查有没有待处理任务有就投递到 Queue。Queue 的消费者每次只处理一条消息处理完如果还有后续分片就再投递一条。这样每个请求都在时长限制内整体任务却能跑很久。这个模式我用了大半年稳定性很好唯一要注意的是幂等——Queue 可能重复投递所以每条消息最好带一个唯一 ID处理前先查一下有没有处理过。5. 部署上线时我踩过的坑和排查链路5.1 打包体积超限从报错到定位第一次wrangler deploy的时候我遇到打包产物超过大小限制的报错。Workers 对压缩后的包体积有上限免费版更紧。排查链路是这样的先看wrangler deploy输出的体积报告它会告诉你压缩后多大、gzip 后多大。然后我用wrangler deploy --dry-run --outdir dist把产物导出到本地再用打包分析工具看哪个依赖占了大头。结果发现是一个我随手引入的工具库它把整个 lodash 都打进来了而我只用了两个函数。解决办法有两个一是换成按需引入import debounce from lodash/debounce而不是import { debounce } from lodash二是干脆自己写那几行逻辑省掉依赖。改完之后体积直接降了一半多。注意Workers 的打包是 tree-shaking 的但前提是你的依赖是 ESM 格式且没有副作用声明问题。CommonJS 的老库往往 shake 不掉选依赖时优先挑 ESM 的。5.2 环境变量与密钥别把 key 写进代码我见过太多人把模型 API key 直接写在源码里然后推到公开仓库。wrangler提供了secret机制pnpm wrangler secret put MODEL_API_KEY执行后会提示你输入值加密存储代码里通过env.MODEL_API_KEY读取。本地开发时可以在项目根目录建一个.dev.vars文件写本地值记得把它加进.gitignore。这里有个坑.dev.vars只在wrangler dev时生效部署后读的是线上 secret。我有一次本地测得好好的部署后一直报 key 无效查了半天才发现线上 secret 没更新。养成习惯改完密钥本地和线上都要同步。5.3 D1 迁移与本地线上数据不一致D1 的 schema 变更要走迁移文件。wrangler d1 migrations这套命令本地和线上是分开执行的# 本地 pnpm wrangler d1 migrations apply agent-db --local # 线上 pnpm wrangler d1 migrations apply agent-db --remote我踩的坑是本地跑完迁移忘了跑线上结果部署后查询报“no such column”。排查的时候先看 Worker 日志wrangler tail错误信息会直接告诉你哪张表哪个字段有问题然后对照迁移记录确认线上到底执行到哪一版。现在我固定流程每次改 schema本地和线上迁移命令连着敲绝不只跑一个。5.4 冷启动导致的首次响应慢有用户反馈第一次发消息要等两三秒。这是冷启动加模型首 token 延迟叠加的结果。缓解办法有几个把不常用的依赖做成动态 import减少启动时要加载的模块在入口处先返回一个“正在处理”的响应把真正的推理放到 Queue 里异步做用户侧感知就快了如果模型服务支持流式输出一定要用流式首 token 出来就能推给用户体感完全不同。6. 把 Agent 从 Demo 变成能用的服务还差哪几步6.1 可观测性日志、指标和追踪Demo 阶段你靠console.log就够了但服务一旦有人用你必须知道它什么时候慢、什么时候错、错在哪。wrangler tail能实时看日志适合调试。长期看建议把关键事件结构化打点比如每次工具调用记录工具名、耗时、成功与否然后定期导出分析。我自己的做法是在 Durable Object 里维护一个轻量的调用记录表每次工具调用写一条包含时间戳、工具名、耗时、结果状态。这样出问题时能快速定位是哪个工具拖慢了整体而不是两眼一抹黑。6.2 限流与防滥用Agent 接口一旦公开就会有人或爬虫来刷。Workers 层面可以用简单的计数器做限流以 IP 或用户 ID 为键在 KV 里记录单位时间内的请求数超了就返回 429。更精细的可以用 Durable Object 做滑动窗口但大多数场景 KV 加固定窗口就够了。另外模型调用是最贵的环节一定要在入口处做校验别让无效请求走到模型那一步。比如先检查消息长度、格式明显异常的请求直接拒掉。6.3 成本控制的几个实操手段Workers 本身很便宜真正花钱的是模型调用和存储读写。几个我常用的手段把重复的问题缓存起来相同输入直接返回缓存结果给模型设置合理的 max tokens别让它无限输出D1 查询加索引避免全表扫描KV 的读虽然便宜但量大也可观热点数据可以在 Durable Object 内存里缓存一份减少 KV 读。6.4 一个我反复验证过的部署检查清单每次上线前我会过一遍这几项基本能挡掉大部分低级事故检查项说明密钥同步本地.dev.vars与线上 secret 一致迁移执行本地和线上 D1 迁移都已 apply体积检查--dry-run确认打包体积在限制内绑定核对wrangler.toml里的绑定 ID 与线上资源匹配限流生效手动压一下确认限流逻辑起作用日志可查wrangler tail能看到结构化日志这套流程看起来啰嗦但每次省下的排查时间远超执行它的成本。尤其是绑定 ID 这一项我因为复制粘贴错了一个字符白白排查了半小时。7. 我对这个项目方向的一点个人判断cloudflare-os这类项目真正有意思的地方不在于它用了多新的模型而在于它把 Agent 的运行成本结构改掉了。过去做 Agent你得先有一台机器再谈功能现在你可以先有功能机器的事交给平台。这个转变对个人开发者特别友好——你可以在几乎零固定成本的前提下把一个 Agent 服务挂上去有人用才产生费用。我在实际使用中最大的体会是Workers 上的 Agent 设计本质是“状态外置 任务分片”。只要把这两点想清楚剩下的都是工程细节。状态放 Durable Objects长任务拆成 Queue 接力工具做成注册表密钥走 secret迁移本地线上同步——这套组合拳打下来一个能稳定对外服务的 Agent 就成型了。如果你正准备动手我的建议是先跑通最小闭环一个 HTTP 入口、一个 Durable Object 存会话、一个工具、一次模型调用。跑通之后再往上加定时、队列、限流。别一上来就追求大而全Agent 这东西能跑起来比设计得漂亮重要得多。
阅读完成 · 觉得有帮助?
咨询建站