跳至内容

实验 05 — 会话

目标: 持久保存并恢复 Copilot SDK 对话,掌握“代理随处可用”体验背后的基础机制。

时间: 约 20 分钟

先决条件: 实验 04 已完成。

步骤 1 — 了解持久化的重要性

如果没有会话持久化,每次进程重启都会让助手失去记忆。助手只能看到当前进程中发送的消息,因此 CLI 崩溃、浏览器刷新或服务器回收都会导致对话丢失。

有了已保存的会话,对话便拥有稳定的身份。您可以从 CLI 开始,在 Web 应用中恢复,之后再从手机继续。这就是“代理随处可用”背后的核心模式:客户端可以改变,但会话历史始终与同一个 ID 关联。

此示例首先提供一个范围较小的证明:在同一进程和同一个 CopilotClient 中恢复会话。它还支持 --resume <id>,因此当环境能够访问相同的会话持久化存储和兼容运行时时,您可以从第二个独立进程进行尝试。

步骤 2 — 使用已知 ID 创建会话

打开 SessionsSample.cs 并找到会话标识符:

var sessionId = $"sdklabs-{Guid.NewGuid():N}"[..24];

接着找出它传入 SDK 的位置:

await using var session = await client.CreateSessionAsync(new SessionConfig
{
    SessionId = sessionId,
    Model = modelId,
    Streaming = false
});

SessionConfig.SessionId 可以让您提供自己的标识符。如果省略,SDK 将代为生成,但应用程序必须提取并保存该生成值,之后才能继续会话。

在实际应用程序中,请使用对领域具有意义且唯一的标识符,例如对话标识符或支持案例标识符。请勿在标识符中放入敏感信息:标识符经常出现在日志、诊断信息与 URL 中。

步骤 3 — 执行示例

dotnet run --project src/AgentOrchestrator/samples/SdkLabs -- sessions

预期输出:

== 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 中,以下代码无法编译:

await client.ResumeSessionAsync(sessionId);

编译器会报告 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 请求会话元数据:

var metadata = await client.GetSessionMetadataAsync(sessionId);

该调用会返回已保存会话的元数据。若要浏览可用的已保存会话,而不是从已知 ID 开始,请使用 ListSessionsAsync(...)

var sessions = await client.ListSessionsAsync(...);

常见模式如下:

  1. 列出已登录用户的会话
  2. 让用户选择一个,或选择最新项目
  3. 将该标识符传入 ResumeSessionAsync(id, config)

步骤 6 — 连接到架构

会话持久化可支持:

  • 跨设备与客户端交接 — 从一处开始,再到另一处继续
  • 崩溃恢复 — 在进程重启后继续,而不是从头重建内容
  • 可审计性 — 稳定的 ID 让对话更容易查看、整理和跟踪

当前的演示应用通过 StorageService 将聊天历史保存在浏览器的 localStorage 中。这适用于单个设备上的单个浏览器,但无法把对话转移到其他设备。如果您在手机或另一台计算机上打开应用,历史记录将不可用。

SDK 会话正是解决方案。UI 可以存储会话标识符,而不是整段对话内容;经授权且使用相同会话存储区的客户端,就能继续同一个服务器端会话。

⚠️ 会话 ID 不是访问控制机制。 ID 仅用于标识,不应被视为机密信息或能力令牌。应用程序在恢复已保存的对话前,仍需执行正常的用户身份验证和授权。

⚠️ 常见陷阱

  • 标识符冲突: 会话标识符就是您的键。重复使用标识符会继续旧对话,而不是创建全新的对话。
  • 不透明标识符: 随机标识符适合示范,但实际系统应能将标识符对应回用户、案例或工作流程。
  • 标识符不是权限: 只知道或猜到标识符,不足以访问对话;请另外强制执行授权。
  • 标识符中的机密信息: 绝不要在会话标识符中包含令牌、电子邮件地址、客户机密或机密数据。
  • 错误的继续重载: ResumeSessionAsync(sessionId) 会失败并显示 CS7036;请传入 ResumeSessionConfig.
  • 错误的设置类型: SessionConfig 用于创建; ResumeSessionConfig 用于继续。

💡 加分练习

继续同一会话两次:

  1. 在元数据调用后添加第三个轮次
  2. 继续同一个 sessionId 再次
  3. 针对原本的内容再问一个问题: 'At Risk' 消息

或者,列出已保存的会话,并继续最近的一个:

var sessions = await client.ListSessionsAsync(...);

接着将选择的标识符传入:

await client.ResumeSessionAsync(id, new ResumeSessionConfig
{
    Model = modelId,
    Streaming = false
});

✅ 检查点

您现在可以说明:

  • [x] 了解为何没有持久化时,进程重启会丢失内容
  • [x] 如何 SessionConfig.SessionId 会为应用程序提供稳定的对话键
  • [x] 为何 ResumeSessionAsync(id, config) 需要 ResumeSessionConfig
  • [x] 如何使用 GetSessionMetadataAsyncListSessionsAsync(...) 发现已保存的会话
  • [x] 了解为何 SDK 会话是实现跨设备聊天历史的正确基础
  • [x] 了解跨处理程序与跨设备继续会话,还依赖于共享存储空间、兼容的运行时行为与授权