diff --git a/research-wiki/design/0008-tool-handlers.md b/research-wiki/design/0008-tool-handlers.md index 24a8490..91e1f17 100644 --- a/research-wiki/design/0008-tool-handlers.md +++ b/research-wiki/design/0008-tool-handlers.md @@ -194,8 +194,21 @@ outputBytes / maxLines / maxBytes` 这一组**绝对量**加两个布尔,不 **停机不是截断**——本库把它落成了停止判定 B 档的 `context_overflow`。也就是说本库这条主路上 根本不该发生截断,`observation_truncated_chars` 恒为 0 不是缺口,是这条路径的真实情况。 +**主流框架没有一家把截断量做成工具返回值上的字段。** Anthropic 的 `tool_result` 内容块只有 +`tool_use_id` / `content` / `is_error` 三个字段;OpenAI Agents SDK 的三个输出类型、LlamaIndex +的 `ToolOutput`、Pydantic AI 的 `ToolReturn` 都没有截断字段。唯一把「截了多少」结构化记下来的 +是 LangChain 的 shell 中间件,而它走的是通用的 `artifact` 通道(一个随便装什么的字典),不是 +为截断专设的字段。截断本身也都发生在工具实现或宿主那一层,不在循环框架里。 + ## 留给后续的 **真出现一个要报截断量的消费者时,加的应该是一个新的返回类型,不是往 `str` 上打补丁。** 那时 把 `ToolHandler` 的返回改成联合类型(`str | ToolOutput`)对已有实现是兼容变更,而字段形状按 那个消费者真实要的来定,不按今天猜的来定。 + +这条路有现成的正反两例。**Pydantic AI 就是这么做的**:它在允许返回的类型集合里加了一个 +`ToolReturn`,旧的「返回任意对象」那条路一点没动,所以没有兼容问题、也不需要迁移说明 +(PR #2060,随 v0.3.5 发布)。**反面是 OpenAI Agents SDK**:它在 v0.4.0 里改成对返回值做运行期 +形状嗅探,一个本意只是返回 `{"msg": "foobar"}` 的工具被当成图片输出转换掉,线上直接 400, +三天后不得不收紧规则、让不合形状的字典回落成 `str()`(PR #1898 → issue #1930 → PR #1965)。 +区别在于加一个显式类型还是去猜一个已有类型的意图——**将来加这个能力时走前一条**。 diff --git a/research-wiki/design/0009-keyword-only-public-types.md b/research-wiki/design/0009-keyword-only-public-types.md index 392fc68..ff3e874 100644 --- a/research-wiki/design/0009-keyword-only-public-types.md +++ b/research-wiki/design/0009-keyword-only-public-types.md @@ -37,6 +37,32 @@ PolyGateway 是这个实验室里唯一另一个「库」形态的项目,它 PolyLoop 还没有任何下游装上,这个约束现在可以不长出来。 +## 库外面也有人踩到同一处,做法几乎一样 + +Pydantic AI 在 2026 年 7 月加了一条 meta-test,扫描整个包、对**新增的**公共数据类强制只收 +关键字参数,超过一个位置参数就失败(PR #6458)。那份 PR 正文里有两句话正好对上本文的两条 +论证: + +> "Pretty much all plain dataclasses need `_: KW_ONLY`!" — the most-repeated unenforced review nit. + +(一条老在评审里被提、却没有机器执行的意见——所以他们把它写成了检查。) + +> It deliberately does **not** add `_: KW_ONLY` to any existing dataclass — that would break +> positional callers. The allowlist only ever shrinks; its drain path is a major version. + +(不给已有的数据类补,因为那会打断按位置构造的调用方;白名单只减不增,清空它得发一个新 +major。) + +它已经付了本文想避开的那笔账:存量类型只能进白名单、只能等下一个 major 才清得掉。本库现在 +一个存量调用方都没有,所以不需要白名单,也不需要弃用期。 + +## 一个已知的连带影响 + +带 `kw_only` 的字段不进 `__match_args__`(Python 官方文档 `dataclasses` 一节写明),也就是说 +结构化模式匹配里那种按位置解构的写法(`case ToolSpec(name, description)`)不成立,要写成按 +关键字的(`case ToolSpec(name=name)`)。本库现在没有一处按位置解构公共类型,接受这个影响; +它和位置构造是同一件事的两面,留着其中一面就等于留着字段顺序这份承诺。 + ## 决策:`src/polyloop/` 下每一个数据类都加 `kw_only=True` 包括三个内部模块里的。一条规则不留判断余地,也才写得成机器检查。