实验 04 — 事件¶
目标: 了解 Copilot SDK 会话事件生命周期:SDK 实际发出哪些事件、事件到达顺序,以及哪些事件对流式传输、遥测、完成和错误等常见工作最重要。
时间: 约 20 分钟
先决条件: 实验 03 已完成。
步骤 1 — 执行事件示例¶
在仓库根目录执行 SDK 实验示例:
预期输出:
== Lab 04: events ==
Model: claude-haiku-4.5
Prompt: Name two retail KPIs. One line each.
1. SessionStartEvent
2. SessionManagedSettingsResolvedEvent
3. PendingMessagesModifiedEvent
4. SessionSkillsLoadedEvent
5. SystemMessageEvent
6. SessionToolsUpdatedEvent
7. UserMessageEvent
8. HookStartEvent
9. SessionTitleChangedEvent
10. HookEndEvent
11. AssistantTurnStartEvent
12. SessionUsageInfoEvent
13. ModelCallStartEvent
14. AssistantStreamingDeltaEvent
15. AssistantReasoningDeltaEvent
16. AssistantStreamingDeltaEvent
17. AssistantReasoningDeltaEvent
18. AssistantStreamingDeltaEvent
19. AssistantReasoningDeltaEvent
20. AssistantStreamingDeltaEvent
21. AssistantReasoningDeltaEvent
22. AssistantStreamingDeltaEvent
23. AssistantReasoningDeltaEvent
24. AssistantStreamingDeltaEvent
25. AssistantReasoningDeltaEvent
26. AssistantStreamingDeltaEvent
27. AssistantMessageStartEvent
28. AssistantStreamingDeltaEvent
29. AssistantStreamingDeltaEvent
30. AssistantUsageEvent
31. AssistantMessageEvent
content: 1. **Conversion Rate** — The percentage of store visitors or website traffic tha…
(preceded by 3 delta events)
32. AssistantReasoningEvent
33. AssistantTurnEndEvent
34. HookStartEvent
35. HookEndEvent
36. SessionUsageCheckpointEvent
37. AssistantIdleEvent
38. SessionIdleEvent
Total delta events: 3
最令人意外的是事件数量。一次简单的单一提示交换,所发出的事件远超过「用户消息、助手消息、完成」。
⚠️ 示例会单独统计 AssistantMessageDeltaEvent,并在打印时隐藏这些事件。因此,即使事件列表中出现了许多 AssistantStreamingDeltaEvent,仍会显示 Total delta events: 3。
⚠️ 上述数字只是一次实际运行的观察结果,并非固定契约。 列表打印了 38 个事件;另有 3 个事件已收到但未打印,因此总计收到 41 个事件。确切数量和顺序会随模型、提示词和 SDK 版本而变化。请关注序列的整体结构,不要将其视为固定规范。
步骤 2 — 逐一了解生命周期阶段¶
如果按阶段分组,更容易记住事件流:
- 会话设置(1–6) — 会话启动、配置解析完成、待处理消息和技能完成加载、系统消息出现,并通过
SessionToolsUpdatedEvent公布工具 - 用户轮次(7–10) — 接受用户消息、钩子事件在流程内执行,而且会话标题可能变更
- 助手轮次开始(11–13) — 助手轮次开始、用量信息出现,模型调用随即开始
- 流式传输(14–29) — 流与推理差异陆续抵达,接着是
AssistantMessageStartEvent - 完成(30–33) — 系统报告助手用量,最终助手消息和推理结果到达,随后助手轮次结束
- 结束与闲置(34–38) — 另一组钩子会执行、用量创建检查点、助手进入闲置状态,最后整个会话进入闲置状态
钩子事件属于同一个有序流。如果您正在探索治理钩子,此顺序很重要,因为 HookStartEvent 以及
HookEndEvent 会出现在会话流程中,而不是独立的旁路通道中。请参阅 额外内容 — 治理钩子 以及
钩子与治理 获取相关示范数据。
使用量也有自己的事件: SessionUsageInfoEvent,
AssistantUsageEvent,以及 SessionUsageCheckpointEvent。当您要获取令牌与成本遥测,而不是文本内容时,应查看这些事件。
步骤 3 — 使用 v1 模式订阅¶
打开
EventsSample.cs
并找到订阅位置:
⚠️ 明确的 <SessionEvent> 很重要。 在 GitHub Copilot SDK v1.x 中,非泛型形式已不再推断类型参数:
这种旧版 v0.x 写法在 v1.x 中会触发 CS0411。如果您复制旧示例后看到该编译错误,请添加明确的类型参数。
迁移旧代码时,也请检查命名空间。SDK v1.0.0 已从
GitHub.Copilot.SDK 变更:
步骤 4 — 与应用程序处理的内容比较¶
示例几乎会记录所有事件,让您能学习生命周期。实际应用程序不需要处理所有事件。
打开
CopilotChatService.cs
并查看事件 switch。它只处理四种事件类型:
case AssistantMessageDeltaEvent delta:
outputChannel.Writer.TryWrite(delta.Data.DeltaContent ?? "");
break;
case AssistantMessageEvent msg:
_logger.LogInformation("Assistant response complete: {Length} chars",
msg.Data.Content?.Length ?? 0);
break;
case SessionIdleEvent:
done.SetResult();
break;
case SessionErrorEvent error:
done.SetException(new Exception(error.Data.Message));
break;
这是一个合理的生产环境选择。对于浏览器流式传输而言,应用程序需要文本块、最终消息记录、完成信号与错误路径;不需要针对每个设置、钩子、推理或遥测事件进行分支处理。
⚠️ 存在两类不同的增量事件:
AssistantStreamingDeltaEvent 以及 AssistantMessageDeltaEvent。在这次执行中,许多 AssistantStreamingDeltaEvent 项目出现,但示例只计算了两个 AssistantMessageDeltaEvent 值。如果您针对工作订阅了错误的事件类型,看到的块可能远少于预期。请优先测量您场景实际发出的内容,不要只根据名称假设两者意义相同。
步骤 5 — 空闲时完成,出错时失败¶
SessionIdleEvent 是示例与应用程序使用的完成信号。常见模式是使用 TaskCompletionSource ,并在会话进入闲置状态时完成:
var done = new TaskCompletionSource();
session.On<SessionEvent>(evt =>
{
switch (evt)
{
case SessionIdleEvent:
done.TrySetResult();
break;
case SessionErrorEvent error:
done.TrySetException(new Exception(error.Data.Message));
break;
}
});
await session.SendAsync(new MessageOptions { Prompt = prompt });
await done.Task;
⚠️ 务必处理 SessionErrorEvent。若会话失败,而您未在以下项目设置异常: TaskCompletionSource, await done.Task 可能会永远等待下去。这种失败模式很容易被忽略,因为正常路径运行得非常顺利。
⚠️ 常见陷阱¶
session.On(evt => ...)是旧结构;请改用session.On<SessionEvent>(evt => ...)搭配 SDK v1.x- 使用以下项目的旧命名空间:
GitHub.Copilot.SDK必须变成GitHub.Copilot AssistantStreamingDeltaEvent以及AssistantMessageDeltaEvent是不同的事件类型;请先测量,不要将它们视为可以互换- 记录每个事件有助于学习,但对应用程序代码来说过于嘈杂
- 仅仅等待助手的消息是不够的;请在以下事件发生时完成操作:
SessionIdleEvent - 忽略
SessionErrorEvent可能会使调用端永远处于等待状态
💡 加分练习¶
请尝试以下一项小型演示:
- 变更示例,只打印工具相关事件,例如名称包含以下内容的事件:
Tool或工具周围的工作流钩子事件 - 如要测量收到第一个令牌的时间,请先启动
Stopwatch之前SendAsync(...),并在第一个您关注的增量事件出现时停止计时 - 分别计算与使用量相关的事件,并记录它们在生命周期中的位置
- 添加筛选器,将事件按上述六个生命周期阶段分组,而不是打印扁平的编号列表
✅ 检查点¶
您现在可以说明:
- [x] 了解 SDK 发出的有序会话生命周期
- [x] 为何
On<SessionEvent>在 v1.x 中需要明确的类型参数 - [x] 了解为何实际应用程序通常只处理所有发出事件中的一小部分
- [x] 了解观察所有事件与流式传输有用部分之间的差异
- [x] 为何
SessionIdleEvent会完成操作 - [x] 为何
SessionErrorEvent必须让等待中的任务失败 - [x] 了解钩子与使用量事件在生命周期中的位置
相关内容¶
- 上一步: 实验 03 — 工具
- 下一步: 实验 05 — 会话
- 演示:Copilot SDK 整合
- 钩子与治理
- 故障排除