feat: taskId 深链会商按任务隔离草稿 + 第三步自动结构化流程图
- 深链 ?taskId=&goPath=*-A 透传 taskId 进会商,按 taskId 隔离存取草稿(一个 taskId 多条、可新建);
后端新增 GET /api/roundtable-drafts/by-task/{id} 列表 + RoundtableJobRepository.status_map_by_drafts 富化。
- 第三步生成 md 报告后自动结构化输出流程图(仅 taskId 模式):有报告基于报告、无报告基于各席位交付,
整段内联数据来源逐条抽取;层级优先用业务链总控提示词写明的层级,否则用 stages 的 goal。
- 流程图节点加 group(层级名)标签 + detail(条目名称+理由);强 prompt 压制 coordinatorPrompt 报告锚定,
禁阶段编号节点;FLOW_STRUCTURE_RULE 精确画法(根=任务、每层每条成节点、按对应关系串分支、排除汇总段落)。
- flow-graph 解析容错:JSON.parse 失败退回正则逐字段抽取,兼容裸换行/尾逗号,避免节点被整体丢弃。
- 修:重开第二步后第三步基于新一轮分析重新生成(复位 step3 闸门 + 中断旧三流)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e6a41dbc97
commit
fca39eeaee
17 files changed
+472
-37
No files matched your search
@@ -57,6 +57,17 @@ interface UseStep3SummaryOptions {
|
||||
selectedModel: string;
|
||||
/** 业务链写给总控的编排提示 —— 作为最高优先级口径注入总结智能体。 */
|
||||
coordinatorPrompt?: string | null;
|
||||
/**
|
||||
* 业务链条层级(取自 stages 的「本阶段目标」goal,有序)—— taskId 深链模式下
|
||||
* 自动结构化输出时,作流程图的层级骨架(任务 → 层级1 → 层级2 → …)。
|
||||
* 例:链层级 目的→行为体→驱动因素 → 流程图 任务→目的→行为体→驱动因素。
|
||||
*/
|
||||
chainLevels?: string[];
|
||||
/**
|
||||
* taskId/深链模式:生成 md 报告后**自动**续跑一轮结构化输出(流程图)。
|
||||
* 普通手动会商为 false —— 结构化输出仍按需(用户输入框打「流程图」才出)。
|
||||
*/
|
||||
autoFlow?: boolean;
|
||||
/** 草稿里已存的上次总结结果 —— 有则进入时恢复展示,不重新生成。 */
|
||||
savedSummary?: DraftStep3SummarySnapshot | null;
|
||||
/** 一次总结成功完成时回调(传出 md/thread/model/时间/收尾语),供上层存草稿。 */
|
||||
@@ -66,6 +77,8 @@ interface UseStep3SummaryOptions {
|
||||
export interface UseStep3SummaryReturn {
|
||||
phase: SummaryPhase;
|
||||
error: string | null;
|
||||
/** taskId/深链模式:md 报告生成后自动续跑流程图(组件据此让流程图画布自动聚焦)。 */
|
||||
autoFlow: boolean;
|
||||
summaryThreadId: string | null;
|
||||
dialogues: Step2Dialogue[];
|
||||
seatStubMessages: Record<string, StubAiMessage[]>;
|
||||
@@ -152,6 +165,117 @@ function buildMaterials(opts: {
|
||||
return parts.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* flow-json 形状契约(内联)。roundtable-summary 的 runtime SOUL 为空,模型拿不到契约,
|
||||
* 所有要画流程图的 prompt 都得带上它,否则模型产出的 JSON 可能解析不出节点。
|
||||
*/
|
||||
const FLOW_JSON_SHAPE =
|
||||
"flow-json 严格用下面这个形状(一个 JSON 对象,`nodes` 是节点数组;示例仅示意结构、勿照抄内容):\n" +
|
||||
"```flow-json\n" +
|
||||
'{"title":"流程图标题","nodes":[\n' +
|
||||
' {"id":"n1","label":"任务简短名","type":"start","group":"任务","detail":"任务目标的完整描述"},\n' +
|
||||
' {"id":"n2","label":"目的核心短语","type":"task","group":"目的","detail":"该目的的完整内容:名称 + 理由全文","from":["n1"]},\n' +
|
||||
' {"id":"n3","label":"行为体核心短语","type":"role","group":"行为体","detail":"该行为体的完整内容:名称 + 理由全文","from":["n2"]}\n' +
|
||||
"]}\n" +
|
||||
"```\n" +
|
||||
"字段:`id`(n1/n2… 唯一短串)、`label`(≤12 字,节点标题,取条目核心短语)、" +
|
||||
"`type`(取 start/end/stage/task/decision/risk/deliverable/role 之一,仅决定配色)、" +
|
||||
"**`group`(该节点所在的层级名称,如 任务 / 目的 / 行为体 / 预期行为 / 驱动因素 / 叙事——画布按它给节点打标签,必须填)**、" +
|
||||
"**`detail`(该条目的名称 + 理由 / 说明,点击节点时展示——要把要点表达完整,但控制在约 2–4 句,**不必**逐字照抄报告整段)**、" +
|
||||
"`from`(前驱节点 id 列表,只引用**前面已出现**的节点)。" +
|
||||
"节点按先后排列、输出**全量**数据、全程简体中文。";
|
||||
|
||||
/**
|
||||
* 「本轮只画流程图、不写报告」的强约束。关键:有的业务链 coordinatorPrompt 自身写着
|
||||
* 「输出结构化结论 / 按阶段产出报告 / 最终方案总结报告按层级结构输出」,模型在同一线程里
|
||||
* 会被它锚定,把用户的「结构化输出」当成「继续写结构化的文字报告」→ 产出 Markdown 而非
|
||||
* flow-json(实测 deepseek-chat 在这类链上必现)。所以必须显式声明本轮压过那些报告措辞。
|
||||
*/
|
||||
const FLOW_ONLY_RULE =
|
||||
"【硬性·本轮只画流程图】本轮唯一产物是一张**流程图**(由节点 + 连线组成的图),不是文字、不是报告、" +
|
||||
"不是章节、不是结论清单、不是 Markdown:①**不要**调用 write_file、**不要**写或改任何 .md 文件;" +
|
||||
"②回复里**不要**再出现任何报告正文 / 章节标题(如「## 四…」)/ 编号结论,正文最多一句话说明;" +
|
||||
"③**忽略**此前关于「方案总结报告」「按层级结构输出报告」「输出结构化结论」「分阶段产出」的一切措辞——" +
|
||||
"那些是给上一轮文字报告用的,本轮一律不适用;④在回复**最末尾**输出一个**完整**的 ```flow-json 代码块。";
|
||||
|
||||
/**
|
||||
* 流程图的层级画法(修「结构画错」):直接从已有报告/席位交付里**提取实体**,
|
||||
* 根=任务名称,每层取具体条目当节点,按报告里的对应关系串成分支,排除汇总/阶段名等无关段落。
|
||||
*/
|
||||
const FLOW_STRUCTURE_RULE =
|
||||
"【流程图画法·严格遵守】\n" +
|
||||
"① 根节点(n1) = 本次任务本身(即报告「任务总览」里的任务名称 / 目标),label 用任务的简短名称(≤12 字)," +
|
||||
"**不要**用「任务」「目标」这种泛称当 label;其 `group` 填「任务」;\n" +
|
||||
"② 从第二层起按层级顺序逐层展开:每个层级在报告/交付里通常列了**多条具体条目**" +
|
||||
"(如『目的』层有 目的1 / 目的2 / 目的3,『行为体』层有各自对应的行为体,后面还有 预期行为·驱动因素、叙事…)," +
|
||||
"把**每一条具体条目各画成一个节点**,label 取该条目核心短语(≤12 字);**不要**把一整层合并成一个笼统节点;\n" +
|
||||
"③ **每个节点的 `group` 必须填它所在层级的名称**(任务 / 目的 / 行为体 / 预期行为 / 驱动因素 / 叙事…,取自报告里该层级的真实名称)——画布据此给节点打分类标签;\n" +
|
||||
"④ **每个节点的 `detail`** 放该条目的名称 + 理由 / 说明,把要点表达完整、约 2–4 句即可(不必逐字照抄报告整段);" +
|
||||
"**`detail` 必须写成一行**——内部不要出现真实换行,需要分隔就用「;」或「/」,避免 JSON 里出现裸换行;\n" +
|
||||
"⑤ 连边严格按报告里的**对应关系**串成分支:根任务 → 各『目的』节点;每个『目的』→ 它对应的『行为体』节点;" +
|
||||
"每个『行为体』→ 它对应的『预期行为·驱动因素』节点 → 对应的『叙事』节点……每一组各成一条自上而下的分支" +
|
||||
"(报告里有几组就有几条分支,组与组之间不要混连);\n" +
|
||||
"⑦ **务必把每个层级的每一条都输出成节点、不要漏层、不要只输出开头一两个节点就停**——" +
|
||||
"典型规模约 13–16 个节点(如 任务1 + 目的3 + 行为体3 + 预期行为·驱动因素3 + 叙事3);\n" +
|
||||
"⑥ **只画**这些层级实体节点;**不要**把『综合分析』『核心洞察』『分层传播策略 / 执行路线 / 建议』『结论』" +
|
||||
"以及『阶段①②③④』等章节名 / 阶段名 / 汇总段落画成节点。";
|
||||
|
||||
/** 把各席位交付拼成一段内联材料(无报告时作流程图的数据来源)。 */
|
||||
function inlineSeatDeliveries(seatDeliveries?: { sender: string; content: string }[]): string {
|
||||
const list = (seatDeliveries ?? []).filter((s) => (s.content ?? "").trim());
|
||||
if (list.length === 0) return "";
|
||||
return list
|
||||
.map((s, i) => `## 席位${i + 1}:${s.sender || "(未具名席位)"}\n${s.content.trim()}`)
|
||||
.join("\n\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* taskId/深链模式:md 报告生成完后自动续跑的「结构化输出」prompt。
|
||||
*
|
||||
* 数据基准(用户拍板):**有报告就基于报告**(报告已把各席位交付清晰总结过,直接提取实体、
|
||||
* 不要再自己重新总结一遍);没有报告则基于**各席位交付产物**。两种来源都**整段内联**进 prompt
|
||||
* (不依赖线程上下文),让模型逐条对着抽取节点。层级顺序优先用总控提示词里写明的层级,否则 chainLevels。
|
||||
*/
|
||||
function buildAutoFlowPrompt(opts: {
|
||||
chainLevels?: string[];
|
||||
coordinatorPrompt?: string | null;
|
||||
reportMd?: string;
|
||||
seatDeliveries?: { sender: string; content: string }[];
|
||||
}): string {
|
||||
const levels = (opts.chainLevels ?? []).map((s) => s.trim()).filter(Boolean);
|
||||
const cp = opts.coordinatorPrompt?.trim();
|
||||
const report = opts.reportMd?.trim() ?? "";
|
||||
const seatMaterial = report ? "" : inlineSeatDeliveries(opts.seatDeliveries);
|
||||
const parts: string[] = [];
|
||||
if (report) {
|
||||
parts.push(
|
||||
"本轮只画一张流程图。**数据来源 = 下面这份你已写好的《方案总结报告》**(它已把各席位交付清晰总结过)——" +
|
||||
"请**逐条对着报告抽取**已有的层级条目作为流程图节点,**不要再自己重新总结 / 概括 / 提炼**,也不要凭空新增报告里没有的内容。",
|
||||
);
|
||||
} else {
|
||||
parts.push(
|
||||
"本轮只画一张流程图。**数据来源 = 下面各席位的交付产物**——请**逐条对着这些交付抽取**已有的层级条目作为流程图节点,不要自己另起总结。",
|
||||
);
|
||||
}
|
||||
parts.push(FLOW_ONLY_RULE);
|
||||
// 整段内联数据来源(报告全文 / 席位交付),用分隔线包起来便于模型对照抽取。
|
||||
if (report) {
|
||||
parts.push("===== 方案总结报告全文(数据来源,逐条抽取,勿改写)=====\n" + report + "\n===== 报告结束 =====");
|
||||
} else if (seatMaterial) {
|
||||
parts.push("===== 各席位交付产物(数据来源,逐条抽取,勿改写)=====\n" + seatMaterial + "\n===== 交付结束 =====");
|
||||
}
|
||||
parts.push(FLOW_JSON_SHAPE);
|
||||
parts.push(FLOW_STRUCTURE_RULE);
|
||||
if (cp) {
|
||||
parts.push("层级顺序优先采用「业务链总控编排提示」里写明的层级链(如它写的 任务 → 目的 → 行为体 → …)。");
|
||||
} else if (levels.length > 0) {
|
||||
parts.push(`层级顺序(自上而下):任务 → ${levels.join(" → ")}。`);
|
||||
}
|
||||
// 收尾再强调(首尾夹击,对抗线程里 coordinatorPrompt 的报告锚定)。
|
||||
parts.push("再次强调:本轮不写任何 Markdown 报告正文 / 章节,整个回复除一句话说明外,只有一个完整的 ```flow-json 代码块。");
|
||||
return parts.join("\n");
|
||||
}
|
||||
|
||||
/** 首轮总结 prompt。`flowMode`=编排提示里明确要求了结构化输出/流程图 → 跳过 md 文件,直出流程图。 */
|
||||
function buildSummaryPrompt(opts: {
|
||||
intentReady: ReportIntent | null;
|
||||
@@ -203,10 +327,15 @@ function buildSummaryPrompt(opts: {
|
||||
* 强调「全量 flow-json + 不写文件」——画图数据必须完整自洽,否则前端增量解析会画出半张图。
|
||||
*/
|
||||
const FLOW_ASK_INSTRUCTION =
|
||||
"(系统指令:用户要求结构化输出/流程图——本轮不要写或修改任何 .md 报告文件," +
|
||||
"在回复正文给出简短的文字说明/总结(用户未指定结构时,默认对各席位交付内容逐席位格式化输出:" +
|
||||
"席位名→核心结论→关键交付要点),并在回复最末尾输出一个**完整**的 ```flow-json 代码块" +
|
||||
"(按你职责设定的「结构化流程数据」契约;输出全量数据而非增量)。)";
|
||||
"(系统指令:用户要求结构化输出 / 流程图——这里的「结构化输出」指的是画一张**流程图**," +
|
||||
"不是写结构化的文字报告。**有报告就直接基于报告提取层级条目**(报告已总结过各席位交付,不要重复总结);" +
|
||||
"没有报告则基于各席位交付。\n" +
|
||||
FLOW_ONLY_RULE +
|
||||
"\n" +
|
||||
FLOW_STRUCTURE_RULE +
|
||||
"\n" +
|
||||
FLOW_JSON_SHAPE +
|
||||
")";
|
||||
|
||||
/**
|
||||
* 上一轮主产物是流程图、但本条消息没显式提「结构化/流程图」时的意图引导指令。
|
||||
@@ -299,6 +428,8 @@ export function useStep3Summary(
|
||||
consensusText,
|
||||
selectedModel,
|
||||
coordinatorPrompt,
|
||||
chainLevels,
|
||||
autoFlow,
|
||||
savedSummary,
|
||||
onSummarySaved,
|
||||
} = options;
|
||||
@@ -901,7 +1032,8 @@ export function useStep3Summary(
|
||||
|
||||
try {
|
||||
// 编排提示里明确要求了结构化输出/流程图 → 首轮就走流程图模式(不写 md,直出 flow-json)。
|
||||
const flowMode = detectFlowIntent(coordinatorPrompt);
|
||||
// 但 autoFlow(taskId/深链)模式要「先 md 再结构化输出」,故首轮强制走 md(忽略 detectFlowIntent)。
|
||||
const flowMode = !autoFlow && detectFlowIntent(coordinatorPrompt);
|
||||
flowActiveRef.current = flowMode; // 流式结束后会按真实产物校正
|
||||
const prompt = buildSummaryPrompt({
|
||||
intentReady,
|
||||
@@ -911,6 +1043,35 @@ export function useStep3Summary(
|
||||
flowMode,
|
||||
});
|
||||
await streamOnce({ bubbleId, prompt, aborter, flowRound: flowMode });
|
||||
|
||||
// taskId/深链模式:报告生成完,**自动**续跑一轮结构化输出(流程图)。
|
||||
// 数据来源:有报告则内联报告全文、否则内联各席位交付(buildAutoFlowPrompt 处理)。
|
||||
// 故触发条件放宽为「有报告 **或** 有席位交付」——保证「无报告也能据席位交付出图」。
|
||||
// 复用同一真线程,与手动「结构化输出」走同一渲染链路。
|
||||
if (
|
||||
autoFlow &&
|
||||
!aborter.signal.aborted &&
|
||||
liveThreadRef.current &&
|
||||
(currentMdRef.current.trim() || seatDeliveries.length > 0)
|
||||
) {
|
||||
const flowBubbleId = appendStreamingDialogue({
|
||||
sender: "方案总结报告智能体",
|
||||
avatarType: "blue",
|
||||
});
|
||||
const flowPrompt = buildAutoFlowPrompt({
|
||||
chainLevels,
|
||||
coordinatorPrompt,
|
||||
reportMd: currentMdRef.current,
|
||||
seatDeliveries,
|
||||
});
|
||||
await streamOnce({
|
||||
bubbleId: flowBubbleId,
|
||||
prompt: flowPrompt,
|
||||
aborter,
|
||||
seedThreadId: liveThreadRef.current,
|
||||
flowRound: true,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
if (aborter.signal.aborted) return;
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
@@ -926,6 +1087,8 @@ export function useStep3Summary(
|
||||
seatDeliveries,
|
||||
consensusText,
|
||||
coordinatorPrompt,
|
||||
chainLevels,
|
||||
autoFlow,
|
||||
appendStreamingDialogue,
|
||||
finalizeDialogue,
|
||||
streamOnce,
|
||||
@@ -1166,6 +1329,7 @@ export function useStep3Summary(
|
||||
return {
|
||||
phase,
|
||||
error,
|
||||
autoFlow: !!autoFlow,
|
||||
summaryThreadId,
|
||||
dialogues,
|
||||
seatStubMessages,
|
||||
|
||||
Reference in new issue
Block a user