## 核心改进
### 1. 移除静默降级,快速失败 (interact_user.py)
- 删除无交互处理器时返回默认值的静默逻辑
- 改为立即抛出异常暴露配置缺失问题
- 符合"暴露问题、快速失败"原则
### 2. 统一配置源 (word.py + settings.py)
- 删除FormatConstants类,消除配置冗余
- Word格式参数统一从settings读取
- 新增Word格式配置项到settings.py
- 实现单一数据源原则
### 3. 统一RAGTool实例化 (init_config.py)
- 在InitConfigNode统一创建RAGTool实例
- generate_content.py和interact_user.py改为验证式获取
- 消除重复实例化逻辑
- 修复single_chapter_agent.py状态传递丢失rag_tool问题
### 4. 拆分复杂方法 (word.py)
- fill_placeholder拆分为6个原子操作:
* _parse_chapter_number_from_placeholder
* _find_heading_by_chapter_id
* _get_chapter_level_from_heading
* _parse_markdown_to_paragraphs
* _insert_parsed_paragraphs
* 保存文档
- 符合"小步快跑"原则
### 5. 优化配置继承 (init_config.py + generate_content.py)
- 新增_expand_config_inheritance预展开继承链
- _get_chapter_config从O(n)降为O(1)查找
- 移除运行时while循环向上查找
### 6. 统一异常处理策略
- 所有错误立即抛出,移除静默返回默认值
- interact_user.py: 获取知识库失败抛出异常
- generate_content.py: RAGTool未初始化抛出异常
### 7. 修复状态传递问题 (single_chapter_agent.py)
- 使用{**state, ...}继承所有字段
- 避免硬编码字典遗漏rag_tool和expanded_configs
- 确保初始化数据正确传递
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
222 lines
7.1 KiB
Python
222 lines
7.1 KiB
Python
"""内容生成节点
|
||
|
||
调用RAG工具生成章节内容。
|
||
"""
|
||
|
||
import logging
|
||
from typing import Any, Dict, Optional
|
||
|
||
from ..base import BaseNode, NodeContext
|
||
from ...config.settings import get_settings
|
||
|
||
logger = logging.getLogger(__name__)
|
||
settings = get_settings()
|
||
|
||
|
||
class GenerateContentNode(BaseNode):
|
||
"""内容生成节点
|
||
|
||
职责:
|
||
1. 获取当前章节配置(如果是子章节,继承父章节配置)
|
||
2. 调用RAGTool生成内容
|
||
3. 保存生成结果到state
|
||
"""
|
||
|
||
@property
|
||
def name(self) -> str:
|
||
return "generate_content"
|
||
|
||
@property
|
||
def description(self) -> str:
|
||
return "生成章节内容"
|
||
|
||
def execute(self, state: Dict[str, Any], context: NodeContext) -> Dict[str, Any]:
|
||
"""执行内容生成
|
||
|
||
Args:
|
||
state: 当前状态
|
||
context: 执行上下文
|
||
|
||
Returns:
|
||
更新后的状态
|
||
"""
|
||
chapter = state.get("current_chapter", {})
|
||
chapter_id = chapter.get("id")
|
||
chapter_title = chapter.get("title")
|
||
|
||
if not chapter_id:
|
||
raise ValueError("当前章节信息缺失")
|
||
|
||
logger.info(f"开始生成章节内容: {chapter_id} - {chapter_title}")
|
||
|
||
# 获取章节配置(继承父章节)
|
||
config = self._get_chapter_config(state, chapter)
|
||
|
||
# 查找当前章节的所有子标题
|
||
sub_chapters = self._find_sub_chapters(state, chapter_id, max_level=settings.max_sub_chapter_level)
|
||
|
||
if sub_chapters:
|
||
# 有子标题:逐个生成后拼接
|
||
logger.info(f"章节 {chapter_id} 包含 {len(sub_chapters)} 个子标题,逐个生成")
|
||
content_parts = []
|
||
for sub in sub_chapters:
|
||
sub_content = self._generate_with_rag(sub, config, state)
|
||
content_parts.append(f"## {sub['title']}\n\n{sub_content}")
|
||
content = "\n\n".join(content_parts)
|
||
else:
|
||
# 无子标题:直接生成
|
||
content = self._generate_with_rag(chapter, config, state)
|
||
|
||
# 保存生成结果
|
||
state.setdefault("generated_contents", {})[chapter_id] = content
|
||
|
||
logger.info(f"章节内容生成完成: {chapter_id}, 长度: {len(content)}字")
|
||
|
||
return state
|
||
|
||
def _get_chapter_config(
|
||
self, state: Dict[str, Any], chapter: Dict[str, Any]
|
||
) -> Dict[str, Any]:
|
||
"""获取章节配置,使用预展开的继承链
|
||
|
||
Args:
|
||
state: 当前状态
|
||
chapter: 当前章节
|
||
|
||
Returns:
|
||
章节配置
|
||
"""
|
||
chapter_id = chapter["id"]
|
||
chapter_configs = state.get("chapter_configs", {})
|
||
|
||
# 如果当前章节有配置,直接返回
|
||
if chapter_id in chapter_configs:
|
||
return chapter_configs[chapter_id]
|
||
|
||
# 使用预展开的配置继承链
|
||
expanded_configs = state.get("expanded_configs", {})
|
||
config_source_id = expanded_configs.get(chapter_id)
|
||
|
||
if config_source_id and config_source_id in chapter_configs:
|
||
logger.info(f"章节 {chapter_id} 继承 {config_source_id} 的配置")
|
||
return chapter_configs[config_source_id]
|
||
|
||
# 返回默认配置
|
||
logger.info(f"章节 {chapter_id} 使用默认配置")
|
||
return self._get_default_config()
|
||
|
||
def _get_default_config(self) -> Dict[str, Any]:
|
||
"""获取默认章节配置
|
||
|
||
Returns:
|
||
默认配置字典
|
||
"""
|
||
return {
|
||
"emphasis": "",
|
||
"rag_enabled": False,
|
||
"rag_store": None
|
||
}
|
||
|
||
def _find_chapter(self, state: Dict[str, Any], chapter_id: str) -> Optional[Dict[str, Any]]:
|
||
"""在队列中查找章节
|
||
|
||
Args:
|
||
state: 当前状态
|
||
chapter_id: 章节ID
|
||
|
||
Returns:
|
||
章节信息或None
|
||
"""
|
||
queue = state.get("chapter_queue", [])
|
||
for chapter in queue:
|
||
if chapter["id"] == chapter_id:
|
||
return chapter
|
||
return None
|
||
|
||
def _find_sub_chapters(
|
||
self, state: Dict[str, Any], parent_id: str, max_level: int = 3
|
||
) -> list:
|
||
"""查找指定章节的所有子标题(最深到3级)
|
||
|
||
Args:
|
||
state: 当前状态
|
||
parent_id: 父章节ID
|
||
max_level: 最大层级(默认3级)
|
||
|
||
Returns:
|
||
子章节列表
|
||
"""
|
||
queue = state.get("chapter_queue", [])
|
||
parent = self._find_chapter(state, parent_id)
|
||
if not parent:
|
||
return []
|
||
|
||
parent_level = parent.get("level", 1)
|
||
target_level = parent_level + 1
|
||
|
||
# 只查找直接子章节(比父章节深1层)且不超过max_level
|
||
sub_chapters = []
|
||
for chapter in queue:
|
||
if (
|
||
chapter.get("parent_id") == parent_id
|
||
and chapter.get("level") == target_level
|
||
and chapter.get("level") <= max_level
|
||
):
|
||
sub_chapters.append(chapter)
|
||
|
||
return sub_chapters
|
||
|
||
def _generate_with_rag(
|
||
self, chapter: Dict[str, Any], config: Dict[str, Any], state: Dict[str, Any]
|
||
) -> str:
|
||
"""使用RAG生成内容
|
||
|
||
Args:
|
||
chapter: 章节信息
|
||
config: 章节配置
|
||
state: 当前状态
|
||
|
||
Returns:
|
||
生成的内容
|
||
"""
|
||
# 从state获取RAGTool实例(由InitConfigNode统一初始化)
|
||
rag_tool = state.get("rag_tool")
|
||
if not rag_tool:
|
||
raise ValueError("RAGTool未初始化,请检查InitConfigNode配置")
|
||
|
||
# 构建生成上下文
|
||
generation_context = {
|
||
"title": chapter["title"],
|
||
"level": chapter["level"],
|
||
"requirements": chapter.get("requirements", ""),
|
||
"emphasis": config.get("emphasis", ""),
|
||
}
|
||
|
||
# 如果启用RAG,添加上下文信息
|
||
if config.get("rag_enabled"):
|
||
# 检索相关内容
|
||
query = f"{chapter['title']} {config.get('emphasis', '')}"
|
||
search_results = rag_tool.search(query, k=settings.rag_search_top_k)
|
||
|
||
if search_results:
|
||
relevant_context = "\n\n".join([r["content"] for r in search_results])
|
||
generation_context["rag_context"] = relevant_context
|
||
logger.info(f"RAG检索到 {len(search_results)} 条相关内容")
|
||
else:
|
||
logger.warning("RAG未检索到相关内容")
|
||
generation_context["rag_context"] = ""
|
||
else:
|
||
generation_context["rag_context"] = ""
|
||
|
||
# 添加父章节上下文
|
||
parent_context = state.get("last_generated_content", "")
|
||
if parent_context and chapter["level"] > 1:
|
||
generation_context["parent_context"] = parent_context[:settings.parent_context_length]
|
||
|
||
# 调用生成方法
|
||
try:
|
||
content = rag_tool.generate_content(chapter["id"], generation_context)
|
||
return content
|
||
except Exception as e:
|
||
logger.error(f"内容生成失败: {e}", exc_info=True)
|
||
raise ValueError(f"章节 {chapter['id']} - {chapter['title']} 内容生成失败") from e |