Cloudflare Project Think 超详细教程,附源码

5 阅读1分钟

Cloudflare Project Think 超详细教程,附源码

上周三凌晨刷到 Cloudflare 的博客推送,差点从椅子上蹦起来——他们搞了个叫 Project Think 的东西,直接把 AI Agent 开发卷到了新高度。

我花了一个周末把文档啃完,踩了七八个坑,终于跑通了一个完整的多步骤研究 Agent。今天就把我折腾的过程完整记录下来,帮你省掉那些我踩过的坑。

为什么需要 Project Think?

先说个扎心的事实:现在主流的编码 Agent(Claude Code、Codex、Cursor Agent)都有个硬伤——它们只能在本地跑,或者你得花大价钱租 VPS。一个 Agent 闲着不动,每月也得烧你好几十刀。

更要命的是扩展性。假如你有 100 个用户同时用 Agent,你就得开 100 个容器。我之前试过用 Docker 跑 10 个 Agent 实例,那台 32G 内存的机器差点被我搞崩。

Cloudflare 给了个思路:用 Durable Objects。每个 Agent 是一个独立的 actor,自带 SQLite 数据库,空闲时零计算成本,收到消息自动唤醒。10 万个 Agent 同时在线,实际活跃的可能就几百个——只为你真正在跑的那些买单。

说白了,就是把"一个容器跑一个 Agent"变成了"一个轻量级隔离环境跑一个 Agent",成本砍到原来的 1% 甚至更少。

环境准备

我试下来的环境配置,亲测可用:

# Node.js 版本要求 >= 18.17,我用的是 20.x
node -v
# v20.11.0

# 安装 wrangler CLI(Cloudflare 的命令行工具)
npm install -g wrangler

# 登录 Cloudflare 账号
wrangler login

# 验证登录状态
wrangler whoami

对,你需要一个 Cloudflare 账号。好消息是 Workers 的免费额度完全够你玩——每天 10 万次请求,Durable Objects 现在也有免费额度了。

# 创建项目
mkdir my-think-agent && cd my-think-agent
npm init -y
npm install agents @cloudflare/workers-types

项目结构长这样:

my-think-agent/
├── src/
│   └── agent.ts       # Agent 主逻辑
├── wrangler.toml      # Cloudflare 配置
├── package.json
└── tsconfig.json

wrangler.toml 配置(这个文件很关键,我第一次就因为少写了 [durable_objects] 整个跑不起来):

name = "my-think-agent"
main = "src/agent.ts"
compatibility_date = "2026-04-15"

[durable_objects]
bindings = [
  { name = "MY_AGENT", class_name = "MyAgent" }
]

[[migrations]]
tag = "v1"
new_classes = ["MyAgent"]

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ES2022",
    "moduleResolution": "bundler",
    "types": ["@cloudflare/workers-types"],
    "strict": true,
    "outDir": "dist"
  },
  "include": ["src/**/*"]
}

开始写代码

第一步:最小可用的 Agent

先把最基础的跑起来,能接收消息、能回复:

// src/agent.ts
import { Agent } from "agents";

interface Env {
  MY_AGENT: DurableObjectNamespace;
  AI: any; // Workers AI binding
}

export class MyAgent extends Agent<Env> {
  
  // Agent 启动时调用
  async onRequest(request: Request): Promise<Response> {
    const { message } = await request.json() as { message: string };
    
    // 调用 Workers AI(Cloudflare 自带的模型)
    const result = await this.env.AI.run(
      "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
      { messages: [{ role: "user", content: message }] }
    );
    
    return Response.json({ 
      reply: result.response,
      agentId: this.id.toString()
    });
  }
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // 从 URL 获取 agent ID,没有就生成一个
    const url = new URL(request.url);
    const agentId = url.searchParams.get("id") || crypto.randomUUID();
    
    // 获取或创建 Durable Object 实例
    const id = env.MY_AGENT.idFromName(agentId);
    const stub = env.MY_AGENT.get(id);
    
    return stub.fetch(request);
  }
};

部署试一下:

npx wrangler deploy

第一次部署会问你确认一些东西,一路回车就行。部署完会给你一个 URL,类似 https://my-think-agent.<你的子域名>.workers.dev

测试一下:

curl -X POST "https://my-think-agent.xxx.workers.dev" \
  -H "Content-Type: application/json" \
  -d '{"message": "你好,介绍一下你自己"}'

能看到回复就说明基础环境 OK 了。

第二步:加上持久化对话(Session API)

上面那个 Agent 每次都是全新的对话,没记忆。Project Think 的 Session API 就是为了解决这个问题:

import { Agent, Session } from "agents";

interface Env {
  MY_AGENT: DurableObjectNamespace;
  AI: any;
}

export class ResearchAgent extends Agent<Env> {
  
  async onStart() {
    // Agent 初始化时创建一个会话
    if (!this.sessions.list().length) {
      this.sessions.create("main");
    }
  }

  async onRequest(request: Request): Promise<Response> {
    const { message } = await request.json() as { message: string };
    const session = this.sessions.get("main");
    
    // 把用户消息存入会话
    session?.push({ role: "user", content: message });
    
    // 拼装完整的对话历史发给模型
    const history = session?.messages || [];
    const result = await this.env.AI.run(
      "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
      { messages: history }
    );
    
    // 把模型回复也存进去
    session?.push({ role: "assistant", content: result.response });
    
    return Response.json({
      reply: result.response,
      historyLength: history.length + 2  // 加上这次一问一答
    });
  }
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const agentId = url.searchParams.get("id") || "default";
    
    const id = env.MY_AGENT.idFromName(agentId);
    const stub = env.MY_AGENT.get(id);
    
    return stub.fetch(request);
  }
};

有意思的是,Session API 支持树状结构的消息——每个消息都有 parent_id,你可以从任意一条消息分叉出新的对话分支,不会破坏原来的上下文。这比简单的线性对话记录灵活太多了。

第三步:Durable Execution(这个是真香功能)

runFiber() 是 Project Think 的核心杀器。它让你的 Agent 能执行长时间任务,中途被中断了也不怕——自动从上次 checkpoint 恢复:

export class ResearchAgent extends Agent<Env> {
  
  // ... 前面的代码 ...
  
  async startResearch(topic: string) {
    // 启动一个 Fiber,这是持久化执行的单元
    void this.runFiber("research", async (ctx) => {
      const findings: string[] = [];
      
      // 分 5 步做研究
      for (let i = 0; i < 5; i++) {
        const prompt = `研究步骤 ${i + 1}/5:针对主题「${topic}」,请给出第 ${i + 1} 个关键发现。`;
        
        const result = await this.env.AI.run(
          "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
          { messages: [{ role: "user", content: prompt }] }
        );
        
        findings.push(result.response);
        
        // 关键:保存 checkpoint
        // 如果 Agent 被中断(比如服务器重启),会从这里恢复
        ctx.stash({ findings, step: i, topic });
        
        // 通知前端进度
        this.broadcast({ 
          type: "progress", 
          step: i + 1, 
          total: 5,
          current: result.response 
        });
      }
      
      // 研究完成,保存最终结果
      ctx.stash({ findings, completed: true });
      this.broadcast({ type: "done", findings });
    });
  }
  
  async onRequest(request: Request): Promise<Response> {
    const url = new URL(request.url);
    
    // 如果是研究请求
    if (url.pathname === "/research") {
      const { topic } = await request.json() as { topic: string };
      await this.startResearch(topic);
      return Response.json({ status: "started", topic });
    }
    
    // 普通对话
    const { message } = await request.json() as { message: string };
    // ... 同上 ...
    return Response.json({ reply: "..." });
  }
}

这里有个让我惊艳的点:ctx.stash() 会把当前状态持久化到 Agent 的 SQLite 数据库里。就算 Cloudflare 的服务器把你这个 Agent 驱逐了(内存回收),下次有请求进来的时候,它会自动从上次 stash 的地方继续执行。

我测试的时候故意在循环中间 wrangler deploy 了一下(相当于重启),研究任务居然真的从断点继续了。这在传统的 serverless 函数里是想都不敢想的事情。

第四步:加个前端页面(可选)

光有 API 不直观,加个简单的 HTML 页面看效果:

// 在 fetch handler 里加个 GET 路由
if (request.method === "GET" && url.pathname === "/") {
  return new Response(HTML_PAGE, {
    headers: { "Content-Type": "text/html; charset=utf-8" }
  });
}

// HTML 页面内容(简化版)
const HTML_PAGE = `<!DOCTYPE html>
<html>
<head>
  <title>Research Agent</title>
  <style>
    body { font-family: system-ui; max-width: 640px; margin: 40px auto; padding: 0 20px; }
    .msg { padding: 12px; margin: 8px 0; border-radius: 8px; }
    .user { background: #e3f2fd; }
    .bot { background: #f5f5f5; }
    input { width: 70%; padding: 10px; font-size: 16px; }
    button { padding: 10px 20px; font-size: 16px; cursor: pointer; }
  </style>
</head>
<body>
  <h1>Research Agent Demo</h1>
  <div id="chat"></div>
  <input id="input" placeholder="输入研究主题..." />
  <button onclick="send()">开始研究</button>
  <script>
    async function send() {
      const input = document.getElementById("input");
      const chat = document.getElementById("chat");
      const topic = input.value;
      chat.innerHTML += '<div class="msg user">' + topic + '</div>';
      
      const res = await fetch("/research", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ topic })
      });
      const data = await res.json();
      chat.innerHTML += '<div class="msg bot">任务已启动: ' + data.status + '</div>';
    }
  </script>
</body>
</html>`;

实际效果:你在页面上输入一个研究主题,Agent 会在后台分步执行研究任务,每完成一步都会通过 WebSocket 推送进度到前端。你在终端里看到的是这样:

[1/5] 正在分析主题背景...
✅ [2/5] 正在收集关键数据...
✅ [3/5] 正在整理核心观点...
✅ [4/5] 正在对比不同方案...
✅ [5/5] 正在生成总结报告...
🎉 研究完成!共收集 5 个关键发现

整个流程大概 30 秒到 2 分钟,取决于模型响应速度。

常见问题 FAQ

Q1:部署时报错 Durable Object namespace not found

八成是 wrangler.toml 里的配置没写对。检查两点:[durable_objects] 里的 class_name 要跟你代码里的类名一模一样(区分大小写),[[migrations]]new_classes 也要写上。我第一次就因为类名写成了 myAgent(小写 m),部署上去直接白屏。

Q2:Workers AI 模型调用报 403

Workers AI 绑定需要在 wrangler.toml 里显式声明:

[ai]
binding = "AI"

没加这行的话 this.env.AI 就是 undefined,调用直接炸。这个在官方文档里藏得比较深,我翻了半天 issue 才找到。

Q3:runFiber 里的异步操作超时了

Durable Objects 的单次请求有 30 秒限制(付费版可以更长)。但 runFiber 不受这个限制——它会在后台持续运行,即使 HTTP 请求已经返回了。如果你发现 Fiber 里的任务没跑完就停了,检查一下是不是在 Fiber 外面做了太多操作。

Q4:想用自己的 API Key 调 GPT/Claude 怎么办?

Project Think 的 Agents SDK 不绑死 Workers AI。你可以直接在代码里 fetch 调 OpenAI 或 Anthropic 的 API:

const response = await fetch("https://api.openai.com/v1/chat/completions", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${this.env.OPENAI_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    model: "gpt-4o",
    messages: history
  })
});

API Key 存在 Cloudflare 的 Secrets 里:wrangler secret put OPENAI_API_KEY

Q5:免费额度够用吗?

亲测够用。Workers 免费版每天 10 万次请求,Durable Objects 现在也有免费额度。我测试了一整天(大概调了 200 多次 Agent),一分钱没花。不过注意 Workers AI 的模型调用有单独的免费额度限制,超过了会返回 429。


以上就是一个最小但完整的 Project Think Agent 了。从零到部署大概 1-2 小时(如果你网络好的话,光 npm install 就能省不少时间)。

Project Think 目前还是 Preview 阶段,API 可能会变。但核心概念——Durable Execution、Session API、Sub-agents——这些方向不会跑偏。Cloudflare 这波操作基本上在说:AI Agent 不该是奢侈品,它应该是基础设施级别的存在。

说实话,我之前一直觉得 serverless 跑 Agent 是个伪命题——没有长连接、状态管理麻烦、冷启动慢。但 Project Think 的 Durable Objects 方案确实解决了这些痛点。空闲零成本、状态自动持久化、自动扩缩容……至少从我这几天的测试来看,它不是 PPT。

如果觉得有帮助,欢迎点赞收藏 ❤️ 更多AI工具实战教程,关注我第一时间获取~