docs: 回写 CHANGELOG、GovDoc 迁移,以及压测那条过期注释
CHANGELOG 落在「未发布」段,按那一段自己的规矩不提前写版本号。写清了四条会抛 ValueError 的 情形与迁移写法,因为这是一次破坏性变更。 migrations/govdoc-saas.md 加一节讲租户命名空间怎么传,含迁完算不算数的四条判据,最终判据是 「同一段文本由两个租户各提交一次,各自拿到自己的那份输出」。参数一律指向 0017 不复述。这份 文档原来说 GovDoc 侧「还没有可迁移的东西」,现在有了第一条能逐条验的接入动作,文件头那句 状态说明跟着补了一句例外。 tools/soak/run_soak.py 那份空绑定上方的注释在复述旧转发规则,顺手改对。它给的理由本来就不准 ——让绑定留空的真正原因是绑定的全部键值都进参数快照,每批都不同的值会让故障注入那一步续跑时 报一次假的参数漂移,和转发哪些键无关。三份压测绑定常量里都没有裸键,所以这次变更打不到压测。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,39 @@
|
|||||||
长期停在 1.0.5,下游 `pip install` 拿不到任何修复且无人发现。提前把号写进这一段就是在重演
|
长期停在 1.0.5,下游 `pip install` 拿不到任何修复且无人发现。提前把号写进这一段就是在重演
|
||||||
那个形态——读到号的人会以为那一版已经在 registry 上,而它不在。
|
那个形态——读到号的人会以为那一版已经在 registry 上,而它不在。
|
||||||
|
|
||||||
|
### 网关适配器改按保留前缀转发绑定,而不是按键名撞
|
||||||
|
|
||||||
|
**这是一次破坏性的行为变更。** 绑定里不带前缀的 `session_id` 与 `parent_call_id` 从「静默转发
|
||||||
|
给网关」变成「抛 `ValueError` 并给出改法」,改法是把键名写成 `gateway.session_id`。
|
||||||
|
|
||||||
|
```python
|
||||||
|
model_binding={
|
||||||
|
"book": "b7", # 项目自己的坐标,不传
|
||||||
|
"gateway.cache_namespace": "acme:v1:tenant:x7", # 传成 chat(cache_namespace=...)
|
||||||
|
"gateway.tenant_id": "x7",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
绑定里键名以 `gateway.` 开头的,前缀之后那一段当作网关 `chat()` 的关键字参数名,值原样传下去;
|
||||||
|
其余的键照旧不传、也不报错。**本库不再持有一份网关参数名单**——上游哪天再加一个字符串维度,
|
||||||
|
下游当天就能用,本库不发版,声明的依赖下界也不用跟着抬。
|
||||||
|
|
||||||
|
原来那份写死的白名单只认两个键,把 `cache_namespace` 挡在外面,而那是 PolyGateway 指定的租户
|
||||||
|
隔离手段:挡掉之后,两个租户提交内容相同的一段文字,第二个会读到第一个那次的模型输出。给出
|
||||||
|
那份白名单的理由是「网关只有两个槽位放得下这类东西」,而这句话在写下的那天就已经不成立——
|
||||||
|
当时装得到的最低版本上已经有四个。
|
||||||
|
|
||||||
|
四条会抛 `ValueError` 的情形:带前缀的键名指向 `messages`、`stream`、`structured`、`overlay`
|
||||||
|
之一(这些改变请求本身,另有权威);带前缀的键取值是空串或纯空白(在网关那边和「没传」分不
|
||||||
|
开,或者不带任何信息,而一个看着配了、实际没配的隔离比没配更糟);键恰好是 `gateway.`、前缀
|
||||||
|
后面没跟参数名;以及不带前缀的那两个历史裸键。
|
||||||
|
判断发生在把请求交给网关之前,所以出错的那次调用不会真的发出去。
|
||||||
|
|
||||||
|
转发的键照旧全部进参数快照,键名不变(`request.binding.gateway.cache_namespace`),所以换一个
|
||||||
|
命名空间续跑会照常撞参数漂移。
|
||||||
|
|
||||||
|
方案与被否掉的三条路见 `research-wiki/design/0017-gateway-forwarding.md`。
|
||||||
|
|
||||||
## 1.0.2(2026-08-27)
|
## 1.0.2(2026-08-27)
|
||||||
|
|
||||||
回应下游项目在 PolyLoop 仓库上提的五个 issue。
|
回应下游项目在 PolyLoop 仓库上提的五个 issue。
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
> 来源与缺口清单。
|
> 来源与缺口清单。
|
||||||
>
|
>
|
||||||
> **当前状态:这不是一份迁移清单,是一份需求清单。** GovDoc-SaaS 的 agent 部分还没有可迁移
|
> **当前状态:这不是一份迁移清单,是一份需求清单。** GovDoc-SaaS 的 agent 部分还没有可迁移
|
||||||
> 的东西,本文的作用是防止 PolyLoop 只按 dissect 一家的形状长。
|
> 的东西,本文的作用是防止 PolyLoop 只按 dissect 一家的形状长。唯一已经能逐条验的接入动作是
|
||||||
|
> 租户隔离参数那一节。
|
||||||
|
|
||||||
PolyLoop 只有 dissect 一个硬消费者(见 `dissect.md`)。只对着一个消费者做,做出来的库会长成
|
PolyLoop 只有 dissect 一个硬消费者(见 `dissect.md`)。只对着一个消费者做,做出来的库会长成
|
||||||
那个消费者的形状,而这件事在完成之前看不出来。GovDoc 这边不做迁移验收,改做**设计级验收**:
|
那个消费者的形状,而这件事在完成之前看不出来。GovDoc 这边不做迁移验收,改做**设计级验收**:
|
||||||
@@ -141,6 +142,59 @@ Codex 对抗审查抓出来了,修法见 `../design/0004-stopping-and-step-rec
|
|||||||
显式注入,重试对它们就静默失效」——那是 PolyGateway 装配的问题,要在装配点解决,不是在
|
显式注入,重试对它们就静默失效」——那是 PolyGateway 装配的问题,要在装配点解决,不是在
|
||||||
Agent 层补一层。
|
Agent 层补一层。
|
||||||
|
|
||||||
|
## 租户隔离参数怎么传
|
||||||
|
|
||||||
|
**绑定**是挂在一次运行请求上的一组字符串到字符串的映射,装的是下游项目自己的坐标。PolyLoop
|
||||||
|
不解释它的内容,原样交给模型调用接缝;自带的网关适配器再从里面挑出该往 PolyGateway 那次调用
|
||||||
|
上传的键。
|
||||||
|
|
||||||
|
GovDoc 在 PolyLoop 仓库提的 issue #6 说的是:适配器挑键的老办法把租户命名空间挡在外面,而那
|
||||||
|
是 PolyGateway 指定的租户隔离手段,挡掉之后两个租户提交相同的一段文本时,第二个会读到第一个
|
||||||
|
那次的模型输出。`../design/0017-gateway-forwarding.md` 决策一换掉了挑键的机制——**绑定里键名
|
||||||
|
以 `gateway.` 开头的才往下传,前缀之后那一段当作网关 `chat()` 的关键字参数名**。
|
||||||
|
|
||||||
|
### 要改的地方
|
||||||
|
|
||||||
|
租户命名空间与租户标识都写成带前缀的绑定键,每次运行按这次运行属于哪个租户填值:
|
||||||
|
|
||||||
|
```python
|
||||||
|
model_binding = {
|
||||||
|
"case": "...", # GovDoc 自己的坐标,不带前缀,不往下传
|
||||||
|
"gateway.cache_namespace": ..., # 这次运行所属租户的命名空间
|
||||||
|
"gateway.tenant_id": ..., # 同一个租户的标识
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
两个值的字符串怎么编码由 GovDoc 自己定(它的设计记录里已经定死),PolyLoop 不解释这两个字符串
|
||||||
|
的内容,也不替它们拼任何前后缀。这两个网关参数各自是什么语义、以及租户坐标为什么走绑定而不是
|
||||||
|
走适配器的构造参数,见 `0017` 的术语节与决策三。
|
||||||
|
|
||||||
|
**issue 里提的第三个维度 `meta` 本库不做**,理由在 `0017` 决策五。它原本要带的那个自己的 trace
|
||||||
|
标识改走 `gateway.session_id`:网关那边这个槽位本来就是一个用来分组的字符串,而 PolyLoop 不往
|
||||||
|
里面填任何东西,它一直留给下游自己写。
|
||||||
|
|
||||||
|
**绑定里现有的不带前缀的 `session_id` 与 `parent_call_id` 要改成带前缀的写法。** 这两个键在旧
|
||||||
|
机制下会被转发,`0017` 之后不再转发,而且会被适配器显式拒绝——用一次报错换掉一次静默的行为
|
||||||
|
变更,理由在 `0017` 决策四的第四条防御。拒绝发生在第一次模型调用时,形态是一次「第 0 步就以
|
||||||
|
模型调用失败收尾」的运行,失败说明里带着出问题的那个键名,没有预算被花掉。落地在哪个版本、
|
||||||
|
以及网关依赖下界要不要跟着动,见 `0017`。
|
||||||
|
|
||||||
|
### 迁完算不算数
|
||||||
|
|
||||||
|
**绑定里再没有不带前缀的 `session_id` 或 `parent_call_id`。** 这条装上新版就自己验了:有残留
|
||||||
|
的话,用到那份绑定的第一次运行会在第一次模型调用上失败。
|
||||||
|
|
||||||
|
**每一次运行的绑定里都带着这次运行所属租户的命名空间键,值不是空串。** 空串会被适配器拒绝
|
||||||
|
(`0017` 决策四的第二条防御)。查法是拿一次真实运行的运行开始记录看参数快照:带前缀的键照旧
|
||||||
|
逐字段进快照,所以「这次运行用的是哪个命名空间」事后查得到。
|
||||||
|
|
||||||
|
**同一段文本由两个租户各提交一次,各自拿到自己的那份模型输出。** issue #6 的失败场景不再复现
|
||||||
|
——这是最终判据,前面两条都是为它服务的。
|
||||||
|
|
||||||
|
**续跑一次运行时命名空间不变。** 命名空间随绑定进参数快照,而续跑开工前会把重算的快照与日志
|
||||||
|
里那份逐字段比对,换过命名空间就会被当成参数漂移中止。撞上这条说明续跑读到的缓存可能属于另一
|
||||||
|
个租户,中止是对的。
|
||||||
|
|
||||||
## 缺口登记
|
## 缺口登记
|
||||||
|
|
||||||
这些是 PolyLoop 现在答不上来的问题。答不上来不等于设计错了,但每一条都得有明确结论——
|
这些是 PolyLoop 现在答不上来的问题。答不上来不等于设计错了,但每一条都得有明确结论——
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ if TYPE_CHECKING:
|
|||||||
APPWORLD = "appworld"
|
APPWORLD = "appworld"
|
||||||
GOVDOC = "govdoc"
|
GOVDOC = "govdoc"
|
||||||
|
|
||||||
#: 传给每次模型调用的项目侧标识。**空的**:网关适配器只往下转发 `session_id` 与
|
#: 传给每次模型调用的项目侧标识。**空的**:绑定的全部键值都进参数快照,而每批都不同的值会让
|
||||||
#: `parent_call_id`,而这两样每批都不同;进了参数快照,故障注入那一步续跑时的逐字段比对
|
#: 故障注入那一步续跑时的逐字段比对报一次假的参数漂移。这里跟转发无关——网关适配器只转发带
|
||||||
#: 就会报一次假的参数漂移。
|
#: `gateway.` 前缀的键,而这份一个都没有。
|
||||||
MODEL_BINDING: Mapping[str, str] = {}
|
MODEL_BINDING: Mapping[str, str] = {}
|
||||||
|
|
||||||
#: GovDoc 的工作区落在 runs 目录下的这个子目录里。记分板枚举 run 用的是
|
#: GovDoc 的工作区落在 runs 目录下的这个子目录里。记分板枚举 run 用的是
|
||||||
|
|||||||
Reference in New Issue
Block a user