跳至内容

实验 05 — 会话

目标: 为一个对话赋予稳定的标识,使其能够经受重启过程,并理解会话持久化能为你带来什么以及不能带来什么。

时间: ~20分钟

前置条件: 实验室 04 完成。

步骤 1 — 问题

到目前为止,所有操作都是无状态的。每次运行都会创建一个新的会话,发送一个提示,并将上下文丢弃。重启过程,模型完全不知道你在聊什么。

那对于一个一次性示例来说是合适的,但对一个真正的助手来说毫无用处。一个零售分析师提出三个后续问题,期望第四个问题仍然围绕同一个客户。

步骤 2 — 运行会话示例

cd src/AgentOrchestrator-python
uv run python -m sdk_labs sessions

添加 --model <id> 来覆盖模型:

uv run python -m sdk_labs sessions --model gpt-5-mini

验证输出:

== 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:

uv run python -m sdk_labs sessions --resume sdklabs-75e6b8f216d0451b

这确实跨越了一个流程边界。此外,从另一台机器恢复还需要访问相同的会话持久化存储、兼容的运行时和适当的授权。

步骤 5 — 发现存储的会话

The sample 也向 SDK 请求元数据:

metadata = await client.get_session_metadata(session_id)

返回 SessionMetadata | None — 当没有存储会话匹配时为 None,因此在使用前请先检查:

print(
    "  (no metadata returned)"
    if metadata is None
    else f"  id={session_id} metadata retrieved"
)

要浏览存储的会话而不是从已知的 id 开始,使用list_sessions

sessions = await client.list_sessions()          # -> list[SessionMetadata]

一个常见的模式是:

  1. 列出已登录用户的会话
  2. 让用户选择一个,或者选择最近的
  3. 将该 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确实关闭了。跳过这一点,你就无法证明任何东西了。

💡 拓展练习

  1. 在元数据调用之后添加一个第三轮,再次使用相同的 'At Risk' 消息提出另一个问题
  2. 运行--resume路径来自第二个终端以证明跨进程恢复
  3. 列出并恢复最近的会话:
sessions = await client.list_sessions()
if sessions:
    resumed = await client.resume_session(sessions[0].id, model=model_id)
  1. 将 id 改为一个稳定的字符串,例如 "analyst-demo",观察到重新运行示例现在继续一个长活的对话

✅ 检查点

您可以现在解释:

  • [x] 为什么重启流程会丢失上下文而没有持久化
  • [x] 如何通过 create_session(session_id=...) 为你的应用提供一个稳定的键
  • [x] 那 resume_session(id, ...) 使用Python接受普通的关键字参数,无需.NET所需的单独配置对象
  • [x] 如何通过 get_session_metadatalist_sessions 发现存储的会话,并且这些元数据可以为 None
  • [x] SDK会话是跨设备聊天历史的正确基础
  • [x] 为什么跨进程和跨设备的恢复也依赖于共享存储、兼容运行时行为以及授权