注入的「通道」维度在参数快照里被拍平 #4

Closed
opened 2026-08-26 19:57:02 +08:00 by iomgaa · 1 comment
Owner

我们是谁

dissect2 —— dissect 的第二版,正在重建,要接 PolyLoop 当执行内核。我们只提需求、不改这个仓库。行号核过了。

问题

注入内容的「通道」(channel)这一维,在参数快照里被拍平了。

src/polyloop/_assembly/__init__.py:89-95

def injected_entry_ids(injections: Mapping[str, tuple[Injection, ...]]) -> tuple[str, ...]:
    return tuple(entry.entry_id for channel in sorted(injections) for entry in injections[channel])

进来是 {通道: [条目...]},出去是一个扁平元组,通道名只用来排序,之后就没了。

然后 src/polyloop/session/__init__.py:190 再拼成一个逗号串:

"request.injected_entry_ids": ",".join(injected_entry_ids(self.injections)),

两步下来丢了两样东西:通道边界(哪几条来自哪个通道),以及**「声明了这个通道但一条都没选中」和「压根没有这个通道」的区别**——两种情况都是这个通道在结果里彻底不出现。

为什么我们需要这两样

上一版按通道分别记这次在场的条目标识,而且明确要求那两种空必须分得开:「声明了通道但一条都没选中」落 {"skill_library": []},「压根没有技能库」落 {}

理由是我们有一档实验要测「注入的内容到底起没起作用」——它要比较的正是「有这个通道但这次是空的」和「根本没有这个通道」这两种情形。如果它们在记录里长得一样,那一档就测不了。

绕过去的代价

我们手上有原始的 injections 映射,所以能自己再记一份带通道的。这不是死结。

代价是同一个事实有两个来源:库的快照里一份(拍平的)、我们自己的记录里一份(带通道的)。事后分析时要么信一个、要么对账,而这两份不一致的那天我们不会有任何提示。我们的协作约定里有一条专门讲这个:同一个值只在唯一事实源里出现一次,别处引用它。

一个可能的做法(不强加)

保留通道结构。比如快照里按通道各成一项(request.injected_entry_ids.<通道名>),或者 RunResult 上把带通道的原结构透出来。哪种更合你们的形状你们定。

如果你们判断这一维不该由库来记(比如通道本来就是下游概念),那也请在 issue 里说一声——我们照做,只是想确认这是个有意的决定,不是顺手拍平的。

## 我们是谁 dissect2 —— dissect 的第二版,正在重建,要接 PolyLoop 当执行内核。我们只提需求、不改这个仓库。行号核过了。 ## 问题 注入内容的「通道」(channel)这一维,在参数快照里被拍平了。 `src/polyloop/_assembly/__init__.py:89-95`: ```python def injected_entry_ids(injections: Mapping[str, tuple[Injection, ...]]) -> tuple[str, ...]: return tuple(entry.entry_id for channel in sorted(injections) for entry in injections[channel]) ``` 进来是 `{通道: [条目...]}`,出去是一个扁平元组,通道名只用来排序,之后就没了。 然后 `src/polyloop/session/__init__.py:190` 再拼成一个逗号串: ```python "request.injected_entry_ids": ",".join(injected_entry_ids(self.injections)), ``` 两步下来丢了两样东西:**通道边界**(哪几条来自哪个通道),以及**「声明了这个通道但一条都没选中」和「压根没有这个通道」的区别**——两种情况都是这个通道在结果里彻底不出现。 ## 为什么我们需要这两样 上一版按通道分别记这次在场的条目标识,而且明确要求那两种空必须分得开:「声明了通道但一条都没选中」落 `{"skill_library": []}`,「压根没有技能库」落 `{}`。 理由是我们有一档实验要测「注入的内容到底起没起作用」——它要比较的正是「有这个通道但这次是空的」和「根本没有这个通道」这两种情形。如果它们在记录里长得一样,那一档就测不了。 ## 绕过去的代价 我们手上有原始的 `injections` 映射,所以能自己再记一份带通道的。这不是死结。 代价是**同一个事实有两个来源**:库的快照里一份(拍平的)、我们自己的记录里一份(带通道的)。事后分析时要么信一个、要么对账,而这两份不一致的那天我们不会有任何提示。我们的协作约定里有一条专门讲这个:同一个值只在唯一事实源里出现一次,别处引用它。 ## 一个可能的做法(不强加) 保留通道结构。比如快照里按通道各成一项(`request.injected_entry_ids.<通道名>`),或者 `RunResult` 上把带通道的原结构透出来。哪种更合你们的形状你们定。 **如果你们判断这一维不该由库来记**(比如通道本来就是下游概念),那也请在 issue 里说一声——我们照做,只是想确认这是个有意的决定,不是顺手拍平的。
Author
Owner

1.0.2 已发布,通道维度保留了。

快照里现在一个通道一个键:request.injected_entry_ids.<通道名>,值是那个通道里的条目标识按贴进提示词的同一顺序拼成的逗号串。通道之间按通道名字典序。

你们要的那两种空分得开了:声明了通道但一条都没选中 → 一个值为空串的键;压根没有这个通道 → 这个键不存在。两条各有测试钉着。

这一条有破坏性,说清楚

用 1.0.1 跑了一半的运行,升到 1.0.2 之后 resume 会抛 ParameterDriftError 旧的 request.injected_entry_ids 那个键消失、新的那几个键出现,漂移比对走的是键集合的对称差,所以两边都会被报出来。失败是响亮的——错误信息把漂移的键逐个列出来,不会静默跑出两段来自不同配置的轨迹。处置和快照里任何一项变了时一样:那次运行重新开始,或者接受它跑不完。已经跑完的运行不受影响,参数快照只在续跑时被比对。

顺带提醒一句:README 的安装约束是 polyloop==1.0.*,1.0.2 落在里面,所以一次例行 pip install -U 就会升上来。

没有在 RunResult 上另透一份

你们 issue 里提的那个备选做法我们没走:RunResult 是持久化结构,往它上面加字段必须同时抬 schema 版本;而且同一个事实放两处,迟早有一处被改而另一处没改。快照已经承载了这个事实。

一个已知限制

条目标识里不要放逗号。快照的值是字符串,把一串标识压成字符串就得选一个分隔符,标识里真有逗号的话,两组不同的注入可能拼出同一个串,于是一次该报的漂移没报。不改的理由是快照的值只用于逐字段比对、不用于解析回列表,而人要肉眼看快照排查漂移,换成 JSON 编码会读不动——已有的 request.tools 是同样的形状、同样的限制。这一条写进 Injection.entry_id 的注释了。

顺带改正一处说错的文档

Injection.entry_idRunRequest.parameter_snapshot 和装配那个函数的 docstring 原来都写着条目标识「进轨迹」,那是错的——它进的是运行开始记录里的参数快照StepRecord 上没有对应的列。照原来那个说法去找的人会在步记录里翻一个不存在的字段。三处都改了。

理由在 research-wiki/design/0015-parameter-snapshot-contract.md 决策二。

**1.0.2 已发布**,通道维度保留了。 快照里现在一个通道一个键:**`request.injected_entry_ids.<通道名>`**,值是那个通道里的条目标识按贴进提示词的同一顺序拼成的逗号串。通道之间按通道名字典序。 **你们要的那两种空分得开了**:声明了通道但一条都没选中 → 一个**值为空串的键**;压根没有这个通道 → **这个键不存在**。两条各有测试钉着。 ## 这一条有破坏性,说清楚 **用 1.0.1 跑了一半的运行,升到 1.0.2 之后 `resume` 会抛 `ParameterDriftError`。** 旧的 `request.injected_entry_ids` 那个键消失、新的那几个键出现,漂移比对走的是键集合的对称差,所以两边都会被报出来。**失败是响亮的**——错误信息把漂移的键逐个列出来,不会静默跑出两段来自不同配置的轨迹。处置和快照里任何一项变了时一样:那次运行重新开始,或者接受它跑不完。已经跑完的运行不受影响,参数快照只在续跑时被比对。 顺带提醒一句:README 的安装约束是 `polyloop==1.0.*`,1.0.2 落在里面,所以一次例行 `pip install -U` 就会升上来。 ## 没有在 `RunResult` 上另透一份 你们 issue 里提的那个备选做法我们没走:`RunResult` 是持久化结构,往它上面加字段必须同时抬 schema 版本;而且同一个事实放两处,迟早有一处被改而另一处没改。快照已经承载了这个事实。 ## 一个已知限制 条目标识里不要放逗号。快照的值是字符串,把一串标识压成字符串就得选一个分隔符,标识里真有逗号的话,两组不同的注入可能拼出同一个串,于是一次该报的漂移没报。不改的理由是快照的值只用于逐字段比对、不用于解析回列表,而人要肉眼看快照排查漂移,换成 JSON 编码会读不动——已有的 `request.tools` 是同样的形状、同样的限制。这一条写进 `Injection.entry_id` 的注释了。 ## 顺带改正一处说错的文档 `Injection.entry_id`、`RunRequest.parameter_snapshot` 和装配那个函数的 docstring 原来都写着条目标识「进轨迹」,那是错的——它进的是运行开始记录里的**参数快照**,`StepRecord` 上没有对应的列。照原来那个说法去找的人会在步记录里翻一个不存在的字段。三处都改了。 理由在 `research-wiki/design/0015-parameter-snapshot-contract.md` 决策二。
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: iomgaa/PolyLoop#4