Files
mnote/bugs/07-ai/done/page-ai-block-edit-model-fallback-missing-content.md
T
lix-2026 a2cb1338c8 chore: 保存当前架构收口与 bug 修复快照
归档本轮 P0/P1 bug 修复、设计审查迁移、AI selection scope 收口与 stream contract 调整,并保留当前 05 主线迁移起点。
2026-05-18 17:01:35 +08:00

5.8 KiB
Raw Blame History

页面 AI 块编辑 — model 主路径操作缺少 contentdirect path 架构定位错误)

发现时间:2026-05-16

更新时间:2026-05-18(运行时收口验证)

状态:[done]

关联主线:07-ai / 05-editor-mainline

关联设计稿:design/07-ai/done/7-13-page-block-editor-runtime-actor-v1.md

关联代码:

  • rust/crates/mnote-web/src/routes/page_ai_workflow.rs — block_edit_workflow, direct_block_edit_operations(应退役), call_block_edit_model(唯一正确路径)
  • rust/crates/mnote-web/src/hermes_tools/block.rs:436 — doc_apply_block_ops, replace 操作缺少 content

症状

用户用自然语言在页面 AI 面板输入块编辑指令时,mnote.page_ai.block_edit_workflow 返回 400

replace 操作缺少 content

复现步骤

  1. 打开任意文档
  2. 点击右下角「AI 助手」
  3. 输入自然语言指令,如 把第一段文字改成:AI成功修改了这一段。
  4. POST /api/page-ai/block-edit-workflow 返回 400
  5. 页面上显示 mnote.page_ai.block_edit_workflow 失败

架构问题:两条路径的设计是错误的

当前 block_edit_workflow 有两条路径,但正确的路径只有一条

唯一正确路径:Model pathcall_block_edit_model

用户自然语言 → 模型理解语义 → 产出 operations JSON → doc_apply_block_ops → 写入

这是 AI 面板应有的行为:用户说人话,模型理解意图,产出操作。这是唯一主路径

应退役路径:Direct pathdirect_block_edit_operations

用户特定格式 → 正则抠「」内文本 → 直接拼 operations → 写入

这不是 AI,这是命令行。它要求用户按固定格式输入(「」引号),本质是把 AI 面板当成 shell 在用。当前它"能用"只是因为绕过了模型调用,看起来"快",但:

  • 不能处理自然语言("把这段话改简洁一些")
  • 不能批量推理("把所有 TODO 改成已完成"
  • 不能跨块理解("把第一段和第二段合并")
  • 和 AI 对话的本意完全背离

结论direct path 应该退役,model path 是唯一主路径。

当前 model 主路径的具体缺陷

系统 promptpage_ai_workflow.rs:274)只说了 op 四选一,没有告诉模型每种 operation 需要的字段

当前 prompt:
"operations 的 op 只能是 replace、insert_after、delete、move_after。优先使用 page_xml 中的 block id;禁止输出解释文字。"

缺少的信息:
- replace 需要 blockId(或 matchText+ content
- insert_after 需要 blockId(或 matchText+ content
- delete 只需要 blockId(或 matchText
- move_after 需要 blockId + targetBlockId

模型不知道 schema,自然会漏掉 content 字段。DeepSeek v4 Flash 的 response_format: json_object 只保证输出是合法 JSON,不保证字段齐全。

更深层问题:块操作粒度是否合理

当前 AI 编辑通过 operations: [{ op: "replace", blockId: "...", content: "..." }] 这种逐个块操作的方式执行。但:

  1. mnote 的在线文档本质上是一个 markdown 文件,Tiptap 只是其块级 UI 表现层
  2. 本地 md 文件模式下,Tiptap 退化为纯显示层,编辑直接在 markdown 文本上进行
  3. 在线文档应该向本地文档靠拢——对 AI 而言,最自然的编辑方式是"给我一段 markdown,我返回修改后的 markdown",而不是"给我 blocks 数组,我逐个块产出 op"

如果在线文档和本地文档走两套 AI 编辑口径(一套块操作、一套文本操作),长期维护成本翻倍。

建议修复方向

立即修复(让 model 主路径可用)

  1. 补全 system prompt 的 operation JSON schema:明确 replace/insert_after 需要 content 字段,给出完整示例

架构收口(应该做的)

  1. 退役 direct pathdirect_block_edit_operations + quoted_segments 整条路径标记 deprecated
  2. 考虑降低块操作粒度AI 编辑是否可以走 markdown diff 而非逐个块 ops?在线文档和本地 md 能否共用同一条 AI 写入路径?
  3. 在线/本地口径收敛:在 design/07-ai/ 中明确在线文档 AI 编辑应向本地 md 的简洁模型靠拢

证据

  • Browser smoke (2026-05-16)
    • Direct path(「」quote,本质是命令行): 177ms — 这说明不了 AI 能力,只是正则匹配
    • Model path(自然语言,真 AI: 400replace 缺少 content
  • 代码:page_ai_workflow.rs system prompt 缺 operation schema
  • 用户反馈:direct path 不是 AI 面板应有的行为,应退役

运行时修复

  • page_ai_workflow.rs 当前运行时已退役 direct path,所有块编辑请求统一走模型输出 search/replace 对,再进入 mnote.doc.markdown_edit
  • page_ai_workflow.rs system prompt 已改为明确要求 operations[].searchoperations[].replace,不再要求模型输出旧的 block op content 字段。
  • page_ai_workflow.rs fast-path 写入已改走统一 mnote tool executor,避免绕过 tool toggle / audit / idempotency。

验证

  • cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_workflow -- --nocapture
    • 结果:3 passed
  • cargo test --manifest-path rust/Cargo.toml -p mnote-web markdown_edit -- --nocapture
    • 结果:8 passed

剩余说明

旧的 direct_block_edit_operations / quoted_segments helper 当前仅作为 dead code 与历史单测保留,不再是 block_edit_workflow 的运行时入口;后续可单独清理测试语义,但不影响本缺陷的运行时修复。