补齐 Anthropic 原生工具与三协议流式工具调用 #112

Merged
kdletters merged 42 commits from fix/anthropic_tool into codex/ai-game-creator-app 2026-07-28 13:33:50 +08:00
Owner

Anthropic 请求发送 tools 与对象形态 tool_choice 并解析 tool_use block,解除本地对 function tools 的拦截
Chat / Responses / Anthropic 三种协议的流式工具增量按槽位聚合,收尾校验参数为完整 JSON,截断流不返回半截参数
解除 App 侧 Anthropic 降级为文本 JSON 协议的两处守卫与对应提示词分支
新增依赖真实凭据的流式工具验收用例,默认 ignore

Anthropic 请求发送 tools 与对象形态 tool_choice 并解析 tool_use block,解除本地对 function tools 的拦截 Chat / Responses / Anthropic 三种协议的流式工具增量按槽位聚合,收尾校验参数为完整 JSON,截断流不返回半截参数 解除 App 侧 Anthropic 降级为文本 JSON 协议的两处守卫与对应提示词分支 新增依赖真实凭据的流式工具验收用例,默认 ignore
lhk229 added 1 commit 2026-07-27 10:17:26 +08:00
补齐 Anthropic 原生工具与三协议流式工具调用
Project CI / Native shell tests (pull_request) Failing after 7m6s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Failing after 24s
7c61e9a532
Anthropic 请求发送 tools 与对象形态 tool_choice 并解析 tool_use block,解除本地对 function tools 的拦截
Chat / Responses / Anthropic 三种协议的流式工具增量按槽位聚合,收尾校验参数为完整 JSON,截断流不返回半截参数
解除 App 侧 Anthropic 降级为文本 JSON 协议的两处守卫与对应提示词分支
新增依赖真实凭据的流式工具验收用例,默认 ignore

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lhk229 added 1 commit 2026-07-27 10:28:44 +08:00
合并 codex/ai-game-creator-app 自主构建产物更新
Project CI / Repository checks (pull_request) Failing after 46s
Project CI / Frontend tests (pull_request) Successful in 2m41s
Project CI / Backend tests (pull_request) Successful in 4m26s
Project CI / Native shell tests (pull_request) Failing after 7m8s
cdbf0d1f32
冲突只在 provider_request_builders.rs:对方把 protocol_prompt 改为 String 并追加完成门禁协议,同时按编辑器密钥和静态验证可用性摘除 canvas.asset_generate 与 project.verify 工具
解析方式为保留对方新增内容,去掉 Anthropic 分支与 api_kind 守卫,使三种协议统一走原生工具目录

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lhk229 added 5 commits 2026-07-27 11:26:46 +08:00
App 实施计划与 Runtime V1.1 更新为三种协议统一使用原生工具目录,补充 Anthropic 的 input_schema 与对象形态 tool_choice 约定
旧 wrapper 与 text JSON parser 只描述为历史响应、确定性 fixture 和模型不守协议时的降级解析,不再写成任何 Provider 的正常请求路径
decision-log 追加 2026-07-27 取代性决策,并给 2026-07-16、2026-07-12 与 2026-07-24 三处受影响描述加上后续更正标记

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
三种协议都提供原生工具后,该段 schema 恒不适用,只是白占 planning 输入 token
随之删除 8 个只针对该段工具枚举的 replace,以及 legacy plan 与「不提供 function tools」两处失效措辞
确定性 lane-defense provider 的观察切分标记改用同位置的「计划更新约定:」,合成上下文行为不变

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
补充 Chat、Responses、Anthropic 三协议工具支持矩阵

说明流式文本回调、slot 聚合与 Responses completed-only 恢复

补充工具参数与 Deserialize、StreamUnavailable、EmptyResponse 边界

同步后端架构与开发运维文档
修正真实端点 live test 对原始 SSE fidelity 的过度描述

区分 checked-in fixture parser 覆盖与真实端点归一工具调用 smoke

补充当前 52 项测试结果及历史 41/41 计数边界

同步 README、Runtime、App、运维文档和 decision-log
修正 live stream 工具验收的运行口径并补进运维文档
Project CI / Frontend tests (pull_request) Failing after 1m2s
Project CI / Backend tests (pull_request) Failing after 1m12s
Project CI / Repository checks (pull_request) Failing after 2m24s
Project CI / Native shell tests (pull_request) Failing after 7m56s
5eee11ac0b
仓库根目录没有 Cargo.toml,测试注释里的命令补上 --manifest-path server-rs/Cargo.toml
运维文档补充四个 PLATFORM_LLM_LIVE_* 变量、bash 与 PowerShell 两种执行方式,以及逐协议切换才算覆盖完整的口径
明确凭据只从进程环境变量读取,不读 .env.secrets.local,真实 API Key 不得提交进仓库

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lhk229 added 1 commit 2026-07-27 11:35:24 +08:00
修正流式工具验收说明
Project CI / Frontend tests (pull_request) Failing after 28s
Project CI / Repository checks (pull_request) Failing after 1m22s
Project CI / Backend tests (pull_request) Failing after 1m38s
Project CI / Native shell tests (pull_request) Failing after 8m43s
5fb9af8303
校正真实端点 smoke 的必填环境变量与可选 API kind 口径

修复 Bash 和 PowerShell 执行示例

明确文本增量字符数仅用于打印观测
lhk229 added 1 commit 2026-07-27 11:58:49 +08:00
修正原生工具 repair 用例中失效的提示词断言
Project CI / Repository checks (pull_request) Failing after 43s
Project CI / Frontend tests (pull_request) Failing after 31s
Project CI / Backend tests (pull_request) Failing after 1m37s
Project CI / Native shell tests (pull_request) Failing after 9m3s
a6c0775fb7
移除 legacy text JSON schema 后,断言「提供原生函数时不得输出这段 JSON」必然失败
改为断言现行原生协议约束,并新增否定断言防止该段 schema 回流
tool_choice=required、arguments.input 嵌套、禁止扁平化和 respond_to_user 断言保持不变

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lhk229 added 2 commits 2026-07-27 12:19:39 +08:00
按仓库 rustfmt 配置整理流式工具调用实现。

格式化 live stream 工具调用验收测试。
修正 platform-llm 契约小节标题避免误入表目录
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Frontend tests (pull_request) Successful in 1m52s
Project CI / Native shell tests (pull_request) Failing after 7m6s
d6828d5893
DDD boundary guard 以 `^### \`name\`` 采集 SpacetimeDB 表目录,
新增的 platform-llm 小节标题以反引号开头,被误判成不存在的表。
按同文档 view 条目的写法加类别前缀,使反引号不在首位。
lhk229 added 1 commit 2026-07-27 12:30:08 +08:00
合并 codex/ai-game-creator-app 上下文预加载说明修复
Project CI / Frontend tests (pull_request) Failing after 28s
Project CI / Repository checks (pull_request) Failing after 1m7s
Project CI / Backend tests (pull_request) Successful in 4m38s
Project CI / Native shell tests (pull_request) Failing after 8m22s
a5c9f68bfe
冲突集中在后台 planning 的 format! 模板:本分支已移除 legacy text
JSON schema,上游把硬编码的预加载说明换成 context_preload_notice
占位符。解析时保留本分支的原生工具文案,只替换该占位符。
lhk229 added 1 commit 2026-07-27 13:23:52 +08:00
修复测试的竞态问题
Project CI / Frontend tests (pull_request) Failing after 31s
Project CI / Repository checks (pull_request) Failing after 52s
Project CI / Backend tests (pull_request) Successful in 4m17s
Project CI / Native shell tests (pull_request) Failing after 10m53s
1580b3daf3
lhk229 added 1 commit 2026-07-27 13:37:12 +08:00
流式工具调用要求协议收尾信号
Project CI / Repository checks (pull_request) Failing after 1m26s
Project CI / Frontend tests (pull_request) Successful in 2m27s
Project CI / Backend tests (pull_request) Successful in 4m40s
Project CI / Native shell tests (pull_request) Failing after 7m34s
445559bfea
字节流干净结束不等于协议收尾:代理超时、网关掐断和 HTTP/2 提前
END_STREAM 都表现为干净 EOF。此前只要工具参数恰好是合法 JSON 就会
返回并执行该调用,且并行槽位在断流后会被静默丢弃。

新增 ParsedStreamEvent::is_completion 与 StreamAccumulation::
completion_observed,按协议判定收尾:Chat 用非空 finish_reason 或
[DONE],Responses 用 response.completed,Anthropic 用带 stop_reason
的 message_delta 或 message_stop。不能统一用 [DONE],MiniMax 兼容层
不发该标记。message_stop 改为产生只带 is_completion 的事件,保持不
伪造 finish_reason 覆盖真实 end_turn 的既有约束。

聚合出过工具 slot 但未观察到收尾信号时返回 Deserialize;判定使用未
固化槽位,使参数恰好闭合的截断仍按截断归因。StreamUnavailable 语义
不变,仍只表示事件形状不受支持。本轮只收严工具路径,纯文本缺收尾信
号仍返回成功并打 warn。

真实端点验证:MiniMax Anthropic 兼容层 stop_reason=tool_use、
MiniMax OpenAI 兼容层 finish_reason=tool_calls,均通过门禁。
lhk229 added 1 commit 2026-07-27 13:49:12 +08:00
修正 AI 游戏创作 Shell ESLint 规范
Project CI / Frontend tests (pull_request) Failing after 56s
Project CI / Repository checks (pull_request) Failing after 2m20s
Project CI / Backend tests (pull_request) Successful in 6m19s
Project CI / Native shell tests (pull_request) Failing after 12m13s
9eca3bd657
整理配置检查脚本与智能体测试的导入顺序。

将配置向导中的常量条件循环改为等价循环语法。
lhk229 added 2 commits 2026-07-27 14:16:39 +08:00
非流式 DTO 为兼容流式分片改成可选字段后,filter_map 会把缺 id / name /
function 的工具调用逐个丢掉;响应同时带解说文本时整个响应被当作普通文本
回复成功返回,带 tool_choice=required 的请求随后退化为格式修复循环,审计
里看不出成因在解析层。Responses 与 Anthropic 的提取有同类缺陷。

把策略收进单一入口 normalize_tool_calls:协议层只做字段映射,识别为工具
调用后字段不全一律返回 Deserialize;arguments 缺省或空白归一为空对象,
非空则必须是完整 JSON。流式收尾改为委派同一入口,行为不变。

顺带修正两处口径不一致:Chat 缺 arguments 从空串改为空对象,Responses
缺 arguments 从整条丢弃改为空对象(零参函数合法)。非流式 arguments 半截
JSON 由此改为显式失败,上游 max_tokens 截断会命中。

补 9 个非流式用例,含缺 id 且有正文不得退化成纯文本回复的回归锁,以及三
协议同一缺失形状必须同类报错的一致性用例。
尾部错误后保留已收尾的纯工具调用响应
Project CI / Repository checks (pull_request) Failing after 1m0s
Project CI / Frontend tests (pull_request) Successful in 2m45s
Project CI / Backend tests (pull_request) Successful in 4m53s
Project CI / Native shell tests (pull_request) Failing after 6m49s
214357b175
retain_completed_stream_after_tail_error 用“正文非空”判断有没有值得保留的
结果,这个判据来自该函数引入时,那时响应就等于正文。工具调用成为一等公民后
它失效了:纯工具调用响应的正文本来就是空的,MiniMax 的 Anthropic 工具流实测
恒定如此,于是每一次这样的响应遇到尾部传输或解析错误都会被丢弃,白跑一轮
Provider 重试。

Anthropic 的 message_stop 与 Responses 的 response.completed 只标记完成、不
标记流终止,收尾事件之后仍会读到 EOF,因此这条尾部路径是常态而非边缘情况。

改为正文或工具调用任一非空即视为有可保留内容。安全性由其余判据保证:协议完成
信号已到、finish_reason 已到、工具参数完整、错误属可容忍尾部错误,与正常路径
一致。补一个纯工具调用遇 body-read 尾部错误的用例,退回旧判据时该用例失败。
lhk229 added 1 commit 2026-07-27 15:01:05 +08:00
非流式工具参数半截 JSON 改为原样透传
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / Frontend tests (pull_request) Failing after 23s
Project CI / Native shell tests (pull_request) Failing after 9m30s
eb63f44271
b1ef45fbd 把“arguments 非空必须是完整 JSON”同时应用到流式和非流式,
越界了。两者的“参数不完整”语义不同:流式意味着流被截断,是传输层事实;
非流式的外层 body 已经完整,参数半截只说明模型输出有问题,属于内容层事实。

App 早有更好的处理——工具计划格式修复循环把畸形响应回灌给模型重写,比硬
报错再重跑整轮 Provider 有效得多。平台层拦下来会丢掉修复所需的 call id、
函数名和原始参数,导致 background_agent_runtime_repairs_malformed_native_
function_arguments 直接以 kind=deserialize 失败,第二次 repair 请求不再发出。

按流式与非流式区分该校验。b1ef45fbd 的其余部分不变:禁止静默丢弃、id 与
函数名必填、空参数归一为空对象、三协议共用一份策略。

原用例反转为断言原样透传,并补一条成对的流式用例锁住流式仍然报错。
lhk229 added 1 commit 2026-07-27 15:10:40 +08:00
合并 codex/ai-game-creator-app 工具计划响应交接误判修复
Project CI / Repository checks (pull_request) Failing after 56s
Project CI / Backend tests (pull_request) Successful in 4m42s
Project CI / Native shell tests (pull_request) Failing after 10m17s
Project CI / Frontend tests (pull_request) Successful in 2m37s
c2d2eb7ea9
lhk229 added 1 commit 2026-07-27 15:46:53 +08:00
修复过期断言
Project CI / Frontend tests (pull_request) Failing after 31s
Project CI / Repository checks (pull_request) Failing after 53s
Project CI / Backend tests (pull_request) Successful in 4m38s
Project CI / Native shell tests (pull_request) Successful in 10m47s
95b8197235
lhk229 marked the pull request as ready for review 2026-07-27 16:05:48 +08:00
lhk229 added 1 commit 2026-07-27 16:13:02 +08:00
拒绝上游宣告未完成的工具调用
Project CI / Backend tests (pull_request) Successful in 4m19s
Project CI / Native shell tests (pull_request) Failing after 7m11s
Project CI / Frontend tests (pull_request) Failing after 23s
Project CI / Repository checks (pull_request) Failing after 49s
5daea56e32
流式的收尾门禁只防传输层截断,把模型侧截断当成了正常收尾:is_completion 只
判断 finish_reason 非空,length / content_filter / max_tokens 一律放行;三条
非流式路径同样原样接受 Chat finish_reason、Responses status 和 Anthropic
stop_reason。下游没有任何一处对该字段做分支,因此参数恰好闭合成合法 JSON 的
截断调用会被真的执行。格式修复循环对这种形态无从察觉,它看到的 JSON 是合法的。

新增 reject_incomplete_tool_calls,流式与三条非流式路径统一拦截已知的截断、
过滤和失败终态:Chat 的 length / content_filter,Responses 的 incomplete /
failed / cancelled,Anthropic 的 max_tokens / pause_turn / refusal。

刻意用黑名单而非白名单,未知值与缺失一律放行,否则会误杀不发或自定义该字段的
兼容网关。只在存在工具调用时生效——正文被 max_tokens 截断仍是可用的降级结果,
一并拒绝会打死所有触及输出上限的长文本回答。

与非流式畸形参数透传同时成立时本检查优先,原用例的 finish_reason 相应改为
tool_calls 以只验证透传本身。补 6 个用例覆盖三协议截断、纯文本截断放行和未知
finish_reason 放行。
lhk229 added 1 commit 2026-07-27 16:22:54 +08:00
流式最终事件立即收口,工具槽位缺失失败关闭
Project CI / Frontend tests (pull_request) Failing after 25s
Project CI / Repository checks (pull_request) Failing after 50s
Project CI / Backend tests (pull_request) Successful in 4m16s
Project CI / Native shell tests (pull_request) Successful in 11m12s
51d10316a9
两处收口不一致,一并处理。

一、Responses 的 response.completed 与 Anthropic 的 message_stop 此前只标记
完成、不终止读取循环。服务端在最终事件后保持连接时,stream_run 会继续等 EOF,
最坏一路等到调用方超时。既有覆盖只有 Chat 的 [DONE]。三者现在统一置终止位。

二、工具事件的协议槽位缺失时此前要么跳过要么猜测。Chat 退回事件内位置,两个各
含一个无 index 调用的事件都落到槽位 0,后者的 id/name 覆盖前者、arguments 被
拼成混合体;Responses 与 Anthropic 五处直接丢事件。跳过只在调用全丢时才会被
StreamUnavailable 断言兜住,丢一半毫无察觉,而 Responses 的 finish_reason 恒为
completed,那道断言对它永远不触发。槽位是并行分片唯一的归并依据,现在一律失败
关闭。收紧只覆盖工具事件,纯文本增量不依赖槽位。

尾错保留用例原本以 message_stop 结尾,加终止位后会在尾部错误之前收口而变成空转,
改为只发 message_delta,使它继续验证“收尾信号已到但流没干净结束”的保留路径。
lhk229 added 1 commit 2026-07-27 16:29:19 +08:00
文档:同步 platform-llm 工具调用契约
Project CI / Repository checks (pull_request) Failing after 1m13s
Project CI / Frontend tests (pull_request) Successful in 2m33s
Project CI / Backend tests (pull_request) Successful in 5m1s
Project CI / Native shell tests (pull_request) Successful in 10m9s
58283e7a93
修正纯工具响应在尾部错误后的保留条件

补充非流式畸形 arguments 交由调用方 repair 的契约

同步两份 Runtime 文档与决策日志的 81 项测试口径

保留历史 52 项验证记录的时间语义
lhk229 added 1 commit 2026-07-27 16:54:01 +08:00
补齐 Responses 的 incomplete 终态
Project CI / Frontend tests (pull_request) Failing after 27s
Project CI / Repository checks (pull_request) Failing after 54s
Project CI / Backend tests (pull_request) Successful in 4m55s
Project CI / Native shell tests (pull_request) Failing after 8m17s
fc53ee5c9c
Responses 的整体收尾信号有两个,此前只处理了 completed,incomplete 落入
通配分支被整个忽略。真实端点抓包确认:撞到 max_output_tokens 时上游只发
response.incomplete、不发 completed,载荷与 completed 同构,带完整 output[],
item 标 status=incomplete,incomplete_details.reason 给出原因。

忽略它造成三件事:不终止读取循环,网关不主动关连接就等到调用方超时;流式
Responses 永远产生不出 incomplete 这个 finish_reason,上一轮加的截断拒绝规则
对它形同虚设;completed-only 型网关的工具调用被静默丢掉。

与 completed 合并到同一分支,只在 finish_reason 上区分。incomplete 由此自动
走 reject_incomplete_tool_calls:工具调用拒绝,正文按降级结果返回,与 Chat 的
length 口径一致。

三个用例的事件序列转录自真实抓包,退回通配分支时三条全部失败。
lhk229 added 1 commit 2026-07-27 16:57:22 +08:00
文档:同步 platform-llm 测试数量
Project CI / Frontend tests (pull_request) Failing after 22s
Project CI / Repository checks (pull_request) Failing after 55s
Project CI / Backend tests (pull_request) Successful in 4m29s
Project CI / Native shell tests (pull_request) Failing after 8m28s
281b818068
更新两份 Runtime 文档的当前测试结果为 84 项通过

同步决策日志的 platform-llm 当前验收口径
lhk229 added 1 commit 2026-07-27 17:09:01 +08:00
合并 codex/ai-game-creator-app 运行续作与工具路径交接修复
Project CI / Repository checks (pull_request) Failing after 46s
Project CI / Frontend tests (pull_request) Successful in 2m9s
Project CI / Backend tests (pull_request) Successful in 4m13s
Project CI / Native shell tests (pull_request) Successful in 9m48s
f8d3f81263
kdletters added this to the 陶泥儿gameAgent project 2026-07-27 17:23:21 +08:00
Author
Owner
<html>

背景

此前只有 OpenAI Chat / Responses 支持原生 function tools,Anthropic 走提示词驱动的文本 JSON,且流式路径固定返回空 tool_calls——工具调用被直接丢弃。

改动

能力补齐

  • Anthropic 改用原生 tools / tool_useinput_schema、对象形式 tool_choice
  • 三协议流式 SSE 工具分片按各自槽位聚合成统一 LlmToolCall

归一统一

  • 识别为工具调用后字段不全一律报错,不再 filter_map 静默丢弃
  • 空参数归一为 {};参数完整性校验只在流式生效,非流式原样透传给上层的格式修复循环

四道门禁

防的问题 | 判据 -- | -- 传输层截断 | 工具流必须观察到协议收尾信号 模型侧截断 | 拒绝 length / max_tokens / incomplete 等未完成终态中的工具调用 并行调用被合并或丢弃 | 协议槽位缺失时失败关闭 网关不关连接导致挂起 | 最终事件同时终止读取循环

另修正"尾部错误后只看正文非空"这条过时判据,纯工具响应不再被误丢。

验证

  • MiniMax 三协议 + OpenAI 原生端点实测通过(response.incomplete 的存在与载荷为抓包确认)
  • 每条修复均以"退回旧实现确认新用例失败"验证
  • platform-llm 单测 44 → 84

附带

清理后台 planning 提示词中失效的 legacy schema;同步后端架构契约、Runtime 技术方案与决策日志;合并 4 次 codex/ai-game-creator-app 上游更新。

</html>
<html> <body> <!--StartFragment--><h3 dir="ltr" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-size: 1em; font-weight: 500; text-rendering: optimizelegibility; color: rgb(11, 11, 11); font-family: &quot;Anthropic Sans&quot;, system-ui, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span data-wf="" class="_wordAnimating_yu34g_11" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; opacity: 1; animation: 0.1s linear 90ms 1 normal both running _fadeInText_yu34g_1;">背景</span></h3><p dir="ltr" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; color: rgb(11, 11, 11); font-family: &quot;Anthropic Sans&quot;, system-ui, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">此前只有 OpenAI Chat / Responses 支持原生 function tools,Anthropic 走提示词驱动的文本 JSON,且<strong style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-weight: 500; text-rendering: optimizelegibility;">流式路径固定返回空<span> </span><code data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-family: &quot;Anthropic Mono&quot;, ui-monospace, monospace; font-feature-settings: &quot;calt&quot; 0, &quot;liga&quot; 0; font-variation-settings: normal; font-size: 13px; font-variant-ligatures: none; text-rendering: optimizelegibility; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(11, 11, 11, 0.04); color: rgb(11, 11, 11); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">tool_calls</code></strong><span data-wf="" class="_wordAnimating_yu34g_11" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; opacity: 1; animation: 0.1s linear 23ms 1 normal both running _fadeInText_yu34g_1;">——工具调用被直接丢弃。</span></p><h3 dir="ltr" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-size: 1em; font-weight: 500; text-rendering: optimizelegibility; color: rgb(11, 11, 11); font-family: &quot;Anthropic Sans&quot;, system-ui, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span data-wf="" class="_wordAnimating_yu34g_11" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; opacity: 1; animation: 0.1s linear 20ms 1 normal both running _fadeInText_yu34g_1;">改动</span></h3><p dir="ltr" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; color: rgb(11, 11, 11); font-family: &quot;Anthropic Sans&quot;, system-ui, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><strong style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-weight: 500; text-rendering: optimizelegibility;"><span data-wf="" class="_wordAnimating_yu34g_11" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; opacity: 1; animation: 0.1s linear 45ms 1 normal both running _fadeInText_yu34g_1;">能力补齐</span></strong></p><ul dir="ltr" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px 0px 0px 20px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); list-style: outside; text-rendering: optimizelegibility; gap: 6px; flex-direction: column; display: flex; color: rgb(11, 11, 11); font-family: &quot;Anthropic Sans&quot;, system-ui, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><li style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility;">Anthropic 改用原生<span> </span><code data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-family: &quot;Anthropic Mono&quot;, ui-monospace, monospace; font-feature-settings: &quot;calt&quot; 0, &quot;liga&quot; 0; font-variation-settings: normal; font-size: 13px; font-variant-ligatures: none; text-rendering: optimizelegibility; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(11, 11, 11, 0.04); color: rgb(11, 11, 11); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">tools</code><span> </span>/<span> </span><code data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-family: &quot;Anthropic Mono&quot;, ui-monospace, monospace; font-feature-settings: &quot;calt&quot; 0, &quot;liga&quot; 0; font-variation-settings: normal; font-size: 13px; font-variant-ligatures: none; text-rendering: optimizelegibility; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(11, 11, 11, 0.04); color: rgb(11, 11, 11); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">tool_use</code>(<code data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-family: &quot;Anthropic Mono&quot;, ui-monospace, monospace; font-feature-settings: &quot;calt&quot; 0, &quot;liga&quot; 0; font-variation-settings: normal; font-size: 13px; font-variant-ligatures: none; text-rendering: optimizelegibility; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(11, 11, 11, 0.04); color: rgb(11, 11, 11); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">input_schema</code>、对象形式<span> </span><code data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-family: &quot;Anthropic Mono&quot;, ui-monospace, monospace; font-feature-settings: &quot;calt&quot; 0, &quot;liga&quot; 0; font-variation-settings: normal; font-size: 13px; font-variant-ligatures: none; text-rendering: optimizelegibility; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(11, 11, 11, 0.04); color: rgb(11, 11, 11); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">tool_choice</code>)</li><li style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility;">三协议流式 SSE 工具分片按各自槽位聚合成统一<span> </span><code data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-family: &quot;Anthropic Mono&quot;, ui-monospace, monospace; font-feature-settings: &quot;calt&quot; 0, &quot;liga&quot; 0; font-variation-settings: normal; font-size: 13px; font-variant-ligatures: none; text-rendering: optimizelegibility; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(11, 11, 11, 0.04); color: rgb(11, 11, 11); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">LlmToolCall</code></li></ul><p dir="ltr" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; color: rgb(11, 11, 11); font-family: &quot;Anthropic Sans&quot;, system-ui, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><strong style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-weight: 500; text-rendering: optimizelegibility;"><span data-wf="" class="_wordAnimating_yu34g_11" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; opacity: 1; animation: 0.1s linear 25ms 1 normal both running _fadeInText_yu34g_1;">归一统一</span></strong></p><ul dir="ltr" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px 0px 0px 20px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); list-style: outside; text-rendering: optimizelegibility; gap: 6px; flex-direction: column; display: flex; color: rgb(11, 11, 11); font-family: &quot;Anthropic Sans&quot;, system-ui, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><li style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility;">识别为工具调用后字段不全一律报错,不再<span> </span><code data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-family: &quot;Anthropic Mono&quot;, ui-monospace, monospace; font-feature-settings: &quot;calt&quot; 0, &quot;liga&quot; 0; font-variation-settings: normal; font-size: 13px; font-variant-ligatures: none; text-rendering: optimizelegibility; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(11, 11, 11, 0.04); color: rgb(11, 11, 11); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">filter_map</code><span> </span>静默丢弃</li><li style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility;">空参数归一为<span> </span><code data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-family: &quot;Anthropic Mono&quot;, ui-monospace, monospace; font-feature-settings: &quot;calt&quot; 0, &quot;liga&quot; 0; font-variation-settings: normal; font-size: 13px; font-variant-ligatures: none; text-rendering: optimizelegibility; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(11, 11, 11, 0.04); color: rgb(11, 11, 11); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">{}</code>;参数完整性校验只在流式生效,非流式原样透传给上层的格式修复循环</li></ul><p dir="ltr" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; color: rgb(11, 11, 11); font-family: &quot;Anthropic Sans&quot;, system-ui, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><strong style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-weight: 500; text-rendering: optimizelegibility;">四道门禁</strong></p><div dir="ltr" class="overflow-x-auto" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); overflow-x: auto; text-rendering: optimizelegibility; color: rgb(11, 11, 11); font-family: &quot;Anthropic Sans&quot;, system-ui, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"> 防的问题 | 判据 -- | -- 传输层截断 | 工具流必须观察到协议收尾信号 模型侧截断 | 拒绝 length / max_tokens / incomplete 等未完成终态中的工具调用 并行调用被合并或丢弃 | 协议槽位缺失时失败关闭 网关不关连接导致挂起 | 最终事件同时终止读取循环 </div><p dir="ltr" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; color: rgb(11, 11, 11); font-family: &quot;Anthropic Sans&quot;, system-ui, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">另修正"尾部错误后只看正文非空"这条过时判据,纯工具响应不再被误丢。</p><h3 dir="ltr" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-size: 1em; font-weight: 500; text-rendering: optimizelegibility; color: rgb(11, 11, 11); font-family: &quot;Anthropic Sans&quot;, system-ui, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span data-wf="" class="_wordAnimating_yu34g_11" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; opacity: 1; animation: 0.1s linear 20ms 1 normal both running _fadeInText_yu34g_1;">验证</span></h3><ul dir="ltr" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px 0px 0px 20px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); list-style: outside; text-rendering: optimizelegibility; gap: 6px; flex-direction: column; display: flex; color: rgb(11, 11, 11); font-family: &quot;Anthropic Sans&quot;, system-ui, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><li style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility;">MiniMax 三协议 + OpenAI 原生端点实测通过(<code data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-family: &quot;Anthropic Mono&quot;, ui-monospace, monospace; font-feature-settings: &quot;calt&quot; 0, &quot;liga&quot; 0; font-variation-settings: normal; font-size: 13px; font-variant-ligatures: none; text-rendering: optimizelegibility; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(11, 11, 11, 0.04); color: rgb(11, 11, 11); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">response.incomplete</code><span> </span>的存在与载荷为抓包确认)</li><li style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility;">每条修复均以"退回旧实现确认新用例失败"验证</li><li style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility;"><code data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-family: &quot;Anthropic Mono&quot;, ui-monospace, monospace; font-feature-settings: &quot;calt&quot; 0, &quot;liga&quot; 0; font-variation-settings: normal; font-size: 13px; font-variant-ligatures: none; text-rendering: optimizelegibility; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(11, 11, 11, 0.04); color: rgb(11, 11, 11); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">platform-llm</code><span> </span>单测 44 →<span> </span><span data-wf="" class="_wordAnimating_yu34g_11" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; opacity: 1; animation: 0.1s linear 5ms 1 normal both running _fadeInText_yu34g_1;">84</span></li></ul><h3 dir="ltr" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-size: 1em; font-weight: 500; text-rendering: optimizelegibility; color: rgb(11, 11, 11); font-family: &quot;Anthropic Sans&quot;, system-ui, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span data-wf="" class="_wordAnimating_yu34g_11" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; opacity: 1; animation: 0.1s linear 20ms 1 normal both running _fadeInText_yu34g_1;">附带</span></h3><p dir="ltr" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; color: rgb(11, 11, 11); font-family: &quot;Anthropic Sans&quot;, system-ui, &quot;Segoe UI&quot;, Roboto, Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(252, 252, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">清理后台 planning 提示词中失效的 legacy schema;同步后端架构契约、Runtime 技术方案与决策日志;合并 4 次<span> </span><code data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); font-family: &quot;Anthropic Mono&quot;, ui-monospace, monospace; font-feature-settings: &quot;calt&quot; 0, &quot;liga&quot; 0; font-variation-settings: normal; font-size: 13px; font-variant-ligatures: none; text-rendering: optimizelegibility; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(11, 11, 11, 0.04); color: rgb(11, 11, 11); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">codex/ai-game-creator-app</code><span data-wf="" class="_wordAnimating_yu34g_11" style="box-sizing: border-box; border: 0px solid color(srgb 0.0431373 0.0431373 0.0431373 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(37, 106, 191); scrollbar-width: thin; scrollbar-color: rgba(30, 30, 29, 0.35) rgba(0, 0, 0, 0); text-rendering: optimizelegibility; opacity: 1; animation: 0.1s linear 15ms 1 normal both running _fadeInText_yu34g_1;"><span> </span>上游更新。</span></p><!--EndFragment--> </body> </html>
lhk229 added 1 commit 2026-07-27 17:43:26 +08:00
文档:同步流式终态协议说明
Project CI / Frontend tests (pull_request) Failing after 28s
Project CI / Repository checks (pull_request) Failing after 59s
Project CI / Backend tests (pull_request) Successful in 5m20s
Project CI / Native shell tests (pull_request) Failing after 8m22s
a00893a0ce
补充 Responses incomplete 的完成与终止语义

修正尾部错误保留和工具槽位失败关闭说明

同步 platform-llm 源码注释与 README
lhk229 added 1 commit 2026-07-27 17:55:02 +08:00
合并 codex/ai-game-creator-app Windows 启动失败修复
Project CI / Repository checks (pull_request) Failing after 1m16s
Project CI / Frontend tests (pull_request) Successful in 2m31s
Project CI / Backend tests (pull_request) Successful in 4m46s
Project CI / Native shell tests (pull_request) Successful in 10m26s
39ffad4d01
lhk229 added 1 commit 2026-07-27 18:06:30 +08:00
修复自主构建收束用例的残留断言竞态
Project CI / Frontend tests (pull_request) Failing after 26s
Project CI / Repository checks (pull_request) Failing after 51s
Project CI / Backend tests (pull_request) Successful in 4m36s
Project CI / Native shell tests (pull_request) Successful in 10m26s
bf97810535
finalization 先把 response stream 标为 committed,之后才调用
remove_..._finalization_recovery_sidecars 清理 sidecar,两步之间有真实时间窗。
用例等 committed 的循环一旦命中前半步就退出,紧随其后的四条残留断言却零等待,
在 CI 负载下偶发读到尚未清理的 finalization journal。表现为只改文档的提交也会
挂、上一个提交却能过。

给 journal 断言补上与前面一致的轮询。它是该清理链最后删除的一项,等它消失即可
覆盖后面几条 handoff 残留断言,无需逐条加循环。

只改测试:先对外可见 committed、再清理内部 sidecar 的顺序本身是对的,崩溃恢复
要靠 journal 判断状态。
kdletters requested changes 2026-07-27 18:09:30 +08:00
kdletters left a comment
Member

[P1] 同一流式 slot 的工具身份会被静默覆盖
push_tool_fragment 遇到已有 slot 时直接覆盖 id/name,却继续保留或追加原参数。重复 index 携带不同工具身份时,可能丢掉前一个调用,甚至形成“后一个工具 + 混合参数”并交给 Runtime 执行。应在身份冲突时 Deserialize 失败关闭。Chat 和 Anthropic 官方流式协议都把 index 作为调用/内容块标识。OpenAI ChatAnthropic streaming

[P2] Responses 仅发送最终事件时会丢正文
response.completed/incomplete 分支 只恢复工具调用,没有从 response.output[] 恢复完整文本。现有测试载荷本身包含“我来查询。”,却没断言 response.text;纯文本 completed-only 响应最终会变成 EmptyResponse。官方终态事件确实携带完整 Response/output。OpenAI Responses streamingresponse.incomplete

[P2] 真实验收脚本会把拼错的协议名默认为 Responses
parse_api_kind 除 anthropic/openai_chat 外全部回落到 Responses。比如 anthopic 可能产生错误的“Anthropic 已验收”证据。应仅允许空值默认,未知非空值直接报错。

[P1] 同一流式 slot 的工具身份会被静默覆盖 [push_tool_fragment](http://192.168.35.82/git/GenarrativeAI/Genarrative/src/commit/a00893a0ce7d2e591fab864cf2ff22cf3e39d52e/server-rs/crates/platform-llm/src/lib.rs#L789) 遇到已有 slot 时直接覆盖 id/name,却继续保留或追加原参数。重复 index 携带不同工具身份时,可能丢掉前一个调用,甚至形成“后一个工具 + 混合参数”并交给 Runtime 执行。应在身份冲突时 Deserialize 失败关闭。Chat 和 Anthropic 官方流式协议都把 index 作为调用/内容块标识。[OpenAI Chat](https://developers.openai.com/api/reference/resources/chat)、[Anthropic streaming](https://docs.anthropic.com/en/api/messages-streaming) [P2] Responses 仅发送最终事件时会丢正文 [response.completed/incomplete 分支](http://192.168.35.82/git/GenarrativeAI/Genarrative/src/commit/a00893a0ce7d2e591fab864cf2ff22cf3e39d52e/server-rs/crates/platform-llm/src/lib.rs#L2834) 只恢复工具调用,没有从 response.output[] 恢复完整文本。现有测试载荷本身包含“我来查询。”,却没断言 response.text;纯文本 completed-only 响应最终会变成 EmptyResponse。官方终态事件确实携带完整 Response/output。[OpenAI Responses streaming](https://platform.openai.com/docs/api-reference/responses-streaming/response/refusal/delta?lang=curl)、[response.incomplete](https://platform.openai.com/docs/api-reference/responses-streaming/response/incomplete?lang=node.js) [P2] 真实验收脚本会把拼错的协议名默认为 Responses [parse_api_kind](http://192.168.35.82/git/GenarrativeAI/Genarrative/src/commit/a00893a0ce7d2e591fab864cf2ff22cf3e39d52e/server-rs/crates/platform-llm/tests/live_stream_tool_calls.rs#L29) 除 anthropic/openai_chat 外全部回落到 Responses。比如 anthopic 可能产生错误的“Anthropic 已验收”证据。应仅允许空值默认,未知非空值直接报错。
lhk229 added 2 commits 2026-07-27 18:46:53 +08:00
push_tool_fragment 遇到已有槽位时无条件覆盖 id / 函数名,参数却是追加
(Chat / Anthropic)或整段覆盖(Responses 的 arguments_complete)。两者
不自洽:前一个调用参数为空时,拼接结果正好是后一个调用的合法 JSON,
参数完整性检查兜不住,调用方只拿到后一个工具,前一个静默消失并直接交给
Runtime 执行。

守规协议不会命中——Chat 的 index、Responses 的 output_index、Anthropic 的
content block index 在单条响应内都不重复。已知触发路径两条:兼容网关把
Chat 的 index 恒置 0;Responses 的 response.completed 回退按 output[] 下标
重建槽位时与流式 output_index 基准错位(如 completed 载荷省略 reasoning
item),后者会产出「前一个调用的身份配后一个调用的参数」。

改为同槽位身份只允许从缺失变已知或重复同一个值,互不相同的非空值返回
Deserialize。id 与函数名都查:并行调用同一个工具时函数名相同,只有 id 能
区分。空白身份按缺失跳过,不算冲突——部分兼容网关在续传分片里回发完整
function 对象且 name / id 为空串,按「不等即冲突」会整批误杀,这也与归一层
的空白即缺失约定一致。身份冲突不走尾部错误保留路径,累加状态已不可信。

隔离验证:退回无条件覆盖后,四条拒绝用例全红,其中 Chat 那条返回
Ok(tool_calls=[call_b/get_air_quality]),call_a/get_weather 无声消失。

platform-llm 90 passed(原 84),新增 6 条:Chat 换工具 / Chat 同名换 id /
Responses completed 与增量矛盾 / Anthropic 复用 index 四条拒绝,外加
网关每片回发同一身份、completed 重复确认同槽位两条兼容性守卫。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
修复真实验收协议名解析
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / Frontend tests (pull_request) Failing after 22s
Project CI / Native shell tests (pull_request) Successful in 9m32s
f3e9fb4508
仅允许空值默认 openai_responses

显式校验 openai_responses、openai_chat 和 anthropic

为未知协议名补充失败用例并同步验收说明
lhk229 added 1 commit 2026-07-27 19:16:41 +08:00
合并 codex/ai-game-creator-app 的 master 更新
Project CI / Frontend tests (pull_request) Failing after 22s
Project CI / Repository checks (pull_request) Failing after 43s
Project CI / Backend tests (pull_request) Successful in 2m53s
Project CI / Native shell tests (pull_request) Successful in 9m18s
85b9c2f193
codex 侧只多一个 6bcd176bd,但它把整个 master 历史带了进来:本分支与
master 的 merge-base 还停在 eb608ac6f,本次一并跟上 1305 个文件。

三处冲突:

- 契约文档:保留本分支的 platform-llm 三协议工具契约整节,图片生成条目
  采纳上游新版(逻辑 SKU 与首选模型固定 gpt-image-2 的表述)。
- scripts/check-native-shells.mjs:双方独立重写了
  assertAiGameCreatorShellUserDevBoundary。取本分支版本,它是上游版本的
  严格超集——多断言 projectSupervisorOnly ? false : isDeveloperMode()、
  <ProjectWorkspaceChatPane 挂载,以及预览 iframe 必须落在 dev-only pane
  内。按块解而非整文件取一侧,上游同文件的小程序订阅消息退役改动保留。
- decision-log.md:双方各自追加。上游那三条退役 bullet 保持贴在
  2026-07-26 节(与上游文件一致),本分支两节排在其后。

验证:platform-llm 91 passed;assertAiGameCreatorShellUserDevBoundary 单独
抽出跑通(完整 check:native-shells 本机跑不了,Windows 上 spawnSync npm.cmd
EINVAL,WSL 里 node_modules 是 win32 装的);check-server-rs-ddd-boundaries
通过;新引入的 check:module-runtime-artifact 在 WSL 通过,Windows 上因 MSVC
rlib 走长名表、脚本按 endsWith('.o') 找成员而无法运行,属上游门禁的平台
限制。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lhk229 added 1 commit 2026-07-27 19:45:58 +08:00
Responses 终态事件恢复正文
Project CI / Frontend tests (pull_request) Failing after 23s
Project CI / Repository checks (pull_request) Failing after 46s
Project CI / Backend tests (pull_request) Successful in 2m55s
Project CI / Native shell tests (pull_request) Successful in 10m6s
902f487ea9
response.completed / incomplete 分支只从终态载荷恢复工具调用,不恢复
message.content[].output_text,导致三种后果(均已实测复现):

- 纯文本的 completed-only 响应 -> EmptyResponse,上层白跑一轮重试或降级
- response.incomplete 携带的截断正文同样退化成 EmptyResponse,连降级
  回复都给不出来
- 「正文 + 工具调用」响应不报错,但模型的前置说明被静默丢掉,最隐蔽

有增量事件的正常流和非流式路径都不受影响。

归属:终态载荷成为「恢复源」是本分支 7c61e9a53 确立的,它加了
extract_responses_completed_tool_fragments 却没同步提取正文,且同一提交新增
的回归载荷里带着「我来查询。」而只断言 tool_calls,缺陷被自己的测试盖住;
fc53ee5c9 把 incomplete 并进同一分支时把这个不对称一起带了过去。更早的
9d684cb7b 虽然也没提正文,但那时终态载荷根本不是恢复源。

修法:ParsedStreamEvent 新增 text_snapshot,与 delta_text 分开——它不是增量,
按增量累加会让正文翻倍。终态分支把 response 反序列化成
ResponsesResponseEnvelope 后复用 extract_responses_text,不另写裸 JSON 提取器:
后者必然漏掉 reasoning / thinking 等隐藏 part 的过滤,会把思维链当正文吐出去。
反序列化失败按「没有快照」静默降级而非报错——兜底路径放过只是回到修复前行为,
与槽位缺失那类会造成静默错配的失败关闭口径不同。

累加时按状态分两条路:累加为空(只发终态事件的网关)把快照当成一次增量发出去,
否则 response.text 对了但调用方流式通道全程收不到文本(Responses 的
emit_finish_only_delta 为 false,只有 Chat 为真);累加非空则按权威值覆盖且不
补发回调,避免调用方侧翻倍。覆盖语义与 arguments_complete 一致。

隔离验证两次:把 text_snapshot 置 None,四条用例转红;把覆盖改成追加,
不重复用例转红。

platform-llm 95 passed(原 91)。新增 completed-only 纯文本、incomplete-only
纯文本、delta 与终态并存不重复、终态 reasoning part 不入正文四条,并收紧既有的
stream_run_recovers_responses_tool_calls_from_completed_event_only 断言正文。
前两条同时断言 on_delta 实际回调内容,只断言 response.text 抓不到流式通道那个坑。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lhk229 added 1 commit 2026-07-27 19:50:13 +08:00
文档:纳入 platform-llm 本地解析测试统计
Project CI / Frontend tests (pull_request) Failing after 20s
Project CI / Repository checks (pull_request) Failing after 42s
Project CI / Backend tests (pull_request) Successful in 2m53s
Project CI / Native shell tests (pull_request) Successful in 9m38s
ea2e1c96d2
将当前确定性测试统计更新为 98 项通过

补充 integration test 中 3 个 parse_api_kind 测试的统计归属

明确真实 Provider smoke 仍为 1 项 ignored
lhk229 added 3 commits 2026-07-27 20:31:06 +08:00
补充 Responses incomplete 的终态恢复与拒绝语义

修正真实端点协议参数的默认与失败关闭说明

同步开发运维文档与长期决策记录
Responses 与 Anthropic 的流式工具参数字段走裸 serde_json::Value 加 as_str,
类型不对时 as_str 返回 None,参数被当成字段缺失;归一层再把空参数补成 {},
于是一个身份完整、参数是合法 JSON 的调用直接交给下游执行。既有校验全都拦不住
——零参函数的 {} 和「参数类型错了所以变成 {}」在归一层无法区分。

实测复现(探针已删):Responses 的 delta / done / 终态载荷 arguments,以及
Anthropic 的 partial_json,无论传对象还是数字,一律返回
Ok(tool_calls=[{id, name, arguments:"{}"}])。同样输入下 Chat 返回
Deserialize("invalid type: map, expected a string")——它走强类型 DTO,本来就
失败关闭。同一平台层三协议对同一种畸形输入行为不一致。

归属:不是先前几次修复的后继问题。两个构成要件都来自 7c61e9a53——那批
as_str 用法是它一次性引入 Responses / Anthropic 流式工具解析时写的,
「空参数归一为 {}」当时也已在 finish_tool_calls 里;b1ef45fbd 把它挪进
normalize_tool_calls 时逐字搬运、语义未动。逐条查过本会话的槽位失败关闭、
身份冲突、截断门禁与终态正文恢复,都走别的分支,没有扩大它的可达面。

改为 tool_argument_str:字段不存在或为 null 返回 None(合法缺省),存在但
不是字符串返回 Deserialize。extract_responses_completed_tool_fragments 随之
改签名返回 Result。只覆盖参数字段——id 与函数名即使类型不对也只会退化成缺失,
随后被归一层按缺 id / 缺函数名拒绝,本来就是失败关闭。

隔离验证:把类型检查退回 as_str,五条拒绝用例全红。

platform-llm 101 passed(原 95)。新增 Responses delta / done / 终态载荷、
Anthropic 对象与数字 partial_json 五条拒绝,外加一条作用域守卫——字段缺失与
显式 null 仍归一为 {},零参函数不能被这条新规则误杀。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
文档:移除易漂移的测试数量统计
Project CI / Backend tests (pull_request) Successful in 3m18s
Project CI / Native shell tests (pull_request) Successful in 9m26s
Project CI / Repository checks (pull_request) Failing after 39s
Project CI / Frontend tests (pull_request) Successful in 2m14s
9400c3b4a9
保留 platform-llm 验收命令与测试类型边界

移除当前文档中的固定测试总数和子测试数量

保留历史测试数量作为带时间语义的验收快照
Member

[P2] Responses 终态快照没有同步修正流式回调
platform-llm/src/lib.rs:2033
已收到任意文本 delta 后,完整终态快照只覆盖 accumulation.text,不再触发 on_delta。例如先流出“杭州今天”,终态正文是“杭州今天多云。”,最终 LlmRunResponse.text 正确,但调用方最后收到的累计正文仍停在半截。建议当前缀一致时补发 suffix;不构成前缀时发送权威修正回调或失败关闭。现有测试只覆盖“没有 delta”和“delta 已经完整”,漏了“部分 delta + 完整终态”。

[P2] Responses 终态快照没有同步修正流式回调 [platform-llm/src/lib.rs:2033](http://192.168.35.82/git/GenarrativeAI/Genarrative/src/commit/9400c3b4a90c2a54841f26b13d5a0e29649979c7/server-rs/crates/platform-llm/src/lib.rs#L2033) 已收到任意文本 delta 后,完整终态快照只覆盖 accumulation.text,不再触发 on_delta。例如先流出“杭州今天”,终态正文是“杭州今天多云。”,最终 LlmRunResponse.text 正确,但调用方最后收到的累计正文仍停在半截。建议当前缀一致时补发 suffix;不构成前缀时发送权威修正回调或失败关闭。现有测试只覆盖“没有 delta”和“delta 已经完整”,漏了“部分 delta + 完整终态”。
lhk229 added 1 commit 2026-07-27 21:06:52 +08:00
Responses 终态快照同步修正流式回调
Project CI / Frontend tests (pull_request) Failing after 24s
Project CI / Repository checks (pull_request) Failing after 42s
Project CI / Backend tests (pull_request) Successful in 3m15s
Project CI / Native shell tests (pull_request) Successful in 9m51s
c086fb8656
902f487ea 引入终态正文快照时,「累加非空」一支只覆盖 accumulation.text、不补
回调。这一支当初只顾着避免正文翻倍,漏了「增量拼接结果本来就可能不等于快照」
——而这恰恰是决定要覆盖的唯一理由,两件事是耦合的。结果是 LlmRunResponse.text
已经是完整值,调用方最后收到的累计正文却停在半截。

实际代价:按累计正文取值的抢救路径(单 Agent 流式在 stream_run 返回 Err 时用
最后一次回调的 accumulated_text 当回复)会拿到半截正文,而「incomplete 且带
工具调用」会被截断拒绝规则判为 Err,恰好走到那里——正是 incomplete 降级回复
该发挥作用的场景。持久化回复流与 UI 实时视图同样停在半截,但 ready 写入用的是
response.text 派生值且直接覆盖 accumulated_text,所以是窗口期问题,不会造成
finalization 冲突或 needs-reconciliation。

改为按「是否等于快照」判断,不一致就补发一次回调。增量字段分三种情形取值:
累加去空白后为空给整个快照(这一支不能并进 strip_prefix,纯空白累加值匹配不上
前缀会退化成空增量,反而让按增量累加的消费者丢内容);快照是增量的延长给后缀;
非前缀关系给空串靠 accumulated_text 纠正。最后一种下按 delta_text 累加的消费者
无法自愈,作为已知残留写进契约文档。

边界:只有「快照延长增量」与「快照与增量分叉」两种情形的回调行为改变,其余
四种(累加为空、完全一致、无快照、纯空白累加)维持字节级不变。不给 Responses
打开 emit_finish_only_delta——那会让每一条流都多一次终态回调,是另一个决定。
LlmStreamDelta 结构、App 侧消费点均未改动。

隔离验证两次:退掉 emit 条件里的 snapshot_corrected,分叉用例转红(前缀延长
那条靠 has_delta 仍绿,说明该标志只对分叉承重);整块回退成旧两支形态,两条
新增用例都转红、其余 101 条全绿,印证边界表里「不变的四行」确实没动。

platform-llm 103 passed(原 101)。新增前缀延长、非前缀分叉两条,并收紧既有的
不重复用例——改为断言回调次数与 (delta_text, accumulated_text) 二元组,原先只
断言累计正文序列,补不补这次回调都可能是绿的。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lhk229 added 1 commit 2026-07-27 22:19:14 +08:00
修复分支审查发现的三处问题
Project CI / Repository checks (pull_request) Failing after 59s
Project CI / Backend tests (pull_request) Successful in 3m24s
Project CI / Frontend tests (pull_request) Successful in 3m47s
Project CI / Native shell tests (pull_request) Successful in 9m59s
57f355b7b6
一、流式工具槽位按 id 归位,消除终态载荷错位产生的重复调用

extract_responses_completed_tool_fragments 用 output[] 数组下标当槽位,而增量
路径用事件自带的 output_index。网关若在 completed 快照里省掉此前占用过某个
output_index 的 reasoning / message 条目,两者基准就错位,终态分片会落进一个
从未占用过的空槽位。空槽位上 merge_tool_identity 的 current 为 None、冲突检测
不触发,于是静默产出两条 id 完全相同的调用。

探针实测:output_index=1 的调用 + 只含一个 function_call 的 completed 载荷,
返回 Ok 且 tool_calls 为两条一模一样的 call_a/get_weather,零告警。

这说明 872a2f645 的身份冲突修复是不完整的——它只堵了「撞上已占用槽位」,没堵
「落进空槽位」。改为在 push_tool_fragment 里先按非空 id 归位到已有槽位:槽位只是
传输层归并键,真正的身份是 id。名字冲突仍由 merge_tool_identity 拦截,并进去之后
函数名不一致照常失败关闭。

下游影响:本仓库 agent_native_tools.rs:308 按 call id 唯一性校验,重复即报
CallIdentity 错误,所以现状是合法响应被误判成协议错误、空耗格式修复配额,不是
重复执行。但 platform-llm 是给 module-ai / module-story 等复用的基础 crate,
不能指望每个消费方自己去重,故在 crate 层修。

二、补回 check-native-shells 丢失的 App.tsx iframe 断言

85b9c2f19 合并 codex 时,check-native-shells.mjs 的 assertAiGameCreatorShell-
UserDevBoundary 双方独立重写产生冲突,当时判定「我方是上游的严格超集」并整段取
我方——这个判断是错的。上游有一条我方没有:App.tsx 全文不得直接出现 <iframe>,
预览必须委托给客户端工作台。我方版本只约束 DeveloperProjectPanels 自身的 iframe
数量与挂载位置,管不到外壳自己内嵌预览框。

补回该断言并反向验证:往 App.tsx 塞一个 iframe 后门禁确实报错,移除后通过。

三、流式工具校验失败分支落盘真实 attempt

stream_run 新增的四处工具调用后置校验失败分支(截断门禁、finish_tool_calls
失败、不完整终态拒绝、一致性断言)调用 log_llm_raw_failure 时把 attempt 硬编码成
字面量 1,同函数其余 12 处均传真实 attempt。重试后落盘日志全部标成第一次尝试,
排障时看不出真实次数。

隔离验证:停用按 id 归位后,两条新增用例转红;第三条(不同 id 的并行调用必须保持
两条)两边均绿,它守的是归并不得过头。

platform-llm 106 passed(原 103)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lhk229 added 1 commit 2026-07-28 11:34:43 +08:00
按 id 重绑收窄到终态快照分片
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Successful in 2m48s
Project CI / Native shell tests (pull_request) Successful in 9m31s
2fab4db4cc
57f355b7b 为修复终态载荷槽位错位引入了「按 id 归位」,但归位条件写成了无差别的
「同一个 id 落到两个槽位就合并」。注释里那句「只可能是上游用了不同的槽位基准」是个
不成立的穷尽性断言,代码照着它写,于是漏掉了第二种成因:上游自己重复使用 call id。

两种实测后果:

- 同一个终态载荷内两条相同 call_id、相同函数名、不同参数:被并成一条,后到的参数
  覆盖先到的,静默丢掉一次调用。修复前流式返回两条(与非流式一致,由调用方的
  call id 唯一性校验拒绝),修复后变成一条且参数是后到的那份——用「调用方拒绝」
  换来了「平台静默挑一份参数」,这个交换是亏的。
- 两次 output_item.added 用同一个 call_id:第二次被重绑到第一个槽位,它自己的参数
  事件随后落到一个没有身份的空槽位上,最终报 Err(Deserialize("缺少 id:slot=1"))
  ——失败关闭但完全指错方向,排查的人会去查为什么没 id,而那个槽位没 id 恰恰是被
  重绑逻辑拿走的。

改判据:ToolCallFragment 新增 from_terminal_snapshot,只有终态快照
(response.completed / incomplete 载荷)的分片允许按 id 重绑,因为只有它是对**已宣告
调用的重述**;增量宣告(output_item.added / content_block_start / Chat delta)永远是新
调用,绝不重绑。快照内部自身重复的 id 另行排除。

不用「是否跨事件」当判据:那只能挡住同事件形态,挡不住上面第二种跨事件形态。

平台层不承担 call id 唯一性判定。重复 id 原样透传,与非流式一致,由调用方统一拒绝
——重复 id 是内容层问题,透传下去调用方的格式修复循环才拿得到 call id、函数名和原始
参数把响应回灌给模型重写,在平台层报错会把这三样一起丢掉。与非流式半截 JSON 透传
同一条理由。

隔离验证两级:去掉 from_terminal_snapshot 门槛,跨事件用例转红;再去掉同事件重复
排除(回到 57f355b7b 状态),两条用例都转红。

platform-llm 109 passed(原 106)。新增同事件重复 id、跨事件重复 id 两条流式用例,
外加一条非流式同构载荷用例锁住两条路径的契约一致。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lhk229 added 1 commit 2026-07-28 11:49:32 +08:00
合并 codex/ai-game-creator-app 手机号登录契约修复
Project CI / Repository checks (pull_request) Failing after 53s
Project CI / Frontend tests (pull_request) Successful in 2m55s
Project CI / Backend tests (pull_request) Successful in 3m19s
Project CI / Native shell tests (pull_request) Successful in 9m51s
8b3f8b422e
lhk229 added 1 commit 2026-07-28 13:31:27 +08:00
Merge branch 'codex/ai-game-creator-app' into fix/anthropic_tool
Project CI / Repository checks (pull_request) Successful in 1m8s
Project CI / Frontend tests (pull_request) Successful in 3m6s
Project CI / Backend tests (pull_request) Successful in 3m19s
Project CI / Native shell tests (pull_request) Successful in 10m30s
9acdfd0454
kdletters merged commit d29240b954 into codex/ai-game-creator-app 2026-07-28 13:33:50 +08:00
kdletters deleted branch fix/anthropic_tool 2026-07-28 13:33:50 +08:00
Sign in to join this conversation.