实验 05 — 会话¶
目标: 为一个对话赋予稳定的标识,使其能够经受重启过程,并理解会话持久化能为你带来什么以及不能带来什么。
时间: ~20分钟
前置条件: 实验室 04 完成。
步骤 1 — 问题¶
到目前为止,所有操作都是无状态的。每次运行都会创建一个新的会话,发送一个提示,并将上下文丢弃。重启过程,模型完全不知道你在聊什么。
那对于一个一次性示例来说是合适的,但对一个真正的助手来说毫无用处。一个零售分析师提出三个后续问题,期望第四个问题仍然围绕同一个客户。
步骤 2 — 运行会话示例¶
添加 --model <id> 来覆盖模型:
验证输出:
== Lab 05: sessions ==
Model: claude-haiku-4.5
Session id: sdklabs-75e6b8f216d0451b
--- Turn 1 (new session) ---
You: Remember this: my favourite retail segment is 'At Risk'. Reply with just OK.
Assistant: OK
Session closed.
--- Turn 2 (resumed session) ---
You: Which retail segment did I say was my favourite?
Assistant: Your favourite retail segment is 'At Risk'.
--- Session metadata ---
id=sdklabs-75e6b8f216d0451b metadata retrieved
会话 ID 每次运行都是随机的,所以你的会话 ID 会不同。重要的证明不是值 ID — 而是第 2 转在第一次会话关闭后记得 'At Risk'。
这次运行证明了在一个进程中执行resume-after-disposal。它本身,并不能证明跨设备交接或重启恢复。
步骤 3 — 为会话分配一个 ID¶
打开 sessions_sample.py. 第一步传递了一个明确的 session_id:
session_id = f"sdklabs-{uuid.uuid4().hex}"[:24]
session = await client.create_session(
session_id=session_id,
model=model_id,
streaming=False,
)
async with session:
await send_and_print(
session,
"Remember this: my favourite retail segment is 'At Risk'. Reply with just OK.",
)
这个 ID 是本实验中的所有其他内容的关键。没有它,SDK 仍然会创建一个会话,但你无法回来。
该 async with session: 代码块随后关闭会话。下一轮并非继续使用仍处于活动状态的对象,而是真正恢复一个已关闭的会话。
第 4 步 — 恢复会话¶
第二步使用相同的 id 和一个不同的 SDK 调用:
resumed = await client.resume_session(session_id, model=model_id, streaming=False)
async with resumed:
await send_and_print(resumed, "Which retail segment did I say was my favourite?")
💡 这比 .NET 的等效方式更简单。 在 C# 中,您必须构造一个 ResumeSessionConfig 并将其作为必需的第二个参数传递;省略它会导致编译错误。Python 没有 ResumeSessionConfig;它接受与普通关键字参数相同设置,且 session_id 是唯一的参数位置之一:
await client.resume_session(session_id) # valid
await client.resume_session(session_id, model="gpt-5") # valid
await client.resume_session(session_id, streaming=False) # valid
⚠️ session_id 是位置参数,但在 create 中是关键字参数。 请注意
不对称性: create_session(session_id=...) 与
resume_session(session_id)在会话ID之后的所有内容都是
resume_session 关键字参数。
To加强证明,运行resume路径在一个第二个进程中,使用第一轮运行打印的id:
这确实跨越了一个流程边界。此外,从另一台机器恢复还需要访问相同的会话持久化存储、兼容的运行时和适当的授权。
步骤 5 — 发现存储的会话¶
The sample 也向 SDK 请求元数据:
返回 SessionMetadata | None — 当没有存储会话匹配时为 None,因此在使用前请先检查:
要浏览存储的会话而不是从已知的 id 开始,使用list_sessions:
一个常见的模式是:
- 列出已登录用户的会话
- 让用户选择一个,或者选择最近的
- 将该 id 传递给
resume_session(id, ...)
步骤 6 — 连接到架构¶
会话持久化支持:
- 跨设备和客户端的交接 — 从一处开始,继续在另一处
- 回滚 — 重启后恢复运行状态,而非从头开始重建上下文
- 可审计性 — 稳定的ID使得审查、组织和追踪对话变得更加容易
在今天的演示应用中,浏览器将聊天历史保存在localStorage(参见app.js)。
SDK 会话是解决办法。UI 可以存储一个会话 ID 而不是整个对话,一个使用相同会话存储的授权客户端可以恢复相同的服务器端会话。
⚠️ 一个会话ID不是访问控制。 将 ID 视为标识符,而不是密钥或能力。你的应用在恢复存储的对话之前,仍然需要正常的用户认证和授权。
Python 技术路线使用 PyPI github-copilot-sdk 1.0.9导入为 copilot,并要求 Python 3.11 或更高版本。你可以在以下文件中查看这些要求: pyproject.toml.
⚠️ 捕捉¶
- Id冲突: 会话ID是你唯一的标识符。重复使用ID会恢复旧对话,而不是创建一个新的。
- 不透明 ID: 随机 id 适用于演示,但真实系统应该能够将 id 映射回用户、案例或工作流。
- ID 不是权限:知道或猜测一个ID并不足以访问一个对话;应单独强制授权。
- 密钥数据在 ids 中: 从不将令牌、电子邮件地址或客户详情放入会话 id 中。
- 假设存在元数据:
get_session_metadata返回None对于未知的 id; 在解引用它之前添加保护。 - 忘记关闭: 该示例使用
async with所以在转轮2恢复之前,确保转轮1确实关闭了。跳过这一点,你就无法证明任何东西了。
💡 拓展练习¶
- 在元数据调用之后添加一个第三轮,再次使用相同的
'At Risk'消息提出另一个问题 - 运行
--resume路径来自第二个终端以证明跨进程恢复 - 列出并恢复最近的会话:
sessions = await client.list_sessions()
if sessions:
resumed = await client.resume_session(sessions[0].id, model=model_id)
- 将 id 改为一个稳定的字符串,例如
"analyst-demo",观察到重新运行示例现在继续一个长活的对话
✅ 检查点¶
您可以现在解释:
- [x] 为什么重启流程会丢失上下文而没有持久化
- [x] 如何通过
create_session(session_id=...)为你的应用提供一个稳定的键 - [x] 那
resume_session(id, ...)使用Python接受普通的关键字参数,无需.NET所需的单独配置对象 - [x] 如何通过
get_session_metadata和list_sessions发现存储的会话,并且这些元数据可以为None - [x] SDK会话是跨设备聊天历史的正确基础
- [x] 为什么跨进程和跨设备的恢复也依赖于共享存储、兼容运行时行为以及授权
相关¶
- 上一步: Lab 04 — 事件
- 下一步: 实验 06 — MCP
- 演示: Copilot SDK 集成
- 解决方法