马一丁

Limiting the occurrence of SWOT

@@ -306,6 +306,10 @@ class ChapterGenerationNode(BaseNode): @@ -306,6 +306,10 @@ class ChapterGenerationNode(BaseNode):
306 # 章节篇幅规划(来自WordBudgetNode),用于指导字数与强调点 306 # 章节篇幅规划(来自WordBudgetNode),用于指导字数与强调点
307 chapter_plan_map = context.get("chapter_directives", {}) 307 chapter_plan_map = context.get("chapter_directives", {})
308 chapter_plan = chapter_plan_map.get(section.chapter_id) if chapter_plan_map else {} 308 chapter_plan = chapter_plan_map.get(section.chapter_id) if chapter_plan_map else {}
  309 +
  310 + # 从 layout 的 tocPlan 中查找该章节是否允许使用SWOT块
  311 + allow_swot = self._get_chapter_swot_permission(section.chapter_id, context)
  312 +
309 payload = { 313 payload = {
310 "section": { 314 "section": {
311 "chapterId": section.chapter_id, 315 "chapterId": section.chapter_id,
@@ -335,6 +339,7 @@ class ChapterGenerationNode(BaseNode): @@ -335,6 +339,7 @@ class ChapterGenerationNode(BaseNode):
335 "language": "zh-CN", 339 "language": "zh-CN",
336 "maxTokens": context.get("max_tokens", 4096), 340 "maxTokens": context.get("max_tokens", 4096),
337 "allowedBlocks": ALLOWED_BLOCK_TYPES, 341 "allowedBlocks": ALLOWED_BLOCK_TYPES,
  342 + "allowSwot": allow_swot,
338 "styleHints": { 343 "styleHints": {
339 "expectWidgets": True, 344 "expectWidgets": True,
340 "forceHeadingAnchors": True, 345 "forceHeadingAnchors": True,
@@ -359,6 +364,36 @@ class ChapterGenerationNode(BaseNode): @@ -359,6 +364,36 @@ class ChapterGenerationNode(BaseNode):
359 payload["globalContext"]["sectionBudgets"] = chapter_plan["sections"] 364 payload["globalContext"]["sectionBudgets"] = chapter_plan["sections"]
360 return payload 365 return payload
361 366
  367 + def _get_chapter_swot_permission(self, chapter_id: str, context: Dict[str, Any]) -> bool:
  368 + """
  369 + 从 layout 的 tocPlan 中查找指定章节是否允许使用 SWOT 块。
  370 +
  371 + 全文最多只有一个章节允许使用 SWOT 块,由文档设计阶段在 tocPlan 中
  372 + 通过 allowSwot 字段标记。
  373 +
  374 + 参数:
  375 + chapter_id: 当前章节ID。
  376 + context: 全局上下文字典。
  377 +
  378 + 返回:
  379 + bool: 如果该章节允许使用 SWOT 块则返回 True,否则返回 False。
  380 + """
  381 + layout = context.get("layout")
  382 + if not isinstance(layout, dict):
  383 + return False
  384 +
  385 + toc_plan = layout.get("tocPlan")
  386 + if not isinstance(toc_plan, list):
  387 + return False
  388 +
  389 + for entry in toc_plan:
  390 + if not isinstance(entry, dict):
  391 + continue
  392 + if entry.get("chapterId") == chapter_id:
  393 + return bool(entry.get("allowSwot", False))
  394 +
  395 + return False
  396 +
362 def _stream_llm( 397 def _stream_llm(
363 self, 398 self,
364 user_message: str, 399 user_message: str,
@@ -139,6 +139,10 @@ document_layout_output_schema = { @@ -139,6 +139,10 @@ document_layout_output_schema = {
139 "anchor": {"type": "string"}, 139 "anchor": {"type": "string"},
140 "display": {"type": "string"}, 140 "display": {"type": "string"},
141 "description": {"type": "string"}, 141 "description": {"type": "string"},
  142 + "allowSwot": {
  143 + "type": "boolean",
  144 + "description": "是否允许该章节使用SWOT分析块,全文最多只有一个章节可设为true",
  145 + },
142 }, 146 },
143 "required": ["chapterId", "display"], 147 "required": ["chapterId", "display"],
144 }, 148 },
@@ -304,7 +308,11 @@ SYSTEM_PROMPT_CHAPTER_JSON = f""" @@ -304,7 +308,11 @@ SYSTEM_PROMPT_CHAPTER_JSON = f"""
304 3. 所有段落都放入paragraph.inlines,混排样式通过marks表示(bold/italic/color/link等)。 308 3. 所有段落都放入paragraph.inlines,混排样式通过marks表示(bold/italic/color/link等)。
305 4. 所有heading必须包含anchor,锚点与编号保持模板一致,比如section-2-1。 309 4. 所有heading必须包含anchor,锚点与编号保持模板一致,比如section-2-1。
306 5. 表格需给出rows/cells/align,KPI卡请使用kpiGrid,分割线用hr。 310 5. 表格需给出rows/cells/align,KPI卡请使用kpiGrid,分割线用hr。
307 -6. SWOT分析必须优先使用 block.type="swotTable":分别填写 strengths/weaknesses/opportunities/threats 数组,单项至少包含 title/label/text 之一,可附加 detail/evidence/impact 字段;title/summary 字段用于概览说明。**特别注意:impact 字段只允许填写影响评级("低"/"中低"/"中"/"中高"/"高"/"极高");任何关于影响的文字叙述、详细说明、佐证或扩展描述必须写入 detail 字段,禁止在 impact 字段中混入描述性文字。** 311 +6. **SWOT块使用限制(重要!)**:
  312 + - 只有在 constraints.allowSwot 为 true 时才允许使用 block.type="swotTable";
  313 + - 如果 constraints.allowSwot 为 false 或不存在,严禁生成任何 swotTable 类型的块,即使章节标题包含"SWOT"字样也不能使用该块类型,应改用表格(table)或列表(list)呈现相关内容;
  314 + - 当允许使用SWOT块时,分别填写 strengths/weaknesses/opportunities/threats 数组,单项至少包含 title/label/text 之一,可附加 detail/evidence/impact 字段;title/summary 字段用于概览说明;
  315 + - **特别注意:impact 字段只允许填写影响评级("低"/"中低"/"中"/"中高"/"高"/"极高");任何关于影响的文字叙述、详细说明、佐证或扩展描述必须写入 detail 字段,禁止在 impact 字段中混入描述性文字。**
308 7. 如需引用图表/交互组件,统一用widgetType表示(例如chart.js/line、chart.js/doughnut)。 316 7. 如需引用图表/交互组件,统一用widgetType表示(例如chart.js/line、chart.js/doughnut)。
309 8. 鼓励结合outline中列出的子标题,生成多层heading与细粒度内容,同时可补充callout、blockquote等。 317 8. 鼓励结合outline中列出的子标题,生成多层heading与细粒度内容,同时可补充callout、blockquote等。
310 9. engineQuote 仅用于呈现单Agent的原话:使用 block.type="engineQuote",engine 取值 insight/media/query,title 必须固定为对应Agent名字(insight->Insight Agent,media->Media Agent,query->Query Agent,不可自定义),内部 blocks 只允许 paragraph,paragraph.inlines 的 marks 仅可使用 bold/italic(可留空),禁止在 engineQuote 中放表格/图表/引用/公式等;当 reports 或 forumLogs 中有明确的文字段落、结论、数字/时间等可直接引用时,优先分别从 Query/Media/Insight 三个 Agent 摘出关键原文或文字版数据放入 engineQuote,尽量覆盖三类 Agent 而非只用单一来源,严禁臆造内容或把表格/图表改写进 engineQuote。 318 9. engineQuote 仅用于呈现单Agent的原话:使用 block.type="engineQuote",engine 取值 insight/media/query,title 必须固定为对应Agent名字(insight->Insight Agent,media->Media Agent,query->Query Agent,不可自定义),内部 blocks 只允许 paragraph,paragraph.inlines 的 marks 仅可使用 bold/italic(可留空),禁止在 engineQuote 中放表格/图表/引用/公式等;当 reports 或 forumLogs 中有明确的文字段落、结论、数字/时间等可直接引用时,优先分别从 Query/Media/Insight 三个 Agent 摘出关键原文或文字版数据放入 engineQuote,尽量覆盖三类 Agent 而非只用单一来源,严禁臆造内容或把表格/图表改写进 engineQuote。
@@ -376,7 +384,12 @@ SYSTEM_PROMPT_DOCUMENT_LAYOUT = f""" @@ -376,7 +384,12 @@ SYSTEM_PROMPT_DOCUMENT_LAYOUT = f"""
376 3. 输出 tocPlan,一级目录固定用中文数字("一、二、三"),二级目录用"1.1/1.2",可在description里说明详略;如需定制目录标题,请填写 tocTitle; 384 3. 输出 tocPlan,一级目录固定用中文数字("一、二、三"),二级目录用"1.1/1.2",可在description里说明详略;如需定制目录标题,请填写 tocTitle;
377 4. 根据模板结构和素材密度,为 themeTokens / layoutNotes 提出字体、字号、留白建议(需特别强调目录、正文一级标题字号保持统一),如需色板或暗黑模式兼容也在此说明; 385 4. 根据模板结构和素材密度,为 themeTokens / layoutNotes 提出字体、字号、留白建议(需特别强调目录、正文一级标题字号保持统一),如需色板或暗黑模式兼容也在此说明;
378 5. 严禁要求外部图片或AI生图,推荐Chart.js图表、表格、色块、KPI卡等可直接渲染的原生组件; 386 5. 严禁要求外部图片或AI生图,推荐Chart.js图表、表格、色块、KPI卡等可直接渲染的原生组件;
379 -6. 不随意增删章节,仅优化命名或描述;若有排版或章节合并提示,请放入 layoutNotes,渲染层会严格遵循。 387 +6. 不随意增删章节,仅优化命名或描述;若有排版或章节合并提示,请放入 layoutNotes,渲染层会严格遵循;
  388 +7. **SWOT块使用规则**:在 tocPlan 中决定是否以及在哪一章使用SWOT分析块(swotTable):
  389 + - 全文最多只允许一个章节使用SWOT块,该章节需设置 `allowSwot: true`;
  390 + - 其他章节必须设置 `allowSwot: false` 或省略该字段;
  391 + - SWOT块适合出现在"结论与建议"、"综合评估"、"战略分析"等总结性章节;
  392 + - 如果报告内容不适合使用SWOT分析(如纯数据监测报告),则所有章节都不设置 `allowSwot: true`。
380 393
381 **tocPlan的description字段特别要求:** 394 **tocPlan的description字段特别要求:**
382 - description字段必须是纯文本描述,用于在目录中展示章节简介 395 - description字段必须是纯文本描述,用于在目录中展示章节简介