fix(roundtable): 结构化抽取逐条抽全(候选项不漏)+报告不足回看席位交付

修复深链接(rwfx等)结构化流程图抽取「同层多条只抽一条」的准确性问题:
报告里 3 个候选目的只抽出 1 个、每个行为体名下多条预期行为被合并成 1 条、
3 组候选叙事只画 2 个且连线错。

- extraction_spec: 新增业务无关的 _COMPLETENESS_NOTE「逐条抽全、候选项也要抽」
  注入通用规范+写实规范; rwfx completeness_note 显式点名候选目的/每行为体多条
  预期行为/候选叙事都要全抽; _SOURCE_NOTE 改为「报告为主+席位交付补充」
- flow_extract._material: 报告与各席位原始交付一起喂给模型(报告把某层条目压缩
  时回到交付逐条抽全,冲突以报告为准),不再 report present 就只用报告
- flow_extract 抽取/校验 prompt: 补「逐条抽全+候选项全抽+不许合并+不要只照搬
  方案总纲精简主干」,校验新增「候选/多条目是否抽全」必查必补项

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
董宏凯andClaude Opus 4.8 committed 2026-06-20 22:16:27 +08:00
1 parent d383b0e9dd
commit b5f11ad790
2 files changed
+73 -19

No files matched your search

@@ -429,25 +429,45 @@ _STRUCTURE = (
"② 从第二层起按层级逐层展开:每层在报告/交付里通常列了多条具体条目(如 目的层有 目的1/2/3、行为体层有各自对应行为体,后面还有 预期行为·驱动因素、叙事…),"
"把每一条具体条目各画成一个节点,不要把整层合并成一个笼统节点;"
"③ 每个节点 group 填它所在层级名称(取自报告里该层级真实名称);"
"④ **连线**:(a)group 标对层级;(b)各层内条目按**相同对应顺序**排列(相互对应的一组在各层都排同一位次);"
"(c)**每个非根节点都必须填 from**,连到上一层级里对应的那个节点(目的i连任务、行为体i连目的i、预期行为i连行为体i、叙事i连预期行为i…)——**最常见错误就是漏 from 导致平铺,务必每个都填**;系统还会按层级校正/补全连线兜底;"
"④ **连线**:(a)group 标对层级;(b)**每个非根节点都必须填 from**,连到它在上一层级里**所属/对应**的那个节点;"
"层间关系(哪层一对一、哪层一对多)**以本轮给出的业务链规范为准**:**一对一**的相邻层按位次配对(下层第i条连上层第i条);"
"**一对多**的相邻层里,上层一条可对应下层多条——下层每条连到它**所属**的那一条上层节点,**绝不能把多条上层节点汇聚连到同一个下层节点**(那是把一对多画反成多对一);"
"(c)最常见错误是漏 from 导致平铺,或把一对多画成多对一,务必每个都填对;系统还会按层级校正/补全连线兜底;"
"⑤ 务必把每层每一条都输出成节点、不要漏层、不要只输出开头一两个节点就停(典型约13-16个节点);"
"⑥ 只画这些层级实体节点,不要把『综合分析/核心洞察/执行路线/建议/结论』及『阶段①②③④』等汇总段落/阶段名画成节点。"
)
def _material(req: FlowExtractRequest) -> str:
"""抽取的数据来源:**报告为主 + 各席位原始交付为补充**一起喂进去。
痛点:报告常把某层级的多条目压缩进一段(如「确定的3个核心行为体」「每个行为体都有具体预期行为」),
只读报告会导致抽取时把一层多条目合并成一条。把各席位**原始交付**一并带上,模型在报告条目不足/被压缩时
可回到交付里把该层每一条抽全(报告与交付冲突时以报告为准)。
"""
# xdfx:把本次「行动名称」作为材料抬头喂进去(根节点就是它),让抽取贴着这条行动展开。
action = (req.root_label or "").strip()
prefix = f"【本次行动】{action}\n\n" if action else ""
report = (req.report_md or "").strip()
if report:
return prefix + "===== 方案总结报告全文(数据来源,逐条抽取,勿改写)=====\n" + report + "\n===== 报告结束 ====="
seats = [s for s in req.seat_deliveries if (s.content or "").strip()]
parts: list[str] = []
if report:
parts.append("===== 方案总结报告全文(主数据来源,逐条抽取,勿改写)=====\n" + report + "\n===== 报告结束 =====")
if seats:
body = "\n\n".join(f"## 席位{i + 1}:{s.sender or '(未具名席位)'}\n{s.content.strip()}" for i, s in enumerate(seats))
return prefix + "===== 各席位交付产物(数据来源,逐条抽取,勿改写)=====\n" + body + "\n===== 交付结束 ====="
return ""
if report:
parts.append(
"===== 各席位原始交付产物(补充数据来源)=====\n"
"用途:报告里若某层级的条目被压缩 / 概括 / 数量不足(例如某行为体名下的多条预期行为被并成一句、"
"候选目的或候选叙事没列全),**请回到这里把该层级的每一条逐条抽全**;与报告冲突时以报告为准。\n"
+ body
+ "\n===== 交付结束 ====="
)
else:
parts.append("===== 各席位交付产物(数据来源,逐条抽取,勿改写)=====\n" + body + "\n===== 交付结束 =====")
if not parts:
return ""
return prefix + "\n\n".join(parts)
def _hierarchy_hint(req: FlowExtractRequest) -> str:
@@ -516,6 +536,10 @@ def _extract_messages(req: FlowExtractRequest) -> list[Any]:
+ "\n\n"
+ _hierarchy_hint(req)
+ _root_label_hint(req)
+ "\n\n【务必逐条抽全(最重要)】每个层级里出现的**每一条**都要各自成一个节点:"
+ "候选清单 / Top-N / 「①②③」/「(确定·选用)」式的**多个候选项全部都要抽**(不是只抽被选中的那一条,落选项也是节点);"
+ "某上层节点名下列了几条下层条目就出几个节点,**不许把多条合并 / 概括成一条**;"
+ "不要只照搬报告结尾「方案总纲 / 最终框架」里被精简的主干,要回到正文把每层全部条目抽全。"
+ "\n\n请分析以上数据来源,按你的职责设定输出一个完整的 ```flow-json 代码块。"
)
return [SystemMessage(content=_system_prompt()), HumanMessage(content=user)]
@@ -548,13 +572,17 @@ def _validate_messages(req: FlowExtractRequest, extracted: str) -> list[Any]:
+ "\n===== 待校验结束 =====\n\n请对照数据来源,**逐个节点检查上面这份 flow-json 是不是缺字段**、并修复它:"
+ "\n**校验要点(务必遵守)**:"
+ "① **连接关系 `from`(最重要,必查必补)**:**逐个**检查上面每个节点——除根任务外,"
+ "**只要某个节点缺了 `from`(或 `from` 为空),就必须给它补上正确的 `from`**:连到它在上一层级里对应的那个节点"
+ "(目的i 连 任务、行为体i 连 目的i、预期行为i 连 行为体i、叙事i 连 预期行为i…)。"
+ "缺 `from` 是这份 JSON 最常见、最致命的错漏(会导致流程图平铺),**绝不能让任何非根节点悬空**;"
+ "② **每层每条都在、没漏层没漏条目**,节点数只能 ≥ 原图、不能变少;"
+ "③ 每个节点的 `group`(层级名)标对;各层内条目按相同对应顺序排列(相互对应的一组在各层排同一位次);"
+ "④ 每个节点的 `detail` **原样保留、不要简化或精简理由**——理由要完整,不要删减内容;"
+ "⑤ 没有汇总/阶段编号节点;只在确有错漏时才动,没问题就原样规范化输出。"
+ "**只要某个节点缺了 `from`(或 `from` 为空),就必须给它补上正确的 `from`**:连到它在上一层级里**所属/对应**的那个节点。"
+ "层间关系(哪层一对一、哪层一对多)**以上面给出的业务链规范为准**:一对一层级按位次配对(下层第i条连上层第i条);"
+ "**一对多**层级里下层每条连到它**所属**的那一条上层节点,**绝不能把多条上层节点汇聚到同一个下层节点**(那是把一对多画反成多对一,是本次要重点纠正的错)。"
+ "缺 `from` / 把一对多画成多对一 是最常见错漏,**绝不能让任何非根节点悬空、也不要张冠李戴**;"
+ "② **候选 / 多条目是否抽全(必查必补)**:对照数据来源逐层核对——候选目的、候选叙事、某行为体名下的多条预期行为等,"
+ "**报告/交付里有几条就该有几个节点**;发现被漏抽的候选项(尤其是没被选中的候选)、或被合并成一条的多个条目,"
+ "**必须拆/补成独立节点**(落选候选项也要在,detail 注明候选/未选用)。报告条目被压缩时回到《各席位原始交付产物》补全。"
+ "③ **每层每条都在、没漏层没漏条目**,节点数只能 ≥ 原图、不能变少;尤其一对多的下层(如某层一条对应下层多条)不要被合并成一条;"
+ "④ 每个节点的 `group`(层级名)标对;一对一层级内条目按相同位次对齐,一对多层级下层每条紧挨其所属上层条目分组;"
+ "⑤ 每个节点的 `detail` **原样保留、不要简化或精简理由**——理由要完整,不要删减内容;"
+ "⑥ 没有汇总/阶段编号节点;只在确有错漏时才动,没问题就原样规范化输出。"
+ "\n输出修复后的完整 ```flow-json 代码块(含全部节点 + 每个非根节点的 from)。"
)
return [SystemMessage(content=_system_prompt()), HumanMessage(content=user)]
@@ -38,7 +38,23 @@ _NAMING_NOTE = (
"节点 label 必须用该层级下的**具体实体 / 概念短名**(≤12 字),"
"**绝不能含「步骤 / 阶段 / 环节 / 第N步 / 阶段①②③」等流程或编号词**,也不要拿层级名本身当 label。"
)
_SOURCE_NOTE = "**优先**从《方案总结报告》抽取;没有报告时再用各席位交付内容。逐条抽取、勿改写、勿精简理由。"
_SOURCE_NOTE = (
"数据来源同时给了《方案总结报告》与《各席位原始交付产物》:**以报告为主**逐条抽取、勿改写、勿精简理由;"
"但报告常把某层级的多条目**压缩/概括进一句话**(只说「确定了 N 个…」却不逐条列出),"
"**一旦报告某层级条目被压缩或数量不足,必须回到《各席位原始交付产物》把该层的每一条抽全**——交付里有最细的逐条清单。"
)
# 「逐条抽全 / 候选项也要抽」—— 本次最关键的准确性约束(治「同一层级里有 3 条却只抽 1 条」「多条被合并成 1 条」)。
# 业务无关措辞(不出现任何具体业务的层级名),通用规范与写实规范都复用它。
_COMPLETENESS_NOTE = (
"**逐条抽全、候选项也要抽(最重要,必须做到)**:\n"
" · 每个层级里出现的**每一条**都要各自成一个独立节点。报告里以「Top N 候选」「N 组候选方案」「①②③」"
"「(确定 / 选用 / 优选)」等形式列出的**多个候选项,全部都要抽成节点**——**不是只抽被标「确定 / 选用」的那一条**,"
"落选的候选项同样是该层级的节点(可在它的 detail 里注明「候选 / 未选用」或「最终选用」),数量一个都不能少;\n"
" · 某个上层节点名下若列了多条下层条目(同一上层名下罗列了好几条),"
"**报告/交付里有几条就出几个节点,绝对不许合并 / 概括 / 只取代表性的一条**;\n"
" · **不要只照搬报告结尾「方案总纲 / 最终框架 / 共识方案」里被精简的主干链**(那里通常只画被选中的一条主线)——"
"要回到正文逐层把每个层级的**全部条目**抽全。"
)
# ── rwfx(6BF):六步法完整链(线性) ─────────────────────────────────────────
@@ -47,12 +63,21 @@ _RWFX = ChainSpec(
label="rwfx",
levels=["任务", "目的", "行为体", "预期行为", "驱动因素", "叙事"],
relations_note=(
"层间关系:**「预期行为」与「驱动因素」一一对应**(两层数量必须相等、一个预期行为恰对一个驱动因素,"
"按相同位次配对);其余相邻层级(任务→目的、目的→行为体、行为体→预期行为、驱动因素→叙事)均为"
"**一对多**(上层一条可带下层多条)。"
"层间关系(务必分清一对一 / 一对多,连线别画反):\n"
"① **「预期行为」与「驱动因素」一一对应**——两层数量必须相等、一个预期行为恰对一个驱动因素,按相同位次配对;\n"
"② **「驱动因素」→「叙事」是一对多**——**一个驱动因素对应多个叙事**(每个驱动因素抽出它名下的若干条叙事),"
"每条叙事的 from **连到它所属的那一个驱动因素**(叙事的上一层是驱动因素,不是预期行为);"
"**绝不能把多个驱动因素汇聚连到同一个叙事节点**(那就把一对多画反成多对一了,是常见错误);也不要把全部叙事合并成一个节点;\n"
"③ 其余相邻层级(任务→目的、目的→行为体、行为体→预期行为)均为**一对多**(上层一条可带下层多条,下层每条连其所属上层条目)。"
),
completeness_note=(
"以上 6 个层级都要出节点;一一对应的层级(预期行为↔驱动因素)两层节点数必须相等并按位次配对。"
"以上 6 个层级都要出节点,且每层的**全部条目都要抽全**:\n"
" · **目的**:报告若列了多个候选目的(如「Top 3 候选目的」「从8个优选出3个」),就把**这些候选目的全部**抽成"
"目的节点(被标「确定」的也只是其中一个),不要只抽被选中的那一个;\n"
" · **行为体 / 预期行为**:每个行为体名下报告列了几条预期行为(常 3-5 条)就出几个预期行为节点,**不许把一个行为体的多条预期行为合并成一条**;\n"
" · 预期行为↔驱动因素 两层节点数必须相等并按位次配对(一个预期行为恰对一个驱动因素);\n"
" · **叙事**:报告若列了多个候选叙事方案(如「3 组候选叙事」),**全部**抽成叙事节点(含未选用的),不要只出一两个;"
"叙事层通常比驱动因素层**更宽**,每条叙事连到它所属/对接的那一个驱动因素——不要让多个驱动因素指向同一叙事。"
),
)
@@ -123,7 +148,7 @@ def build_structure_directive(business_code: str | None) -> str:
"- 本业务**没有固定层级模板**:请严格按《方案总结报告》/ 各席位交付里**真实出现的层级**,"
"自「任务」为根逐层完整展开,每层每一条都出成节点,不要只抽开头一两层就停;\n"
"- **不要套用「目的→行为体」之类的固定示例层级**——层级名一律取自本次报告 / 交付里的真实层级;\n"
f"- {_NAMING_NOTE}\n- {_SOURCE_NOTE}"
f"- {_COMPLETENESS_NOTE}\n- {_NAMING_NOTE}\n- {_SOURCE_NOTE}"
)
arrows = " → ".join(spec.levels)
completeness = spec.completeness_note or f"以上 {len(spec.levels)} 个层级都要出节点,不得缺层。"
@@ -131,6 +156,7 @@ def build_structure_directive(business_code: str | None) -> str:
f"【{spec.label} 业务链抽取规范(强制遵守,优先级高于系统提示里的任何默认/示例层级)】",
f"- **完整层级**(自上而下,必须逐层抽全、不得缺层、不得停在中间层):{arrows};",
f"- {spec.relations_note}",
f"- {_COMPLETENESS_NOTE}",
f"- {_NAMING_NOTE}",
f"- {_SOURCE_NOTE}",
f"- **完整性**:{completeness}",