8.9 KiB
多智能体「后台挂起」—— 当前状态、遗留问题与下次继续指南
配套设计文档:
multi-agent-background-run-dev.md(分 Phase 0-9 的总方案)。 本文记录截至本次会话的实际进展 + 还没解决的遗留问题,方便下次直接续接。
一、整体状态:跑通到哪了
「后台挂起」= Step2 把研讨交给后端进程内驱动真模型(leader→各席位→共识→报告), survive 刷新/关页,对话与报告作为一个 run 写进草稿(和手动跑同一存取),可在历史回看。
真网关已在本地验证可跑通(2026-06-04 日志确认:roundtable-coordinator + 各席位 + roundtable-report 真调 deepseek、真搜索、跑到 report run success)。但还有几个内容/产物层面的遗留问题(见第三节)。
已完成(可用)
- ✅ 后端持久化:
roundtable_jobs表(迁移20260604_01/20260605_01dialogues /20260605_02step3)。 - ✅ 编排引擎
packages/harness/deerflow/agents/roundtable_orchestrator/(recommend + chain + 报告,纯逻辑可单测)。 - ✅ 控制面
app/gateway/routers/roundtable_jobs.py:start/get/by-draft/stream(SSE)/resume/cancel/admin-cleanup。 - ✅ 执行器
app/gateway/roundtable_job_executor.py:后台任务 +set_current_user+ 生命周期 + 每轮把对话作为job-<id>run 写进draft.step2.runs[](同手动存取) + done 写 step3。 - ✅ 真网关
app/gateway/roundtable_inprocess_gateway.py:SimpleNamespace(app)+start_run进程内驱动 agent;复用multi_agent._extract_dispatch解析派活;checkpointer 读最终 messages。 - ✅ 模拟网关
roundtable_orchestrator/simulated_gateway.py(不调真模型,前端联调用)。 - ✅ 前端:
api/roundtable-jobs.ts、lib/job-types.ts、Step2「后台挂起」按钮、历史下拉 loading、DispatchChainModal/DispatchChainJobModal(流程图 + 对话面板 markdown + 加宽 + 澄清作答 + 取消 + 查看)。 - ✅ 上下文「广播」(inline):席位/总控 prompt 带上「任务背景 + 前序各席位交付 + 别提问」约束(修「席位拿不到关键信息、反问用户、总控瞎编」)。
- ✅ 后端测试:
tests/test_roundtable_jobs.py/test_roundtable_orchestrator.py/test_roundtable_job_executor.py/test_roundtable_jobs_api.py全过。
二、怎么启动 / 验证(每次开工先做)
-
开真网关(默认是模拟网关):已写进
offline-backend-20260512/.env:ROUNDTABLE_JOB_GATEWAY=inprocess启动日志应出现
[roundtable-jobs] gateway = inprocess (real models)。 -
必须用
make gateway(不带 --reload)跑后端!make dev的--reload监视backend/目录,agent 运行时往.deer-flow/写文件 (memory.json / checkpoint / 沙箱输出)会触发热重载,把长时后台任务杀掉。 (改进项:给 uvicorn 加--reload-exclude '.deer-flow/*',或始终用make gateway。) -
直接查库验证(最有效,PowerShell 控制台是 GBK,导出到文件看中文):
cd offline-backend-20260512/backend # 查最新作业的 dialogues / step3 PYTHONPATH=. uv run python -c "import sqlite3,json; con=sqlite3.connect('.deer-flow/data/deerflow.db'); ..."- 作业表:
.deer-flow/data/deerflow.db→ 表roundtable_jobs(dialogues/step3列)。 - 草稿表:
roundtable_drafts→step2列里应有runs[ {id:"job-<jobId>", jobId, jobStatus, step2RoundtableDialogues:[...], step3:{...}} ]。
- 作业表:
-
前端验证:后台挂起 → 等跑完 → 从历史加载该草稿 → Step2 应显示后台研讨完整对话(走现有
normalizeStep2Runs/hydrateStep2Run)。
三、遗留问题(下次重点,已按优先级排)
P0 —— 报告 agent 不写 HTML 文件(结果绘制拿不到产物)
- 现象:后台报告 run 跑完,日志
report html: no .html found in ...threads/<id>/user-data/outputs;run.step3.html回退成报告 agent 的纯文字(不含<html>),第3步画不出东西。 - 已做:
build_report_prompt(roundtable_orchestrator/engine.py)已对齐前端useStep3Report.buildReportPrompt,明确「用 write_file 写入 /mnt/user-data/outputs/方案总览.html」;_read_report_html(roundtable_inprocess_gateway.py)已改为扫 outputs 目录任意.html。 - 仍没解决:报告 agent(deepseek-chat)还是没调
write_file。 - 下次怎么查:
- 对比前端那条能写出 HTML 的报告 run 的确切调用(
useStep3Report.ts里streamMultiAgent调/api/multi-agent/run/stream的 body:agent_type/excluded_tools/context等), 和后台_run_agent_turn(roundtable_inprocess_gateway.py,agent_name=roundtable-report,excluded_tools=_REPORT_EXCLUDED)逐项比对,找出差异(怀疑 reasoning_effort / thinking / 某个 context 字段 / recursion 步数导致它没走到 write_file)。 - 看报告 run 的最终 messages(checkpoint)里有没有 write_file 的 tool_call(被 dangling 截断?)。
- 报告 agent 的 SOUL:
app/gateway/routers/_roundtable_seed/roundtable-report/SOUL.md——确认它默认行为。
- 对比前端那条能写出 HTML 的报告 run 的确切调用(
P1 —— 内容质量(本次刚加 inline 广播,待真机验证)
- 已做:席位/总控 prompt 注入「任务背景 + 前序交付 + 别提问」。
- 待验证:真机跑一次,看席位是否基于真实上下文产出(不再反问/凭记忆瞎编)。
- 若 inline 不够:考虑实现真·thread 广播(像
multi_agent._append_thread_message那样把 「用户意图 / 派活 / 各席位交付」追加进各 thread 历史)。进程内要避开 HTTP loopback 鉴权, 得用 checkpointer 直接 append human 消息(参考threads路由 POST/state的内部逻辑)。这块较重,先看 inline 够不够。
P2 —— 对话「没有步骤(steps)」
- 现象:手动跑的气泡带工具调用步骤卡(
onPhaseChange→appendStepIfNew→Step2Dialogue.steps); 后台对话只存了最终正文,没有 steps。 - 下次怎么做:后台
_run_agent_turn跑完后,从 checkpoint 的 messages 里抽出 tool_calls, 映射成Step2Dialogue.steps({id,key,label,ts,args}),在执行器_to_step2_dialogue里带上。 纯展示增强,不影响内容/报告。
P3 —— 双跑浪费 + --reload 坑(工程健壮性)
- 进 Step2 时前端会自动跑一遍本地研讨(真模型、真线程);点后台挂起后前端那条 run 在后端还会跑完
(
stopOrchestration只断客户端、停不了后端 run)→ 和后台作业短暂双跑、双倍 token。- 优化方向:进 Step2 改成「显式开始」而非自动跑;或后台挂起时真正 cancel 前端那条 run。
make dev的--reload会杀后台任务(见第二节);改进项:--reload-exclude '.deer-flow/*'。
四、关键文件清单(下次定位用)
后端
| 文件 | 作用 |
|---|---|
app/gateway/roundtable_inprocess_gateway.py |
真网关:进程内驱动 leader/seat/re 编排循环 + build_report_prompt + inline 广播(_seat_message/_deliveries_block) |
.../roundtable_orchestrator/progress.py |
进度落库 + on_dialogue 每轮回调 |
.../persistence/roundtable_jobs/ |
job 表 model/sql |
app/gateway/deps.py |
ROUNDTABLE_JOB_GATEWAY 选网关 + 注入 executor(draft_store) |
前端
| 文件 | 作用 |
|---|---|
src/roundtable-planning/api/roundtable-jobs.ts |
作业 API + SSE |
src/roundtable-planning/lib/job-types.ts |
类型(RoundtableJob/JobDialogue/...) |
src/roundtable-planning/components/DispatchChainModal.tsx / DispatchChainJobModal.tsx |
流程图弹窗 + 对话面板 |
src/roundtable-planning/pages/RoundtablePlanningPage.tsx |
后台挂起按钮、历史下拉、runs[] 多运行模型(hydrateStep2Run/getStep2SnapshotWithRuns/normalizeStep2Runs) |
src/roundtable-planning/api/drafts.ts |
DraftStep2RunSnapshot(带 jobId/jobStatus/step3) |
五、关键架构事实(避免重复踩坑)
- 真网关进程内驱动:
start_run(body, thread_id, SimpleNamespace(app=app))+set_current_user, 不走 HTTP(loopback 调/api/multi-agent/*不可行:_auth_headers只转发用户 token + owner check)。 - 后台作业首启自建全新线程(
StartParams.is_resume),绝不复用前端本地研讨的线程(会撞 409)。 - 存取统一:后台作业 =
draft.step2.runs[]里一个带 jobId 的 run,和手动跑同一存取/hydrate 路径, 不要再开 observe/回显分支(之前试过,被否)。 - 聊天记录确实存上了(job.dialogues + draft 的 run);之前"没存上"是测了旧代码 / 看的是前端本地残缺数据。
- 多 worker / SSE:状态进 DB、SSE 走 DB 轮询;真网关进程内无 loopback。