同步行为与时序
精确说明上下文更新何时到达角色、对话开始前会发生什么,以及 SDK 如何决定发送什么内容。
动态上下文更新如何传输以及何时传输
理解 Dynamic Context 的更新何时被传输,以及以何种形式被传输,可以避免最常见的集成意外。SDK 并不会把每次更新都作为单个 Append 消息发送。根据 tracker 中已经存在的状态,它可能会发送两条消息,也可能会将更新排队,直到对话开始。本页将详细说明全部四种同步场景、对话前队列,以及规范上下文格式。
本页面面向正在调试集成行为、优化更新频率,或将 Dynamic Context 与外部系统集成的开发者。初学者可以先跳过,直到遇到意外的字符行为再回来查看。
四种同步场景
同步行为取决于被更新的状态是否已经存在于 tracker 中。这些场景适用于活动对话期间。对话前行为将在下一节中说明。
场景 1 — 新状态已添加
当 SetState("Name", "Value") 被调用,且 "Name" 在 tracker 中尚不存在:
会发送一条 Append 消息。角色接收到新事实,并将其纳入自身感知。
场景 2 — 已有状态发生变化
当 SetState("Name", "NewValue") 被调用,且 "Name" 已经存在,但其值 不同 :
会发送两条消息: 先发送一个携带完整规范上下文的 Replace(其中已更新的值就地替换),再发送一个携带人类可读差异说明的 Append。Replace 为角色提供权威、完整的视图;Append 则用清晰可读的方式说明具体发生了什么变化,从而让它在对话中能够自然地提及这一过渡。
这种双消息模式是有意设计且不可配置的。如果你在调试时监控网络流量,遇到已有状态被修改时,请预期会有两条 上下文更新 消息。
场景 3 — 状态被移除
当 RemoveState("Name") 被调用:
会发送一条 Replace 消息,携带完整的规范上下文,但已移除的状态会被排除在外。不会有差异 Append——该状态的缺失从 Replace 载荷中即可一目了然。
场景 4 — 批量 SetStates,且包含新旧混合状态
当 SetStates(dict) 被调用,且字典中至少包含一个已经存在于 tracker 中的状态(无论其值为何):
Replace + Append,其结构与场景 2 相同,但 Append 会将批处理中的所有变更汇总到一条消息里。如果字典中的所有状态都是新的(在 tracker 中都尚不存在),则只会发送一条 Append。
对话前队列
所有受跟踪的方法—— SetState, SetStates, AddEvent, RemoveState,以及 Reset ——在角色不处于活动对话时,会自动将其效果排队。对话开始时,队列会被刷新。
队列机制
每当在对话开始前对受跟踪的状态或事件进行更新时,SDK 都会记录一个待同步项。对话开始时,会发送一条 Replace 消息,其中包含当时完整的规范上下文——而不是一系列单独的更新。
当 Reset() 如果在对话开始前被调用,则会记录一个待重置项,并覆盖任何待同步项。对话开始时,角色接收到的是 Reset 消息,而不是 Replace。
优先级规则:
调用
Reset()会取消任何待同步项。随后SetState()在Reset()会将队列恢复为待同步状态。只会发送在对话开始时 tracker 的最终状态。多次
SetState调用在对话开始前只会产生一条包含最终值的 Replace,而不会是一系列递增消息。
Apply() 不会排队。 如果 Apply() 如果在对话开始前被调用,更新会被静默丢弃。如果你需要让对话开始前的设置保留到会话启动,请使用受跟踪的方法。
规范上下文格式
规范上下文字符串是在某一时刻对所有受跟踪状态和事件的完整、权威的文本表示。它用于 Replace 消息以及对话前的刷新。
格式:
规则:
状态最先出现,按 插入顺序 排列——也就是每个状态名称首次引入的顺序。就地更新已有状态会保持其位置不变。
事件在所有状态之后出现,按 时间顺序 排列——也就是
AddEvent被调用的顺序。每个状态都按
"{Name} 是 {Value}"的形式格式化,每行一个。每个事件都是单独一行,没有前缀。
示例:
在以下调用序列之后:
规范上下文是:
"Station" 即使 "Hazard Level" 在其之后被更新,其插入位置仍保持不变。事件则会按添加顺序保留。
Apply() 与 Tracker 边界
Apply(ConvaiDynamicContextUpdate update) 是唯一一个完全绕过 tracker 的方法。它会将 update 中的内容直接发送到 transport,而不会重建规范上下文,也不会记录本地状态。
后果:
TryGetStateValue不会被Apply()调用所更新。如果你发送Apply(new ConvaiDynamicContextUpdate("Score is 95", ConvaiContextUpdateMode.Append)),查询TryGetStateValue("Score", out _)返回false.随后由
SetState调用触发的规范同步不会包含任何通过Apply().Apply()发送的内容。如果角色不在对话中,它是一个静默的无操作——不会排队。
使用 Apply() 仅在你确实需要绕过 tracker 时才使用,例如从外部评分系统发送后端格式化的文本块,或强制进行一种与受跟踪状态相互独立的模式-Reset 更新。
传输层参考
Dynamic Context 更新通过 WebRTC 数据通道以 RTVI 消息的形式传输。
type
"context-update"
固定的消息类型标识符。
data.text
任意字符串,或省略
上下文文本载荷。当 mode 为 重置.
data.mode
"append", "replace", "reset"
对应 ConvaiContextUpdateMode.
data.run_llm
"auto", "true", "false"
对应 ConvaiContextReactionMode. Auto → "auto", 立即响应 → "true", 仅同步 → "false".
此信息提供给正在检查网络流量或构建自定义传输集成的开发者。在正常使用 SDK 时无需执行任何操作——从类型化 C# 值到 JSON 的映射由 SDK 自动处理。
接下来做什么
故障排除与诊断 ——用于诊断当更新未产生预期角色响应时的异常行为。
结论
SDK 的同步逻辑——新状态使用 Append,已更改状态使用 Replace 然后 Append,移除状态使用 Replace——确保角色始终接收到对世界的权威视图。理解这些模式可以让集成行为变得可预测,调试也更直接。如果你遇到意外行为,请参阅 故障排除与诊断.
最后更新于
这有帮助吗?