解决platform-llm和策划agent reasoning问题,并修复若干bug #350
Reference in New Issue
Block a user
Delete Branch "opt/design_agent"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
WIP: 解决策划agent reasoning问题to WIP: 解决策划agent reasoning问题,并修复若干bugWIP: 解决策划agent reasoning问题,并修复若干bugto WIP: 解决platform-llm和策划agent reasoning问题,并修复若干bug审查意见
整体结论:改动目标清晰(Provider reasoning 与正文分离 → 策划 Agent 展示),实现、测试、文档(
platform-llmREADME 第 5 条、技术方案第 15 节、decision-log 补充、里程碑计划文档)同步齐全。已验证:cargo test -p platform-llm --lib153 个测试全部通过,npm run check:encoding与git diff --check干净。reasoning 不进delta_text/accumulated_text/ 正式 assistant message / 会话 history,GameAgent、Direct/Codex 与通用 response stream 以空串填充保持行为不变,边界守得好。以下问题建议处理后再合入:
1.【主要】多段 reasoning summary 会被
.done快照整体覆盖而丢段server-rs/crates/platform-llm/src/lib.rsconsume_stream_events中的快照应用逻辑:response.reasoning_summary_text.done是每个 summary part 各发一个,快照只携带该 part 的完整文本(事件里带summary_index,当前解析未使用)。当一次响应有多段 summary 时:part1 的 delta 已累加 → part2 的 delta 继续追加 → part2 的 done 到达时snapshot(仅 part2 文本)不等于累计值(part1+part2),strip_prefix失败 →reasoning_delta变成空串,且accumulation.reasoning被覆盖成只剩 part2,part1 的推理文本从累计值和流式 UI 中消失。终态
response.completed的快照是全部段落拼接,通常能把最终值修正回来;但如果网关只转发增量事件、终态 output 里 reasoning 被省略(encrypted/redacted 场景),最终response.reasoning会永久丢段。建议:只在
accumulation.reasoning.is_empty()或snapshot.starts_with(&accumulation.reasoning)时应用快照,否则忽略该 done 快照、信任已累加的 delta;或按summary_index分段跟踪再拼接。无论哪种,都建议补一个“两段 summary + done”的回归测试。2.【次要】
#[allow(dead_code)]保留的兼容包装parse_chat_completions_response/parse_responses_response现在只剩测试调用,非测试构建靠#[allow(dead_code)]压制。按仓库“四不写”约定,建议直接删掉这两个包装,存量测试改用_with_capture(..., false, ...);或至少改成#[cfg(test)]限定,避免给后续读者留下“这是公开保留入口”的错觉。3.【次要】Anthropic 路径对
capture_reasoning静默无效parse_anthropic_response完全忽略 capture 开关(thinking 块不进reasoning字段)。当前策划 Runtime 不走 Anthropic 没问题,但如果后续允许 Anthropic provider,UI 会永远没有思考过程且无任何信号。建议在 README 第 5 条或代码注释里点明“Anthropic 暂不捕获 reasoning 是刻意范围”。4.【观察项】
register_design_artifacts_at的 changed 判定不含文件内容assets.rs的 changed 只比较kind/media_type/source元数据。若策划 Agent 重写了design_artifacts下已有文件的内容(路径、kind、media_type 均不变),切换到 game 时不会推进 revision。如果 revision 消费方只关心 manifest 条目本身则无影响,请确认这是预期语义;否则需要考虑内容哈希或 mtime 参与判定。5.【观察项】
designAgentEventSubscriptionReady的时序兜底App.tsx中 effect 清理会把 ready ref 置 null,之后再调用designAgentEventSubscriptionReady()会创建一个在下一次订阅 effect 运行前永不 resolve 的新 promise。现有调用点(continue / decide_design_phase)都在策划界面激活期内,实际不会触发;后续新增调用点时请注意这个隐含前提。其余部分(前端打字机收尾后再落地 view 的 pending 机制、重试/失败路径清空 reasoning、历史 reasoning 按回合内响应顺序配对、UAC 修复与本进程 harden 决策、重复切换不重复推进 revision)实现与测试都对得上,LGTM。
重审(9e4f06b3e,含 4 个修复提交)
上一轮意见的处理情况逐条核对:
summary_index分段独立累计(reasoning_summary_parts: BTreeMap<u64, String>),单段.done只校正对应段,不再整体覆盖;回归测试stream_run_keeps_multiple_responses_reasoning_summary_parts还特意让终态response.completed不带 reasoning,验证不依赖终态快照恢复。README 第 5 条已同步口径。capture_reasoning已透传进 Anthropic 路径,非流式解析thinkingblock、流式解析thinking_delta,并各有开关开/关的回归测试。capture_reasoning参数。designAgentEventSubscriptionReady()改为只读当前代次(?? Promise.resolve()),promise 创建收归订阅 effect,清理窗口先重建下一代 promise 再挂起等待——避免了清理后业务调用自建悬挂 promise 的问题。验证:
cargo test -p platform-llm --lib156 个测试全部通过(净增 3 个),npm run check:encoding、git diff --check干净,PR 文件清单与上轮一致(28 个文件),merge master 未引入额外行为变化。本轮新发现(均不阻塞合入)
【次要】
summary_index跨 reasoning item 会串段。 Responses 一次响应可以产出多个 reasoning item(典型场景:function_call 前后各一段推理),而reasoning_summary_text.*事件的summary_index是每个 item 内重新从 0 计,事件里真正区分 item 的是item_id(当前解析未读)。两个 reasoning item 的 part 0 会在BTreeMap里撞到同一个 key,第二段的增量会拼到第一段的 part 里,流式中间态的拼接顺序和内容都会错。缓解因素:终态response.completed的整量快照 + 策划 Runtime 在Ok(response)时无条件再发一次最终 reasoning,最终落盘/展示值能自校正,只有流式中间过程可能短暂错乱。建议后续把累计 key 改成(item_id, summary_index),或在 README 注明当前只保证终态正确。【观察】单段快照“替换式校正”不发增量事件。 分段路径里若
.done快照不是当前段累计值的前缀延伸(Provider 改写而非续写该段),strip_prefix得到空 delta,段内容被替换但不产生 reasoning 事件,前端中间态要到回合结束才纠正。发生概率低,且有最终值兜底,仅记录。【遗留·待确认】 上轮第 4 条(
register_design_artifacts_at的 changed 判定不含文件内容,内容重写但元数据不变时切换 game 不推进 revision)本轮未见处理。如果确认 revision 消费方只关心 manifest 条目、内容变化无需触发前端清单刷新,回复确认即可关闭此条。结论:上轮 4 个阻塞/次要项均已妥善解决,剩余为新发现的小边界与一条待确认观察项,LGTM,可以合入(建议合入后把
(item_id, summary_index)串段问题记为后续事项)。第三轮重审(ea16e49ac)
上轮两条新发现的处理情况:
(item_id, summary_index),并用reasoning_summary_item_order记录 item 首次出现顺序,拼接时按 item 顺序 + 段索引排序,缺失item_id的事件归入统一默认桶。回归测试stream_run_keeps_reasoning_parts_separate_across_items覆盖两个 item 同summary_index=0的场景。README 口径已同步。reasoning_snapshot_corrected并纳入两个发射条件,即使增量为空也会触发一次携带新accumulated_reasoning的回调,测试stream_run_notifies_when_reasoning_snapshot_replaces_non_prefix_part验证了「累计值已替换、增量为空」的通知形态。验证:
cargo test -p platform-llm --lib158 个测试全部通过(净增 2 个),npm run check:encoding、git diff --check干净,PR 仍为 28 个文件,merge master 只带入 CI/端口修复,无行为交叉。本轮唯一残留(小边界,不阻塞)
reasoning_snapshot_corrected的通知在策划 Runtime 消费侧仍被门槛挡掉。design_runtime.rs流式回调里 reasoning 事件的发射条件是!delta.reasoning_delta.is_empty(),而快照替换通知的形态恰好是「reasoning_delta为空、accumulated_reasoning已更新」——platform-llm 这层发出的校正回调会被这里丢弃,前端仍要等回合结束Ok(response)的最终 reasoning 事件才纠正。建议把发射条件放宽为「reasoning_delta非空 或accumulated_reasoning与上次发射值不同」,让 platform-llm 新增的校正语义真正贯通到 UI。Provider 流式中途改写 summary 段的概率很低,所以这条可以合入后随手补,也可以现在一行改掉。待确认遗留
第一轮第 4 条(
register_design_artifacts_at的 changed 判定不含文件内容)目前仍未处理也未回应,等确认是预期语义即可关闭。结论:LGTM,可以合入。 三轮审查的所有阻塞项与次要项均已闭环。
关于前几轮审查中遗留的待确认项(
register_design_artifacts_at的 changed 判定不含文件内容、内容重写但元数据不变时切换 game 不推进 revision):经 PR 作者确认为预期语义——revision 消费方只关心 manifest 条目本身,此条关闭,后续审查不再跟踪。第四轮重审(6ded5b15)
上轮唯一残留的修复确认:
✅ 快照校正转发门槛:
design_runtime流式回调现在按「reasoning_delta非空 或accumulated_reasoning与上次发射值不同」发射 reasoning 事件,空 delta 的快照校正不再被消费侧丢弃,同时重复累计值不会重复发事件。实现与建议口径一致。验证:
cargo check --tests通过,cargo test --bin genarrative-ai-game-creator-shell design_runtime12 个测试全部通过,npm run check:encoding、git diff --check干净。至此三轮审查提出的全部问题均已闭环,无剩余事项。LGTM,可以合入。