diff --git a/offline-backend-20260512/backend/app/gateway/routers/_roundtable_seed_assets/roundtable-structure/SOUL.md b/offline-backend-20260512/backend/app/gateway/routers/_roundtable_seed_assets/roundtable-structure/SOUL.md index 45d760cc..44fccb50 100644 --- a/offline-backend-20260512/backend/app/gateway/routers/_roundtable_seed_assets/roundtable-structure/SOUL.md +++ b/offline-backend-20260512/backend/app/gateway/routers/_roundtable_seed_assets/roundtable-structure/SOUL.md @@ -30,6 +30,22 @@ 节点按先后排列、输出**全量**数据、全程简体中文。 +### ✅ 正确节点示例 vs ❌ 错误(必看) + +**✅ 正确**(`group` 标对层级,且**带上 `from` 连到前驱**——每个非根节点都必须连上前一层级对应的节点): + +``` +{"id":"n2","label":"精准追踪管理公众情绪曲线","type":"task","group":"目的","detail":"目的一:精准追踪并管理公众情绪曲线,识别关键情绪拐点;理由:暑期文旅涉及高频消费场景,情绪易受价格、服务、灾害影响呈多峰多谷震荡,情绪拐点是危机化解窗口,需精细跟踪作为前哨感知维度","from":["n1"]} +``` + +**❌ 错误**(缺了 `from`,导致节点孤立、流程图平铺——这是最常见的错误,务必避免): + +``` +{"id":"n2","label":"精准追踪管理公众情绪曲线","type":"task","group":"目的","detail":"…"} ← 少了 "from" +``` + +**硬性要求**:除根节点(任务,n1)外,**每个节点都必须有 `from`**(连到它在上一层级里对应的那个节点);目的i 连 任务,行为体i 连 目的i,预期行为i 连 行为体i,叙事i 连 预期行为i……一组一条自上而下的链,不要让任何节点悬空。 + ## 二、画法(严格遵守) ① 根节点 n1 = 本次任务本身(报告 / 任务总览里的任务名称),`label` 用任务简短名,`group` 填「任务」,**不要**用「任务」「目标」这种泛称当 `label`; @@ -38,7 +54,11 @@ ③ 每个节点的 `group` 填它所在层级的名称(取自报告里该层级的真实名称); -④ **连线由系统按层级关系自动生成,你不用纠结连线**——你只需做对两件事:(a) 把每个节点的 `group` 标对层级;(b) **各层内的条目按"相同的对应顺序"排列**——即报告里相互对应的一组(目的1 ↔ 它对应的行为体 ↔ 对应的预期行为 ↔ 对应的叙事)要在各自层里**都排在第 1 位**,第 2 组都排第 2 位,依此类推。这样系统就能把 任务→各目的、目的i→行为体i→预期行为i→叙事i 正确连成每组一条自上而下的分支。`from` 字段可填可不填(系统会以层级对应关系为准); +④ **连线**:你要做对三件事 —— + (a) 每个节点的 `group` 标对层级; + (b) **各层内的条目按"相同的对应顺序"排列**——报告里相互对应的一组(目的1 ↔ 它对应的行为体 ↔ 对应的预期行为 ↔ 对应的叙事)在各自层里**都排同一位次**(第 1 组都排第 1 位、第 2 组都排第 2 位…); + (c) **每个非根节点都填上 `from`**,连到它在上一层级里对应的那个节点(目的i 连 任务、行为体i 连 目的i、预期行为i 连 行为体i、叙事i 连 预期行为i……一组一条自上而下的链)——**最常见的错误就是漏填 from 导致节点平铺,务必每个都填**。 + 系统还会按 group 层级关系**校正 / 补全连线**作为兜底,但你必须先把 from 填全、填对; ⑤ **务必把每个层级的每一条都输出成节点、不要漏层、不要只输出开头一两个节点就停**(典型规模约 13–16 个节点); diff --git a/offline-backend-20260512/backend/app/gateway/routers/flow_extract.py b/offline-backend-20260512/backend/app/gateway/routers/flow_extract.py index ae8cac9e..d55d2dbc 100644 --- a/offline-backend-20260512/backend/app/gateway/routers/flow_extract.py +++ b/offline-backend-20260512/backend/app/gateway/routers/flow_extract.py @@ -329,10 +329,19 @@ def parse_flow_graph(text: str) -> dict[str, Any] | None: keys.add(k) valid_edges.append(e) - # 连线**确定性推导**(不信任模型连边——弱模型常乱连):按 group 分层、按层级对应关系算边, - # 保证流程图连线质量与模型能力无关。能算出干净的层级连线就用它;层级不规则才回退模型连边。 + # 连线选取(两全:尊重模型 from + 弱模型兜底): + # · 模型把连线**连全了**(除根节点外每个节点都有入边)→ 直接用模型的 from/edges; + # · 模型**漏连**(≥2 个节点没入边,最常见就是漏 from 导致平铺)→ 按 group 层级关系 + # **确定性推导补全**,保证不平铺、连线质量与模型能力无关。 + incoming = {e["to"] for e in valid_edges} + orphans = [n for n in nodes if n["id"] not in incoming] derived = _derive_layered_edges(nodes) - final_edges = derived if derived is not None else valid_edges + if valid_edges and len(orphans) <= 1: + final_edges = valid_edges # 模型连全(仅根无入边)→ 用模型连边 + elif derived is not None: + final_edges = derived # 模型漏连 → 按层级推导补全(永不平铺) + else: + final_edges = valid_edges return {"title": title, "nodes": nodes, "edges": final_edges} @@ -378,7 +387,10 @@ def _derive_layered_edges(nodes: list[dict[str, Any]]) -> list[dict[str, Any]] | elif len(a) == len(b): out.extend({"from": a[i], "to": b[i]} for i in range(len(a))) else: - return None # 不规则层对(两边都 >1 且不等)→ 放弃推导,回退模型连边 + # 不规则层对(两边都 >1 且不等,多因模型漏/多了某层条目):**不放弃、不平铺**, + # 按比例把下层每个节点连到上层对应位置,保证每个节点都有入边(连上 > 连得完美)。 + for j, nid in enumerate(b): + out.append({"from": a[j * len(a) // len(b)], "to": nid}) return out @@ -405,8 +417,8 @@ _STRUCTURE = ( "② 从第二层起按层级逐层展开:每层在报告/交付里通常列了多条具体条目(如 目的层有 目的1/2/3、行为体层有各自对应行为体,后面还有 预期行为·驱动因素、叙事…)," "把每一条具体条目各画成一个节点,不要把整层合并成一个笼统节点;" "③ 每个节点 group 填它所在层级名称(取自报告里该层级真实名称);" - "④ **连线由系统按层级自动生成,你不用纠结连线**;只需:(a)group 标对层级;" - "(b)各层内条目按**相同对应顺序**排列——相互对应的一组(目的1↔对应行为体↔对应预期行为↔对应叙事)在各层都排同一位次(第1组排第1位、第2组排第2位…);from 可填可不填(系统以层级对应为准);" + "④ **连线**:(a)group 标对层级;(b)各层内条目按**相同对应顺序**排列(相互对应的一组在各层都排同一位次);" + "(c)**每个非根节点都必须填 from**,连到上一层级里对应的那个节点(目的i连任务、行为体i连目的i、预期行为i连行为体i、叙事i连预期行为i…)——**最常见错误就是漏 from 导致平铺,务必每个都填**;系统还会按层级校正/补全连线兜底;" "⑤ 务必把每层每一条都输出成节点、不要漏层、不要只输出开头一两个节点就停(典型约13-16个节点);" "⑥ 只画这些层级实体节点,不要把『综合分析/核心洞察/执行路线/建议/结论』及『阶段①②③④』等汇总段落/阶段名画成节点。" ) @@ -496,11 +508,14 @@ def _validate_messages(req: FlowExtractRequest, extracted: str) -> list[Any]: + extracted + "\n===== 待校验结束 =====\n\n请对照数据来源校验并修复这份 flow-json(连线由系统按层级自动生成、你不用管连线):" + "\n**校验要点(务必遵守)**:" - + "① **每层每条都在、没漏层没漏条目**,节点数只能 ≥ 原图、不能变少;" - + "② 每个节点的 `group`(层级名)标对;**各层内条目按相同对应顺序排列**——相互对应的一组在各层都排同一位次(第1组都排第1位…),这样系统才能正确连线;" - + "③ 每个节点的 `detail` **原样保留、不要简化或精简理由**——理由要完整,不要删减内容;" - + "④ 没有汇总/阶段编号节点;只在确有错漏时才动,没问题就原样规范化输出。" - + "\n输出修复后的完整 ```flow-json 代码块(含全部节点)。" + + "① **连接关系(最重要)**:检查**每个非根节点是否都有 `from`(入边)**——这是最常见的错漏!" + + "凡是缺 `from` 的节点(根任务除外),**必须补上正确的 from**:连到它在上一层级里对应的那个节点" + + "(目的i 连 任务、行为体i 连 目的i、预期行为i 连 行为体i、叙事i 连 预期行为i…),绝不能让任何节点悬空/平铺;" + + "② **每层每条都在、没漏层没漏条目**,节点数只能 ≥ 原图、不能变少;" + + "③ 每个节点的 `group`(层级名)标对;各层内条目按相同对应顺序排列(相互对应的一组在各层排同一位次);" + + "④ 每个节点的 `detail` **原样保留、不要简化或精简理由**——理由要完整,不要删减内容;" + + "⑤ 没有汇总/阶段编号节点;只在确有错漏时才动,没问题就原样规范化输出。" + + "\n输出修复后的完整 ```flow-json 代码块(含全部节点 + 每个非根节点的 from)。" ) return [SystemMessage(content=_system_prompt()), HumanMessage(content=user)]