feat: 流程图连线后端确定性推导(弱模型也连对) + 校验不删边/不简化理由 + 调度中即时反馈
- 连线质量:新增 _derive_layered_edges——连线不再信任模型,后端按节点 group 分层 + 层级对应关系确定性推导(任务→各目的扇出、相邻等量层按下标一一对应、不规则层对回退模型边)。 模型只需 group 标对 + 各层内按相同对应顺序排列,连线就一定对。SOUL/_STRUCTURE 据此调整。 - 校验不删边:validate 要求节点数≥原图 + gen() 兜底「校验后节点/边变少则回退抽取结果」。 - 不简化理由:_SHAPE + SOUL detail 改为完整理由原样保留、不简化/精简。 - 调度中即时反馈:ask() 意图分类前立刻建「调度中」气泡,定意图后各分支复用(relabelDialogue + runFlowExtract.bubbleId),不再等 intent 接口返回才出内容。 - 测试 tests/test_flow_extract_parse.py 加层级连线推导 3 例(共 10 过)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
0890b3c375
commit
cc06d3f4d2
3 files changed
+112
-21
No files matched your search
@@ -501,6 +501,11 @@ export function useStep3Summary(
|
||||
);
|
||||
}, []);
|
||||
|
||||
/** 改某气泡的发送方名称(意图分类期间先建中性「调度中」气泡,定了意图再改成对应智能体名复用)。 */
|
||||
const relabelDialogue = useCallback((id: number, sender: string) => {
|
||||
setDialogues((prev) => prev.map((d) => (d.id === id ? { ...d, sender } : d)));
|
||||
}, []);
|
||||
|
||||
const appendStepIfNew = useCallback(
|
||||
(
|
||||
bubbleId: number,
|
||||
@@ -989,17 +994,22 @@ export function useStep3Summary(
|
||||
const runFlowExtract = useCallback(
|
||||
async (
|
||||
aborter: AbortController,
|
||||
opts?: { instruction?: string; currentGraph?: FlowGraphData | null },
|
||||
opts?: { instruction?: string; currentGraph?: FlowGraphData | null; bubbleId?: number },
|
||||
): Promise<void> => {
|
||||
const isEdit = !!opts?.instruction && !!opts?.currentGraph;
|
||||
setFlowPhase("extracting");
|
||||
if (!isEdit) setFlowGraphState(null); // 编辑模式保留旧图直到新图就绪(防画布闪空)
|
||||
// 两个气泡:抽取/修改 + 校验——把过程(思考 `<think>` + 文字 + flow-json 代码块)都留着展示,
|
||||
// 由 Step2Message 渲染(思考折叠块 + markdown 代码块),不折叠成一句话。
|
||||
const extractBubbleId = appendStreamingDialogue({
|
||||
sender: isEdit ? "结构化流程图 · 修改" : "结构化流程图 · 抽取",
|
||||
avatarType: "blue",
|
||||
});
|
||||
// 传入 bubbleId(意图分类期间已建的「调度中」气泡)则复用并改名,避免空窗 + 不重复建气泡。
|
||||
const extractSender = isEdit ? "结构化流程图 · 修改" : "结构化流程图 · 抽取";
|
||||
let extractBubbleId: number;
|
||||
if (opts?.bubbleId != null) {
|
||||
extractBubbleId = opts.bubbleId;
|
||||
relabelDialogue(extractBubbleId, extractSender);
|
||||
} else {
|
||||
extractBubbleId = appendStreamingDialogue({ sender: extractSender, avatarType: "blue" });
|
||||
}
|
||||
let validateBubbleId: number | null = null;
|
||||
try {
|
||||
const graph = await streamFlowExtract({
|
||||
@@ -1055,6 +1065,7 @@ export function useStep3Summary(
|
||||
appendStreamingDialogue,
|
||||
appendToDialogue,
|
||||
finalizeDialogue,
|
||||
relabelDialogue,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1176,9 +1187,14 @@ export function useStep3Summary(
|
||||
// 意图识别(参考 ai_writing 一次性 JSON 范式):先判断用户想 重做报告 / 改流程图 /
|
||||
// 重画流程图 / 提问 / 模糊待澄清,再分流——不再用关键词硬路由(避免「说重做报告却出流程图」)。
|
||||
// 带附件时跳过分类(附件多用于报告 Q&A/改报告);澄清后用户点选则用 forcedIntent 跳过分类。
|
||||
// reuseBubbleId:意图分类期间先建的「调度中」气泡,定了意图后由对应分支复用(不等接口返回、像第一步)。
|
||||
let reuseBubbleId: number | null = null;
|
||||
if (text && !hasFiles) {
|
||||
let intent: FlowIntent | undefined = opts?.forcedIntent;
|
||||
let dispatchBubbleId: number | null = null;
|
||||
if (!intent) {
|
||||
// **立刻**显示「智能体调度中」气泡(不等意图接口返回),消除连接卡顿时的"卡住"感。
|
||||
dispatchBubbleId = appendStreamingDialogue({ sender: "智能体", avatarType: "blue" });
|
||||
const r = await classifyFlowIntent({
|
||||
message: text,
|
||||
hasReport: !!currentMdRef.current.trim(),
|
||||
@@ -1188,6 +1204,9 @@ export function useStep3Summary(
|
||||
intent = r.intent;
|
||||
}
|
||||
if (intent === "unclear") {
|
||||
if (dispatchBubbleId != null) {
|
||||
finalizeDialogue(dispatchBubbleId, "意图不太明确,请在下方选择你想做的:", "待确认");
|
||||
}
|
||||
setPendingFlowClarification(text); // 组件渲染选项让用户自己定
|
||||
return;
|
||||
}
|
||||
@@ -1195,13 +1214,16 @@ export function useStep3Summary(
|
||||
const flowAborter = new AbortController();
|
||||
aborterRef.current = flowAborter;
|
||||
const cur = flowGraphRef.current;
|
||||
const reuse = dispatchBubbleId ?? undefined;
|
||||
if (intent === "modify_flow" && cur) {
|
||||
await runFlowExtract(flowAborter, { instruction: text, currentGraph: cur });
|
||||
await runFlowExtract(flowAborter, { instruction: text, currentGraph: cur, bubbleId: reuse });
|
||||
} else {
|
||||
await runFlowExtract(flowAborter);
|
||||
await runFlowExtract(flowAborter, { bubbleId: reuse });
|
||||
}
|
||||
return;
|
||||
}
|
||||
// regenerate_report | qa → 复用调度气泡走下方总结线程路径。
|
||||
reuseBubbleId = dispatchBubbleId;
|
||||
// intent === "regenerate_report" | "qa" → 落到下方总结线程续问路径:
|
||||
// **追加**用户气泡 + 在原线程上让智能体重写报告 / 增量改 / 回答(**不清空历史**)。
|
||||
// regenerate_report 不再走 run()(run 会 setDialogues([]) 清空,导致"覆盖聊天记录、
|
||||
@@ -1248,11 +1270,15 @@ export function useStep3Summary(
|
||||
}));
|
||||
}
|
||||
|
||||
// 用户气泡已在 ask 开头立刻追加(不等模型/分类返回),这里不再重复。
|
||||
const bubbleId = appendStreamingDialogue({
|
||||
sender: "方案总结报告智能体",
|
||||
avatarType: "blue",
|
||||
});
|
||||
// 用户气泡已在 ask 开头立刻追加。智能体气泡:复用意图分类期间的「调度中」气泡(改名),
|
||||
// 没有则新建——这样提问/重做报告时分类阶段就有「调度中」反馈、不等模型返回(像第一步)。
|
||||
let bubbleId: number;
|
||||
if (reuseBubbleId != null) {
|
||||
bubbleId = reuseBubbleId;
|
||||
relabelDialogue(bubbleId, "方案总结报告智能体");
|
||||
} else {
|
||||
bubbleId = appendStreamingDialogue({ sender: "方案总结报告智能体", avatarType: "blue" });
|
||||
}
|
||||
setPhase("streaming");
|
||||
|
||||
if (hadLiveThread) {
|
||||
@@ -1313,6 +1339,8 @@ export function useStep3Summary(
|
||||
freezeStreaming,
|
||||
appendUserDialogue,
|
||||
appendStreamingDialogue,
|
||||
relabelDialogue,
|
||||
finalizeDialogue,
|
||||
streamOnce,
|
||||
runFlowExtract,
|
||||
],
|
||||
|
||||
Reference in new issue
Block a user