bidmaster-cli/src/bidmaster/nodes/toc/generate_sub_chapters.py
sladro c74ae0b418 fix: 修复CLI导入问题并简化交互流程
主要修改:
- 修复CLI模块导入:添加__main__.py和正确的包配置
- 修复InteractionHandler调用错误:使用__call__而非.interact()
- 删除重复交互:AnalysisAgent和TocAgent都询问生成模式
- 简化生成方式:删除模板选项,只保留AI生成
- 消除代码重复:BidParser专注解析,TocAgent负责生成

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-28 20:21:10 +08:00

274 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""生成二三级子标题的节点
通过AI智能生成或模板生成二三级子标题。
"""
import json
import logging
from typing import Dict, List, Any, Optional
from ..base import BaseNode, NodeContext
from ...tools.parser import ScoringCriteria, DocumentChapter
from ...tools.llm import llm_service
logger = logging.getLogger(__name__)
class GenerateSubChaptersNode(BaseNode):
"""生成二三级子标题的节点"""
@property
def name(self) -> str:
return "generate_sub_chapters"
@property
def description(self) -> str:
return "生成二三级子标题"
def execute(self, state: Dict[str, Any], context: NodeContext) -> Dict[str, Any]:
"""执行子标题生成"""
self._log_start()
try:
preliminary_chapters = state.get("preliminary_chapters", [])
technical_criteria = state.get("technical_criteria", [])
if not preliminary_chapters:
raise ValueError("缺少初步章节")
# 只使用AI生成不再需要交互选择
generation_mode = "ai"
template_file = None
# 为每个章节生成子标题
enhanced_chapters = []
for chapter in preliminary_chapters:
enhanced_chapter = self._enhance_chapter_with_subs(
chapter, technical_criteria, generation_mode, template_file
)
enhanced_chapters.append(enhanced_chapter)
logger.info(f"完成{len(enhanced_chapters)}个章节的子标题生成")
# 更新状态
state["preliminary_chapters"] = enhanced_chapters
state["current_step"] = self.name
self._log_success()
return state
except Exception as e:
self._log_error(e)
state["error"] = str(e)
state["should_continue"] = False
raise
def _enhance_chapter_with_subs(self,
chapter: DocumentChapter,
technical_criteria: List[ScoringCriteria],
generation_mode: str,
template_file: Optional[str]) -> DocumentChapter:
"""为章节增强子标题
Args:
chapter: 原始章节
technical_criteria: 技术评分项
generation_mode: 生成模式
template_file: 模板文件仅template模式使用
Returns:
增强后的章节
"""
# 找到该章节对应的评分项
corresponding_criteria = self._find_corresponding_criteria(chapter, technical_criteria)
if not corresponding_criteria:
logger.warning(f"章节 {chapter.title} 没有找到对应的评分项")
return chapter
# 根据模式生成子标题
if generation_mode == "ai":
sub_chapters = self._generate_ai_sub_chapters(corresponding_criteria, chapter)
else:
sub_chapters = self._generate_template_sub_chapters(
corresponding_criteria[0], chapter, template_file
)
# 设置子章节
chapter.children = sub_chapters
logger.info(f"章节 {chapter.title} 生成了 {len(sub_chapters)} 个子标题")
return chapter
def _find_corresponding_criteria(self,
chapter: DocumentChapter,
technical_criteria: List[ScoringCriteria]) -> List[ScoringCriteria]:
"""查找章节对应的评分项
Args:
chapter: 章节
technical_criteria: 技术评分项列表
Returns:
对应的评分项列表
"""
# 从章节ID提取类别
if "_" not in chapter.id:
return []
parts = chapter.id.split("_")
if len(parts) < 3:
return []
category = "_".join(parts[2:])
# 找到该类别的所有评分项
return [
c for c in technical_criteria
if c.category.value == category
]
def _generate_ai_sub_chapters(self,
criteria_list: List[ScoringCriteria],
parent_chapter: DocumentChapter) -> List[DocumentChapter]:
"""AI生成子标题
Args:
criteria_list: 对应的评分项列表
parent_chapter: 父章节
Returns:
子章节列表
"""
try:
# 构建评分项信息
criteria_info = []
for criteria in criteria_list:
criteria_info.append(f"- {criteria.item_name} ({criteria.max_score}分)")
prompt = f"""
为以下大类别生成专业的标书子标题:
【大类别】: {parent_chapter.title}
【评分项】:
{chr(10).join(criteria_info)}
生成要求:
1. 为每个评分项生成对应的子标题名称(不要包含编号)
2. 重要评分项可添加三级子标题(不要包含编号)
3. 只返回标题文本编号由Word自动管理
返回JSON格式
{{
"sub_chapters": [
{{"title": "技术架构设计", "level": 2, "score": 5, "children": []}}
]
}}
只返回JSON"""
# 使用统一的LLM服务
response = llm_service.call(prompt)
if not response:
raise ValueError("AI生成子标题失败: API无响应")
# 解析响应
result_data = self._parse_ai_response(response)
sub_chapters_data = result_data.get("sub_chapters", [])
# 构建子章节对象
sub_chapters = []
for i, sub_data in enumerate(sub_chapters_data, 1):
title = sub_data.get("title", f"子标题{i}")
sub_chapter = DocumentChapter(
id=f"{parent_chapter.id}_sub_{i:02d}",
title=title, # 不添加编号
level=sub_data.get("level", 2),
score=sub_data.get("score", 0),
template_placeholder=f"{{{{{parent_chapter.id}_sub_{i:02d}_content}}}}"
)
# 处理三级标题
for j, child_data in enumerate(sub_data.get("children", []), 1):
child_title = child_data.get("title", f"三级标题{j}")
child_chapter = DocumentChapter(
id=f"{parent_chapter.id}_sub_{i:02d}_{j:02d}",
title=child_title, # 不添加编号
level=child_data.get("level", 3),
template_placeholder=f"{{{{{parent_chapter.id}_sub_{i:02d}_{j:02d}_content}}}}"
)
sub_chapter.children.append(child_chapter)
sub_chapters.append(sub_chapter)
return sub_chapters
except Exception as e:
logger.error(f"AI生成子标题失败: {e}")
# 立即失败,不提供后备方案
raise
def _parse_ai_response(self, response: str) -> Dict[str, Any]:
"""解析AI响应
Args:
response: AI响应文本
Returns:
解析后的数据
Raises:
ValueError: 解析失败时抛出
"""
try:
clean_response = response.strip()
if clean_response.startswith("```json"):
clean_response = clean_response[7:]
if clean_response.endswith("```"):
clean_response = clean_response[:-3]
return json.loads(clean_response.strip())
except (json.JSONDecodeError, KeyError) as e:
raise ValueError(f"解析AI响应失败: {e}")
def _generate_template_sub_chapters(self,
criteria: ScoringCriteria,
parent_chapter: DocumentChapter,
template_file: Optional[str]) -> List[DocumentChapter]:
"""基于模板生成子标题
Args:
criteria: 评分项(仅作参考)
parent_chapter: 父章节
template_file: 模板文件路径
Returns:
子章节列表
"""
# 提供默认结构(不包含编号)
default_sub_chapters = [
DocumentChapter(
id=f"{parent_chapter.id}_def_01",
title="方案概述", # 不包含编号
level=2,
template_placeholder=f"{{{{{parent_chapter.id}_def_01_content}}}}"
),
DocumentChapter(
id=f"{parent_chapter.id}_def_02",
title="具体实施", # 不包含编号
level=2,
template_placeholder=f"{{{{{parent_chapter.id}_def_02_content}}}}"
),
DocumentChapter(
id=f"{parent_chapter.id}_def_03",
title="保障措施", # 不包含编号
level=2,
template_placeholder=f"{{{{{parent_chapter.id}_def_03_content}}}}"
)
]
logger.info(f"使用默认模板为 {parent_chapter.title} 生成子标题")
return default_sub_chapters