跳至正文
AI-Agents — Cloudflare Agents SDK:使用方式、业务场景与运行机制

Cloudflare Agents SDK:使用方式、业务场景与运行机制

AI 参与说明(2026-10-23,Agent:Codex):本文由 Codex 使用 Context7 检索 Cloudflare 官方文档,结合固定版本源码、npm 依赖和本地运行结果辅助撰写、校验。模型与 reasoning effort 未取得任务级运行记录。示例基于 agents@0.30.0;源码核对基线为 9c79ba12778cbb53b6c50db79282d780466c9897。

场景补充(2026-10-23,Agent:Codex):通过 Context7 与 Linear 官方文档补充 Issue 自动化场景,区分 Issue 事实、自动化运行状态与 AgentSession。执行入口 Codex Desktop;模型与 reasoning effort 未取得任务级运行记录。

一个客服工单,上午由用户提交,下午由客服处理,第二天还可能需要提醒。用户会关掉页面,多名客服会同时查看,处理过程中也可能调用模型。工单需要跨请求存在,并在这些事件之间保留自己的状态。

Cloudflare Agents SDK 把这样的业务实体组织成一个有稳定身份、持久存储、实时连接和调度能力的服务端实例。 模型可以参与其中的一步;实例的身份、状态、连接和提醒,在没有模型时也成立。

本文从一个不调用模型的工单程序开始,再解释何时加入模型、聊天框架和持久工作流。这样既能看懂 Agents SDK 自己提供了什么,也能理解为什么有的应用会再引入 AI SDK、Pi 或 TanStack AI。

术语对照

English中文本文含义
Agent instanceAgent 实例按业务名寻址、拥有独立状态与行为的服务端实体
Durable Object持久对象Cloudflare 提供身份、协调执行与独立存储的运行单元
namespace / binding命名空间/绑定定位某类实例的空间,以及 Worker 访问它的配置入口
state snapshot状态快照用于当前界面的小型整体状态,更新时整体替换
RPC远程过程调用客户端或其他服务调用实例方法的通信方式
hibernation休眠保留平台管理的连接与持久数据,同时允许回收内存对象
alarm / schedule闹钟/调度到时唤醒实例,以及 SDK 保存的待执行回调记录
journal / replay执行日志/重放根据保存的步骤证据重新运行,并复用已完成结果
idempotency key幂等键让同一业务操作的重复请求能被识别和去重的稳定标识
resumable streaming可恢复流传输重连后补发已保存片段,并继续接收新的流内容
model orchestration模型编排管理模型调用、工具执行、上下文与继续条件

从一张工单理解它的角色

假设工单 ticket-42 有状态、处理记录和提醒时间。

普通 Worker 可以接收“修改状态”的请求,再访问数据库。后续如果需要让多个页面实时更新、按工单协调操作、断线后重新取得状态、到时提醒,还需要建立相应的连接与调度机制。

使用 Agents SDK 时,可以让每张工单对应一个命名的 Agent 实例。Worker 根据名字把请求交给它;实例读取自己的存储、处理操作、保存结果,并向正在查看这张工单的客户端发送状态更新。用户关闭页面后,存储和已登记的提醒仍由服务端管理。

即使没有聊天、prompt 或工具循环,这样的实例也能支撑多人协作和后续业务动作。

第二阶段,工单加入“总结对话”。Agent 收到命令后读取记录,调用一次模型,把摘要保存下来。模型调用可以直接使用 Workers AI binding、厂商 SDK 或 HTTP API;如果需要统一多个模型的调用方式、处理流式输出或工具调用,可以在这一步使用模型 SDK。

第三阶段,产品升级为客服助手:模型要查订单、选择工具、等待审批,再决定下一步。此时需要管理模型与工具之间的循环、上下文和中断恢复。可以采用 Cloudflare 的 Think 等上层方案,也可以把自己的循环放进 Agent。工单的业务身份和状态,仍由原来的实例承接。

An Agent owns durable identity and state. Model orchestration is an additional choice.

哪些场景值得使用

业务场景实例按什么命名实例需要协调的事情
客服工单工单 ID多人修改状态、处理记录、超时提醒,按需生成摘要
协作房间房间或文档 ID连接中的成员、共享状态、服务端命令;复杂协同编辑另选合适的数据算法
个人助手用户或独立会话 ID长期偏好、任务状态、提醒,以及可选的模型交互
外部事件监测被监测项目 ID接收 webhook、保存上次结果、周期检查、通知订阅者
报告或审批任务业务任务 ID展示进度、接收人工操作,让持久工作流推进后续步骤

这些场景都需要一个跨事件存在的协调单元。只有一次无状态模型请求时,普通 Worker 加模型调用就可能足够;只有批量分发任务时,可以先考虑 Queues;流程重点是多个持久步骤和长期等待时,更适合由 Workflows 负责推进。简单应用如果已经能用 Durable Object 清楚表达,也可以直接使用 Durable Objects。

代入 Linear Issue:实例可以跟随一条 Issue

工单与 Issue 都是跨多次操作存在的业务实体。如果自己开发一个类似 Linear 的系统,示例里的 ticket-42 可以换成 Issue 的稳定 ID,实例继续管理它的状态、处理记录、订阅者和提醒。

如果接入现成的 Linear,需要先明确两边各自管理什么:

由谁管理记录什么
LinearIssue 的标题、描述、负责人、状态和评论等业务事实
每条 Issue 对应的 Agent已处理的事件、当前自动化任务、等待用户补充的信息、执行器任务 ID,以及重试和提醒安排

例如,用户把一条 Issue 交给助手后,可以这样组织处理流程:

  1. Worker 接收并验证 Linear webhook,定位对应 Issue 的 Agent。
  2. Agent 记录已接收的任务,取得所需上下文,再按规则处理,或调用模型分析需求。
  3. 需要读仓库、改代码和运行测试时,启动具备相应权限与运行环境的执行器,并保存它的任务 ID。
  4. 需求不完整时保存等待状态,收到用户补充后继续;收到执行器完成的回调时,按任务 ID 关联原任务。
  5. 根据应用权限与业务规则,把结果或进度写回 Linear;需要长期重试或等待的步骤交给持久任务机制。

页面、webhook 请求或模型调用结束后,实例仍可保存这条 Issue 的处理进度。代码执行由执行器承担;Agents SDK 负责接收这些事件、保存状态并协调后续动作。

命名时,可以在 workspace 作用域内使用 Issue 的 UUID;ENG-42 这类 identifier 留作展示。Linear 的 GraphQL API允许部分 Issue 操作接受 UUID 或短编号,这不要求应用也用展示编号作为持久实例名。

还要区分 Issue 的生命周期与一次助手工作的生命周期。Linear 原生 Agent 集成提供用于跟踪助手一次工作的 AgentSession:提及或委派可以创建 session,后续 prompted 事件向已有 session 补充输入。

实现时可让 Issue Agent 协调该 Issue 的各次任务,再用 runId 或 AgentSession ID 隔离执行上下文;也可以直接按 session 建实例,由 Issue ID 关联业务对象。实例粒度取决于哪些事件需要共同协调,不必让一条 Issue 只对应一次执行。

因此,Agents SDK 尤其适合等待补充信息、跟踪外部执行、跨时间提醒等 Issue 自动化。只有“收到事件后调一次 API”的简单处理,普通 Worker 往往已经足够。规则催办本身不需要模型;需要理解描述、总结讨论或规划步骤时,再加入模型能力。

底层机制:名字找到实例,事件推动实例

Agents SDK 运行在 Cloudflare Workers 与 Durable Objects 上。Worker 是请求入口,Durable Object 为每个命名实例提供身份、执行位置和独立存储;Agents SDK 在此基础上组织状态同步、RPC、连接和调度等能力。

在本文核对的 agents@0.30.0 中,Agent 直接继承 DurableObject,并通过 Lifecycle 组合 State、WebSockets、Scheduler 等能力。部分官方网页仍展示经过 PartyServer 的旧继承链,当前实现应以固定版本源码与 Lifecycle 文档为准。

身份来自 namespace 与 name

同一个 Durable Object namespace 下,相同的实例名会定位同一个业务身份。它可以在内存中退出、随后重新创建,名字与持久存储的对应关系仍然存在。

routeAgentRequest() 的默认路由结构是:

text
/agents/<Env binding 的 kebab-case>/<实例名>

如果 binding 叫 TicketAgent,实例名叫 ticket-42,路径就是:

text
/agents/ticket-agent/ticket-42

这里的路由段来自 Env binding 名。示例把 binding 和类都命名为 TicketAgent,所以看起来像按类名路由;两者不同时,应按 binding 理解。

名字也决定协调范围。每个请求随机生成新名字,后续请求就无法回到原工单实例;所有工单共用 global,又会把无关业务集中在同一个实例。对跨工单搜索和统计,通常还需要独立的查询索引或共享数据库,不能把每个实例的本地 SQLite 当作一个自动支持全局查询的数据库。

客户端操作如何进入实例

下面的图省略鉴权,展示首次连接和一次状态修改。WebSocket 建立后,RPC 在客户端与 Agent 连接之间传输,不需要为每个方法调用再走一次 Worker 路由逻辑。

%%{init: {"sequence": {"actorMargin": 24, "width": 100, "wrap": true}}}%%
sequenceDiagram
  participant C as 客户端
  participant W as Worker
  participant A as Ticket Agent
  participant D as SQLite
  C->>W: 连接 ticket-42
  W->>A: 按 binding 和 name 路由
  A->>D: 读取已存状态
  D-->>A: 当前工单
  A-->>C: 初始状态
  C->>A: RPC updateStatus
  A->>D: 保存事件与新状态
  A-->>C: RPC 结果与状态广播

SDK 自带通用客户端:React 使用 useAgent,其他环境可以使用 AgentClient。它们能建立连接、同步通用 state、调用服务端方法,这些功能不依赖聊天 SDK。

浏览器经由 WebSocket 调用的方法,需要用 @callable() 暴露。Worker 或另一个 Agent 调用时,可以通过 getAgentByName(namespace, name) 获取 stub,再使用原生 Durable Object RPC;这种内部调用不以 @callable() 为前提。

实例名和 @callable() 都不提供业务授权。生产入口仍需确认当前用户是否有权访问该工单,服务端方法还要验证参数和操作权限。

一个可以本地运行的最小程序

这个程序提供修改工单状态、保存 SQL 处理记录、向连接发送状态更新,以及延迟提醒。它不调用模型,也没有安装 AI SDK 或 Pi。

示例在 Node.js 24.19.0、agents@0.30.0、wrangler@4.149.0 下实际运行。它只用于本地教学,没有应用鉴权;接入真实工单前需要补上访问控制。

1. 创建配置文件

在一个空目录中创建以下文件。package.json 固定本次验证的直接依赖,首次安装后保留生成的 lockfile:

json
{
  "name": "agents-sdk-guide-example",
  "private": true,
  "type": "module",
  "scripts": { "dev": "wrangler dev --local --port 8791 --persist-to .local-state" },
  "dependencies": { "agents": "0.30.0", "zod": "4.3.6" },
  "devDependencies": { "wrangler": "4.149.0" }
}

tsconfig.json 使用 SDK 的配置:

json
{
  "extends": "agents/tsconfig"
}

@callable() 使用当前的 standard decorators 语义,不要另加 experimentalDecorators: true。本例由 Wrangler 编译;接入 Vite 8 时,还要按官方说明配置 agents/vite transform。

wrangler.jsonc 注册 SQLite-backed Durable Object:

jsonc
{
  "name": "agents-sdk-guide-example",
  "main": "server.ts",
  "compatibility_date": "2026-10-01",
  "compatibility_flags": ["nodejs_compat"],
  "durable_objects": {
    "bindings": [{ "name": "TicketAgent", "class_name": "TicketAgent" }]
  },
  "migrations": [{ "tag": "v1", "new_sqlite_classes": ["TicketAgent"] }]
}

compatibility_date 固定运行时兼容语义,不必与文章日期相同。本例选用 2026-10-01,在固定 Wrangler 携带的 workerd 支持范围内。

2. 实现 Agent

创建 server.ts:

ts
import { Agent, callable, routeAgentRequest } from 'agents'

type TicketState = { status: 'open' | 'done'; updates: number; reminders: number }
type Env = { TicketAgent: DurableObjectNamespace<TicketAgent> }

export class TicketAgent extends Agent<Env, TicketState> {
  initialState: TicketState = { status: 'open', updates: 0, reminders: 0 }

  onStart() {
    this.sql`CREATE TABLE IF NOT EXISTS events (
      id INTEGER PRIMARY KEY AUTOINCREMENT, status TEXT NOT NULL
    )`
  }

  // All business changes go through server methods in this example.
  validateStateChange(_next: TicketState, source: unknown) {
    if (source !== 'server') throw new Error('Use the updateStatus RPC')
  }

  @callable()
  updateStatus(status: 'open' | 'done') {
    if (status !== 'open' && status !== 'done') throw new Error('Invalid status')
    this.sql`INSERT INTO events (status) VALUES (${status})`
    this.setState({ ...this.state, status, updates: this.state.updates + 1 })
    return this.state
  }

  @callable()
  async followUp(seconds = 3) {
    if (!Number.isInteger(seconds) || seconds < 1 || seconds > 3600) {
      throw new Error('seconds must be an integer from 1 to 3600')
    }
    const scheduled = await this.schedule(seconds, 'remind', {})
    return scheduled.id
  }

  remind() {
    this.setState({ ...this.state, reminders: this.state.reminders + 1 })
  }

  @callable()
  snapshot() {
    return {
      state: this.state,
      events: this.sql<{ id: number; status: string }>`SELECT * FROM events ORDER BY id`,
    }
  }

  async onRequest(request: Request) {
    if (request.method === 'POST') {
      const body = await request.json() as { status: 'open' | 'done' }
      this.updateStatus(body.status)
    }
    return Response.json(this.snapshot())
  }
}

export default {
  async fetch(request: Request, env: Env) {
    return await routeAgentRequest(request, env) ?? new Response('Not found', { status: 404 })
  },
} satisfies ExportedHandler<Env>

initialState 给新工单提供默认值。onStart() 创建事件表,IF NOT EXISTS 让初始化可以重复执行。

客户端调用 updateStatus(),由服务端检查参数、写入事件,再基于当前 state 更新状态和计数。validateStateChange() 拒绝客户端直接替换整个业务快照,让写入统一经过服务端方法;它仍不能替代用户鉴权。

followUp() 保存一个到时调用 remind() 的调度记录。返回的是调度 ID,表示登记了后续工作,不表示提醒已经执行。

snapshot() 同时返回小型 state 和 SQL 事件,便于观察两类存储。示例将每次处理都计为一条记录,没有把重复提交去重;真实接口如果允许客户端重试,应按业务请求 ID 设计去重。

3. 连接并调用方法

创建 client.mjs:

js
import { AgentClient } from 'agents/client'

const client = new AgentClient({
  host: '127.0.0.1:8791',
  protocol: 'ws',
  agent: 'ticket-agent',
  name: 'ticket-a',
  onStateUpdate(state) { console.log('state update:', JSON.stringify(state)) },
})

try {
  await client.ready
  console.log('initial:', JSON.stringify(client.state))
  console.log('updated:', JSON.stringify(await client.call('updateStatus', ['done'])))
  console.log('schedule id:', await client.call('followUp', [2]))
  await new Promise(resolve => setTimeout(resolve, 2500))
  console.log('snapshot:', JSON.stringify(await client.call('snapshot')))
} finally {
  client.close()
}

这里用 Node 客户端把 SDK 的连接与 RPC 单独展示出来。在 React 页面中,可以通过 useAgent 使用同类能力;页面是否采用 TanStack Router 或 TanStack Query,不改变服务端实例的身份。

第一个终端安装并启动:

bash
npm install
npm run dev

Wrangler 显示 Ready on http://localhost:8791 后,在第二个终端执行:

bash
node client.mjs

首次使用新的本地存储目录时,输出如下,调度 ID 每次生成:

text
state update: {"status":"open","updates":0,"reminders":0}
initial: {"status":"open","updates":0,"reminders":0}
state update: {"status":"done","updates":1,"reminders":0}
updated: {"status":"done","updates":1,"reminders":0}
schedule id: <generated id>
state update: {"status":"done","updates":1,"reminders":1}
snapshot: {"state":{"status":"done","updates":1,"reminders":1},"events":[{"id":1,"status":"done"}]}

再次运行仍访问 ticket-a,所以计数继续累加。换一个名字则是另一张工单:

bash
curl http://127.0.0.1:8791/agents/ticket-agent/ticket-b
json
{"state":{"status":"open","updates":0,"reminders":0},"events":[]}

本次还使用独立的本地黑盒脚本验证了:同名实例的两个 WebSocket 客户端共享更新、另一实例隔离、SQL 记录可读;登记提醒后停止 Wrangler/workerd,再用相同持久目录启动,原状态与 SQL 记录仍在,提醒随后执行。依赖树也确认没有 AI SDK 和 Pi 包。这些是本地运行与受控重启结果,不代表生产环境的精确触发时间或外部副作用只执行一次。

state、SQL 和内存各存什么

工单程序里出现了三种容易混淆的数据位置。

位置适合放什么生命周期与通知方式
this.state / setState()当前工单状态、计数、进度、最新记录 ID保存到 SQLite,并通过 SDK 协议向客户端同步
this.sql完整事件历史、可查询的业务行保存在该实例的 SQLite;直接修改表不会自动广播业务查询结果
普通类字段、局部变量缓存、当前调用中的临时数据随内存对象消失;不能承担重启后的事实来源

initialState 只在没有保存状态时作为默认值。实例重新创建后会读取已有状态;重新执行初始化不应把工单改回 open。

setState() 是整体替换

当前版本的主要顺序是:写入前调用 validateStateChange(),序列化并保存新状态,更新内存缓存,广播状态,随后异步调用 onStateChanged() 通知 hook。

因此,setState({ status: 'done' }) 不能理解为只合并一个字段;示例显式展开 ...this.state,保留其他字段。它也没有自动提供 CRDT 或多客户端冲突合并。

写入前的拒绝应放在校验阶段。onStateChanged() 执行时,状态已经保存并广播,不能再靠它撤销不合法写入。当前服务端 hook 叫 onStateChanged,客户端选项 onStateUpdate 则仍是正确名称。

直接执行 INSERT 或 UPDATE 也不会自动使页面中的所有 SQL 查询刷新。本例通过 state 广播更新计数,需要历史时再调用 snapshot();复杂页面应明确使用状态快照、失效通知还是专门的订阅协议。

一次 await 期间可能发生别的操作

一个实例中的同步 JavaScript 和同步 SQL 不会在语句之间任意切换;但等待模型、HTTP 等外部 I/O 时,其他事件可能推进。

例如,先读取 const before = this.state,再等待模型,最后以 before 为基础写回整个状态,就可能覆盖客服在等待期间完成的修改。可以在返回后重新读取当前状态,检查业务版本,或把模型结果作为独立记录提交,再按业务规则决定是否更新工单。

因此,示例让客户端发送“修改状态”命令,再由服务端读取当前 state。blockConcurrencyWhile() 适合初始化等短关键区,不宜用它包住整个慢模型调用来规避冲突处理。

空闲休眠之后,什么还在

实例身份持久存在,不等于一个 JavaScript 对象一直驻留内存。 当前 Agent WebSocket 使用 Hibernation API,平台可以在保留连接的同时回收内存对象;后续事件再使实例恢复运行。

flowchart TB
  E["HTTP、RPC、WebSocket 消息或 alarm"] --> M{"实例仍在内存?"}
  M -->|是| H["处理本次事件"]
  M -->|否| R["重建实例并运行 onStart"]
  R --> S["从 SQLite 读取持久状态"]
  S --> H
  H --> P["保存业务结果与后续调度"]
  P --> I["等待后续事件"]
  I -. "平台可能回收内存对象" .-> E

onStart() 每个内存对象生命周期执行一次:仍在内存时处理新请求,不会每次重跑;对象重建后会再次执行。因此建表要可重复,业务状态不能无条件清零,也不能每次启动都新增同一个延迟提醒。

对象重建后可以依靠什么不能依靠什么
已提交的 SQLite 数据、state、持久调度记录普通类字段和本地缓存
仍由平台保留的 WebSocket 连接及其 attachment/state断线后新建连接自动继承旧连接的全部状态
根据持久记录重新开展工作原来的 setTimeout、悬空 Promise 和 JavaScript 调用栈

不能依赖固定的休眠时间,也不应照抄旧版本的 hibernate: false 配置。keepAlive() 或 keepAliveWhile() 可以减少空闲回收,但不能防止部署替换或故障,更不会把普通函数变成持久执行。

Durable storage survives object recreation. An in-flight JavaScript stack does not.

提醒、长任务和恢复分别怎么做

工单“明天提醒”和“执行到一半失败后继续”需要保存的东西不同:前者保存何时调用哪个方法,后者还要保存执行进展。

到时做一件事:Scheduler

schedule()、scheduleEvery() 和 cron 调度,把调度记录保存在实例的 SQLite 中,并通过 Durable Object alarm 唤醒实例。到时执行时,SDK 根据保存的方法名找到回调,再调用它。

示例停止本地进程后,提醒仍有机会在重启后执行,因为记录保存在存储里,不依赖原来的定时器。调度回调支持重试;发邮件、提交订单等动作要传递稳定的业务幂等键,避免外部系统已经接受操作、SDK 却还没记录成功时重复提交。

scheduleEvery() 对相同配置的注册有去重机制,这只解决重复登记。延迟或指定日期的调度默认语义不同,如果在 onStart() 中注册,应使用适当的幂等选项或单独保存注册状态,避免每次唤醒增加一条提醒。

多步骤与长期等待:Workflows

如果工单流程变成“抓取资料 → 生成报告 → 等待主管审批 → 发布”,可以让 Cloudflare Workflows 保存步骤结果、重试和等待状态;Agent 保存工单身份、展示当前进度,并接收页面上的审批操作。

SDK 提供 AgentWorkflow 与 runWorkflow() 等集成入口。Workflow 使用 step.do() 执行可重试的步骤,使用等待事件的机制接住人工反馈。主管明天才审批时,无需一直保持浏览器请求或一条 RPC 等待。

完成结果被持久记录后,重放可以复用它;外部副作用仍要按业务键去重。详细用法可继续阅读 Workflows API 与恢复机制。

实例内部的持久后台步骤:Tasks

agents@0.30.0 还发布了 agents/tasks,官方明确标记为 Experimental。它为实例内部任务提供持久接收记录、步骤 journal、休眠和重放。

它的恢复方式是重新运行 handler:已经完成的 step.do() 返回保存的结果,step.sleep() 使用保存的截止时间,未完成的步骤则可能再次执行。因此步骤之间的普通代码也会重跑,时间、随机值和外部副作用都需要按可重放的规则处理。

任务定义需要在实例构造或类字段初始化阶段建立,使唤醒后的运行时能按持久记录找到它。实验阶段应锁版本并验证迁移,本文的入门程序使用已经实际运行的基础调度,不把 Tasks 混进最小例子。

旧文章还可能提到 runFiber() 与 stash()。当前源码把旧 Fiber API 标为 Legacy;保存快照后的恢复需要应用实现 onFiberRecovered(),默认实现只记录 warning。不能把它理解成保存任意闭包、从上一个 await 原地继续。

浏览器断线与服务端中断要分开

发生了什么需要恢复什么对应机制
刷新工单页面当前业务状态同名实例的持久 state、SQL 与客户端重连
到了提醒时间待执行回调持久调度记录与 alarm
多步骤任务执行中断已完成步骤与后续执行Workflows,或实验性 Tasks
聊天页面断线,服务端仍在生成已产生的流片段和实时尾部AIChatAgent 的 resumable streaming
模型生成过程中服务端实例丢失保存的部分消息、工具证据及继续策略框架内建聊天执行恢复;按业务配置预算与继续策略,可能重新调用模型

最后两行是聊天框架的能力。通用 AgentClient 重连并不会自动保存任意模型输出流;SQLite 中有数据,也不会自动恢复原来的模型网络连接。

什么时候再引入模型 SDK 或聊天框架

在工单应用中加入模型时,应根据新增的交互和执行需求选择接入方式。

只增加一个“生成摘要”按钮

Agent 负责读取记录、验证权限、保存结果和通知页面。调用模型的那一步,可以直接通过 Workers AI binding、厂商 SDK 或 HTTP API 完成。

此时需要自己处理对应 API 的输入、错误和返回值。使用 AI SDK、Pi 或 TanStack AI,则是选择相应的模型调用抽象与工具,而不是为了让基础 Agent 得以启动。

Cloudflare Models 文档中的 agents/models/ai-sdk 与 agents/models/pi-ai,是把 Cloudflare 模型能力接入对应模型库的桥接入口。它们不表示每个 Agent 都必须二选一。基础包中相关模型库的 peer dependency 是 optional,本地例子的依赖与运行结果也印证了这一点。

增加完整聊天与模型工具循环

如果页面需要聊天消息、工具输入输出、流式回答、断线续收和中断恢复,可以采用现成的聊天与模型编排方案。

选择接手哪部分工作仍需理解的边界
基础 Agent + 自己的模型调用实例、state、SQL、连接、RPC、调度模型调用和循环、消息与输出协议由应用组织
AIChatAgent在 Agent 上管理聊天消息、聊天协议、流缓冲与恢复机制当前 @cloudflare/ai-chat 依赖 AI SDK;应用编写 onChatMessage() 等生成逻辑,并按业务配置恢复预算与策略
Think在聊天能力之上进一步提供模型工具循环、上下文、session 等当前是实验性上层方案,底层仍使用 AI SDK;采用它意味着选择这一套编排方式
Pi 相关集成接入 Pi 的模型与 Agent 循环生态按所选集成使用其模型、消息和循环接口
TanStack AI采用其模型调用、工具定义、客户端消息处理及 AG-UI 事件体系需要明确与 Agent 的 RPC、业务 state 和聊天恢复机制如何衔接

AIChatAgent 当前默认启用 durable chat recovery;chatRecovery 用于配置恢复预算和终结策略。它的流恢复也分层:浏览器断线时,可以补发已经缓冲的 chunks,再接上正在产生的内容;服务端实例丢失后,执行恢复需要依据保存的消息与工具记录判断重试或续写,可能产生新的模型请求。它没有保存原来的 JavaScript 调用栈,恢复策略也要考虑业务副作用与模型费用。

具体依赖以 lockfile 为准。本次同时核对了 @cloudflare/ai-chat@0.12.2 与 @cloudflare/think@0.21.0,不要由“Cloudflare 自己提供上层框架”推导出它不再使用模型 SDK。

使用 TanStack 时,先区分三个问题

前端采用 TanStack Start,不代表需要 TanStack AI。 Start 负责应用路由与服务端框架;工单页面可以直接连接 useAgent,聊天也可以采用 Cloudflare 的客户端方案。

真正需要评估 TanStack AI 的场景,是希望采用它的模型抽象、工具定义、客户端状态与 AG-UI 事件体系,或已有后端产生这种事件流。Agents SDK 则可以继续承接部署在 Cloudflare 上的业务实例、持久存储和调度。组合时要明确谁拥有聊天消息、谁管理工具循环、谁负责恢复,避免两套机制同时接管同一件事。

AG-UI 描述 Agent 与界面之间的事件契约;通用 useAgent state/RPC 是 Cloudflare 实例通信机制;Generative UI 是根据模型结果呈现组件的产品方式。三者可以协作,层次不同。AI SDK 在界面通信层的对应方案是 UI Message Stream Protocol、UIMessage、ChatTransport 与客户端消息状态处理这一套机制。

相关协议映射与 Cloudflare 当前 TanStack 集成范围,见 TanStack AI 与 AI SDK 对比。局部适配不等于整套聊天框架可以直接互换。

落到业务设计时,先确定这些边界

工单应用可以由一个 TicketAgent 承接同一张工单的命令、快照和提醒。随着规模增加,要继续明确以下边界:

  • 身份与授权:由可信入口验证用户及工单权限,再路由到实例。知道名字不等于有访问权;同步给客户端的 state 也不能夹带内部密钥或其他用户的私有数据。
  • 权威写入:让客户端发送“完成工单”“新增备注”等命令;服务端校验运行时参数与业务条件。TypeScript 类型本身不会验证收到的 JSON。
  • 重试与幂等:为用户命令、webhook 和外部副作用设计稳定的业务 ID。持久化状态、调度登记和外部操作完成,是不同的确认阶段。
  • 实例范围与查询:把需要共同协调的业务放在同一个实例;全局检索、统计和跨实例关系另行设计,避免所有用户共用一个热点实例。
  • 数据与执行证据:保存当前状态、历史记录、任务 ID 与终态错误。页面重开后应能区分等待、运行、完成和失败,而不是只恢复一个永远转动的图标。

这些边界确定以后,模型才能作为一个可替换、可失败、可重试的业务步骤加入;是否引入某个模型 SDK,也就能按具体工作量判断。

关联阅读

一手资料与核验范围

本文先通过 Context7 检索官方资料,再对照发布包、固定源码与本地运行结果。官方站点部分页面与新实现存在时间差,因此涉及继承链、Lifecycle、Tasks 和恢复机制的描述,优先采用以下固定源码快照。

运行验证覆盖本地 Wrangler 编译、独立客户端调用、状态广播、实例隔离、SQL 持久化,以及进程重启后的调度执行;不包含 Cloudflare 生产休眠时序或真实模型服务调用。

博客助手

正在打开博客助手…