1. 从“cloudflare-os”这个标题说起:它到底想解决什么问题
第一次看到“cloudflare-os”这个标题,很多人会误以为Cloudflare要发布一款桌面操作系统。实际上,结合Cloudflare Workers、AI Agent、开源这几个关键词来看,它指向的是一个更务实的方向:把Cloudflare的边缘计算网络当作一个“操作系统”来用,在上面跑AI Agent,并且整套东西是开源的。这个思路并不复杂,但背后的工程价值很高。
传统跑AI Agent的方式,基本逃不开一台常驻的服务器或者容器。你要买机器、配环境、装依赖、做健康检查、处理扩缩容,Agent稍微多一点,运维成本就上来了。而Cloudflare Workers的特点是:全球边缘节点部署、按请求计费、冷启动极快、天然支持无服务器架构。把Agent跑在Workers上,等于把“运维一台机器”变成了“调用一个函数”。这就是“cloudflare-os”这个项目最核心的卖点——用边缘函数当Agent的运行底座。
它适合谁看?三类人最值得花时间研究:一是正在做AI Agent开发、被服务器成本和运维折腾得够呛的工程师;二是想用低成本方式搭建多Agent协作系统的独立开发者;三是对Cloudflare Workers生态感兴趣、想找一个完整项目练手的技术爱好者。哪怕你之前没写过Workers,只要懂JavaScript或TypeScript,跟着思路走也能理解个七八成。
我接下来会从整体设计、核心细节、实操落地、问题排查几个层面,把这个项目的骨架和血肉都拆开讲清楚。不是官方文档的复述,而是站在一个实际动手搭过类似系统的人的角度,把踩过的坑和想明白的道理分享出来。
2. 整体架构设计:为什么把Agent塞进边缘函数是合理的
2.1 边缘计算跑Agent的底层逻辑
要理解这个项目,先得理解Cloudflare Workers的运行模型。Workers不是传统意义上的“服务器”,它跑在V8隔离环境里,每个请求触发一个独立的执行上下文。你写一个fetch处理函数,收到请求、处理逻辑、返回响应,整个过程可能只有几毫秒到几百毫秒。没有常驻进程,没有可以随便写的本地文件系统,也没有长时间挂起的后台线程。
那Agent怎么办?Agent的典型行为是:接收用户输入、调用大模型API、可能还要调用工具、维护对话状态、返回结果。这些动作拆开看,每一步都是短时任务。把Agent的一次完整交互拆成若干个Worker调用,用外部存储(如KV、D1、R2)来保存状态,就能在边缘环境里跑通。这个思路的关键在于:不要试图让Worker“记住”什么,而是让Worker“查得到”什么。
举个例子。用户发来一条消息,Worker A负责接收并写入对话历史到D1数据库;然后触发Worker B去调用大模型;模型返回后,Worker C负责解析工具调用请求;如果有工具调用,Worker D去执行具体工具;最后Worker E把结果整合返回给用户。每个Worker都是无状态的,状态全部落在数据库或KV里。这样做的好处是:任何一个环节挂掉都不影响其他环节,而且天然支持水平扩展。用户量涨了,不需要加机器,Cloudflare会自动分配更多边缘节点来处理请求。
2.2 开源策略背后的考量
这个项目选择开源,不是单纯为了“做慈善”。Agent框架这个赛道,闭源产品已经很多了,但开发者真正需要的是一个可以自己掌控、可以深度定制、不会被厂商锁死的底座。开源意味着你可以看到每一行代码怎么处理上下文、怎么管理Token、怎么做工具路由。这对于需要做私有化部署或者有特殊合规要求的团队来说,是刚需。
另外,开源还有一个隐性好处:社区会帮你发现边缘场景下的坑。Cloudflare Workers的运行时限制不少,比如CPU时间限制、内存限制、子请求数量限制。这些限制在官方文档里写得比较笼统,只有真正跑过各种奇怪场景的人才知道边界在哪。开源项目把这些经验固化在代码里,后来者可以直接站在前人的肩膀上。
从技术选型上看,项目大概率使用TypeScript作为主要开发语言,因为Workers对TS的支持最好,类型系统能帮你在部署前发现很多低级错误。构建工具可能是Wrangler(Cloudflare官方的CLI),配合esbuild做打包。数据库层可能用D1(Cloudflare的Serverless SQL数据库)或者KV来存对话历史,R2用来存大文件或者日志。这些组件的组合不是随意的,而是经过权衡的:D1适合结构化查询,KV适合高频读写,R2适合冷数据存储。
2.3 与同类方案的对比
市面上跑Agent的方案大致分三类:一是自建服务器跑Python框架(如LangChain、AutoGPT);二是用Serverless函数(AWS Lambda、Vercel Functions);三是用专门的Agent托管平台。cloudflare-os走的是第二条路,但比普通Serverless更激进——它把边缘网络当成了操作系统。
和自建服务器比,它的优势是零运维、全球低延迟、按量计费。你不用关心服务器在哪、有没有宕机、带宽够不够。缺点是冷启动虽然快但仍有开销,且不适合长时间运行的任务。和AWS Lambda比,Cloudflare Workers的冷启动更短(通常小于5毫秒),而且边缘节点更多,对全球用户更友好。但Lambda的生态更成熟,支持的语言和运行时更多。和Agent托管平台比,cloudflare-os的可控性更强,你可以改任何一行代码,不用担心平台突然改API或者涨价。
注意:边缘函数不适合跑需要大量CPU计算的任务,比如本地推理大模型。它的定位是“调度层”和“轻量工具层”,重活还是要交给外部API或者专用推理服务。
3. 核心细节拆解:Agent在Workers上怎么跑起来
3.1 请求生命周期与状态管理
一个Agent请求从进入系统到返回结果,大致经历这几个阶段:接收输入、加载上下文、调用模型、解析动作、执行工具、更新状态、返回响应。在传统服务器上,这些步骤可以写在一个长函数里,用局部变量保存中间状态。但在Workers上,每个步骤可能在不同的请求里完成,所以状态必须外置。
具体怎么做?项目里通常会设计一张conversations表,字段包括conversation_id、user_id、messages(JSON数组)、created_at、updated_at。每次用户发消息,先往messages里追加一条用户消息,然后触发模型调用。模型返回后,再把助手消息追加进去。如果模型返回的是工具调用请求,就解析出工具名和参数,执行工具,把工具结果作为一条新消息追加,再次触发模型。这个过程可能循环多次,直到模型返回最终文本。
这里有个关键设计:用conversation_id作为幂等键。因为边缘环境可能重试请求,如果没有幂等控制,同一条消息可能被处理两次,导致重复扣费或者重复回复。实现方式很简单:在处理前先查一下这条消息ID是否已经处理过,如果处理过就直接返回缓存结果。
// 伪代码示例:幂等检查 async function handleMessage(conversationId: string, messageId: string, content: string) { const existing = await db.get(`processed:${messageId}`); if (existing) { return existing; } // ... 处理逻辑 await db.put(`processed:${messageId}`, result, { expirationTtl: 3600 }); return result; }3.2 模型调用的Token管理与成本控制
Agent跑在边缘上,模型调用是最大的成本项。如果不加控制,一个用户可能几轮对话就把Token烧光了。项目里通常会有几层防护:输入截断、上下文窗口管理、输出长度限制、调用频率限制。
输入截断很好理解:用户输入超过一定长度就截掉,避免把整个网页内容塞进去。上下文窗口管理更复杂一些:不是把所有历史消息都发给模型,而是只保留最近N轮,或者用摘要的方式压缩早期对话。具体策略取决于业务场景。如果是客服Agent,可能需要保留完整历史;如果是工具型Agent,可能只需要最近几轮。
输出长度限制通过max_tokens参数控制。这个值不能设太大,否则模型可能生成很长的废话,浪费Token。一般设置在500到2000之间,根据任务复杂度调整。调用频率限制可以用Cloudflare的Rate Limiting规则,或者在Worker里用KV做计数器。
实操心得:我试过把
max_tokens设成4096,结果模型经常生成一大段无关内容,成本直接翻倍。后来改成1024,配合提示词里明确要求“简洁回答”,效果反而更好。Token这东西,省着用比放开用更考验提示词功底。
3.3 工具调用的路由与执行
Agent的核心能力之一是调用工具。在Workers环境里,工具可以是一个内部函数,也可以是一个外部API。项目里通常会设计一个工具注册表,每个工具包含名称、描述、参数Schema、执行函数。模型返回工具调用请求后,系统根据名称查找对应的执行函数,传入参数,拿到结果。
这里有个容易踩的坑:工具执行可能超时。Workers对子请求有数量限制(免费版50个,付费版1000个),而且每个子请求都有超时时间。如果工具是一个慢速API,可能会拖垮整个请求。解决办法是给工具执行加超时控制,超时后返回一个错误信息给模型,让模型决定下一步怎么做。
// 工具执行超时控制示例 async function executeToolWithTimeout(toolFn: Function, args: any, timeoutMs: number) { return Promise.race([ toolFn(args), new Promise((_, reject) => setTimeout(() => reject(new Error('Tool execution timeout')), timeoutMs) ) ]); }另一个坑是工具参数校验。模型生成的参数不一定符合Schema,可能少字段、类型不对、甚至包含恶意内容。所以执行前必须做校验,校验失败就返回错误信息让模型重新生成。这个环节不能省,否则一个格式错误的参数可能导致整个Worker崩溃。
3.4 多Agent协作的编排方式
“多AI协作”是热词里反复出现的概念。在cloudflare-os的语境下,多Agent协作通常有两种模式:串行编排和并行编排。
串行编排就是Agent A的输出作为Agent B的输入,像流水线一样。比如一个“研究Agent”负责搜集资料,一个“写作Agent”负责生成报告,一个“审核Agent”负责检查错误。每个Agent是一个独立的Worker,通过消息队列或者直接HTTP调用串联。
并行编排则是多个Agent同时处理同一个任务的不同部分,最后汇总结果。比如一个Agent负责查天气,一个Agent负责查交通,一个Agent负责查餐厅,最后汇总成一份出行建议。并行编排的难点在于结果聚合和错误处理:如果某个Agent失败了,是整体失败还是降级处理?项目里通常会用Promise.allSettled来等待所有Agent完成,然后根据成功和失败的数量决定下一步。
注意:多Agent协作会显著增加Token消耗和请求延迟。如果不是必要场景,单Agent加多工具往往更划算。我见过不少项目为了“看起来高级”硬上多Agent,结果成本翻了三倍,效果还不如单Agent。
4. 实操落地:从零搭一个最小可用的Agent
4.1 环境准备与项目初始化
动手之前,你需要准备这些东西:一个Cloudflare账号(免费版就够跑Demo)、Node.js环境(建议18以上)、Wrangler CLI、一个支持API调用的大模型服务(比如OpenAI兼容接口)。如果你还没有Cloudflare账号,去官网注册一个,整个过程不超过五分钟。
安装Wrangler很简单:
npm install -g wrangler wrangler login登录后会跳转浏览器授权,授权完成后本地就保存了凭证。然后创建项目:
npm create cloudflare@latest my-agent -- --template=hello-world cd my-agent这个模板会生成一个最简单的Worker,包含src/index.ts和wrangler.toml。wrangler.toml是配置文件,里面定义了Worker的名称、入口文件、兼容性日期等。兼容性日期很重要,它决定了Worker运行时的行为,建议设置成比较新的日期,比如2024-09-23。
接下来安装依赖。项目里通常需要这些包:hono(轻量Web框架,比原生Fetch API好用)、zod(参数校验)、ai(Vercel的AI SDK,统一模型调用接口)。如果你不想用框架,直接用原生Fetch API也行,但代码会啰嗦一些。
npm install hono zod ai4.2 配置D1数据库和KV命名空间
状态管理需要数据库。D1是Cloudflare的Serverless SQL数据库,基于SQLite,适合存对话历史。创建D1数据库:
wrangler d1 create agent-db命令会返回一个database_id,把它填到wrangler.toml里:
[[d1_databases]] binding = "DB" database_name = "agent-db" database_id = "你的database_id"然后创建表结构:
CREATE TABLE IF NOT EXISTS conversations ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, messages TEXT NOT NULL DEFAULT '[]', created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS processed_messages ( message_id TEXT PRIMARY KEY, result TEXT NOT NULL, created_at INTEGER NOT NULL );执行迁移:
wrangler d1 execute agent-db --file=./schema.sqlKV用来做缓存和限流。创建KV命名空间:
wrangler kv:namespace create AGENT_KV同样把返回的ID填到wrangler.toml:
[[kv_namespaces]] binding = "KV" id = "你的kv_id"4.3 编写核心Agent逻辑
核心逻辑分三块:路由处理、模型调用、工具执行。我用Hono来组织路由,代码更清晰。
import { Hono } from 'hono'; import { z } from 'zod'; type Env = { DB: D1Database; KV: KVNamespace; OPENAI_API_KEY: string; }; const app = new Hono<{ Bindings: Env }>(); const MessageSchema = z.object({ conversationId: z.string(), messageId: z.string(), content: z.string().max(4000), }); app.post('/chat', async (c) => { const body = await c.req.json(); const parsed = MessageSchema.safeParse(body); if (!parsed.success) { return c.json({ error: 'Invalid input' }, 400); } const { conversationId, messageId, content } = parsed.data; // 幂等检查 const cached = await c.env.KV.get(`msg:${messageId}`); if (cached) { return c.json(JSON.parse(cached)); } // 加载对话历史 const conversation = await c.env.DB.prepare( 'SELECT messages FROM conversations WHERE id = ?' ).bind(conversationId).first(); const history = conversation ? JSON.parse(conversation.messages) : []; history.push({ role: 'user', content }); // 调用模型 const response = await callModel(history, c.env.OPENAI_API_KEY); // 处理工具调用(如果有) const finalResponse = await handleToolCalls(response, history, c.env); // 保存状态 history.push({ role: 'assistant', content: finalResponse }); await c.env.DB.prepare( 'INSERT OR REPLACE INTO conversations (id, user_id, messages, created_at, updated_at) VALUES (?, ?, ?, ?, ?)' ).bind(conversationId, 'default', JSON.stringify(history), Date.now(), Date.now()).run(); // 缓存结果 await c.env.KV.put(`msg:${messageId}`, JSON.stringify({ reply: finalResponse }), { expirationTtl: 3600, }); return c.json({ reply: finalResponse }); }); export default app;模型调用函数:
async function callModel(messages: any[], apiKey: string) { const response = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, }, body: JSON.stringify({ model: 'gpt-4o-mini', messages, max_tokens: 1024, temperature: 0.7, }), }); if (!response.ok) { throw new Error(`Model API error: ${response.status}`); } const data = await response.json(); return data.choices[0].message.content; }工具执行函数:
async function handleToolCalls(response: string, history: any[], env: Env) { // 简化版:检测是否包含工具调用标记 const toolMatch = response.match(/\[TOOL:(\w+)\](.*?)\[\/TOOL\]/s); if (!toolMatch) { return response; } const toolName = toolMatch[1]; const toolArgs = JSON.parse(toolMatch[2]); let toolResult: string; switch (toolName) { case 'get_weather': toolResult = await getWeather(toolArgs.city); break; case 'search_web': toolResult = await searchWeb(toolArgs.query); break; default: toolResult = 'Unknown tool'; } // 把工具结果追加到历史,再次调用模型 history.push({ role: 'assistant', content: response }); history.push({ role: 'user', content: `工具执行结果:${toolResult}` }); return await callModel(history, env.OPENAI_API_KEY); }4.4 部署与验证
本地测试用wrangler dev,它会启动一个本地服务器,模拟Workers环境。测试通过后,用wrangler deploy部署到全球边缘网络。部署完成后,你会得到一个*.workers.dev的域名,可以直接调用。
验证步骤:
- 发送一条简单消息,看是否返回正常回复。
- 发送一条需要工具调用的消息,看工具是否被执行。
- 重复发送同一条消息(相同
messageId),看是否命中缓存。 - 查看D1数据库,确认对话历史被正确保存。
curl -X POST https://your-worker.workers.dev/chat \ -H "Content-Type: application/json" \ -d '{"conversationId":"test-1","messageId":"msg-1","content":"今天天气怎么样?"}'实操心得:部署后第一次请求可能会慢一些,因为要初始化数据库连接。后续请求会快很多。如果发现请求超时,先检查模型API的响应时间,Workers对子请求有30秒的超时限制,模型调用最好控制在10秒以内。
5. 常见问题与排查技巧实录
5.1 Worker启动报错与依赖问题
新手最容易遇到的错误是fatal error: cannot open source file这类编译错误。在Workers环境里,这通常意味着依赖没有正确打包。Workers不支持Node.js原生模块,所有依赖必须是纯JavaScript或者被正确polyfill过的。如果你用了fs、path、crypto这些Node内置模块,需要换成Workers兼容的替代品。
比如crypto可以用Web Crypto API替代:
// 不推荐:Node.js crypto import crypto from 'crypto'; // 推荐:Web Crypto API const hash = await crypto.subtle.digest('SHA-256', data);另一个常见错误是../core/src/main.c(27): error: #5: cannot open input file "lcd.h"这类C语言编译错误。这通常出现在你试图在Workers里跑WASM模块时。Workers支持WASM,但需要把WASM文件作为模块导入,不能直接引用C源文件。如果你确实需要跑C代码,先用Emscripten编译成WASM,再在Worker里加载。
5.2 模型调用超时与重试策略
模型API偶尔会超时或者返回5xx错误。如果不做重试,用户体验会很差。但重试也不能无脑重试,否则可能重复扣费。我的做法是:只对幂等操作重试,且最多重试两次,每次间隔指数退避。
async function callModelWithRetry(messages: any[], apiKey: string, maxRetries = 2) { for (let i = 0; i <= maxRetries; i++) { try { return await callModel(messages, apiKey); } catch (error) { if (i === maxRetries) throw error; await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000)); } } }注意:如果模型调用已经产生了费用但响应超时,重试会导致重复扣费。解决办法是在调用前生成一个唯一的
request_id,模型服务端如果支持幂等键就传入,不支持的话只能在应用层做去重。
5.3 数据库连接与查询性能
D1的查询性能在数据量小的时候很好,但数据量大了之后需要注意索引。conversations表的id字段是主键,查询很快。但如果你经常按user_id查询,就需要加索引:
CREATE INDEX idx_conversations_user_id ON conversations(user_id);另一个坑是D1的写入限制。免费版D1每天有写入次数限制,如果Agent对话频繁,可能很快就用完了。解决办法是批量写入:不要每轮对话都写一次数据库,而是攒几条一起写。或者用KV做一级缓存,定期同步到D1。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| Worker启动报错找不到模块 | 依赖未打包或用了Node内置模块 | 检查wrangler.toml的compatibility_flags | 换用Workers兼容的依赖或加polyfill |
| 模型调用返回401 | API Key未配置或过期 | 检查环境变量 | 用wrangler secret put重新设置 |
| 请求超时 | 模型响应慢或工具执行慢 | 查看Worker日志 | 加超时控制,优化提示词减少Token |
| 数据库查询报错 | 表不存在或字段名错误 | 用wrangler d1 execute手动查询 | 检查迁移脚本是否执行成功 |
| 重复回复 | 幂等检查失效 | 检查KV是否正常写入 | 确保messageId唯一且KV过期时间合理 |
| 工具调用失败 | 参数格式错误或工具超时 | 打印工具参数和错误信息 | 加参数校验和超时控制 |
5.5 独家避坑技巧
技巧一:用waitUntil处理非关键路径。Workers有一个ctx.waitUntil方法,可以在返回响应后继续执行一些后台任务,比如写日志、更新统计。这样不会阻塞用户响应。
app.post('/chat', async (c) => { const result = await handleChat(c); c.executionCtx.waitUntil(logToAnalytics(result)); return c.json(result); });技巧二:用AbortController控制模型调用超时。Fetch API支持AbortController,可以在指定时间后取消请求。这比Promise.race更干净,因为底层请求真的被取消了。
const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); try { const response = await fetch(url, { signal: controller.signal }); // ... } finally { clearTimeout(timeoutId); }技巧三:提示词里加“不要编造”。Agent最容易犯的错误是编造工具执行结果。如果工具调用失败,模型可能会自己编一个结果出来。解决办法是在系统提示词里明确写:“如果工具执行失败,如实告知用户,不要编造结果。”
技巧四:用环境变量区分开发和生产。wrangler.toml支持多环境配置,可以用[env.production]和[env.dev]分别配置不同的数据库和API Key。部署时用wrangler deploy --env production指定环境。
6. 扩展方向:这个项目还能怎么玩
6.1 接入更多模型提供商
目前项目默认用OpenAI兼容接口,但你可以轻松扩展到其他模型。只要把callModel函数里的URL和请求格式改一下就行。比如接入Anthropic的Claude,请求体格式略有不同,但整体逻辑一样。建议做一个模型适配层,用统一的接口封装不同提供商的差异。
interface ModelProvider { chat(messages: any[], options: any): Promise<string>; } class OpenAIProvider implements ModelProvider { async chat(messages: any[], options: any) { // OpenAI格式 } } class AnthropicProvider implements ModelProvider { async chat(messages: any[], options: any) { // Anthropic格式 } }6.2 增加Agent记忆与长期上下文
目前的对话历史存在D1里,但只是简单的消息列表。如果要实现长期记忆,可以引入向量数据库。Cloudflare有Vectorize服务,可以把对话摘要转成向量存起来,需要的时候做相似度检索。这样Agent就能“记住”几个月前的对话内容,而不是只保留最近几轮。
具体做法:每轮对话结束后,用模型生成一个摘要,把摘要转成向量存入Vectorize。下次用户提问时,先用问题向量检索相关历史摘要,把检索结果作为上下文注入提示词。这个方案的成本比全量历史低得多,效果也不错。
6.3 多Agent协作的进阶编排
如果要做复杂的多Agent系统,可以引入一个“协调者Agent”来管理任务分配。协调者接收用户请求,拆解成子任务,分发给不同的专业Agent,最后汇总结果。这个模式在Workers上完全可行,因为每个Agent都是一个独立的Worker,通过Service Binding互相调用,延迟很低。
Service Binding是Cloudflare的一个特性,允许Worker之间直接调用,不走公网。配置方式:
[[services]] binding = "RESEARCH_AGENT" service = "research-agent" [[services]] binding = "WRITER_AGENT" service = "writer-agent"然后在代码里直接调用:
const researchResult = await c.env.RESEARCH_AGENT.fetch(request);这种方式比HTTP调用更快、更安全,而且不消耗子请求配额。
6.4 监控与可观测性
生产环境跑Agent,监控必不可少。Cloudflare自带Analytics,可以看到请求量、错误率、延迟分布。但更细粒度的监控需要自己埋点。我通常会在关键节点记录日志,比如模型调用耗时、Token消耗、工具执行结果。这些日志可以写到R2或者外部日志服务。
async function logEvent(env: Env, event: string, data: any) { const logEntry = { timestamp: Date.now(), event, data, }; await env.KV.put(`log:${Date.now()}:${Math.random()}`, JSON.stringify(logEntry), { expirationTtl: 86400 * 7, }); }实操心得:日志不要写太细,否则KV的写入成本会很高。我一般只记录模型调用的Token数和耗时,以及工具调用的成功/失败状态。这些数据足够分析成本和性能瓶颈了。
6.5 安全加固建议
Agent系统面临的安全风险主要有三类:提示词注入、工具滥用、数据泄露。提示词注入是指用户输入恶意内容,诱导模型执行非预期操作。防范方法是在系统提示词里明确边界,并且对用户输入做过滤。工具滥用是指模型被诱导调用敏感工具,防范方法是工具权限分级,敏感工具需要额外验证。数据泄露是指对话历史被未授权访问,防范方法是加密存储和访问控制。
具体措施包括:用zod严格校验所有输入;工具执行前检查用户权限;对话历史加密后存入D1;API Key用wrangler secret管理,不要硬编码在代码里。这些措施看起来繁琐,但一旦出问题,代价会大得多。
7. 一些个人体会
这个项目我断断续续跟了两周,最大的感受是:边缘计算和AI Agent的结合,比想象中更自然。Agent的每一次交互本来就是短时任务,和Workers的执行模型天然匹配。以前觉得Serverless跑Agent不靠谱,是因为状态管理麻烦,但现在有了D1、KV、R2这些配套服务,状态问题基本解决了。
另一个体会是:成本控制是Agent项目的生命线。我见过太多项目Demo跑得很漂亮,一上生产就被Token账单吓退。cloudflare-os这个项目在成本控制上做了不少设计,比如幂等检查、上下文压缩、工具超时控制,这些都是实战中总结出来的经验,值得借鉴。
最后分享一个小技巧:先用最小可行产品验证核心流程,再逐步加功能。不要一上来就搞多Agent、向量记忆、复杂工具链。先把“接收消息-调用模型-返回结果”这条链路跑通,确认成本和延迟可接受,再往上叠功能。这样每一步都有反馈,不会在错误的方向上浪费太多时间。