ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Cloudflare Workers 上跑 AI Agent:cloudflare-os 架构与实战

Cloudflare Workers 上跑 AI Agent:cloudflare-os 架构与实战

1. 从"cloudflare-os"这个名字说起:它到底想解决什么问题

第一次看到cloudflare-os这个仓库名,我脑子里冒出来的第一个念头是:Cloudflare 什么时候开始做操作系统了?点进去翻了一圈才反应过来,这里的 "OS" 不是传统意义上管进程、管内存、管文件系统的操作系统,而是一个面向 Cloudflare Workers 生态的 AI Agent 运行时骨架。你可以把它理解成"给 AI 代理准备的一套工作台"——它不负责调度 CPU 时间片,它负责的是让一个 AI Agent 能在边缘节点上跑起来、能调用工具、能维护会话状态、能按需扩展。

这个定位其实挺有意思。过去一年我接触过不少 AI Agent 项目,绝大多数都是跑在本地或者一台常驻服务器上的,用 Python 写个循环,接上 OpenAI 或者别的模型 API,再挂几个工具函数就完事了。这种方案做 demo 很快,但一旦要上线给真实用户用,问题就来了:你得考虑并发、考虑冷启动、考虑会话状态存哪、考虑成本。而 Cloudflare Workers 这套东西恰好在这几个维度上有天然优势——全球边缘节点、毫秒级冷启动、按请求计费、自带 KV 和 D1 这些存储原语。cloudflare-os本质上就是把这些能力打包成一个 Agent 框架,让你不用从零搭脚手架。

那它适合谁?我的判断是三类人:第一类是已经熟悉 Cloudflare Workers 开发、想往 AI 方向延伸的前端或全栈工程师;第二类是想做轻量级 AI 应用、但不想维护服务器的独立开发者;第三类是想研究 Agent 架构、找一个可读性强的开源实现来拆解学习的人。如果你连wrangler是什么都还没搞明白,那建议先把 Workers 的基础过一遍再来看这个项目,不然会有点吃力。

关键词里出现了pnpm、wrangler、Cloudflare Workers、AI、ai agent这几个词,基本勾勒出了这个项目的技术栈轮廓:用 pnpm 做包管理,用 wrangler 做本地开发和部署,跑在 Workers 运行时上,核心功能是 AI Agent。下面我就按这个脉络,把整个项目从环境搭建到核心机制再到实战踩坑,完整地捋一遍。

2. 环境准备:pnpm 和 wrangler 这两个坎必须先过

2.1 为什么这个项目偏偏要用 pnpm

热词里pnpm出现的频率高得离谱,甚至还有"pnpm下载失败""pnpm离线安装""删除pnpm"这种词,说明很多人在这一步就卡住了。我先说说为什么现代前端/边缘计算项目越来越倾向于用 pnpm 而不是 npm 或 yarn。

pnpm 的核心优势在于它的内容寻址存储机制。npm 和 yarn 的做法是每个项目都把依赖复制一份到自己的node_modules里,十个项目用同一个 lodash,硬盘上就存十份。pnpm 不一样,它在全局维护一个存储池,所有项目的依赖都通过硬链接指向这个池子,同一个版本的包在硬盘上只存一份。对于cloudflare-os这种依赖树可能比较深的项目来说,pnpm 能省下大量磁盘空间,安装速度也明显更快。

另一个关键点是 pnpm 对幽灵依赖的处理更严格。npm 的扁平化node_modules结构会导致你可以引用那些没有写在package.json里的包,这在本地开发时可能没事,但部署到 Workers 这种严格环境里就会炸。pnpm 用符号链接构建的嵌套结构从根源上杜绝了这个问题,让你在本地就能发现潜在的依赖缺失。

安装 pnpm 本身不复杂,但坑往往出在细节上。Windows 用户如果用npm install -g pnpm装完之后在 PowerShell 里敲pnpm -v报"无法将'pnpm'项识别为 cmdlet",八成是 npm 全局 bin 目录没加到 PATH 里。你可以先跑npm config get prefix看看全局目录在哪,然后手动把这个路径加进系统环境变量。macOS 和 Linux 用户如果用corepack enable来启用 pnpm 会更省心,corepack 是 Node.js 自带的包管理器版本管理工具,能自动帮你锁定项目指定的 pnpm 版本。

提示:如果你所在的环境网络受限,pnpm 官方提供了独立的安装脚本和离线包。离线安装的思路是先在能联网的机器上把 pnpm 的二进制和依赖缓存导出,再拷贝到目标机器上。具体做法可以参考 pnpm 官方文档的 standalone 安装章节,这里不展开。

2.2 wrangler 的定位和初始化流程

wrangler是 Cloudflare 官方的 CLI 工具,你可以把它类比成create-react-app加vercel的结合体——既能帮你初始化项目脚手架,又能帮你本地调试和部署上线。对于cloudflare-os这类项目,wrangler 基本是绕不开的。

初始化一个 Workers 项目的标准流程是这样的:

# 用 pnpm 创建一个新的 Workers 项目 pnpm create cloudflare@latest my-agent # 进入项目目录 cd my-agent # 本地开发调试 pnpm wrangler dev # 部署到线上 pnpm wrangler deploy

wrangler dev会在本地起一个模拟 Workers 运行时的环境,默认监听localhost:8787。这个本地模拟环境很重要,因为 Workers 的运行时和 Node.js 有本质区别——它基于 V8 的 isolate 机制,没有完整的 Node.js API,很多你在 Node 里习以为常的模块(比如fs、path)在这里是用不了的。所以本地用 wrangler 调试能帮你提前发现这类兼容性问题,而不是等部署上去才报错。

wrangler.toml是这个项目的核心配置文件,里面定义了 Worker 的名称、入口文件、兼容性日期、环境变量、KV 命名空间绑定、D1 数据库绑定等等。我见过不少人部署失败就是因为compatibility_date设得太老,导致一些新的运行时 API 用不了。建议直接设成当前日期,除非你有明确的兼容性需求。

2.3 依赖安装阶段最容易翻车的几个点

热词里"pnpm下载失败""pnpm安装"这些词扎堆出现,说明依赖安装是新手的第一道坎。我总结几个高频问题和对应的排查思路。

第一个是镜像源问题。默认的 npm registry 在国内访问可能不稳定,导致安装超时。解决办法是切换到国内镜像源,比如pnpm config set registry https://registry.npmmirror.com。但要注意,有些包在镜像源上同步不及时,如果遇到某个包死活装不上,可以临时切回官方源试试。

第二个是Node.js 版本不匹配。cloudflare-os这类项目通常会在package.json里声明engines字段,要求特定的 Node 版本。如果你本地是 Node 16 而项目要求 Node 18+,pnpm 会直接报错。用nvm或fnm这类版本管理工具切换一下就行。

第三个是lockfile 冲突。如果项目里同时存在package-lock.json和pnpm-lock.yaml,pnpm 可能会警告甚至报错。这时候应该删掉package-lock.json和node_modules,重新跑pnpm install生成干净的 lockfile。

3. 拆开看核心:Agent 在 Workers 上是怎么跑起来的

3.1 Workers 运行时对 Agent 架构的约束

要理解cloudflare-os的设计,必须先理解 Workers 运行时的几个硬约束,因为这些约束直接决定了 Agent 架构能怎么设计。

约束一:没有常驻进程。Workers 是请求驱动的,一个请求进来,运行时分配一个 isolate 处理,处理完这个 isolate 可能就被回收了。这意味着你不能像在服务器上那样起一个常驻的 Agent 循环,一直挂着等消息。Agent 的每一次"思考"都必须绑定到一次请求上。

约束二:执行时间有限制。免费版 Workers 的 CPU 时间限制是 10 毫秒,付费版是 30 秒(可配置到更高)。这个限制对 Agent 来说是个挑战,因为一次 LLM 调用加上工具执行可能就要好几秒。所以 Agent 的设计必须考虑分步执行——把一个大任务拆成多个请求,每个请求只做一小步,用外部存储来保存中间状态。

约束三:没有本地文件系统。你不能往磁盘上写文件,所有持久化都得走 KV、D1、R2 这些云存储服务。这对 Agent 的记忆管理提出了新要求——会话历史、工具调用记录、中间结果,全都得序列化后存到外部。

理解了这三条约束,你就能明白为什么cloudflare-os的架构看起来和本地 Agent 框架不太一样。它不是简单地"把 Python Agent 翻译成 JS",而是针对边缘运行时的特性重新设计了执行模型。

3.2 会话状态管理:KV 和 D1 怎么选

Agent 要能记住上下文,就必须有地方存会话状态。cloudflare-os里通常会用到两种存储:KV 和 D1。

KV 是键值存储,特点是读快写慢、最终一致。适合存那种"读多写少"的数据,比如用户的偏好设置、Agent 的配置信息。它的读写延迟在全球边缘节点上都很低,但写入后需要一段时间才能在全球同步。

D1 是基于 SQLite 的关系型数据库,支持 SQL 查询,适合存结构化的会话历史、工具调用日志这类需要复杂查询的数据。它的写入是一致性的,但读延迟比 KV 高一些。

我的选型经验是这样的:如果只是存一个会话的最近 N 条消息,用 KV 就够了,key 用session:${sessionId},value 存 JSON 序列化后的消息数组。如果需要按时间范围查询、按用户聚合统计、或者做全文搜索,那就上 D1。实际项目里两者经常混用——KV 存热数据做快速读取,D1 存冷数据做分析和回溯。

这里有个容易踩的坑:KV 的写入有频率限制。同一个 key 每秒最多写一次,如果你在 Agent 循环里每步都往同一个 key 写状态,很容易触发限流。解决办法是把状态按步骤分 key 存,比如session:${id}:step:1、session:${id}:step:2,或者用 D1 来存高频写入的数据。

3.3 工具调用机制的设计取舍

Agent 的核心能力之一是调用工具。在cloudflare-os里,工具通常被定义成一个个独立的函数,每个函数有名称、描述、参数 schema,Agent 根据 LLM 的输出决定调用哪个工具、传什么参数。

工具定义的结构大概长这样:

const tools = { getWeather: { description: "查询指定城市的天气", parameters: { type: "object", properties: { city: { type: "string", description: "城市名称" } }, required: ["city"] }, execute: async ({ city }) => { // 实际调用天气 API const res = await fetch(`https://api.example.com/weather?city=${city}`); return await res.json(); } } };

这个结构看起来简单,但实际设计时有几个关键决策点。

决策点一:工具执行是同步还是异步?在 Workers 环境下,工具执行必须是异步的,因为任何网络请求都是异步的。但更重要的是,你要考虑工具执行超时怎么办。如果一个工具调用卡住了,整个 Agent 循环就会挂起。我的做法是给每个工具调用加一个超时包装,超过阈值就返回错误信息让 LLM 决定下一步。

决策点二:工具执行结果怎么回传给 LLM?有两种模式:一种是把结果直接拼进下一轮的 prompt 里,另一种是用 function calling 的标准格式回传。前者兼容性好但 token 消耗大,后者更规范但要求模型支持 function calling。cloudflare-os通常两种都支持,根据模型能力自动选择。

决策点三:工具能不能并行调用?如果 LLM 一次返回了多个工具调用请求,你是串行执行还是并行执行?并行能省时间,但要注意 Workers 的并发限制和工具之间的依赖关系。我的经验是,无依赖的工具调用可以并行,有依赖的必须串行,这个判断逻辑最好在框架层面处理好,不要让每个工具自己操心。

4. 从零跑通一个最小 Agent:完整实操链路

4.1 项目初始化的具体命令和配置

假设你现在要从零开始,基于cloudflare-os的思路搭一个最小可用的 Agent。我把完整流程拆成几步,每步都说明为什么这么做。

第一步,创建项目。用pnpm create cloudflare@latest会引导你选择模板,选 "Hello World" 类型的 Worker 模板就行,后面我们自己加 Agent 逻辑。

第二步,安装依赖。核心依赖通常包括:ai(Vercel 的 AI SDK,提供了统一的模型调用接口)、zod(做参数校验)、hono(轻量级 Web 框架,比原生 Workers 的 fetch handler 好用)。安装命令:

pnpm add ai zod hono

第三步,配置wrangler.toml。关键配置项包括:

name = "my-agent" main = "src/index.ts" compatibility_date = "2024-09-23" [[kv_namespaces]] binding = "AGENT_KV" id = "你的KV命名空间ID" [vars] MODEL_NAME = "your-model-name"

这里的compatibility_date建议设成比较新的日期,这样能用上最新的运行时特性。KV 命名空间需要先在 Cloudflare 控制台创建,然后把 ID 填进来。

第四步,写入口逻辑。一个最小的 Agent 入口大概是这样:

import { Hono } from 'hono'; type Env = { AGENT_KV: KVNamespace; MODEL_NAME: string; }; const app = new Hono<{ Bindings: Env }>(); app.post('/chat', async (c) => { const { sessionId, message } = await c.req.json(); // 读取历史 const history = await c.env.AGENT_KV.get(`session:${sessionId}`, 'json') || []; // 调用模型(这里简化处理) const reply = await callModel(message, history, c.env.MODEL_NAME); // 更新历史 history.push({ role: 'user', content: message }); history.push({ role: 'assistant', content: reply }); await c.env.AGENT_KV.put(`session:${sessionId}`, JSON.stringify(history.slice(-20))); return c.json({ reply }); }); export default app;

这段代码虽然简单,但已经包含了 Agent 的核心循环:读状态、调模型、写状态。后面所有的复杂功能都是在这个骨架上叠加的。

4.2 本地调试时怎么模拟边缘环境

wrangler dev默认起的是本地模拟环境,但有些行为和生产环境还是有差异。我建议在本地调试时注意几点。

第一,KV 在本地是存在内存或本地文件里的,重启wrangler dev后数据可能丢失。如果你需要持久化的本地 KV,可以用wrangler dev --persist参数,它会把数据存到.wrangler/state目录下。

第二,本地环境的网络请求走的是你本机的网络,而生产环境走的是 Cloudflare 的边缘网络。这意味着某些依赖特定地理位置的 API 在本地测试时可能行为不一致。如果遇到这种情况,可以在wrangler.toml里配置[dev]段落来模拟不同的区域。

第三,本地调试时日志直接打在终端上,但生产环境的日志需要用wrangler tail来实时查看。建议在开发阶段就把日志格式统一好,方便上线后排查问题。

4.3 部署上线的完整流程和验证方法

部署本身就一条命令pnpm wrangler deploy,但部署前后的验证工作才是重点。

部署前,先跑一遍pnpm wrangler deploy --dry-run,它会模拟部署过程但不实际上传,能帮你提前发现配置错误。然后检查wrangler.toml里的所有绑定是否都正确——KV 的 ID 对不对、环境变量有没有漏、路由规则是否符合预期。

部署后,第一件事是用wrangler tail看实时日志,确认没有报错。第二件事是发一个测试请求,验证核心功能是否正常。第三件事是检查响应时间,如果明显比本地慢,可能是某个外部 API 调用拖了后腿。

我踩过的一个坑是:部署后忘记更新 KV 的命名空间 ID,结果本地测试一切正常,线上却读不到任何数据。原因是本地用的模拟 KV 和生产 KV 是两套东西。所以每次部署前,我都会对照wrangler.toml和控制台里的资源列表逐项核对一遍。

5. 那些文档里不会写的踩坑记录

5.1 pnpm 在 CI/CD 环境里的诡异行为

本地跑得好好的 pnpm,一到 CI 环境就各种报错,这是很多人遇到过的场景。我遇到最多的问题是lockfile 版本不匹配。pnpm 的 lockfile 格式在不同大版本之间有变化,如果 CI 环境装的 pnpm 版本和本地不一致,就可能出现 lockfile 解析失败。

解决办法是在package.json里加一个packageManager字段,明确指定 pnpm 的版本:

{ "packageManager": "pnpm@9.0.0" }

这样 corepack 会自动使用指定版本的 pnpm,避免版本漂移。另外,CI 环境里建议用pnpm install --frozen-lockfile,这个参数会强制要求 lockfile 和package.json完全一致,不一致就直接报错,避免出现"本地能跑、CI 跑不了"的情况。

还有一个坑是pnpm 的 store 目录权限问题。在某些 CI 容器里,pnpm 的全局 store 目录可能没有写权限,导致安装失败。可以通过pnpm config set store-dir把 store 目录指到一个有写权限的位置来解决。

5.2 Workers 冷启动和 LLM 调用的时间博弈

Workers 的冷启动通常在几毫秒级别,这个很快,不是问题。真正的问题是LLM 调用的延迟。一次 LLM 调用动辄几秒,如果 Agent 需要多轮调用,总时间很容易超过 Workers 的执行时间限制。

我的应对策略是把长任务拆成多个短请求。具体做法是:Agent 每完成一步就把状态存到 KV 或 D1,然后返回一个"进行中"的响应给客户端,客户端轮询或者用 WebSocket 接收后续结果。这样每个请求的执行时间都控制在几秒以内,不会触发超时。

另一个技巧是用流式响应。Workers 支持流式返回,你可以在 LLM 生成第一个 token 时就开始往客户端推数据,而不是等全部生成完再返回。这样用户感知的延迟会低很多,即使总生成时间较长,也不会觉得卡。

5.3 会话数据膨胀导致 KV 读写变慢

Agent 跑久了,会话历史会越来越长。如果每次都把完整历史读出来、拼进 prompt、再写回去,KV 的读写量会迅速膨胀,延迟也会上升。

我的做法是分层存储加滑动窗口。最近 N 条消息存在 KV 里做快速读取,更早的历史归档到 D1 或 R2 里。每次读取时只取最近 N 条,需要回溯时才去查归档。N 的取值根据模型上下文窗口和成本来定,一般 10 到 20 条是个比较平衡的值。

另外,写入 KV 时要注意序列化开销。如果历史记录是复杂的嵌套对象,JSON 序列化和反序列化的开销不可忽视。可以考虑用更紧凑的格式,或者只存必要的字段。

5.4 工具调用失败时的优雅降级

工具调用失败是常态,不是异常。外部 API 可能超时、可能返回错误、可能限流。如果每次失败都让整个 Agent 崩溃,那这个 Agent 就没法用了。

我的做法是给每个工具调用包一层错误处理加降级逻辑。工具执行失败时,不直接抛错,而是返回一个结构化的错误信息给 LLM,让 LLM 决定是重试、换工具、还是告诉用户"这个功能暂时不可用"。这样 Agent 的行为会更鲁棒,用户体验也更好。

具体实现上,可以用Promise.race加超时:

const result = await Promise.race([ tool.execute(params), new Promise((_, reject) => setTimeout(() => reject(new Error('工具调用超时')), 5000) ) ]).catch(err => ({ error: err.message }));

这样即使工具卡住,5 秒后也会返回错误信息,不会无限等待。

6. 这套东西还能怎么扩展

跑通最小 Agent 之后,能扩展的方向其实挺多的。我列几个我觉得比较有价值的。

多 Agent 协作。热词里出现了"多ai协作",这在 Workers 上完全可行。你可以把不同的 Agent 部署成不同的 Worker,通过 Service Binding 互相调用。比如一个负责规划、一个负责执行、一个负责审核,各司其职。Service Binding 的好处是调用不走公网,延迟低且不消耗额外的请求配额。

接入更多模型。cloudflare-os本身不绑定特定模型,你可以通过 AI SDK 接入多家模型服务。Workers AI 是 Cloudflare 自家的推理服务,和 Workers 集成最紧密,延迟最低。如果要用外部模型,注意把 API key 存在环境变量里,不要硬编码在代码中。

加持久化记忆。除了会话历史,还可以给 Agent 加长期记忆。用 Vectorize 做向量存储,把重要的信息 embedding 后存进去,需要时做相似度检索。这样 Agent 就能"记住"跨会话的信息,而不是每次对话都从零开始。

做可观测性。Agent 的行为比较难调试,建议从第一天就加上日志和追踪。每次 LLM 调用、每次工具执行都记一条日志,包含输入、输出、耗时、token 消耗。这些数据存到 D1 里,后面做分析和优化时非常有用。

成本控制。LLM 调用是按 token 计费的,Agent 跑起来 token 消耗可能很惊人。建议加一个 token 计数器,超过阈值就截断或者降级到更便宜的模型。另外,缓存重复的 LLM 调用结果也能省不少钱——如果两个请求的 prompt 完全一样,直接返回缓存结果就行。

我在实际项目里最深的体会是:Agent 的难点从来不是"怎么调模型",而是"怎么管理状态"和"怎么处理失败"。模型调用本身几行代码就搞定了,但要让 Agent 在真实环境下稳定运行,状态管理、错误处理、成本控制这些"脏活累活"才是真正花时间的地方。cloudflare-os这类框架的价值,就在于它把这些脏活累活封装好了,让你能专注于 Agent 的业务逻辑本身。

返回列表