实验 05 — 会话¶
目标: 持久保存并恢复 Copilot SDK 对话,掌握“代理随处可用”体验背后的基础机制。
时间: 约 20 分钟
先决条件: 实验 04 已完成。
步骤 1 — 了解持久化的重要性¶
如果没有会话持久化,每次进程重启都会让助手失去记忆。助手只能看到当前进程中发送的消息,因此 CLI 崩溃、浏览器刷新或服务器回收都会导致对话丢失。
有了已保存的会话,对话便拥有稳定的身份。您可以从 CLI 开始,在 Web 应用中恢复,之后再从手机继续。这就是“代理随处可用”背后的核心模式:客户端可以改变,但会话历史始终与同一个 ID 关联。
此示例首先提供一个范围较小的证明:在同一进程和同一个 CopilotClient 中恢复会话。它还支持
--resume <id>,因此当环境能够访问相同的会话持久化存储和兼容运行时时,您可以从第二个独立进程进行尝试。
步骤 2 — 使用已知 ID 创建会话¶
打开
SessionsSample.cs
并找到会话标识符:
接着找出它传入 SDK 的位置:
await using var session = await client.CreateSessionAsync(new SessionConfig
{
SessionId = sessionId,
Model = modelId,
Streaming = false
});
SessionConfig.SessionId 可以让您提供自己的标识符。如果省略,SDK 将代为生成,但应用程序必须提取并保存该生成值,之后才能继续会话。
在实际应用程序中,请使用对领域具有意义且唯一的标识符,例如对话标识符或支持案例标识符。请勿在标识符中放入敏感信息:标识符经常出现在日志、诊断信息与 URL 中。
步骤 3 — 执行示例¶
预期输出:
== Lab 05: sessions ==
Model: claude-haiku-4.5
Session id: sdklabs-7946e93975844b2e
--- Turn 1 (new session) ---
You: Remember this: my favourite retail segment is 'At Risk'. Reply with just OK.
Assistant: OK.
Session disposed.
--- Turn 2 (resumed session) ---
You: Which retail segment did I say was my favourite?
Assistant: 'At Risk'.
--- Session metadata ---
id=sdklabs-7946e93975844b2e metadata retrieved
每次运行都会随机生成会话 ID,因此您看到的值会不同。关键不在于 ID 的具体值,而在于第二轮仍记得 'At Risk',并且第一个会话对象已经释放。
这次运行证明:在单个进程中,即使原会话对象已释放,仍可恢复该会话;但它本身不能证明跨设备移交或重启后的恢复能力。
步骤 4 — 继续会话¶
第二轮使用相同的 ID,但调用不同的 SDK 方法:
await using var resumed = await client.ResumeSessionAsync(
sessionId,
new ResumeSessionConfig
{
Model = modelId,
Streaming = false
});
⚠️ 设置参数为必填项。 在 SDK v1.0.9 中,以下代码无法编译:
编译器会报告 CS7036 ,因为必填设置参数没有对应参数。
⚠️ 请使用 ResumeSessionConfig,而不是 SessionConfig. 创建新会话时使用 SessionConfig;继续现有会话则使用 ResumeSessionConfig。继续会话设置包含此处使用的同类设置,包括
Model 以及 Streaming,但类型不同。
为了进一步验证,请使用第一次运行时打印的会话 ID,在第二个进程中执行恢复路径:
dotnet run --project src/AgentOrchestrator/samples/SdkLabs -- sessions --resume sdklabs-7946e93975844b2e
第二次调用的已验证输出:
== Lab 05: sessions ==
Model: claude-haiku-4.5
Session id: sdklabs-7946e93975844b2e
--- Resumed existing session ---
You: Which retail segment did I say was my favourite?
Assistant: 'At Risk'.
--- Session metadata ---
id=sdklabs-7946e93975844b2e metadata retrieved
第二个命令确实跨越了进程边界。若要从另一台计算机恢复会话,还必须能够访问相同的会话持久化存储、使用兼容的运行时,并具备适当的授权。
步骤 5 — 发现已保存的会话¶
示例还会向 SDK 请求会话元数据:
该调用会返回已保存会话的元数据。若要浏览可用的已保存会话,而不是从已知 ID 开始,请使用 ListSessionsAsync(...):
常见模式如下:
- 列出已登录用户的会话
- 让用户选择一个,或选择最新项目
- 将该标识符传入
ResumeSessionAsync(id, config)
步骤 6 — 连接到架构¶
会话持久化可支持:
- 跨设备与客户端交接 — 从一处开始,再到另一处继续
- 崩溃恢复 — 在进程重启后继续,而不是从头重建内容
- 可审计性 — 稳定的 ID 让对话更容易查看、整理和跟踪
当前的演示应用通过 StorageService 将聊天历史保存在浏览器的 localStorage 中。这适用于单个设备上的单个浏览器,但无法把对话转移到其他设备。如果您在手机或另一台计算机上打开应用,历史记录将不可用。
SDK 会话正是解决方案。UI 可以存储会话标识符,而不是整段对话内容;经授权且使用相同会话存储区的客户端,就能继续同一个服务器端会话。
⚠️ 会话 ID 不是访问控制机制。 ID 仅用于标识,不应被视为机密信息或能力令牌。应用程序在恢复已保存的对话前,仍需执行正常的用户身份验证和授权。
⚠️ 常见陷阱¶
- 标识符冲突: 会话标识符就是您的键。重复使用标识符会继续旧对话,而不是创建全新的对话。
- 不透明标识符: 随机标识符适合示范,但实际系统应能将标识符对应回用户、案例或工作流程。
- 标识符不是权限: 只知道或猜到标识符,不足以访问对话;请另外强制执行授权。
- 标识符中的机密信息: 绝不要在会话标识符中包含令牌、电子邮件地址、客户机密或机密数据。
- 错误的继续重载:
ResumeSessionAsync(sessionId)会失败并显示CS7036;请传入ResumeSessionConfig. - 错误的设置类型:
SessionConfig用于创建;ResumeSessionConfig用于继续。
💡 加分练习¶
继续同一会话两次:
- 在元数据调用后添加第三个轮次
- 继续同一个
sessionId再次 - 针对原本的内容再问一个问题:
'At Risk'消息
或者,列出已保存的会话,并继续最近的一个:
接着将选择的标识符传入:
await client.ResumeSessionAsync(id, new ResumeSessionConfig
{
Model = modelId,
Streaming = false
});
✅ 检查点¶
您现在可以说明:
- [x] 了解为何没有持久化时,进程重启会丢失内容
- [x] 如何
SessionConfig.SessionId会为应用程序提供稳定的对话键 - [x] 为何
ResumeSessionAsync(id, config)需要ResumeSessionConfig - [x] 如何使用
GetSessionMetadataAsync和ListSessionsAsync(...)发现已保存的会话 - [x] 了解为何 SDK 会话是实现跨设备聊天历史的正确基础
- [x] 了解跨处理程序与跨设备继续会话,还依赖于共享存储空间、兼容的运行时行为与授权
相关内容¶
- 上一步: 实验 04 — 事件
- 下一步: 实验 06 — MCP
- 演示:Copilot SDK 整合
- 故障排除