Genkit JS Agent Branching 实战:基于不可变快照的分支会话(Beta)
Genkit JS Agent Branching 实战基于不可变快照的分支会话Beta【免费下载链接】skillsAgent Skills for Google products and technologies项目地址: https://gitcode.com/GitHub_Trending/skills29/skills本篇指南围绕 GenkitNode.js/TypeScriptAgent Beta 版 API 的核心能力之一——分支Branching展开讲解如何借助不可变快照snapshotId从任意历史会话节点分叉出多条相互独立的时间线覆盖服务端分叉、浏览器端多方案选择、以及从快照恢复历史等完整实战场景。读完本文你将掌握chat({ snapshotId })的分支语义、配套的会话存储选型与 HTTP 端点配置能够直接在 Genkit 应用中实现一次会话、多个候选分支、用户择优继续的交互形态。本文档是 Genkit JS Agent 参考文档系列中的一篇。建议按序阅读agents.mdAgent 基础与 HTTP 服务→ agents-sessions.md会话存储与快照持久化→ 本篇。前置要求与适用范围分支能力属于Beta / preview API使用前需要明确以下前提依据 agents.md 与 SKILL.md必须使用genkit/beta导入路径。服务端 API 来自genkit/beta如InMemorySessionStore浏览器端客户端来自genkit/beta/client如remoteAgent而不是稳定的genkit入口导入路径与函数签名后续可能变更。要求genkit 1.39.0CLI 最低版本 1.29.0可通过genkit --version校验。分支必须配合会话存储session store。因为快照需要持久化store是启用分支的前提无 store 的纯无状态 Agent 虽然可以做多轮对话但不具备快照分支能力。读者应已掌握 Agent 的基本用法ai.defineAgent、agent.chat()、remoteAgent本文不再重复基础定义。核心概念快照即不可变检查点分支的基石是snapshotId。Genkit 把每一轮对话的会话状态消息历史 自定义状态 工件固化为一个不可变检查点immutable checkpoint——正如 agents.md 所述chat.send()的返回结果中带有res.snapshotId它就是这一轮结束时的快照 ID。snapshotId的语义与 git commit 高度相似理解这一点就理解了整个分支模型不可变快照一旦产生就不可修改原快照在分支后保持不变可派生从同一个快照可以派生任意数量的独立时间线independent timelines它们互不影响分叉即新快照从快照S上开启的一轮新对话会产生一个新的、独立的快照S而S原封不动。用代码表达就是一行调用const branchA assistant.chat({ snapshotId: checkpoint });打开一个附着在较早快照上的新chat即完成一次分支后续该chat的每一轮都会自动把状态沿分支链向前推进。服务端分支从同一快照分叉出独立时间线在服务端代码中分支操作非常直观原文示例完整保留import { z } from genkit; import { InMemorySessionStore } from genkit/beta; import { ai } from ./genkit.js; export const assistant ai.defineAgent({ name: assistant, system: You are a helpful assistant., store: new InMemorySessionStore(), }); const root assistant.chat(); const res1 await root.send(Hello!); const checkpoint res1.snapshotId; // 分支点 // 分支 A —— 从 checkpoint 分叉。 const branchA assistant.chat({ snapshotId: checkpoint }); await branchA.send(My name is Bob.); const resA await branchA.send(What is my name?); // - Bob // 分支 B —— 从同一个 checkpoint 分叉完全独立。 const branchB assistant.chat({ snapshotId: checkpoint }); await branchB.send(My name is John.); const resB await branchB.send(What is my name?); // - John这段代码的关键行为root.send(Hello!)完成后checkpoint指向包含这条问候历史的快照branchA从checkpoint继续Agent 记住了Bob这个名字branchB也从checkpoint继续Agent 记住了John这个名字两条分支对彼此的状态一无所知resA回答 Bob、resB回答 John互不干扰。从源码结构看见 agents-sessions.md这里store承担了快照的读写职责每个chat会持久化到存储并按快照链自动向前推进chat.send()会沿用上一个快照而chat({ snapshotId })则显式指定从哪个历史快照续接。客户端分支多方案择优模式服务端分支是后端能力而**客户端分支client-side branching**解决的是一个非常典型的交互需求并行生成多个候选方案让用户挑选一个再从被选中的快照继续。原文给出了完整的浏览器/Node 客户端实现genkit/beta/client中的remoteAgentimport { remoteAgent } from genkit/beta/client; const agent remoteAgent({ url: /api/branchingAgent }); let snapshotId: string | undefined; // 当前分支点 async function twoVariants(text: string) { // 每个变体都从同一快照分叉出自己的 chat //尚未有分支点时则开启全新会话。 const makeChat () snapshotId ? agent.chat({ snapshotId }) : agent.chat(); const [a, b] await Promise.all([ makeChat().send(text), makeChat().send(text), ]); // a.snapshotId ! b.snapshotId —— 两者从同一点分叉。 return { a, b }; } // 当用户选中某个变体时其 snapshotId 成为新的分支点 function pick(chosenSnapshotId: string) { snapshotId chosenSnapshotId; }要点解析makeChat()是一个工厂函数存在分支点时用agent.chat({ snapshotId })从旧快照分叉否则agent.chat()开新会话两个变体通过Promise.all并行发起各自拿到独立的snapshotIda.snapshotId ! b.snapshotIdpick(chosenSnapshotId)把用户的选择写入snapshotId变量之后生成的任何新变体、或用户的后续对话都从这条被选中的分支继续。这就是git 分支思想在对话 UI 中的落地用户可以比较多个续写方向再决定合并进哪条主线。从快照恢复历史不发起回合读取状态有时你不需要继续对话只想读取某个快照里的状态——例如页面刷新后根据 URL 中保存的snapshotId恢复聊天 UI。为此 Genkit 提供了agent.getSnapshot(snapshotId)它只读状态、不启动任何回合import type { Part } from genkit/beta; import { remoteAgent } from genkit/beta/client; const agent remoteAgent({ url: /api/branchingAgent }); const snapshot await agent.getSnapshot(snapshotId); const history (snapshot?.state?.messages ?? []) .filter((m) m.role user || m.role model) .map((m) ({ role: m.role, text: (m.content ?? []) .filter((p: Part) p.text) .map((p: Part) p.text) .join(), }));这段代码的实用价值在于只保留user与model两种角色的消息过滤掉工具调用等内部消息把消息内容中的文本片段Part拼接为纯文本便于直接渲染到 UIgetSnapshot返回的snapshot.state.messages与后台 Agent 内部的消息结构一致因此这段快照 → 可渲染历史的转换逻辑也可复用到 agents-background.md 中轮询后台任务的场景那里同样从snapshot.state.messages提取最终文本。注意getSnapshot是一个远程调用服务端必须暴露 Agent 的getSnapshotDataAction对应POST /api/name/getSnapshot端点详见下文 HTTP 配置一节。分支背后的存储机制选对 SessionStore分支依赖快照持久化而快照存在哪里由store决定。agents-sessions.md 给出了三类内置存储理解它们的差异有助于为分支场景选型InMemorySessionStore —— 测试与本地开发import { InMemorySessionStore } from genkit/beta; const memStore new InMemorySessionStore();快照保存在内存中进程重启即丢失。分支语义完整可用适合验证逻辑、跑测试生产环境不建议。FileSessionStore —— 本地文件持久化import { FileSessionStore } from genkit/beta; // 快照持久化在 dir/global/snapshotId.json 下 const fileStore new FileSessionStore(./.snapshots); // 带链修剪每条链只保留最近 N 个快照 const pruning new FileSessionStore(./.snapshots, { maxPersistedChainLength: 3, });每个快照对应磁盘上的一个 JSON 文件global/snapshotId.jsonmaxPersistedChainLength可以限制单条分支链上保留的快照数量避免分支泛滥时磁盘无限增长——注意它修剪的是链上旧快照不影响被多个分支共享的分叉点因为分叉点的快照属于每条分支链。FirestoreSessionStore —— 生产级可扩展存储import { genkit } from genkit/beta; import { FirestoreSessionStore } from genkit-ai/google-cloud/beta; const myAgent ai.defineAgent({ name: myAgent, system: You are a helpful assistant., store: new FirestoreSessionStore(), });针对长会话聊天/编码 Agent设计每轮以JSON Patch 增量 diff形式写入并锚定周期性分片检查点避免单个文档逼近 Firestore 1 MiB 限制。可选参数db显式传入 Firestore 实例默认新建Firestore()遵循FIRESTORE_EMULATOR_HOSTcollection快照集合名默认genkit-sessions配套集合collection-pointers与collection-shards自动派生checkpointInterval全量检查点间隔轮数默认25状态小且读多可调低单轮状态大则调高shardSize单个分片/diff 文档的最大字节数默认512 KiB。自定义 SessionStore若内置存储不满足需求可自行实现SessionStoreS接口见 agents-sessions.md核心是两个方法import type { SessionStore } from genkit/beta; // S 为自定义状态类型。 const store: SessionStoreMyState { // 按 snapshotId 或 sessionId二选一加载快照。 async getSnapshot(opts) { /* ... */ return undefined; }, // 原子地 读取 → 变更 → 持久化。返回用到的 snapshotId // 当 mutator 返回 null 时返回 null。 async saveSnapshot(snapshotId, mutator, options) { /* ... */ return snapshotId ?? new-id; }, // 可选订阅快照状态变更后台 Agent 使用。 onSnapshotStateChange(snapshotId, callback, options) { return () {}; // 取消订阅 }, };实现自定义存储时需保证saveSnapshot的原子性——分支场景下多个 chat 可能同时基于同一快照写入读改写必须串行化否则会出现分支覆盖。HTTP 端点配置让客户端能够分支与恢复客户端分支remoteAgent和快照恢复getSnapshot都依赖服务端暴露的 HTTP 端点。agents.md 与 agents-deployment.md 给出了标准做法。用 expressHandler 暴露 Agent 及其伴随动作import { expressHandler } from genkit-ai/express; import express from express; import { weatherAgent } from ./weather-agent.js; const app express(); app.use(express.json()); // 主回合端点 app.post(/api/weatherAgent, expressHandler(weatherAgent)); // 伴随动作快照恢复 / 分支 / 后台任务需要 app.post( /api/weatherAgent/getSnapshot, expressHandler(weatherAgent.getSnapshotDataAction) ); app.post( /api/weatherAgent/abort, expressHandler(weatherAgent.abortAgentAction) ); app.listen(8080);getSnapshotDataAction正是remoteAgent.getSnapshot()在服务端的对应实现因此分支与恢复功能的服务端配置就靠这一个额外端点。remoteAgent的默认路径约定是${url}/getSnapshot与${url}/abort与上述注册路径天然匹配客户端只需提供基础url。伴随动作的选择矩阵agents-deployment.md 提供了一个可复用的exposeAgent辅助函数并给出不同能力所需的伴随动作对照表Agent 能力snapshotabort普通对话客户端或服务端状态––快照恢复 /分支✓–后台任务 / detach✓✓即纯分支场景只需getSnapshot一个伴随动作如果同时支持后台执行见 agents-background.md才需要额外暴露abort。对应注册代码// 快照恢复 / 分支 —— 需要 getSnapshot exposeAgent(branchingAgent, branchingAgent, { snapshot: true }); // 后台 Agent —— 需要 getSnapshot轮询与 abort exposeAgent(backgroundAgent, backgroundAgent, { snapshot: true, abort: true });若使用 Next.js可通过genkit-ai/next的appRoute将伴随动作注册为独立路由文件如app/api/weatherAgent/getSnapshot/route.tsHono/Bun/Deno 等 Fetch 运行时可用genkit-ai/fetch的fetchHandler实现同样效果。快照状态与分支的边界行为分支产生的每个快照都有生命周期状态。虽然分支本身不涉及后台执行但理解快照状态有助于正确判断能否从这个快照继续pending—— 仍在处理中后台任务场景completed—— 成功完成只有completed快照可被恢复/续接failed/aborted/expired—— 终态保留供检查但不可续接依据 agents-human-in-the-loop.md 与 agents-background.md。对于分支而言被放弃的分支会作为不可变快照继续留在存储中分支不会覆盖任何已有数据原文明确说明nothing is overwritten when you branch。这意味着你可以放心地反复从同一个checkpoint分叉产生 N 条实验性分支被放弃的分支不会污染主线也不会因为新分支的产生而丢失配合getSnapshot(snapshotId)用户随时可以回到过去重新选择另一条分支继续。典型应用场景与最佳实践综合以上能力分支在 Genkit Agent 应用中最常见的落地方案是写作/创作助手的多方案生成用户输入一个主题服务端或客户端并行从同一快照生成 23 个不同风格的续写用户择优后pick(chosenSnapshotId)锚定选中分支继续打磨对话历史恢复把snapshotId存入 URL页面刷新后用agent.getSnapshot(snapshotId)重建 UI无需重新发起回合A/B 实验与回溯同一会话上下文下测试不同的提示策略或工具编排被放弃的分支以快照形式留档便于事后审计。实战中还需注意分支点必须在会话存储启用后才有意义无store的 Agent 虽然可用 客户端托管状态 完成多轮对话但chat({ snapshotId })依赖服务端快照检索因此分支场景务必配置store保存分支点客户端需要在内存或 URL中维护当前分支点snapshotId并在每次pick后更新否则后续生成的分支会从错误的节点分叉生产环境存储选型本地开发用InMemorySessionStore/FileSessionStore长会话或高并发生产环境优先FirestoreSessionStore并可按需配置checkpointInterval与shardSize客户端过滤消息渲染历史时过滤非user/model角色并拼接Part.text如本文恢复历史示例避免把工具调用等内部消息暴露给用户。小结Genkit JS 的 Agent 分支能力用极简的 API 表达了一套强大的会话语义快照不可变、分支可并行、历史可恢复。服务端一行assistant.chat({ snapshotId: checkpoint })即可分叉客户端通过remoteAgent与getSnapshotDataAction端点把多方案择优变成现实。其底层依赖 agents-sessions.md 中介绍的会话存储体系选型得当即可支撑从开发调试到生产级长会话的完整分支场景。想要深入掌握 Agent 全貌可继续阅读本系列其他参考文档agents.md、agents-state.md、agents-multi-agent.md 与 agents-deployment.md。【免费下载链接】skillsAgent Skills for Google products and technologies项目地址: https://gitcode.com/GitHub_Trending/skills29/skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →