From 922c43e65b110bfde635d611b727e6f33f440469 Mon Sep 17 00:00:00 2001 From: sladro Date: Tue, 30 Sep 2025 10:07:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=8F=8D=E9=A6=88=E4=BC=98=E5=8C=96=E6=B5=81=E7=A8=8B=E4=B8=AD?= =?UTF-8?q?AI=E8=BF=94=E5=9B=9E=E7=BB=93=E6=9E=9C=E8=A2=AB=E4=B8=A2?= =?UTF-8?q?=E5=A4=B1=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: - 用户提供目录优化反馈后,AI返回了正确的优化结果,但最终显示时优化内容丢失 - 验证逻辑缺少对"质量安全"等章节的关键词识别 修复: 1. 修复数据流:optimize_with_feedback更新adjusted_chapters,finalize_chapters从adjusted_chapters读取 2. 添加"质量安全"关键词到验证逻辑 3. 清理调试日志(删除🔥符号和JSON打印) 影响: - 用户反馈优化功能现在能正确保留AI的修改结果 - 验证逻辑能准确识别更多章节类型 - 日志输出更简洁 🤖 Generated with Claude Code Co-Authored-By: Claude --- src/bidmaster/nodes/toc/apply_suggestions.py | 1 - src/bidmaster/nodes/toc/base_mixins.py | 1 - src/bidmaster/nodes/toc/finalize_chapters.py | 2 +- .../nodes/toc/optimize_with_feedback.py | 343 ++++++++++++++++++ 4 files changed, 344 insertions(+), 3 deletions(-) create mode 100644 src/bidmaster/nodes/toc/optimize_with_feedback.py diff --git a/src/bidmaster/nodes/toc/apply_suggestions.py b/src/bidmaster/nodes/toc/apply_suggestions.py index 86facff..b5d7197 100644 --- a/src/bidmaster/nodes/toc/apply_suggestions.py +++ b/src/bidmaster/nodes/toc/apply_suggestions.py @@ -29,7 +29,6 @@ class ApplyReviewSuggestionsNode(BaseNode, TocNodeBase): def _do_apply_suggestions(self, state: Dict[str, Any]) -> Dict[str, Any]: """执行实际的建议应用逻辑""" - logger.info("🔥🔥🔥 ApplyReviewSuggestionsNode 正在执行!!!") # 验证必需字段 required_fields = ["preliminary_chapters", "structure_review"] if not self.validate_required_fields(state, required_fields): diff --git a/src/bidmaster/nodes/toc/base_mixins.py b/src/bidmaster/nodes/toc/base_mixins.py index b4ef7dc..2c42c82 100644 --- a/src/bidmaster/nodes/toc/base_mixins.py +++ b/src/bidmaster/nodes/toc/base_mixins.py @@ -154,7 +154,6 @@ class WorkflowUtilsMixin: "continue" 如果应该继续,"end" 如果应该停止 """ result = "end" if (state.get("error") or not state.get("should_continue", True)) else "continue" - logger.info(f"🔥🔥🔥 should_continue_workflow: {result}, error={state.get('error')}, should_continue={state.get('should_continue', True)}") return result @staticmethod diff --git a/src/bidmaster/nodes/toc/finalize_chapters.py b/src/bidmaster/nodes/toc/finalize_chapters.py index 702087f..c383787 100644 --- a/src/bidmaster/nodes/toc/finalize_chapters.py +++ b/src/bidmaster/nodes/toc/finalize_chapters.py @@ -48,7 +48,7 @@ class FinalizeChaptersNode(BaseNode, TocNodeBase): return self._update_state(state, final_chapters=final_chapters, - should_continue=False) + should_continue=True) # 让后续user_feedback节点决定是否继续 def _ensure_standard_chapters(self, chapters: List[DocumentChapter]) -> List[DocumentChapter]: diff --git a/src/bidmaster/nodes/toc/optimize_with_feedback.py b/src/bidmaster/nodes/toc/optimize_with_feedback.py new file mode 100644 index 0000000..8f8fb56 --- /dev/null +++ b/src/bidmaster/nodes/toc/optimize_with_feedback.py @@ -0,0 +1,343 @@ +"""根据用户反馈优化目录节点 + +基于用户反馈和当前目录结构,使用AI进行优化调整。 +""" + +import json +import logging +from typing import Dict, List, Any, Optional + +from ..base import BaseNode, NodeContext +from ...tools.parser import DocumentChapter +from .base_mixins import TocNodeBase +from .llm_helper import LLMHelper +from .factories import ChapterFactory + +logger = logging.getLogger(__name__) + + +class OptimizeWithFeedbackNode(BaseNode, TocNodeBase): + """根据用户反馈优化目录""" + + @property + def name(self) -> str: + return "optimize_with_feedback" + + @property + def description(self) -> str: + return "根据用户反馈优化目录" + + def execute(self, state: Dict[str, Any], context: NodeContext) -> Dict[str, Any]: + """执行目录优化""" + return self.safe_execute(self._do_optimize, state, "根据用户反馈优化目录") + + def _do_optimize(self, state: Dict[str, Any]) -> Dict[str, Any]: + """执行实际的目录优化逻辑""" + # 验证必需字段 + required_fields = ["final_chapters", "user_feedback"] + if not self.validate_required_fields(state, required_fields): + raise ValueError("缺少目录数据或用户反馈") + + final_chapters = state.get("final_chapters", []) + user_feedback = state.get("user_feedback", "") + + if not user_feedback.strip(): + # 没有反馈,直接返回 + logger.warning("用户反馈为空,跳过优化") + return self._update_state(state, + needs_optimization=False) + + # 构建优化prompt + optimized_chapters = self._optimize_with_ai(final_chapters, user_feedback) + + if optimized_chapters: + # 验证是否真的有修改 + if self._verify_modifications(final_chapters, optimized_chapters, user_feedback): + self.log_step_info("optimize_with_feedback", f"根据用户反馈优化了{len(optimized_chapters)}个章节") + return self._update_state(state, + adjusted_chapters=optimized_chapters, + user_feedback="", # 清空反馈 + needs_optimization=False) + else: + logger.warning("AI优化结果未按用户要求修改,保持原有结构") + return self._update_state(state, + user_feedback="", + needs_optimization=False) + else: + # 优化失败,保持原有结构 + logger.warning("AI优化失败,保持原有结构") + return self._update_state(state, + user_feedback="", + needs_optimization=False) + + def _optimize_with_ai(self, chapters: List[DocumentChapter], feedback: str) -> Optional[List[DocumentChapter]]: + """使用AI优化目录结构 + + Args: + chapters: 当前章节列表 + feedback: 用户反馈 + + Returns: + 优化后的章节列表,失败时返回None + """ + try: + # 格式化当前目录 + current_toc = self._format_chapters_for_prompt(chapters) + + prompt = f""" +你是一个专业的标书目录结构优化助手。 + +**⚠️ 警告:如果你不按照用户的要求修改目录结构,将被视为任务失败!⚠️** + +**🔥 强制要求:你必须完全执行用户的修改要求,不能返回未修改的结构!🔥** + +当前目录结构: +{current_toc} + +用户反馈意见: +{feedback} + +**执行步骤(必须严格遵守):** +1. **理解要求**:分析用户具体要求什么修改 +2. **执行修改**:必须按要求修改目录结构,不能保持原样 +3. **确认修改**:检查确保已经按用户要求进行了修改 + +**重要修改规则:** +- 如果用户说"内容太少,多补充"或"多增加",必须增加新的同级别子章节 +- 如果用户指定某个章节,只修改该章节 +- 如果用户要求"调整顺序",必须重新排列 +- 如果用户要求"修改标题",必须更新标题 + +**具体示例:** + +示例1:用户说"售后服务章节多增加一个子标题" +- 在"售后服务"下新增子章节(如"技术支持服务"、"培训服务"等) + +示例2:用户说"合规响应的内容太少,多补充一些" +- 在"合规响应"下除了"技术实力",还要增加: + * "资质认证展示" + * "成功案例介绍" + * "行业认可度说明" + * "合规承诺声明" + +**最终检查:** +修改完成后,你必须确认已经按用户要求进行了实际修改,不能返回相同的结构! + +请返回优化后的目录结构JSON,格式如下: +{{ + "chapters": [ + {{ + "id": "chapter_1", + "title": "章节标题", + "level": 1, + "score": 0.0, + "children": [ + {{ + "id": "chapter_1_1", + "title": "子章节标题", + "level": 2, + "score": 0.0, + "children": [] + }} + ] + }} + ] +}} + +只返回JSON,不要其他内容。 +""" + + # 调用AI优化 + logger.info("开始调用AI进行目录优化") + response = LLMHelper.call_llm_with_retry(prompt, max_retries=2) + if not response: + logger.error("AI优化无响应") + return None + + logger.debug(f"AI响应: {response[:200]}...") + + # 解析AI返回的JSON + return self._parse_optimized_structure(response) + + except Exception as e: + logger.error(f"AI优化失败: {e}") + return None + + def _format_chapters_for_prompt(self, chapters: List[DocumentChapter]) -> str: + """格式化章节用于AI prompt + + Args: + chapters: 章节列表 + + Returns: + 格式化的章节字符串 + """ + def format_chapter(chapter: DocumentChapter, level: int = 0) -> str: + indent = " " * level + score_text = f" ({chapter.score}分)" if chapter.score and chapter.score > 0 else "" + result = f"{indent}- {chapter.title}{score_text}" + + if chapter.children: + for child in chapter.children: + result += "\n" + format_chapter(child, level + 1) + + return result + + return "\n".join([format_chapter(ch) for ch in chapters]) + + def _parse_optimized_structure(self, json_response: str) -> Optional[List[DocumentChapter]]: + """解析AI返回的优化结构 + + Args: + json_response: AI返回的JSON字符串 + + Returns: + 解析后的章节列表,失败时返回None + """ + try: + # 清理响应,提取JSON部分 + json_str = json_response.strip() + if json_str.startswith("```json"): + json_str = json_str[7:] + if json_str.endswith("```"): + json_str = json_str[:-3] + json_str = json_str.strip() + + # 解析JSON + data = json.loads(json_str) + chapters_data = data.get("chapters", []) + + # 转换为DocumentChapter对象 + chapters = [] + for ch_data in chapters_data: + chapter = self._create_chapter_from_data(ch_data) + if chapter: + chapters.append(chapter) + + logger.info(f"解析完成,生成了{len(chapters)}个章节") + + return chapters + + except json.JSONDecodeError as e: + logger.error(f"解析AI返回的JSON失败: {e}") + return None + except Exception as e: + logger.error(f"处理优化结构失败: {e}") + return None + + def _create_chapter_from_data(self, data: Dict[str, Any]) -> Optional[DocumentChapter]: + """从数据字典创建DocumentChapter + + Args: + data: 章节数据字典 + + Returns: + DocumentChapter对象,失败时返回None + """ + try: + chapter = DocumentChapter( + id=data.get("id", ""), + title=data.get("title", ""), + level=data.get("level", 1), + score=float(data.get("score", 0.0)) + ) + + # 处理子章节 + children_data = data.get("children", []) + for child_data in children_data: + child = self._create_chapter_from_data(child_data) + if child: + chapter.children.append(child) + + return chapter + + except Exception as e: + logger.error(f"创建章节失败: {e}") + return None + + def _verify_modifications(self, original_chapters: List[DocumentChapter], + optimized_chapters: List[DocumentChapter], + user_feedback: str) -> bool: + """验证AI优化是否按用户要求进行了修改 + + Args: + original_chapters: 原始章节列表 + optimized_chapters: 优化后的章节列表 + user_feedback: 用户反馈内容 + + Returns: + 是否按要求进行了修改 + """ + try: + # 基本检查:结构是否有变化 + if len(original_chapters) != len(optimized_chapters): + logger.info("检测到章节数量变化,认为修改有效") + return True + + # 特殊检查:如果用户要求增加子标题 + if "增加" in user_feedback and ("子标题" in user_feedback or "子章节" in user_feedback): + return self._verify_subchapter_addition(original_chapters, optimized_chapters, user_feedback) + + # 检查标题是否有变化 + for orig, opt in zip(original_chapters, optimized_chapters): + if orig.title != opt.title: + logger.info(f"检测到标题变化: {orig.title} -> {opt.title}") + return True + + # 递归检查子章节 + if self._has_structural_changes(orig, opt): + return True + + logger.warning("未检测到明显的结构变化") + return False + + except Exception as e: + logger.error(f"验证修改失败: {e}") + return True # 出错时默认认为有修改,避免误判 + + def _verify_subchapter_addition(self, original_chapters: List[DocumentChapter], + optimized_chapters: List[DocumentChapter], + user_feedback: str) -> bool: + """验证是否按要求增加了子章节""" + # 查找用户提到的章节 + target_keywords = [] + if "售后服务" in user_feedback or "售后" in user_feedback: + target_keywords = ["售后服务", "售后", "after_sales"] + elif "合规响应" in user_feedback or "合规" in user_feedback: + target_keywords = ["合规响应", "合规", "compliance"] + elif "质量安全" in user_feedback or "质量" in user_feedback: + target_keywords = ["质量安全", "质量", "quality_safety"] + + for orig, opt in zip(original_chapters, optimized_chapters): + # 检查是否是目标章节 + if target_keywords: + is_target = any(keyword in orig.title or keyword in orig.id for keyword in target_keywords) + else: + # 如果没有特定关键词,检查所有章节 + is_target = True + + if is_target and len(opt.children) > len(orig.children): + logger.info(f"检测到{orig.title}章节子标题增加: {len(orig.children)} -> {len(opt.children)}") + return True + + # 递归检查子章节的子章节 + if orig.children and opt.children: + if self._verify_subchapter_addition(orig.children, opt.children, user_feedback): + return True + + return False + + def _has_structural_changes(self, orig: DocumentChapter, opt: DocumentChapter) -> bool: + """递归检查章节结构是否有变化""" + if len(orig.children) != len(opt.children): + logger.info(f"检测到{orig.title}子章节数量变化: {len(orig.children)} -> {len(opt.children)}") + return True + + for orig_child, opt_child in zip(orig.children, opt.children): + if orig_child.title != opt_child.title: + logger.info(f"检测到子章节标题变化: {orig_child.title} -> {opt_child.title}") + return True + if self._has_structural_changes(orig_child, opt_child): + return True + + return False \ No newline at end of file