关于 ai-agent
这是用于构建 AI Agent 后端的 RivetKit 技能,提供持久化对话、队列消息处理、流式响应和沙箱编码 Agent 示例,适合实现对话型 Agent 的开发者。
主要功能
- 01每段对话使用一个 Rivet Actor
- 02持久化对话状态
- 03按序处理消息队列
- 04广播流式响应
- 05提供沙箱编码 Agent 变体
适用场景
- 01构建具有独立对话记忆的聊天 Agent
- 02按序处理每段对话中的消息
- 03在隔离沙箱中创建编码 Agent
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ rivet-dev/ skills --skill ai-agent安装后在 Claude Code 中输入/ai-agent确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
BASE_SKILL.mdindex.jsonopenapi.jsonreference/actors/access-control.mdactions.mdactor-runtime-socket.mdappearance.mdauthentication.mdcommunicating-between-actors.mdconnections.mddebugging.mddesign-patterns.mddestroy.mderrors.mdevents.mdfetch-and-websocket-handler.mdhelper-types.mdhttp-api.mdinput.mdinspector-tabs.mdkeys.mdkv.mdlifecycle.mdlimits.mdmetadata.mdqueues.mdreference/actors/quickstart/backend.mdcloudflare.mdeffect.mdnext-js.md详细介绍
AI Agent
重要:在开始任何操作之前,必须先阅读此技能目录中的 BASE_SKILL.md。其中包含有关调试、错误处理、状态管理、部署和项目设置的重要指导。这些规则和模式适用于所有 RivetKit 工作。以下内容均假设你已阅读并理解该文件。
可运行示例
如需参考实现,请阅读以下模板中的原始可运行示例代码:
使用 RivetKit 构建 AI Agent 后端的模式:每段对话对应一个 Rivet Actor,由它管理自己的记忆、消息队列和流式输出。
起始代码
从 GitHub 上的一个可运行示例开始并对其进行调整。除非特别指出某个变体,否则下文介绍的是旗舰示例 ai-agent。
| 变体 | 起始代码 | 适用场景 |
|---|---|---|
| 队列驱动的 AI SDK Agent | GitHub | 你需要一个流式聊天 Agent,其中每段对话都有自己的持久记忆,并且每次处理一条消息。 |
| 沙箱编码 Agent | GitHub | Agent 需要通过 Docker、Daytona 或 E2B,在隔离的 沙箱中运行编码 Agent(默认使用 Codex)。 |
| 持久流 Agent(实验性) | GitHub | 你希望通过持久流而非 Actor 状态和事件,实现可重放且在重启后仍安全可靠的提示词和响应传递。 |
对话记忆
每段对话使用一个 Actor,并以对话 ID 或 Agent ID 为键(参见Actor 键)。Agent Actor 的持久化状态就是对话记忆:在 ai-agent 示例中,messages 和 status 存储在 JSON Actor 状态中,并且在休眠和重启后仍会保留,无需外部数据库。每次调用模型时,都会根据 c.state.messages 和系统提示词重新构建提示词,因此记忆和推理输入使用的是同一份数据。
| 变体 | 记忆存储位置 | 持久化状态字段 |
|---|---|---|
ai-agent | JSON Actor 状态 | messages、status |
sandbox-coding-agent | JSON Actor 状态和沙箱 ACP 会话 | messages、status、sessionId |
experimental-durable-streams-ai-agent | 持久流;Actor 仅存储对话 ID 和读取游标 | conversationId、promptStreamOffset |
消息处理
在 ai-agent 示例中,客户端通过 agent.connection.send("message", { text, sender }) 将用户输入推送到 Agent 的 message队列中。这是队列推送,而非 action 调用。Actor 的 run 钩子(参见生命周期)通过 for await (const queued of c.queue.iter()) 按顺序消费队列。
按顺序消费队列可保证每段对话的并发安全:每个 Actor 同一时间最多只有一次模型调用在执行,无需额外加锁。status 字段(模型调用进行中时为 thinking)仅用于向 UI 传递状态;真正的锁是运行循环。循环还会在令牌流中检查 c.aborted,以便在关闭时正常退出。
| 变体 | 消息入口 | 顺序处理保证 |
|---|---|---|
ai-agent | 通过 connection.send 推送到 message 队列 | run 钩子通过 c.queue.iter() 每次取出一条排队消息。 |
sandbox-coding-agent | sendMessage action,不使用队列 | 每次调用都会等待沙箱往返完成,然后再广播结果。 |
experimental-durable-streams-ai-agent | 在 onWake 中通过长轮询读取持久提示词流 | 每个数据块的 promptStreamOffset 都会持久化,因此重启后可继续处理,而不会重复处理提示词。 |
流式响应
ai-agent actor 会为每个模型文本增量广播一个 response 事件。负载包含 messageId、每个 token 的 delta、累积的 content 和 done 标志(失败时还会包含 error),因此客户端可以追加增量,也可以使用 content 按 messageId 幂等地替换消息。示例前端按 messageId 替换,这样即使事件丢失也能正常处理。终止广播的 delta 为空,包含完整的 content,并且 done: true。
由于助手消息对象存储在 c.state.messages 中,并在流式传输期间原地修改,因此如果 actor 在流式传输中途重启,部分内容仍会保留。示例会针对每个 AI SDK 增量广播一次,不进行节流;对于高流量部署,建议将增量分批或进行节流扩展,但示例并未实现这些功能。
变体差异:sandbox-coding-agent 会在沙箱运行结束后发送一条 done: true 的 response 广播(不进行增量流式传输);experimental-durable-streams-ai-agent 会将逐 token 分块内容追加到持久化响应流,然后广播 responseComplete 或 responseError。
架构
| 主题 | 摘要 |
|---|---|
| 拓扑 | agentManager["primary"] 单例目录,以及每个对话对应一个 agent[agentId] actor。 |
| 接入 | 客户端通过 connection.send 将 AgentQueueMessage 负载推送到 agent 的 message 队列。 |
| 流式传输 | 每个模型增量广播一次 response,并通过 done: true 发送终止广播。 |
| 记忆 | 完整对话记录和状态保存在 JSON actor 状态中;不使用外部数据库。 |
管理器会创建 AgentInfo 记录,并通过actor 之间的通信预热每个 agent:createAgent 调用 c.client<typeof registry>(),然后调用 client.agent.getOrCreate([info.id]) 并等待 getStatus(),确保对话 actor 在客户端连接前已存在。沙箱变体在此拓扑上扩展了一个 codingSandbox actor,该 actor 与 agent 共用同一个键(codingSandbox.getOrCreate([c.key[0]])),因此 agent 到沙箱的映射隐含在键空间中。
Actors
-
键:
agentManager["primary"] -
职责:目录 actor。创建
AgentInfo记录、列出 agent,并通过c.client()预热每个 agent actor。 -
操作
createAgentlistAgents
-
队列
- 无
-
状态
- JSON
agents
-
键:
agent[agentId] -
职责:每个对话对应一个 actor。保存完整的消息历史和状态,在其
run循环中消费排队的用户消息,通过 AI SDK 调用模型,并广播流式增量。 -
操作
getHistorygetStatus
-
队列
message
-
事件
messageAddedstatusresponse
-
状态
- JSON
messagesstatus
生命周期
sequenceDiagram
participant C as Client
participant AM as agentManager
participant A as agent
participant LLM as Model API
C->>AM: createAgent(name)
AM->>A: getOrCreate([info.id]) + getStatus()
AM-->>C: AgentInfo
C->>A: connection.send("message", {text, sender})
Note over A: run loop pops queue via c.queue.iter()
A-->>C: messageAdded (user message)
A-->>C: messageAdded (assistant placeholder)
A-->>C: status (thinking)
A->>LLM: streamText(system prompt + history)
loop each text delta
LLM-->>A: delta
A-->>C: response {messageId, delta, content, done: false}
end
A-->>C: response {delta: "", content, done: true}
A-->>C: status (idle)
安全检查清单
示例未配置身份验证,以保持精简。在公开 Agent 后端之前,请应用以下基线措施。
- API 密钥保留在服务器端:AI SDK 会在 actor 进程中读取
OPENAI_API_KEY(或ANTHROPIC_API_KEY)。密钥永远不会发送到浏览器;客户端只通过 RivetKit 与 actor 通信。沙箱变体会将密钥传递到沙箱环境中,绝不会发送给客户端。 - 添加身份验证:示例没有身份验证,因此任何能够访问服务器的人都可以创建 Agent、列出 Agent,并向任何其密钥可被猜中的 Agent 发送消息。建议扩展时添加
onBeforeConnect或createConnState检查,并使用作用域令牌。请参阅身份验证。 - 验证队列负载并限制速率:示例只会跳过不含字符串
text的请求正文。建议扩展时强制执行负载大小限制、架构验证和每连接速率限制。 - 在服务器端确定发送者身份:示例会原样信任客户端提供的
sender字段。请改为将发送者身份绑定到经过身份验证的连接。 - 限制或裁剪消息历史记录:示例在每次模型调用时都会发送完整的对话记录,且不设上限。建议扩展时裁剪或总结较早的消息,以限制提示词和状态的大小。
- 为每次对话设置成本上限:建议扩展时添加每个 Agent 的令牌预算和配额。沙箱变体会运行实际计算,因此还应强制执行每用户沙箱配额,并限制沙箱的网络出口流量。
参考索引
Actors
- 访问控制
- 操作
- Actor 密钥
- Actor 运行时套接字
- Actor 状态
- 身份验证
- Cloudflare Workers 快速入门
- Actor 之间的通信
- 连接
- 自定义检查器标签页
- 调试
- 设计模式
- 销毁 Actor
- Effect.ts 快速入门(Beta)
- 错误
- Fetch 和 WebSocket 处理程序
- 辅助类型
- 图标和名称
- 内存中状态
- 输入参数
- 生命周期
- 限制
- 底层 HTTP 请求处理程序
- 底层 KV 存储
- 底层 WebSocket 处理程序
- 元数据
- Next.js 快速入门
- Node.js 和 Bun 快速入门
- 队列和运行循环
- React 快速入门
- 实时功能
- Rust 快速入门(Beta)
- 扩展和并发
- 计划任务和 Cron
- 共享和加入状态
- SQLite
- SQLite + Drizzle
- Supabase Functions 快速入门
- 测试
- 故障排除
- 类型
- 原生 HTTP API
- 版本和升级
- 工作流
命令行
客户端
指南
部署
- 容器运行器
- 部署到 Amazon Web Services Lambda
- 部署到 AWS ECS
- 部署到 Cloudflare Workers
- 部署到 Freestyle
- 部署到 Google Cloud Run
- 部署到 Hetzner
- 部署到 Kubernetes
- 部署到 Railway
- 部署到 Rivet Compute
- 部署到 Supabase Functions
- 部署到 Vercel
- 部署到虚拟机和裸机