From 9f462beec5d0dbb3f29554191b378197369333ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Fri, 26 Jun 2026 23:31:48 +0800 Subject: [PATCH] =?UTF-8?q?fix(ai=E5=86=99=E4=BD=9C):=20=E7=BB=A7=E7=BB=AD?= =?UTF-8?q?(=E6=9A=82=E5=81=9C=E5=90=8E)=E4=BB=8Echeckpoint=E7=BB=AD?= =?UTF-8?q?=E8=B7=91=E8=80=8C=E9=9D=9E=E4=BB=8E=E5=A4=B4=E9=87=8D=E8=B7=91?= =?UTF-8?q?=E6=84=8F=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:resumeWriting 的 stream.submit(undefined) 没带 command。后端 worker (runtime/runs/worker.py:283) 只有收到 command 才翻成 Command() 让 Pregel 从最近 checkpoint 恢复;不带 command 时按 graph_input 起新一轮=从图入口 intent_parser 重跑,于是"规划大纲/检索素材时暂停后继续又从识别意图开始"。 修复:继续时带空 command:{},走 Command 恢复路径(与4个干预卡续跑同机制), Pregel 从 checkpoint 续跑当前未完成阶段(已完成阶段不丢),不再重跑 intent。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/open-canvas/contexts/AIWritingContext.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend-web/src/open-canvas/contexts/AIWritingContext.tsx b/frontend-web/src/open-canvas/contexts/AIWritingContext.tsx index 92d797ab..0b6834c4 100644 --- a/frontend-web/src/open-canvas/contexts/AIWritingContext.tsx +++ b/frontend-web/src/open-canvas/contexts/AIWritingContext.tsx @@ -528,8 +528,14 @@ export function AIWritingProvider({ children }: { children: ReactNode }) { dispatch({ type: 'pause' }); }, []); - // 「继续」:清掉当前阶段流式缓冲后,从最近的 checkpoint 重新驱动 graph 续跑 + // 「继续」:清掉当前阶段流式缓冲后,从最近的 checkpoint 恢复 graph 续跑 // (会重跑当前未完成的阶段,已完成阶段不丢)。同时用于断网后 run 已死的兜底恢复。 + // + // **关键**:必须带一个 `command`(哪怕是空对象),后端 worker 才会把它翻成 + // `langgraph.types.Command` 让 Pregel **从最近 checkpoint 恢复**(与 4 个干预卡续跑同机制); + // 不带 command 时 worker 按 graph_input **起新一轮**=从图入口 intent 重跑(就是之前 + // "暂停后继续又从识别意图开始" 的根因,见 runtime/runs/worker.py 注释)。空 Command + // 没有 interrupt 可消费 → Pregel 直接续跑 checkpoint 里挂起的当前阶段节点。 const resumeWriting = useCallback(() => { const stream = streamRef.current; if (!stream) { @@ -541,6 +547,7 @@ export function AIWritingProvider({ children }: { children: ReactNode }) { void (async () => { try { await stream.submit(undefined, { + command: {}, streamMode: [...WRITING_STREAM_MODE], ...RESILIENT_RUN_OPTS, ...(resumeModelName ? { config: { configurable: { model_name: resumeModelName } } } : {}),