Explorar el Código

v0.0.5-功能优化
- 增加_ai_review_node_check_item 基础逻辑

WangXuMing hace 3 meses
padre
commit
59c3283c02

+ 18 - 2
core/base/task_models.py

@@ -34,8 +34,9 @@ class TaskFileInfo:
         self.file_type = file_info.get('file_type', '')
         self.file_type = file_info.get('file_type', '')
         self.file_content = file_info.get('file_content', b'')
         self.file_content = file_info.get('file_content', b'')
 
 
-        # 审查配置信息
-        review_config_raw = file_info.get('review_config', [])
+        # 审查配置信息(使用 .get() 提供默认值,支持键不存在的情况)
+        review_config_raw = file_info.get('review_config')
+        review_item_config_raw = file_info.get('review_item_config')
 
 
         # 类型校验:确保review_config是列表
         # 类型校验:确保review_config是列表
         if isinstance(review_config_raw, list):
         if isinstance(review_config_raw, list):
@@ -48,6 +49,17 @@ class TaskFileInfo:
             )
             )
             self.review_config = []
             self.review_config = []
 
 
+        # 类型校验:确保review_item_config是列表
+        if isinstance(review_item_config_raw, list):
+            self.review_item_config = review_item_config_raw
+        else:
+            # 如果不是列表,记录警告并使用默认空列表
+            logger.warning(
+                f"review_item_config类型错误,期望list,实际{type(review_item_config_raw).__name__},"
+                f"值: {review_item_config_raw},将使用空列表"
+            )
+            self.review_item_config = []
+
         self.project_plan_type = file_info.get('project_plan_type', '')
         self.project_plan_type = file_info.get('project_plan_type', '')
         self.tendency_review_role = file_info.get('tendency_review_role', '')
         self.tendency_review_role = file_info.get('tendency_review_role', '')
         self.test_designation_chunk_flag = file_info.get('test_designation_chunk_flag', '')
         self.test_designation_chunk_flag = file_info.get('test_designation_chunk_flag', '')
@@ -67,6 +79,10 @@ class TaskFileInfo:
         """获取审查配置列表"""
         """获取审查配置列表"""
         return self.review_config.copy()
         return self.review_config.copy()
 
 
+    def get_review_item_config_list(self) -> list:
+        """获取审查项配置列表(章节_审查维度格式)"""
+        return self.review_item_config.copy()
+
     def get_project_plan_type(self) -> str:
     def get_project_plan_type(self) -> str:
         """获取工程方案类型"""
         """获取工程方案类型"""
         return self.project_plan_type
         return self.project_plan_type

+ 123 - 159
core/construction_review/component/ai_review_engine.py

@@ -192,17 +192,110 @@ class AIReviewEngine(BaseReviewer):
             async with self.semaphore:
             async with self.semaphore:
                 return await check_func(*args, **kwargs)
                 return await check_func(*args, **kwargs)
         return wrapped_check()
         return wrapped_check()
-    
 
 
-    async def basic_compliance_check(self,trace_id_idx: str, unit_content: Dict[str, Any],
-                                   review_location_label: str,state:Dict[str, Any],stage_name:str) -> Dict[str, Any]:
+    def rag_enhanced_check(self, unit_content: Dict[str, Any]) -> Dict[str, Any]:
+        """
+        RAG增强审查
+
+        流程:
+        1. 提取查询对
+        2. 实体增强检索
+        3. 自动拼接父文档内容
+
+        Args:
+            unit_content: 待审查单元内容
+
+        Returns:
+            Dict[str, Any]: RAG增强审查结果
+        """
+        logger.info(f"[RAG增强] 开始处理, 内容长度: {len(unit_content)}")
+
+        # Step 2: 构建查询对
+        query_pairs = query_rewrite_manager.query_extract(unit_content)
+        logger.info(f"[RAG增强] 提取到 {len(query_pairs)} 个查询对")
+
+        # Step 3: 根据查询对主实体、辅助实体,进行实体增强召回
+        bfp_result_lists = entity_enhance.entities_enhance_retrieval(query_pairs)
+
+
+        # # 🔍 保存关键节点结果(用于对比分析)
+        # os.makedirs("temp/ai_review_engine", exist_ok=True)
+        # with open("temp/ai_review_engine/bfp_result_lists.json", "w", encoding='utf-8') as f:
+        #     json.dump(bfp_result_lists, f, ensure_ascii=False, indent=4)
+        # logger.info("[RAG增强] ✅ 已保存 bfp_result_lists 到 temp/ai_review_engine/bfp_result_lists.json")
+
+        # Step 4: 检查检索结果
+        if not bfp_result_lists:
+            logger.warning("[RAG增强] 实体检索未返回结果")
+            return {
+                'vector_search': [],
+                'retrieval_status': 'no_results',
+                'file_name': '',
+                'text_content': '',
+                'metadata': {}
+            }
+
+        # Step 5: 父文档增强(使用分组增强策略)
+        try:
+            enhancement_result = enhance_with_parent_docs_grouped(
+                self.milvus,
+                bfp_result_lists,
+                score_threshold=0.5,  # bfp_rerank_score 阈值
+                max_parents_per_pair=3,  # 每个查询对最多3个父文档
+                max_parent_text_length=8000  # 单个父文档最大8000字符(约5300 tokens)
+            )
+            enhanced_results = enhancement_result['enhanced_results']
+            enhanced_count = enhancement_result['enhanced_count']
+            enhanced_pairs = enhancement_result.get('enhanced_pairs', 0)
+            total_pairs = enhancement_result.get('total_pairs', 0)
+
+            # 确保目录存在
+            os.makedirs("temp/ai_review_engine", exist_ok=True)
+
+            with open("temp/ai_review_engine/enhance_with_parent_docs_grouped.json", "w", encoding='utf-8') as f:
+                json.dump(enhancement_result, f, ensure_ascii=False, indent=4)
+            logger.info(f"[RAG增强] ✅ 已保存分组增强结果到 temp/ai_review_engine/enhance_with_parent_docs_grouped.json")
+            logger.info(f"[RAG增强] 分组增强完成: {enhanced_pairs}/{total_pairs} 个查询对进行了增强")
+            logger.info(f"[RAG增强] 成功增强 {enhanced_count} 个结果,使用了 {len(enhancement_result['parent_docs'])} 个父文档")
+        except Exception as e:
+            logger.error(f"[RAG增强] 父文档增强失败: {e}", exc_info=True)
+            # 失败时使用原始结果
+            enhanced_results = bfp_result_lists
+
+        # Step 5: 提取查询对结果(只保留得分>0.5的结果)
+        entity_results = extract_query_pairs_results(enhanced_results, query_pairs, score_threshold=0.5)
+
+        # 保存最终结果用于调试
+        # with open(rf"temp\ai_review_engine\extract_query_pairs_results.json", "w", encoding='utf-8') as f:
+        #     json.dump(entity_results, f, ensure_ascii=False, indent=4)
+
+        # 如果没有结果通过阈值过滤,返回空结果
+        if not entity_results:
+            logger.warning("[RAG增强] 没有结果通过阈值过滤(得分>0.5),返回空结果")
+            return {
+                'vector_search': [],
+                'retrieval_status': 'no_results',
+                'file_name': '',
+                'text_content': '',
+                'metadata': {},
+                'entity_results': [],
+                'total_entities': 0
+            }
+
+        # 返回格式:返回列表形式的 entity_results
+        return {
+            'retrieval_status': 'success',
+            'entity_results': entity_results,
+            'total_entities': len(entity_results)
+        }
+
+    async def basic_compliance_check(self,trace_id_idx: str, unit_content: Dict[str, Any],state:Dict[str, Any],stage_name:str) -> Dict[str, Any]:
         """
         """
         基础合规性检查
         基础合规性检查
 
 
         Args:
         Args:
             trace_id_idx: 追踪ID索引
             trace_id_idx: 追踪ID索引
             unit_content: 待审查单元内容
             unit_content: 待审查单元内容
-            review_location_label: 审查位置标签
             state: 状态字典
             state: 状态字典
             stage_name: 阶段名称
             stage_name: 阶段名称
             current_progress: 当前进度
             current_progress: 当前进度
@@ -227,7 +320,7 @@ class AIReviewEngine(BaseReviewer):
             basic_tasks.append(
             basic_tasks.append(
                 asyncio.create_task(
                 asyncio.create_task(
                     asyncio.wait_for(
                     asyncio.wait_for(
-                        check_with_semaphore(self.sensitive_word_check, trace_id_idx=trace_id_idx, review_content=review_content, review_references=None, review_location_label=review_location_label, state=state, stage_name=stage_name),
+                        check_with_semaphore(self.sensitive_word_check, trace_id_idx=trace_id_idx, review_content=review_content, state=state, stage_name=stage_name),
                         timeout=TASK_TIMEOUT
                         timeout=TASK_TIMEOUT
                     )
                     )
                 )
                 )
@@ -236,7 +329,7 @@ class AIReviewEngine(BaseReviewer):
             basic_tasks.append(
             basic_tasks.append(
                 asyncio.create_task(
                 asyncio.create_task(
                     asyncio.wait_for(
                     asyncio.wait_for(
-                        check_with_semaphore(self.check_semantic_logic, trace_id_idx=trace_id_idx, review_content=review_content, review_references=None, review_location_label=review_location_label, state=state, stage_name=stage_name),
+                        check_with_semaphore(self.check_semantic_logic, trace_id_idx=trace_id_idx, review_content=review_content, state=state, stage_name=stage_name),
                         timeout=TASK_TIMEOUT
                         timeout=TASK_TIMEOUT
                     )
                     )
                 )
                 )
@@ -245,7 +338,7 @@ class AIReviewEngine(BaseReviewer):
             basic_tasks.append(
             basic_tasks.append(
                 asyncio.create_task(
                 asyncio.create_task(
                     asyncio.wait_for(
                     asyncio.wait_for(
-                        check_with_semaphore(self.check_sensitive, trace_id_idx=trace_id_idx, review_content=review_content, review_references=None, review_location_label=review_location_label, state=state, stage_name=stage_name),
+                        check_with_semaphore(self.check_sensitive, trace_id_idx=trace_id_idx, review_content=review_content, state=state, stage_name=stage_name),
                         timeout=TASK_TIMEOUT
                         timeout=TASK_TIMEOUT
                     )
                     )
                 )
                 )
@@ -254,7 +347,7 @@ class AIReviewEngine(BaseReviewer):
         #     basic_tasks.append(
         #     basic_tasks.append(
         #         asyncio.create_task(
         #         asyncio.create_task(
         #             asyncio.wait_for(
         #             asyncio.wait_for(
-        #                 check_with_semaphore(self.check_completeness, trace_id_idx=trace_id_idx, review_content=unit_content, review_references=None, review_location_label=review_location_label, state=state, stage_name=stage_name),
+        #                 check_with_semaphore(self.check_completeness, trace_id_idx=trace_id_idx, review_content=unit_content, state=state, stage_name=stage_name),
         #                 timeout=TASK_TIMEOUT
         #                 timeout=TASK_TIMEOUT
         #             )
         #             )
         #         )
         #         )
@@ -326,8 +419,9 @@ class AIReviewEngine(BaseReviewer):
             'sensitive_check': sensitive_result,
             'sensitive_check': sensitive_result,
             #'completeness_check': completeness_result,
             #'completeness_check': completeness_result,
         }
         }
+
     async def technical_compliance_check(self,trace_id_idx: str, unit_content: Dict[str, Any],
     async def technical_compliance_check(self,trace_id_idx: str, unit_content: Dict[str, Any],
-                                      review_location_label: str,state:Dict[str, Any],stage_name:str) -> Dict[str, Any]:
+                                      state:Dict[str, Any],stage_name:str) -> Dict[str, Any]:
         """
         """
         技术性合规检查(包含RAG增强审查)
         技术性合规检查(包含RAG增强审查)
 
 
@@ -353,7 +447,7 @@ class AIReviewEngine(BaseReviewer):
 
 
         if 'non_parameter_compliance_check' in self.task_info.get_review_config_list() or 'parameter_compliance_check' in self.task_info.get_review_config_list():
         if 'non_parameter_compliance_check' in self.task_info.get_review_config_list() or 'parameter_compliance_check' in self.task_info.get_review_config_list():
             logger.info("执行专业性审查,开始RAG增强检索")
             logger.info("执行专业性审查,开始RAG增强检索")
-            rag_result = self.rag_enhanced_check(unit_content)
+            rag_result = self.rag_enhanced_check(review_content)
             entity_results = rag_result.get('entity_results', [])
             entity_results = rag_result.get('entity_results', [])
             logger.info(f"[RAG增强] 获取到 {len(entity_results)} 个查询对结果")
             logger.info(f"[RAG增强] 获取到 {len(entity_results)} 个查询对结果")
 
 
@@ -398,7 +492,6 @@ class AIReviewEngine(BaseReviewer):
                                     review_content=review_content,
                                     review_content=review_content,
                                     review_references=text_content,  # 使用查询对的检索结果作为参考
                                     review_references=text_content,  # 使用查询对的检索结果作为参考
                                     reference_source=file_name,
                                     reference_source=file_name,
-                                    review_location_label=review_location_label,
                                     state=state,
                                     state=state,
                                     stage_name=stage_name,
                                     stage_name=stage_name,
                                     entity_query=combined_query  # 传入组合查询条文
                                     entity_query=combined_query  # 传入组合查询条文
@@ -420,7 +513,6 @@ class AIReviewEngine(BaseReviewer):
                                     review_content=review_content,
                                     review_content=review_content,
                                     review_references=text_content,  # 使用查询对的检索结果作为参考
                                     review_references=text_content,  # 使用查询对的检索结果作为参考
                                     reference_source=file_name,
                                     reference_source=file_name,
-                                    review_location_label=review_location_label,
                                     state=state,
                                     state=state,
                                     stage_name=stage_name,
                                     stage_name=stage_name,
                                     entity_query=combined_query  # 传入组合查询条文
                                     entity_query=combined_query  # 传入组合查询条文
@@ -551,115 +643,14 @@ class AIReviewEngine(BaseReviewer):
                 'parameter_compliance': parameter_result
                 'parameter_compliance': parameter_result
             }
             }
 
 
-    def rag_enhanced_check(self, unit_content: Dict[str, Any]) -> Dict[str, Any]:
-        """
-        RAG增强审查
-
-        流程:
-        1. 提取查询对
-        2. 实体增强检索
-        3. 自动拼接父文档内容
-
-        Args:
-            unit_content: 待审查单元内容
-
-        Returns:
-            Dict[str, Any]: RAG增强审查结果
-        """
-        # Step 1: 获取待审查单元内容
-        query_content = unit_content['content']
-        logger.info(f"[RAG增强] 开始处理, 内容长度: {len(query_content)}")
-
-        # Step 2: 构建查询对
-        query_pairs = query_rewrite_manager.query_extract(query_content)
-        logger.info(f"[RAG增强] 提取到 {len(query_pairs)} 个查询对")
-
-        # Step 3: 根据查询对主实体、辅助实体,进行实体增强召回
-        bfp_result_lists = entity_enhance.entities_enhance_retrieval(query_pairs)
-
-
-        # # 🔍 保存关键节点结果(用于对比分析)
-        # os.makedirs("temp/ai_review_engine", exist_ok=True)
-        # with open("temp/ai_review_engine/bfp_result_lists.json", "w", encoding='utf-8') as f:
-        #     json.dump(bfp_result_lists, f, ensure_ascii=False, indent=4)
-        # logger.info("[RAG增强] ✅ 已保存 bfp_result_lists 到 temp/ai_review_engine/bfp_result_lists.json")
-
-        # Step 4: 检查检索结果
-        if not bfp_result_lists:
-            logger.warning("[RAG增强] 实体检索未返回结果")
-            return {
-                'vector_search': [],
-                'retrieval_status': 'no_results',
-                'file_name': '',
-                'text_content': '',
-                'metadata': {}
-            }
-
-        # Step 5: 父文档增强(使用分组增强策略)
-        try:
-            enhancement_result = enhance_with_parent_docs_grouped(
-                self.milvus,
-                bfp_result_lists,
-                score_threshold=0.5,  # bfp_rerank_score 阈值
-                max_parents_per_pair=3,  # 每个查询对最多3个父文档
-                max_parent_text_length=8000  # 单个父文档最大8000字符(约5300 tokens)
-            )
-            enhanced_results = enhancement_result['enhanced_results']
-            enhanced_count = enhancement_result['enhanced_count']
-            enhanced_pairs = enhancement_result.get('enhanced_pairs', 0)
-            total_pairs = enhancement_result.get('total_pairs', 0)
-
-            # 确保目录存在
-            os.makedirs("temp/ai_review_engine", exist_ok=True)
-
-            with open("temp/ai_review_engine/enhance_with_parent_docs_grouped.json", "w", encoding='utf-8') as f:
-                json.dump(enhancement_result, f, ensure_ascii=False, indent=4)
-            logger.info(f"[RAG增强] ✅ 已保存分组增强结果到 temp/ai_review_engine/enhance_with_parent_docs_grouped.json")
-            logger.info(f"[RAG增强] 分组增强完成: {enhanced_pairs}/{total_pairs} 个查询对进行了增强")
-            logger.info(f"[RAG增强] 成功增强 {enhanced_count} 个结果,使用了 {len(enhancement_result['parent_docs'])} 个父文档")
-        except Exception as e:
-            logger.error(f"[RAG增强] 父文档增强失败: {e}", exc_info=True)
-            # 失败时使用原始结果
-            enhanced_results = bfp_result_lists
-
-        # Step 5: 提取查询对结果(只保留得分>0.5的结果)
-        entity_results = extract_query_pairs_results(enhanced_results, query_pairs, score_threshold=0.5)
-
-        # 保存最终结果用于调试
-        # with open(rf"temp\ai_review_engine\extract_query_pairs_results.json", "w", encoding='utf-8') as f:
-        #     json.dump(entity_results, f, ensure_ascii=False, indent=4)
-
-        # 如果没有结果通过阈值过滤,返回空结果
-        if not entity_results:
-            logger.warning("[RAG增强] 没有结果通过阈值过滤(得分>0.5),返回空结果")
-            return {
-                'vector_search': [],
-                'retrieval_status': 'no_results',
-                'file_name': '',
-                'text_content': '',
-                'metadata': {},
-                'entity_results': [],
-                'total_entities': 0
-            }
-
-        # 返回格式:返回列表形式的 entity_results
-        return {
-            'retrieval_status': 'success',
-            'entity_results': entity_results,
-            'total_entities': len(entity_results)
-        }
-
-
-    async def sensitive_word_check(self, trace_id_idx: str, review_content: str, review_references: str,
-                          review_location_label: str, state: str, stage_name: str) -> Dict[str, Any]:
+    async def sensitive_word_check(self, trace_id_idx: str, review_content: str,
+                          state: str, stage_name: str) -> Dict[str, Any]:
         """
         """
         词句语法检查
         词句语法检查
 
 
         Args:
         Args:
             trace_id_idx: 追踪ID索引
             trace_id_idx: 追踪ID索引
             review_content: 审查内容
             review_content: 审查内容
-            review_references: 审查参考信息
-            review_location_label: 审查位置标签
             state: 状态字典
             state: 状态字典
             stage_name: 阶段名称
             stage_name: 阶段名称
 
 
@@ -669,7 +660,6 @@ class AIReviewEngine(BaseReviewer):
         from core.construction_review.component.reviewers.sensitive_word_check import sensitive_word_check_reviewer
         from core.construction_review.component.reviewers.sensitive_word_check import sensitive_word_check_reviewer
         
         
         # 构造trace_id
         # 构造trace_id
-        reviewer_type = Stage.BASIC.value['reviewer_type']
         prompt_name = Stage.BASIC.value['grammar']
         prompt_name = Stage.BASIC.value['grammar']
         trace_id = prompt_name + trace_id_idx
         trace_id = prompt_name + trace_id_idx
         
         
@@ -677,26 +667,20 @@ class AIReviewEngine(BaseReviewer):
         result = await sensitive_word_check_reviewer.check_grammar(
         result = await sensitive_word_check_reviewer.check_grammar(
             trace_id=trace_id,
             trace_id=trace_id,
             review_content=review_content,
             review_content=review_content,
-            review_references=review_references,
-            review_location_label=review_location_label,
             state=state,
             state=state,
             stage_name=stage_name
             stage_name=stage_name
         )
         )
         
         
         return result
         return result
 
 
-    
-
-    async def check_semantic_logic(self, trace_id_idx: str, review_content: str, review_references: str,
-                                 review_location_label: str, state: str, stage_name: str) -> Dict[str, Any]:
+    async def check_semantic_logic(self, trace_id_idx: str, review_content: str,
+                                 state: str, stage_name: str) -> Dict[str, Any]:
         """
         """
         语义逻辑检查
         语义逻辑检查
-        
+
         Args:
         Args:
             trace_id_idx: 追踪ID索引
             trace_id_idx: 追踪ID索引
             review_content: 审查内容
             review_content: 审查内容
-            review_references: 审查参考信息
-            review_location_label: 审查位置标签
             state: 状态字典
             state: 状态字典
             stage_name: 阶段名称
             stage_name: 阶段名称
 
 
@@ -706,7 +690,6 @@ class AIReviewEngine(BaseReviewer):
         from core.construction_review.component.reviewers.semantic_logic import semantic_logic_reviewer
         from core.construction_review.component.reviewers.semantic_logic import semantic_logic_reviewer
         
         
         # 构造trace_id
         # 构造trace_id
-        reviewer_type = Stage.BASIC.value['reviewer_type']
         prompt_name = Stage.BASIC.value['semantic']
         prompt_name = Stage.BASIC.value['semantic']
         trace_id = prompt_name + trace_id_idx
         trace_id = prompt_name + trace_id_idx
         
         
@@ -714,36 +697,22 @@ class AIReviewEngine(BaseReviewer):
         result = await semantic_logic_reviewer.check_semantic_logic(
         result = await semantic_logic_reviewer.check_semantic_logic(
             trace_id=trace_id,
             trace_id=trace_id,
             review_content=review_content,
             review_content=review_content,
-            review_references=review_references,
-            review_location_label=review_location_label,
             state=state,
             state=state,
             stage_name=stage_name
             stage_name=stage_name
         )
         )
         
         
         return result
         return result
         
         
-        
-        ### TODO: 使用review模块,并传入指定模型名称lq-Qwen3-30B,修改底层review接口模块,层层传参直至底层。修改底层model_generate模块,支持初始化多种模型,支持根据传入模型名称参数调用指定模型。
-        # 在review内复刻使用上面的语义逻辑审查模块
-        pass
-        # reviewer_type = Stage.BASIC.value['reviewer_type']
-        # prompt_name = Stage.BASIC.value['semantic']
-        # trace_id = prompt_name+trace_id_idx
-        # return await self.review("semantic_logic_check", trace_id, reviewer_type, prompt_name, review_content, review_references,
-        #                        None, review_location_label, state, stage_name)
-
-    async def check_completeness(self, trace_id_idx: str, review_content: List[Dict[str, Any]], review_references: str,
-                               review_location_label: str, state: str, stage_name: str) -> Dict[str, Any]:
+    async def check_completeness(self, trace_id_idx: str, review_content: Dict[str, Any],
+                               state: str, stage_name: str) -> Dict[str, Any]:
         """
         """
         完整性检查
         完整性检查
 
 
         Args:
         Args:
             trace_id_idx: 追踪ID索引
             trace_id_idx: 追踪ID索引
             review_content: 审查内容,单个文档块(chunk)的字典,格式如文档切分预处理结果.json中的chunks项
             review_content: 审查内容,单个文档块(chunk)的字典,格式如文档切分预处理结果.json中的chunks项
-            review_references: 审查参考信息
-            stage_name: 阶段名称
             state: 状态字典
             state: 状态字典
-            review_location_label: 审查位置标签
+            stage_name: 阶段名称
 
 
         Returns:
         Returns:
             Dict[str, Any]: 完整性检查结果
             Dict[str, Any]: 完整性检查结果
@@ -941,16 +910,14 @@ class AIReviewEngine(BaseReviewer):
                 execution_time=execution_time
                 execution_time=execution_time
             )
             )
 
 
-    async def check_sensitive(self, trace_id_idx: str, review_content: str, review_references: str,
-                            review_location_label: str, state: str, stage_name: str) -> Dict[str, Any]:
+    async def check_sensitive(self, trace_id_idx: str, review_content: str,
+                            state: str, stage_name: str) -> Dict[str, Any]:
         """
         """
         敏感信息检查
         敏感信息检查
 
 
         Args:
         Args:
             trace_id_idx: 追踪ID索引
             trace_id_idx: 追踪ID索引
             review_content: 审查内容
             review_content: 审查内容
-            review_references: 审查参考信息
-            review_location_label: 审查位置标签
             state: 状态字典
             state: 状态字典
             stage_name: 阶段名称
             stage_name: 阶段名称
 
 
@@ -983,9 +950,9 @@ class AIReviewEngine(BaseReviewer):
             logger.info(f"格式化后的敏感词信息:\n{formatted_sensitive_words}")
             logger.info(f"格式化后的敏感词信息:\n{formatted_sensitive_words}")
             
             
             # 调用大模型得到敏感词审查结果
             # 调用大模型得到敏感词审查结果
-            return await self.review("sensitive_check", trace_id, "basic", "sensitive_word_check", 
+            return await self.review("sensitive_check", trace_id, "basic", "sensitive_word_check",
                                    review_content, formatted_sensitive_words,
                                    review_content, formatted_sensitive_words,
-                                   None, review_location_label, state, stage_name)
+                                   None, None, state, stage_name)
         else:
         else:
             # 没有检测到敏感词,构造返回体
             # 没有检测到敏感词,构造返回体
             logger.info("没有检测到敏感词,未进入二审")
             logger.info("没有检测到敏感词,未进入二审")
@@ -1027,7 +994,7 @@ class AIReviewEngine(BaseReviewer):
             return result
             return result
 
 
     async def check_non_parameter_compliance(self, trace_id_idx: str, review_content: str, review_references: str,
     async def check_non_parameter_compliance(self, trace_id_idx: str, review_content: str, review_references: str,
-                                         reference_source: str, review_location_label: str, state: str, stage_name: str,
+                                         reference_source: str, state: str, stage_name: str,
                                          entity_query: str = None) -> Dict[str, Any]:
                                          entity_query: str = None) -> Dict[str, Any]:
         """
         """
         非参数合规性检查 - 安全相关/强制性条文知识库
         非参数合规性检查 - 安全相关/强制性条文知识库
@@ -1037,7 +1004,6 @@ class AIReviewEngine(BaseReviewer):
             review_content: 审查内容
             review_content: 审查内容
             review_references: 审查参考信息
             review_references: 审查参考信息
             reference_source: 参考来源
             reference_source: 参考来源
-            review_location_label: 审查位置标签
             state: 状态字典
             state: 状态字典
             stage_name: 阶段名称
             stage_name: 阶段名称
             entity_query: 实体组合查询条文(可选,来自 entity_results)
             entity_query: 实体组合查询条文(可选,来自 entity_results)
@@ -1058,10 +1024,10 @@ class AIReviewEngine(BaseReviewer):
             combined_content = review_content
             combined_content = review_content
 
 
         return await self.review("non_parameter_compliance_check", trace_id, reviewer_type, prompt_name, combined_content, review_references,
         return await self.review("non_parameter_compliance_check", trace_id, reviewer_type, prompt_name, combined_content, review_references,
-                               reference_source, review_location_label, state, stage_name, timeout=45, model_name="qwen3_30b")
+                               reference_source, state, stage_name, timeout=45, model_name="qwen3_30b")
 
 
     async def check_parameter_compliance(self, trace_id_idx: str, review_content: str, review_references: str,
     async def check_parameter_compliance(self, trace_id_idx: str, review_content: str, review_references: str,
-                                        reference_source: str, review_location_label: str, state: str, stage_name: str,
+                                        reference_source: str, state: str, stage_name: str,
                                         entity_query: str = None) -> Dict[str, Any]:
                                         entity_query: str = None) -> Dict[str, Any]:
         """
         """
         参数合规性检查 - 实体概念/工程术语知识库
         参数合规性检查 - 实体概念/工程术语知识库
@@ -1071,7 +1037,6 @@ class AIReviewEngine(BaseReviewer):
             review_content: 审查内容
             review_content: 审查内容
             review_references: 审查参考信息
             review_references: 审查参考信息
             reference_source: 参考来源
             reference_source: 参考来源
-            review_location_label: 审查位置标签
             state: 状态字典
             state: 状态字典
             stage_name: 阶段名称
             stage_name: 阶段名称
             entity_query: 实体组合查询条文(可选,来自 entity_results)
             entity_query: 实体组合查询条文(可选,来自 entity_results)
@@ -1092,7 +1057,7 @@ class AIReviewEngine(BaseReviewer):
             combined_content = review_content
             combined_content = review_content
 
 
         return await self.review("parameter_compliance_check", trace_id, reviewer_type, prompt_name, combined_content, review_references,
         return await self.review("parameter_compliance_check", trace_id, reviewer_type, prompt_name, combined_content, review_references,
-                               reference_source, review_location_label, state, stage_name, timeout=45, model_name="qwen3_30b")
+                               reference_source, state, stage_name, timeout=45, model_name="qwen3_30b")
 
 
     async def outline_check(self, trace_id_idx: str, outline_content: Dict[str, Any],
     async def outline_check(self, trace_id_idx: str, outline_content: Dict[str, Any],
                                    state:dict,stage_name:str) -> Dict[str, Any]:
                                    state:dict,stage_name:str) -> Dict[str, Any]:
@@ -1111,9 +1076,9 @@ class AIReviewEngine(BaseReviewer):
         overall_outline = ""
         overall_outline = ""
 
 
         # 添加调试信息
         # 添加调试信息
-        logger.info(f"outline_content结构: {list(outline_content.keys()) if outline_content else 'None'}")
+        logger.debug(f"outline_content结构: {list(outline_content.keys()) if outline_content else 'None'}")
         outline_data = outline_content.get('outline', {})
         outline_data = outline_content.get('outline', {})
-        logger.info(f"outline_data结构: {list(outline_data.keys()) if outline_data else 'None'}")
+        logger.debug(f"outline_data结构: {list(outline_data.keys()) if outline_data else 'None'}")
         chapters = outline_data.get('chapters', [])
         chapters = outline_data.get('chapters', [])
         logger.info(f"chapters数量: {len(chapters)}")
         logger.info(f"chapters数量: {len(chapters)}")
 
 
@@ -1176,8 +1141,7 @@ class AIReviewEngine(BaseReviewer):
         return {
         return {
             'outline_review_result': outline_review_result
             'outline_review_result': outline_review_result
         }
         }
-
-
+    
     async def reference_basis_reviewer(self, review_data: Dict[str, Any], trace_id: str,
     async def reference_basis_reviewer(self, review_data: Dict[str, Any], trace_id: str,
                                 state: dict = None, stage_name: str = None) -> Dict[str, Any]:
                                 state: dict = None, stage_name: str = None) -> Dict[str, Any]:
         """
         """

+ 2 - 126
core/construction_review/component/reviewers/base_reviewer.py

@@ -29,15 +29,14 @@ class ReviewResult:
 class BaseReviewer(ABC):
 class BaseReviewer(ABC):
     """审查器基类"""
     """审查器基类"""
 
 
-    def __init__(self, review_location_label: str = ""):
+    def __init__(self):
         self.model_client = generate_model_client
         self.model_client = generate_model_client
         self.prompt_loader = prompt_loader
         self.prompt_loader = prompt_loader
-        self.review_location_label = review_location_label
         self.reference_source = ""
         self.reference_source = ""
     
     
     #@obverse
     #@obverse
     async def  review(self, name: str, trace_id: str, reviewer_type: str, prompt_name: str, review_content: str, review_references: str = None,
     async def  review(self, name: str, trace_id: str, reviewer_type: str, prompt_name: str, review_content: str, review_references: str = None,
-                    reference_source: str = None, review_location_label: str = None,state:str =None,stage_name:str = None, timeout: int = 60, model_name: str = None) -> ReviewResult:
+                    reference_source: str = None, state:str =None,stage_name:str = None, timeout: int = 60, model_name: str = None) -> ReviewResult:
         """
         """
         执行审查
         执行审查
 
 
@@ -53,7 +52,6 @@ class BaseReviewer(ABC):
             review_content: 待审查内容 (必填)
             review_content: 待审查内容 (必填)
             review_references: 审查参考内容 (可选)
             review_references: 审查参考内容 (可选)
             reference_source: 参考来源 (可选)
             reference_source: 参考来源 (可选)
-            review_location_label: 审查位置标签 (可选)
             state: 状态字典 (可选,用于进度更新)
             state: 状态字典 (可选,用于进度更新)
             stage_name: 阶段名称 (可选,用于进度更新)
             stage_name: 阶段名称 (可选,用于进度更新)
             timeout: 模型调用超时时间,默认60秒 (可选)
             timeout: 模型调用超时时间,默认60秒 (可选)
@@ -78,9 +76,6 @@ class BaseReviewer(ABC):
             prompt_kwargs = {}
             prompt_kwargs = {}
             prompt_kwargs["review_content"] = review_content
             prompt_kwargs["review_content"] = review_content
             prompt_kwargs["review_references"] = review_references or ""
             prompt_kwargs["review_references"] = review_references or ""
-            # 添加审查位置标签到提示词参数中
-            if review_location_label:
-                prompt_kwargs["review_location_label"] = review_location_label
             # 添加reference_source到提示词参数中
             # 添加reference_source到提示词参数中
             # if reference_source:
             # if reference_source:
             #     prompt_kwargs["reference_source"] = reference_source
             #     prompt_kwargs["reference_source"] = reference_source
@@ -142,125 +137,6 @@ class BaseReviewer(ABC):
                 error_message=error_msg,
                 error_message=error_msg,
                 execution_time=execution_time
                 execution_time=execution_time
             )
             )
-
-    async def sensitive_review(self, name: str, trace_id: str, review_content: str,
-                              review_location_label: str = None, state: dict = None,
-                              stage_name: str = None) -> ReviewResult:
-        """
-        执行敏感词审查
-
-        Args:
-            name: 审查器名称
-            trace_id: 追踪ID
-            review_content: 待审查内容
-            review_location_label: 审查位置标签
-            state: 状态字典
-            stage_name: 阶段名称
-
-        Returns:
-            ReviewResult: 审查结果
-        """
-        from core.construction_review.component.reviewers.utils import (
-            check_sensitive_words_async,
-            format_check_results
-        )
-
-        start_time = time.time()
-        try:
-            logger.info(f"开始执行 {name} 审查,trace_id: {trace_id}, 内容长度: {len(review_content)}")
-
-            results = await check_sensitive_words_async(review_content)
-            formatted = format_check_results(results, review_content)
-            
-            logger.info(f"敏感词检查完成,发现 {formatted['count']} 个敏感词")
-            
-            if not formatted['has_sensitive']:
-                response_str = "无明显问题"
-            else:
-                ###################
-                # 在此处设计大模型上下文审核,防止过于敏感
-                ###################
-                prompt_kwargs = {} 
-                # 提示词模板参数填充物
-                prompt_kwargs["review_content"] = review_content
-                #处理一下 results
-                prompt_kwargs["review_references"] = results or ""
-                # 构造提示词
-                task_prompt_info = {
-                    "task_prompt": self.prompt_loader.get_prompt_template("basic", "sensitive_check", **prompt_kwargs),
-                    "task_name": name
-                } 
-                # 调用模型
-                model_response = await self.model_client.get_model_generate_invoke(
-                    trace_id=trace_id,
-                    task_prompt_info=task_prompt_info
-                )
-                issues = []
-                for item in formatted['details']:
-                    context = item.get('context', review_content[max(0, item['position']-20):min(len(review_content), item['end_position']+20)])
-                    issue = {
-                        "issue_point": f"发现敏感词: {item['word']}",
-                        "location": f"{review_location_label or '文档内容'} (位置: {item['position']}-{item['end_position']}),上下文: {context}",
-                        "suggestion": f"建议删除或替换敏感词 '{item['word']}'",
-                        "reason": f"该词属于敏感词库{item['source']}中的禁用词汇",
-                        "risk_level": "高风险"
-                    }
-                    issues.append(issue)
-
-                import json
-                response_str = json.dumps(issues, ensure_ascii=False, indent=2)
-
-            details = {
-                "name": name,
-                "response": response_str
-            }
-
-            result = ReviewResult(
-                success=True,
-                details=details,
-                error_message=None
-            )
-
-            result.execution_time = time.time() - start_time
-
-            review_result_data = {
-                'name': name,
-                'success': result.success,
-                'details': result.details,
-                'error_message': result.error_message,
-                'execution_time': result.execution_time,
-                'timestamp': time.time()
-            }
-
-            if state and state.get("progress_manager"):
-                asyncio.create_task(
-                    state["progress_manager"].update_stage_progress(
-                        callback_task_id=state["callback_task_id"],
-                        stage_name=stage_name,
-                        current=None,
-                        status="processing",
-                        message=f"{name} 审查完成,发现 {formatted['count']} 个敏感词,耗时: {result.execution_time:.2f}s",
-                        issues=[review_result_data],
-                        event_type="processing"
-                    )
-                )
-
-            logger.info(f"{name} 审查完成,耗时: {result.execution_time:.2f}s")
-            print(result)
-            return result
-
-        except Exception as e:
-            execution_time = time.time() - start_time
-            error_msg = f"{name} 审查失败: {str(e)}"
-            logger.error(error_msg, exc_info=True)
-
-            return ReviewResult(
-                success=False,
-                details={},
-                error_message=error_msg,
-                execution_time=execution_time
-            )
-
     def format_result(self, model_response: str, name: str = None,reference_source:str = None,review_references:str = None) -> ReviewResult:
     def format_result(self, model_response: str, name: str = None,reference_source:str = None,review_references:str = None) -> ReviewResult:
         """
         """
         格式化模型返回结果为ReviewResult对象
         格式化模型返回结果为ReviewResult对象

+ 0 - 77
core/construction_review/component/reviewers/prompt/prep_basis_reviewers.yaml

@@ -1,77 +0,0 @@
-# ============================================================
-# 编制依据审查提示词模板
-# ============================================================
-
-# 编制依据规范性审查器
-basis_status_check:
-  system_prompt: |
-    你是一个专业的编制依据规范性审查专家。你的任务是对照国家规范要求,对施工组织设计中的编制依据进行全面、细致的规范性审查。
-
-    审查原则:
-    1. **完整性审查**:逐一核对标准规范引用的完整性,确保重要的国家标准、行业标准都已包含
-    2. **规范性检查**:检查标准引用格式是否符合规范要求,包含完整的标准号和标准名称
-    3. **有效性验证**:确保引用的标准在有效期内,未使用已废止的标准
-
-  user_prompt_template: |
-    【任务说明】
-    请对以下施工组织设计的编制依据进行规范性审查,确保标准引用的完整性和有效性。
-
-    【编制依据内容】
-    {review_content}
-
-    【审查要求】
-
-    ## 审查标准
-    - **完整性检查**:逐一核对是否包含必要的国家标准、行业标准、地方标准等
-    - **格式规范性**:检查标准引用格式是否正确,应包含标准编号和标准名称
-    - **有效性验证**:确认引用标准是否为现行有效版本
-
-    ## 判定标准
-    - **✔ 符合要求**:标准引用格式规范、内容完整、在有效期内
-    - **⚠ 部分符合**:标准引用基本符合要求,但存在格式问题或轻微缺失
-    - **✘ 不符合要求**:标准引用存在严重错误、缺失重要标准或引用已废止标准
-
-    【输出格式规范】
-
-    ## 格式要求
-    如果未发现明显的规范性问题,请输出:无明显问题
-    如果发现问题,请按以下格式输出:
-    使用 JSON 数组格式输出审查结果,每个元素对应一个标准引用或问题项。
-    location字段直接输出原字段内容,不得猜测
-
-    ## 字段说明
-    - **issue_point**:问题标题描述,包含具体问题点和判定结果,格式为"符号 + 简要结论"
-      - ✔ 表示符合要求
-      - ⚠ 表示部分符合或需要注意
-      - ✘ 表示不符合要求
-    - **location**:当前问题对应的原始标准引用内容及位置,以及其语境上下文
-    - **suggestion**:具体的修改建议内容,包括在编制依据中找到的具体位置、存在的问题描述、整改建议
-    - **reason**:问题的原因分析和依据说明,基于标准规范要求的详细说明
-    - **risk_level**:风险等级分类(高风险[不符合要求]/中风险[部分符合]/无风险[符合要求])
-
-    ## 注意事项
-    - JSON 格式必须严格规范,可被标准 JSON 解析器解析
-    - location字段应提供足够详细的信息,便于定位具体的标准引用
-    - 对于缺失的重要标准,应在建议中明确指出需要补充的具体标准名称和编号
-    
-    ## 输出示例
-    ```json
-    [
-      {{
-        "issue_point": "GB 50204-2015 引用规范 ✔ 符合要求",
-        "location": "《建筑工程施工质量验收统一标准》(GB 50204-2015)",
-        "suggestion": "标准引用格式规范,包含完整的标准编号和标准名称,符合要求",
-        "reason": "依据国家标准编写规范,标准引用应包含标准编号和标准名称,该引用格式正确",
-        "risk_level": "无风险"
-      }},
-      {{
-        "issue_point": "缺少安全技术规范 ✘ 不符合要求",
-        "location": "编制依据中缺少相关安全技术规范标准",
-        "suggestion": "建议补充《建筑施工安全检查标准》(JGJ 59-2011)等安全技术规范",
-        "reason": "依据施工组织设计编制规范,应包含相关的安全技术标准",
-        "risk_level": "高风险"
-      }}
-    ]
-    ```
-
-

+ 2 - 2
core/construction_review/component/reviewers/prompt/technical_reviewers.yaml

@@ -58,7 +58,7 @@ parameter_compliance_check:
     审查要求:
     审查要求:
     - 重点检查技术参数的准确性和合理性
     - 重点检查技术参数的准确性和合理性
     - 识别参数错误或不合理设置
     - 识别参数错误或不合理设置
-    - 检查实体概念和工程术语的正确使用
+    - 检查实体概念和工程术语的参数是否正确使用
     - 验证设计值与标准的符合性
     - 验证设计值与标准的符合性
     - 简明扼要指出参数问题和修正建议
     - 简明扼要指出参数问题和修正建议
     - 风险等级分类:
     - 风险等级分类:
@@ -70,7 +70,7 @@ parameter_compliance_check:
     1. 务必结合语境进行分析检查
     1. 务必结合语境进行分析检查
     2. 对于表格制表符、不需要检查
     2. 对于表格制表符、不需要检查
     3. 对于术语概念不得曲解
     3. 对于术语概念不得曲解
-    4. 没有明显参数合规问题的内容不予检查,输出无明显问题
+    4. 没有明显参数问题的内容不予检查,输出无明显问题
     5. 已检查出的问题项仅输出一次检查结果,禁止对同一内容重复检查
     5. 已检查出的问题项仅输出一次检查结果,禁止对同一内容重复检查
     6. 若审查参考与审查内容相关性过低,不予检查,输出无明显问题
     6. 若审查参考与审查内容相关性过低,不予检查,输出无明显问题
     7. 务必注意,只有在审查参考与审查内容相关时才能依据审查参考的内容进行问题检查,否则输出无明显问题
     7. 务必注意,只有在审查参考与审查内容相关时才能依据审查参考的内容进行问题检查,否则输出无明显问题

+ 7 - 12
core/construction_review/component/reviewers/semantic_logic.py

@@ -37,25 +37,21 @@ class SemanticLogicReviewer:
         self.max_tokens = SEMANTIC_LOGIC_MODEL_CONFIG["max_tokens"]
         self.max_tokens = SEMANTIC_LOGIC_MODEL_CONFIG["max_tokens"]
         
         
     async def check_semantic_logic(
     async def check_semantic_logic(
-        self, 
+        self,
         trace_id: str,
         trace_id: str,
-        review_content: str, 
-        review_references: str = "",
-        review_location_label: str = "",
+        review_content: str,
         state: Dict[str, Any] = None,
         state: Dict[str, Any] = None,
         stage_name: str = None
         stage_name: str = None
     ) -> ReviewResult:
     ) -> ReviewResult:
         """
         """
         执行语义逻辑检查
         执行语义逻辑检查
-        
+
         Args:
         Args:
             trace_id: 追踪ID
             trace_id: 追踪ID
             review_content: 待审查内容
             review_content: 待审查内容
-            review_references: 审查参考信息
-            review_location_label: 审查位置标签
             state: 状态字典(包含progress_manager和callback_task_id)
             state: 状态字典(包含progress_manager和callback_task_id)
             stage_name: 阶段名称
             stage_name: 阶段名称
-            
+
         Returns:
         Returns:
             ReviewResult: 审查结果对象
             ReviewResult: 审查结果对象
         """
         """
@@ -67,12 +63,11 @@ class SemanticLogicReviewer:
             # 构造提示词参数
             # 构造提示词参数
             prompt_kwargs = {}
             prompt_kwargs = {}
             prompt_kwargs["review_content"] = review_content
             prompt_kwargs["review_content"] = review_content
-            prompt_kwargs["review_references"] = review_references or ""
-            
+
             # 获取提示词模板
             # 获取提示词模板
             prompt_template = prompt_loader.get_prompt_template(
             prompt_template = prompt_loader.get_prompt_template(
-                "basic", 
-                "semantic_logic_check", 
+                "basic",
+                "semantic_logic_check",
                 **prompt_kwargs
                 **prompt_kwargs
             )
             )
             
             

+ 7 - 12
core/construction_review/component/reviewers/sensitive_word_check.py

@@ -37,25 +37,21 @@ class GrammarCheckReviewer:
         self.max_tokens = sensitive_word_check_MODEL_CONFIG["max_tokens"]
         self.max_tokens = sensitive_word_check_MODEL_CONFIG["max_tokens"]
         
         
     async def check_grammar(
     async def check_grammar(
-        self, 
+        self,
         trace_id: str,
         trace_id: str,
-        review_content: str, 
-        review_references: str = "",
-        review_location_label: str = "",
+        review_content: str,
         state: Dict[str, Any] = None,
         state: Dict[str, Any] = None,
         stage_name: str = None
         stage_name: str = None
     ) -> ReviewResult:
     ) -> ReviewResult:
         """
         """
         执行语法检查
         执行语法检查
-        
+
         Args:
         Args:
             trace_id: 追踪ID
             trace_id: 追踪ID
             review_content: 待审查内容
             review_content: 待审查内容
-            review_references: 审查参考信息
-            review_location_label: 审查位置标签
             state: 状态字典(包含progress_manager和callback_task_id)
             state: 状态字典(包含progress_manager和callback_task_id)
             stage_name: 阶段名称
             stage_name: 阶段名称
-            
+
         Returns:
         Returns:
             ReviewResult: 审查结果对象
             ReviewResult: 审查结果对象
         """
         """
@@ -67,12 +63,11 @@ class GrammarCheckReviewer:
             # 构造提示词参数
             # 构造提示词参数
             prompt_kwargs = {}
             prompt_kwargs = {}
             prompt_kwargs["review_content"] = review_content
             prompt_kwargs["review_content"] = review_content
-            prompt_kwargs["review_references"] = review_references or ""
-            
+
             # 获取提示词模板
             # 获取提示词模板
             prompt_template = prompt_loader.get_prompt_template(
             prompt_template = prompt_loader.get_prompt_template(
-                "basic", 
-                "sensitive_word_check", 
+                "basic",
+                "sensitive_word_check",
                 **prompt_kwargs
                 **prompt_kwargs
             )
             )
             
             

+ 52 - 43
core/construction_review/component/reviewers/utils/inter_tool.py

@@ -126,7 +126,7 @@ class InterTool:
 
 
     def _format_review_results_to_issues(self, callback_task_id: str, unit_index: int, review_location_label: str,
     def _format_review_results_to_issues(self, callback_task_id: str, unit_index: int, review_location_label: str,
                                         chapter_code:str,unit_content: Dict[str, Any], basic_result: Dict[str, Any],
                                         chapter_code:str,unit_content: Dict[str, Any], basic_result: Dict[str, Any],
-                                        technical_result: Dict[str, Any]) -> List[Dict[str, Any]]:
+                                        technical_result: Dict[str, Any], merged_results: Dict[str, Any] = None) -> List[Dict[str, Any]]:
         """
         """
         将审查结果格式化为issues结构
         将审查结果格式化为issues结构
 
 
@@ -138,6 +138,7 @@ class InterTool:
             unit_content: 单元内容,包含原始文本等信息
             unit_content: 单元内容,包含原始文本等信息
             basic_result: 基础合规性审查结果,包含各项检查结果
             basic_result: 基础合规性审查结果,包含各项检查结果
             technical_result: 技术性审查结果,包含技术标准检查结果
             technical_result: 技术性审查结果,包含技术标准检查结果
+            merged_results: 合并的审查结果(check_item 模式使用),如果提供则直接使用,忽略 basic_result 和 technical_result
 
 
         Returns:
         Returns:
             List[Dict]: 格式化后的issues列表,每个issue包含:
             List[Dict]: 格式化后的issues列表,每个issue包含:
@@ -149,6 +150,7 @@ class InterTool:
         Note:
         Note:
             自动跳过overall_score字段,提取所有检查项的详细结果
             自动跳过overall_score字段,提取所有检查项的详细结果
             支持风险等级统计和最高风险等级确定
             支持风险等级统计和最高风险等级确定
+            check_item 模式:直接使用 merged_results,无需区分 basic_result 和 technical_result
         """
         """
         TRCH_CHECK_ITEMS = [
         TRCH_CHECK_ITEMS = [
             "non_parameter_compliance_check",
             "non_parameter_compliance_check",
@@ -161,50 +163,57 @@ class InterTool:
 
 
         # 合并所有审查结果
         # 合并所有审查结果
         all_results = {}
         all_results = {}
-        if basic_result:
-            logger.debug(f"🔍 [DEBUG] basic_result 类型: {type(basic_result)}, 键: {list(basic_result.keys()) if isinstance(basic_result, dict) else 'N/A'}")
-            all_results.update(basic_result)
-
-        if technical_result:
-            logger.debug(f"🔍 [DEBUG] technical_result 类型: {type(technical_result)}, 键: {list(technical_result.keys()) if isinstance(technical_result, dict) else 'N/A'}")
-
-            # 检查是否是 entity_based 模式
-            if technical_result.get('review_mode') == 'entity_based' and 'entity_review_results' in technical_result:
-                # entity_based 模式:从 entity_review_results 中提取实际审查结果
-                logger.debug(f"🔍 [DEBUG] 检测到 entity_based 模式,从 entity_review_results 提取审查结果")
-                entity_review_results = technical_result.get('entity_review_results', [])
-                total_entities = technical_result.get('total_entities', 0)
-
-                for idx, entity_item in enumerate(entity_review_results):
-                    entity = entity_item.get('entity', f'entity_{idx}')
-                    entity_info = f"{entity}_{idx}"  # 使用 entity+索引 避免重复
-
-                    # 提取非参数性审查结果
-                    if 'non_parameter_compliance' in entity_item:
-                        result_key = f'non_parameter_compliance_{entity_info}'
-                        all_results[result_key] = entity_item['non_parameter_compliance']
-                        logger.debug(f"🔍 [DEBUG] 提取审查结果: {result_key}")
-
-                    # 提取参数性审查结果
-                    if 'parameter_compliance' in entity_item:
-                        result_key = f'parameter_compliance_{entity_info}'
-                        all_results[result_key] = entity_item['parameter_compliance']
-                        logger.debug(f"🔍 [DEBUG] 提取审查结果: {result_key}")
-
-                logger.debug(f"🔍 [DEBUG] entity_based 模式处理完成,共提取 {len(entity_review_results)} 个实体的审查结果")
 
 
-            else:
-                # general 模式:过滤掉元数据字段,保留实际审查结果
-                filtered_technical = {}
-                metadata_keys = ['review_mode', 'entity_review_results', 'total_entities', 'total_results_processed']
-                for key, value in technical_result.items():
-                    if key not in metadata_keys:
-                        filtered_technical[key] = value
-                    else:
-                        logger.debug(f"跳过技术审查元数据字段: {key} = {value} (类型: {type(value).__name__})")
+        # check_item 模式:如果提供了 merged_results,直接使用
+        if merged_results is not None:
+            logger.debug(f"使用 check_item 模式的合并结果: {list(merged_results.keys()) if isinstance(merged_results, dict) else 'N/A'}")
+            all_results.update(merged_results)
+        else:
+            # core_review 模式:合并 basic_result 和 technical_result
+            if basic_result:
+                logger.debug(f"🔍 [DEBUG] basic_result 类型: {type(basic_result)}, 键: {list(basic_result.keys()) if isinstance(basic_result, dict) else 'N/A'}")
+                all_results.update(basic_result)
+
+            if technical_result:
+                logger.debug(f"🔍 [DEBUG] technical_result 类型: {type(technical_result)}, 键: {list(technical_result.keys()) if isinstance(technical_result, dict) else 'N/A'}")
+
+                # 检查是否是 entity_based 模式
+                if technical_result.get('review_mode') == 'entity_based' and 'entity_review_results' in technical_result:
+                    # entity_based 模式:从 entity_review_results 中提取实际审查结果
+                    logger.debug(f"🔍 [DEBUG] 检测到 entity_based 模式,从 entity_review_results 提取审查结果")
+                    entity_review_results = technical_result.get('entity_review_results', [])
+                    total_entities = technical_result.get('total_entities', 0)
+
+                    for idx, entity_item in enumerate(entity_review_results):
+                        entity = entity_item.get('entity', f'entity_{idx}')
+                        entity_info = f"{entity}_{idx}"  # 使用 entity+索引 避免重复
+
+                        # 提取非参数性审查结果
+                        if 'non_parameter_compliance' in entity_item:
+                            result_key = f'non_parameter_compliance_{entity_info}'
+                            all_results[result_key] = entity_item['non_parameter_compliance']
+                            logger.debug(f"🔍 [DEBUG] 提取审查结果: {result_key}")
+
+                        # 提取参数性审查结果
+                        if 'parameter_compliance' in entity_item:
+                            result_key = f'parameter_compliance_{entity_info}'
+                            all_results[result_key] = entity_item['parameter_compliance']
+                            logger.debug(f"🔍 [DEBUG] 提取审查结果: {result_key}")
+
+                    logger.debug(f"🔍 [DEBUG] entity_based 模式处理完成,共提取 {len(entity_review_results)} 个实体的审查结果")
+
+                else:
+                    # general 模式:过滤掉元数据字段,保留实际审查结果
+                    filtered_technical = {}
+                    metadata_keys = ['review_mode', 'entity_review_results', 'total_entities', 'total_results_processed']
+                    for key, value in technical_result.items():
+                        if key not in metadata_keys:
+                            filtered_technical[key] = value
+                        else:
+                            logger.debug(f"跳过技术审查元数据字段: {key} = {value} (类型: {type(value).__name__})")
 
 
-                logger.debug(f"🔍 [DEBUG] 过滤后的 technical_result 键: {list(filtered_technical.keys())}")
-                all_results.update(filtered_technical)
+                    logger.debug(f"🔍 [DEBUG] 过滤后的 technical_result 键: {list(filtered_technical.keys())}")
+                    all_results.update(filtered_technical)
 
 
         logger.debug(f"开始格式化审查结果,合并后结果: {list(all_results.keys())}")
         logger.debug(f"开始格式化审查结果,合并后结果: {list(all_results.keys())}")
 
 

+ 1 - 1
core/construction_review/component/reviewers/utils/prompt_loader.py

@@ -252,7 +252,7 @@ class PromptLoader:
             Dict[str, Any]: 加载结果统计
             Dict[str, Any]: 加载结果统计
         """
         """
 
 
-        reviewer_types = ['basic', 'technical', 'rag', 'ai', 'outline', 'prep_basis','query_extract']
+        reviewer_types = ['basic', 'technical', 'rag', 'ai', 'outline', 'query_extract']
 
 
         stats = {
         stats = {
             'loaded_types': [],
             'loaded_types': [],

+ 235 - 569
core/construction_review/workflows/ai_review_workflow.py

@@ -17,26 +17,21 @@
 ├── execute()                       # 执行AI审查工作流
 ├── execute()                       # 执行AI审查工作流
 ├── _start_node()                   # 开始节点
 ├── _start_node()                   # 开始节点
 ├── _initialize_progress_node()     # 初始化进度节点
 ├── _initialize_progress_node()     # 初始化进度节点
-├── _ai_review_node()               # AI审查核心节点
+├── _ai_review_node()               # AI审查核心节点(基于review_config)
+├── _ai_review_node_check_item()    # AI审查项检查节点(基于review_item_config)
 ├── _save_results_node()            # 保存结果节点(入库/本地文件)
 ├── _save_results_node()            # 保存结果节点(入库/本地文件)
 ├── _complete_node()                # 完成节点
 ├── _complete_node()                # 完成节点
-└── _error_handler_node()           # 错误处理节点
+├── _error_handler_node()           # 错误处理节点
+└── _terminate_node()               # 终止节点
 
 
-🔍 审查处理方法:
-├── _filter_review_units()          # 筛选审查单元
-├── _calculate_overall_risk()       # 计算总体风险等级
-├── _aggregate_results()            # 汇总审查结果
-├── _format_review_results_to_issues() # 格式化审查结果为问题列表
-└── parse_ai_review_response()     # 解析AI审查响应
-
-🛠️ 工具辅助方法:
-├── _check_ai_review_result()       # 检查AI审查结果
+🛠️ 工作流辅助方法:
+├── _should_check_item_or_dimensions() # 检查应该使用哪种审查配置
+├── _should_terminate_or_error()    # 检查是否应该终止或发生错误
 ├── _get_workflow_graph()           # 获取工作流图(可视化)
 ├── _get_workflow_graph()           # 获取工作流图(可视化)
-└── _get_status()                   # 获取工作流状态
+├── _save_workflow_graph()          # 保存工作流图到temp文件夹
+├── _get_status()                   # 获取工作流状态
+└── _dummy_review_task()            # 空任务(方法不存在时使用)
 
 
-⚙️ 配置管理:
-├── __init__()                      # 初始化工作流(支持审查模式配置)
-└── set_review_location_label()     # 设置审查位置标签
 '''
 '''
 
 
 import asyncio
 import asyncio
@@ -46,6 +41,7 @@ import re
 from sre_parse import JUMP
 from sre_parse import JUMP
 import time
 import time
 import os
 import os
+from typing import Dict, Tuple, Union, List
 from dataclasses import dataclass, asdict
 from dataclasses import dataclass, asdict
 from typing import Optional, Callable, Dict, Any, TypedDict, Annotated, List
 from typing import Optional, Callable, Dict, Any, TypedDict, Annotated, List
 from langgraph.graph import StateGraph, END
 from langgraph.graph import StateGraph, END
@@ -55,15 +51,10 @@ from foundation.observability.logger.loggering import server_logger as logger
 from foundation.infrastructure.cache.redis_connection import RedisConnectionFactory
 from foundation.infrastructure.cache.redis_connection import RedisConnectionFactory
 from ..component import AIReviewEngine
 from ..component import AIReviewEngine
 from ..component.reviewers.utils.inter_tool import InterTool
 from ..component.reviewers.utils.inter_tool import InterTool
-from core.base.task_models import TaskFileInfo  
+from core.base.task_models import TaskFileInfo
+from .core_functions import AIReviewCoreFun
+from .types import AIReviewState
 
 
-# 常量定义
-DEFAULT_SLICE_START_INDEX = 30
-MAX_PROGRESS_PERCENTAGE = 100
-RISK_LEVELS = {"high": "高风险", "medium": "中风险", "low": "低风险"}
-DEFAULT_RISK_LEVEL = "medium"
-REVIEW_TIMEOUT = 120  # 单个审查任务超时时间(秒),包括基础审查和技术审查
-WORKFLOW_TIMEOUT = 3600  # 整个工作流超时时间(秒,30分钟)
 
 
 
 
 @dataclass
 @dataclass
@@ -76,23 +67,6 @@ class ReviewResult:
     rag_enhanced: Dict[str, Any]
     rag_enhanced: Dict[str, Any]
     overall_risk: str
     overall_risk: str
 
 
-class AIReviewState(TypedDict):
-    """AI审查工作流状态"""
-
-    file_id: str
-    callback_task_id: str
-    file_name: str
-    user_id: str
-    structured_content: Dict[str, Any]
-    review_results: Optional[Dict[str, Any]]
-    current_stage: str
-    status: str
-    error_message: Optional[str]
-    progress_manager: Optional[Any]
-    # 消息日志(用于LangGraph状态追踪)
-    messages: Annotated[List[BaseMessage], add_messages]
-
-
 
 
 class AIReviewWorkflow:
 class AIReviewWorkflow:
     """基于LangGraph的AI审查工作流"""
     """基于LangGraph的AI审查工作流"""
@@ -109,10 +83,12 @@ class AIReviewWorkflow:
             max_review_units: 最大审查单元数量(None表示审查所有)
             max_review_units: 最大审查单元数量(None表示审查所有)
             review_mode: 审查模式 ("all"=全部, "first"=前N个, "random"=随机N个)
             review_mode: 审查模式 ("all"=全部, "first"=前N个, "random"=随机N个)
         """
         """
+        # 工作流超时时间定义
+        self.WORKFLOW_TIMEOUT = 3600
+
         # 任务文件信息
         # 任务文件信息
         self.task_info = task_file_info
         self.task_info = task_file_info
-
-
+        
         self.file_id = task_file_info.file_id
         self.file_id = task_file_info.file_id
         self.callback_task_id = task_file_info.callback_task_id
         self.callback_task_id = task_file_info.callback_task_id
         self.user_id = task_file_info.user_id
         self.user_id = task_file_info.user_id
@@ -130,6 +106,7 @@ class AIReviewWorkflow:
         self.inter_tool = InterTool()
         self.inter_tool = InterTool()
 
 
         self.max_review_units = max_review_units
         self.max_review_units = max_review_units
+        self.max_concurrent = 20 # 规范性与时效性审查最大并发数
         self.review_mode = review_mode
         self.review_mode = review_mode
 
 
         # 延迟导入 WorkflowManager(避免循环导入)
         # 延迟导入 WorkflowManager(避免循环导入)
@@ -155,6 +132,7 @@ class AIReviewWorkflow:
         workflow.add_node("start", self._start_node)
         workflow.add_node("start", self._start_node)
         workflow.add_node("initialize_progress", self._initialize_progress_node)
         workflow.add_node("initialize_progress", self._initialize_progress_node)
         workflow.add_node("ai_review", self._ai_review_node)
         workflow.add_node("ai_review", self._ai_review_node)
+        workflow.add_node("ai_review_check_item",self._ai_review_node_check_item)
         workflow.add_node("save_results", self._save_results_node)  # 添加保存结果节点
         workflow.add_node("save_results", self._save_results_node)  # 添加保存结果节点
         workflow.add_node("complete", self._complete_node)
         workflow.add_node("complete", self._complete_node)
         workflow.add_node("error_handler", self._error_handler_node)
         workflow.add_node("error_handler", self._error_handler_node)
@@ -162,7 +140,15 @@ class AIReviewWorkflow:
 
 
         workflow.set_entry_point("start")
         workflow.set_entry_point("start")
         workflow.add_edge("start", "initialize_progress")
         workflow.add_edge("start", "initialize_progress")
-        workflow.add_edge("initialize_progress", "ai_review")
+        # 添加条件边:根据配置类型选择不同的审查节点
+        workflow.add_conditional_edges(
+            "initialize_progress",
+            self._should_check_item_or_dimensions,
+            {
+                "activate_ai_review_check_item": "ai_review_check_item",  # 使用 review_item_config
+                "activate_ai_review": "ai_review"                 # 使用 review_config
+            }
+        )
 
 
         # 添加条件边(错误处理 + 终止检查)
         # 添加条件边(错误处理 + 终止检查)
         workflow.add_conditional_edges(
         workflow.add_conditional_edges(
@@ -175,6 +161,17 @@ class AIReviewWorkflow:
             }
             }
         )
         )
 
 
+                # 添加条件边(错误处理 + 终止检查)
+        workflow.add_conditional_edges(
+            "ai_review_check_item",
+            self._should_terminate_or_error,
+            {
+                "terminate": "terminate",  # 终止路径
+                "success": "save_results",  # 成功后先保存结果
+                "error": "error_handler"  # 错误处理
+            }
+        )
+
         # 添加保存结果到完成的边
         # 添加保存结果到完成的边
         workflow.add_edge("save_results", "complete")
         workflow.add_edge("save_results", "complete")
         workflow.add_edge("complete", END)
         workflow.add_edge("complete", END)
@@ -206,7 +203,7 @@ class AIReviewWorkflow:
             # 执行LangGraph工作流,添加超时控制
             # 执行LangGraph工作流,添加超时控制
             result = await asyncio.wait_for(
             result = await asyncio.wait_for(
                 self.graph.ainvoke(initial_state),
                 self.graph.ainvoke(initial_state),
-                timeout=WORKFLOW_TIMEOUT
+                timeout=self.WORKFLOW_TIMEOUT
             )
             )
 
 
             logger.info(f"LangGraph AI审查工作流完成,文件ID: {self.file_id}")
             logger.info(f"LangGraph AI审查工作流完成,文件ID: {self.file_id}")
@@ -227,7 +224,7 @@ class AIReviewWorkflow:
             return review_results
             return review_results
 
 
         except asyncio.TimeoutError:
         except asyncio.TimeoutError:
-            logger.error(f"AI审查工作流超时({WORKFLOW_TIMEOUT}秒),文件ID: {self.file_id}")
+            logger.error(f"AI审查工作流超时({self.WORKFLOW_TIMEOUT}秒),文件ID: {self.file_id}")
             raise TimeoutError(f"AI审查工作流执行超时,请检查文件大小或网络连接")
             raise TimeoutError(f"AI审查工作流执行超时,请检查文件大小或网络连接")
         except Exception as e:
         except Exception as e:
             logger.error(f"LangGraph AI审查工作流执行失败: {str(e)}")
             logger.error(f"LangGraph AI审查工作流执行失败: {str(e)}")
@@ -268,6 +265,163 @@ class AIReviewWorkflow:
             "messages": [AIMessage(content="进度初始化完成")]
             "messages": [AIMessage(content="进度初始化完成")]
         }
         }
     
     
+    async def _ai_review_node_check_item(self, state: AIReviewState) -> AIReviewState:
+        """
+        检查当前项是否需要AI审查 - 新版本
+
+        执行流程:
+        1. 终止信号检查
+        2. 解析审查项配置
+        3. 优先处理大纲审查
+        4. 按章节处理(basis章节 vs 普通章节)
+        5. 汇总结果并构建响应
+        """
+        try:
+            logger.info(f"AI审查项检查开始执行,任务ID: {self.task_info.callback_task_id}")
+
+            # 1️ 终止信号检查
+            if await self.workflow_manager.check_terminate_signal(state["callback_task_id"]):
+                logger.warning(f"AI审查项检查检测到终止信号,任务ID: {state['callback_task_id']}")
+                return {
+                    "status": "terminated",
+                    "current_stage": "ai_review_check_item",
+                    "messages": [AIMessage(content="检测到终止信号")]
+                }
+
+            # 2️ 解析审查项配置
+            review_func_mapping: Dict[str, Union[str, List[str]]] = {
+                'sensitive_word_check': 'sensitive_word_check',
+                'semantic_logic_check': 'check_semantic_logic',
+                'completeness_check': ['check_completeness', 'outline_check'],
+                'timeliness_check': 'timeliness_basis_reviewer',
+                'reference_check': 'reference_basis_reviewer',
+                'sensitive_check': 'check_sensitive',
+                'non_parameter_compliance_check': 'check_non_parameter_compliance',
+                'parameter_compliance_check': 'check_parameter_compliance'
+            }
+
+            review_item_config_raw = self.task_info.get_review_item_config_list()
+            review_item_config = self.core_fun._replace_review_suffix(review_item_config_raw, review_func_mapping)
+
+            review_item_dict = {}
+            for item in review_item_config:
+                key, value = item.split("_", 1)
+                review_item_dict.setdefault(key, []).append(value)
+
+            logger.info(f"审查项配置解析完成: {review_item_dict}")
+
+            # 3️ 获取结构化内容
+            structured_content = state.get("structured_content", {})
+            chunks = structured_content.get("chunks", [])
+            total_chapters = len(review_item_dict)
+            total_chunks = len(chunks)
+
+            logger.info(f"准备执行动态审查任务,总章节数: {total_chapters}, 总块数: {total_chunks}")
+
+            # 发送开始审查进度
+            await self.core_fun._send_start_review_progress(state, total_chunks, 'check_item_review')
+
+            # 初始化issues列表
+            all_issues = []
+            completed_chunks = 0
+
+            # 4️ 优先处理大纲审查(在章节循环之前)
+            outline_data = structured_content.get("outline", {})
+            has_outline_check = any(
+                "outline_check" in funcs
+                for funcs in review_item_dict.values()
+            )
+
+            if has_outline_check and outline_data:
+                logger.info(" 开始执行大纲审查")
+                try:
+                    outline_result = await self.ai_review_engine.outline_check(
+                        state["callback_task_id"],
+                        {"outline": outline_data},
+                        state,
+                        "大纲审查"
+                    )
+                    if outline_result:
+                        all_issues.append(outline_result)
+                        logger.info(f"大纲审查完成")
+                except Exception as e:
+                    logger.error(f"大纲审查失败: {str(e)}", exc_info=True)
+
+            # 5️ 按章节分组
+            chapter_chunks_map = self.core_fun._group_chunks_by_chapter(chunks)
+            logger.info(f"内容分组完成,共 {len(chapter_chunks_map)} 个章节")
+
+            # 6️ 按章节处理
+            for chapter_idx, (chapter_code, func_names) in enumerate(review_item_dict.items()):
+                logger.info(f" 处理章节 [{chapter_idx+1}/{total_chapters}]: {chapter_code},包含 {len(func_names)} 个审查任务")
+
+                # 终止信号检查(章节级别)
+                if await self.workflow_manager.check_terminate_signal(state["callback_task_id"]):
+                    logger.warning(f"章节审查检测到终止信号,任务ID: {state['callback_task_id']}")
+                    break
+
+                # 获取当前章节的内容
+                chapter_content = chapter_chunks_map.get(chapter_code, [])
+                if not chapter_content:
+                    logger.warning(f"章节 {chapter_code} 没有找到对应内容,跳过")
+                    continue
+
+                # 判断章节类型并分支处理
+                if chapter_code == "basis":
+                    # === 编制依据章节:拼接所有chunk后一次性审查 ===
+                    await self.core_fun._process_basis_chapter(
+                        chapter_code, chapter_content, func_names, state, all_issues, completed_chunks, total_chunks
+                    )
+                    # 更新已完成块数
+                    completed_chunks += len(chapter_content)
+                else:
+                    # === 普通章节:逐块审查 ===
+                    chunks_completed = await self.core_fun._process_normal_chapter(
+                        chapter_code, chapter_content, func_names, state, all_issues
+                    )
+                    # 更新已完成块数
+                    completed_chunks += chunks_completed
+
+                logger.info(f"章节 {chapter_code} 处理完成")
+
+            # 7️ 汇总结果
+            summary = self.inter_tool._aggregate_results(all_issues)
+
+            # 8️ 构建完整的响应结构
+            review_results = {
+                "callback_task_id": state["callback_task_id"],
+                "file_name": state.get("file_name", ""),
+                "user_id": state["user_id"],
+                "current": 100,
+                "stage_name": "审查项检查结果",
+                "status": "full_review_result",
+                "message": f"审查项检查完成,共发现{summary.get('total_issues', 0)}个问题",
+                "overall_task_status": "completed",
+                "updated_at": int(time.time()),
+                "issues": all_issues
+            }
+
+            logger.info(f"AI审查项检查执行成功,任务ID: {state['callback_task_id']}, 共发现{summary.get('total_issues', 0)}个问题")
+
+            # 返回新的状态
+            return {
+                "current_stage": "ai_review_check_item_completed",
+                "review_results": review_results,
+                "status": "completed",
+                "messages": [AIMessage(content=f"AI审查项检查完成,共发现{summary.get('total_issues', 0)}个问题")]
+            }
+
+        except Exception as e:
+            logger.error(f"AI审查项检查执行失败,任务ID: {state['callback_task_id']}, 错误: {str(e)}", exc_info=True)
+
+            # 返回错误状态
+            return {
+                "current_stage": "ai_review_check_item_failed",
+                "error_message": str(e),
+                "status": "failed",
+                "messages": [AIMessage(content=f"AI审查项检查失败: {str(e)}")]
+            }
+
     async def _ai_review_node(self, state: AIReviewState) -> AIReviewState:
     async def _ai_review_node(self, state: AIReviewState) -> AIReviewState:
         """
         """
         AI审查节点
         AI审查节点
@@ -291,7 +445,7 @@ class AIReviewWorkflow:
                 }
                 }
 
 
             test_designation_chunk_flag = self.task_info.get_test_designation_chunk_flag()
             test_designation_chunk_flag = self.task_info.get_test_designation_chunk_flag()
-            logger.info(f"测试定位标志: {test_designation_chunk_flag}")
+            logger.debug(f"测试定位标志: {test_designation_chunk_flag}")
 
 
             # 1. 准备审查单元数据
             # 1. 准备审查单元数据
             review_chunks, total_units = await self.core_fun._prepare_review_units(state, test_designation_chunk_flag)
             review_chunks, total_units = await self.core_fun._prepare_review_units(state, test_designation_chunk_flag)
@@ -336,6 +490,8 @@ class AIReviewWorkflow:
                     "messages": [AIMessage(content="检测到终止信号")]
                     "messages": [AIMessage(content="检测到终止信号")]
                 }
                 }
 
 
+            # 开始条文完整性审查
+            
 
 
             # 开始大纲审查
             # 开始大纲审查
             await self.core_fun._send_start_review_progress(state, total_units,'outline')
             await self.core_fun._send_start_review_progress(state, total_units,'outline')
@@ -368,7 +524,7 @@ class AIReviewWorkflow:
                     # 准备编制依据审查数据
                     # 准备编制依据审查数据
                     prep_basis_review_data = {
                     prep_basis_review_data = {
                         'content': prep_basis_content,
                         'content': prep_basis_content,
-                        'max_concurrent': 20
+                        'max_concurrent': self.max_concurrent
                     }
                     }
 
 
                     # 执行编制依据审查
                     # 执行编制依据审查
@@ -398,7 +554,7 @@ class AIReviewWorkflow:
                     # 准备编制依据审查数据
                     # 准备编制依据审查数据
                     timeliness_check_data = {
                     timeliness_check_data = {
                         'content': prep_basis_content,
                         'content': prep_basis_content,
-                        'max_concurrent': 20
+                        'max_concurrent': self.max_concurrent
                     }
                     }
 
 
                     # 执行编制依据审查
                     # 执行编制依据审查
@@ -633,6 +789,37 @@ class AIReviewWorkflow:
             "messages": [AIMessage(content="任务已被终止")]
             "messages": [AIMessage(content="任务已被终止")]
         }
         }
 
 
+    def _should_check_item_or_dimensions(self, state: AIReviewState) -> str:
+        """
+        检查应该使用 review_item_config 还是 review_config
+
+        Args:
+            state: AI审查工作流状态
+
+        Returns:
+            str: "success" 使用 review_item_config(章节_审查维度格式)
+                 "error" 使用 review_config(审查维度格式)
+
+        Note:
+            基于互斥验证逻辑:
+            1. review_config 和 review_item_config 互斥,只有一个有值
+            2. 如果 review_item_config 不为 None,走 ai_review_check_item 节点
+            3. 如果 review_config 不为 None,走 ai_review 节点
+        """
+        # 获取 review_item_config 列表(使用 get_review_item_config_list 方法)
+        review_item_config = self.task_info.get_review_item_config_list()
+
+        # 判断是否应该使用 review_item_config
+        if len(review_item_config) > 0:
+            # 使用 review_item_config(章节_审查维度格式)
+            logger.info(f"使用 review_item_config 进行审查: {review_item_config}")
+            return "activate_ai_review_check_item"
+        else:
+            # 使用 review_config(审查维度格式)
+            review_config = self.task_info.get_review_config_list()
+            logger.info(f"使用 review_config 进行审查: {review_config}")
+            return "activate_ai_review"
+
     def _should_terminate_or_error(self, state: AIReviewState) -> str:
     def _should_terminate_or_error(self, state: AIReviewState) -> str:
         """
         """
         检查是否应该终止或发生错误
         检查是否应该终止或发生错误
@@ -748,524 +935,3 @@ class AIReviewWorkflow:
         return {}
         return {}
 
 
 
 
-class AIReviewCoreFun:
-    """AI审查核心功能类 - 负责具体的审查逻辑和数据处理"""
-
-    def __init__(self, task_file_info: TaskFileInfo, ai_review_engine, max_review_units: int = None, review_mode: str = "all"):
-        """
-        初始化AI审查核心功能类
-
-        Args:
-            task_file_info: TaskFileInfo 实例,包含任务相关信息
-            ai_review_engine: AI审查引擎实例
-            max_review_units: 最大审查单元数量(None表示审查所有)
-            review_mode: 审查模式 ("all"=全部, "first"=前N个, "random"=随机N个)
-        """
-        self.task_info = task_file_info  # ✅ 保存 TaskFileInfo 实例
-        self.ai_review_engine = ai_review_engine
-        self.max_review_units = max_review_units
-        self.review_mode = review_mode
-        self.message_lock = asyncio.Lock()
-        self.inter_tool = InterTool()
-
-        # ✅ 便捷访问属性
-        self.file_id = task_file_info.file_id
-        self.callback_task_id = task_file_info.callback_task_id
-        self.user_id = task_file_info.user_id
-        self.review_config = task_file_info.review_config
-        self.project_plan_type = task_file_info.project_plan_type
-        
-
-    async def _execute_concurrent_reviews(self, review_chunks: List[Dict[str, Any]],
-                                          total_units: int, state: AIReviewState,
-                                          check_terminate: bool = False) -> List[Dict[str, Any]]:
-        """
-        执行并发审查
-
-        Args:
-            review_chunks: 审查单元列表
-            total_units: 总单元数
-            state: AI审查状态
-            check_terminate: 是否检查终止信号(默认False)
-
-        Returns:
-            List[Dict[str, Any]]: 审查结果列表(issues格式)
-        """
-
-        try:
-            # 获取 workflow_manager 实例(延迟导入避免循环依赖)
-            from core.base.workflow_manager import WorkflowManager
-            workflow_manager = WorkflowManager()
-
-            semaphore = asyncio.Semaphore(3)  # 并发审查数
-
-            async def process_unit_and_notify(unit_index, unit_content):
-                """处理单个单元,完成后立即推送通知"""
-                async with semaphore:
-                    # ⚠️ 检查终止信号(每个单元审查前)
-                    if check_terminate:
-                        if await workflow_manager.check_terminate_signal(state["callback_task_id"]):
-                            logger.warning(f"并发审查检测到终止信号,停止后续审查: {state['callback_task_id']}")
-                            return None  # 返回 None 表示被终止
-
-                    # 执行单个单元审查
-                    result = await self._review_single_unit(unit_content, unit_index, total_units, state)
-                    
-                    # 审查完成后立即推送通知
-                    if result.overall_risk != "error":
-                        section_label = unit_content.get('section_label', f'第{unit_index + 1}部分')
-                        chapter_code = unit_content.get('chapter_classification', '')
-                        logger.info(f"section_label:  {section_label}")
-                        # 格式化issues以获取问题数量
-                        issues = self.inter_tool._format_review_results_to_issues(
-                            state["callback_task_id"],
-                            unit_index,
-                            f"第{unit_content.get('page', '')}页:{section_label}",
-                            chapter_code,
-                            unit_content,
-                            result.basic_compliance,
-                            result.technical_compliance
-                        )
-
-                        current = int(((unit_index + 1) / total_units) * 100)
-
-                        # 立即发送单元审查详情(包含unit_review和processing_flag事件)
-                        await self._send_unit_review_progress(state, unit_index, total_units, section_label, issues, current)
-                        return issues
-                    else:
-                        logger.error(f"执行单个单元审查失败: {str(result.error_message)}")
-                    return None
-
-
-            # 创建并发任务
-            tasks = [
-                asyncio.create_task(process_unit_and_notify(i, unit_content))
-                for i, unit_content in enumerate(review_chunks)
-            ]
-
-            # 等待所有任务完成
-            all_results = await asyncio.gather(*tasks)
-
-            # 过滤有效结果(issues格式)
-            successful_results = [issues for issues in all_results if issues and isinstance(issues, list)]
-            return successful_results
-
-        except Exception as e:
-            logger.error(f"执行并发审查失败: {str(e)}")
-            return []
-
-
-
-    async def _prepare_review_units(self, state: AIReviewState, test_designation_chunk_flag) -> tuple:
-        """准备审查单元数据 (增加清理旧进度缓存)"""
-        try:
-            # 筛选要审查的单元
-            all_chunks = state['structured_content']['chunks']
-            # 筛除编制依据章节
-            clearned_chunks = self._remove_basis_chunks(all_chunks)
-
-            # 判断是否需要筛选指定测试章节
-            if test_designation_chunk_flag is not None:
-                # 用户指定了测试章节,进行筛选
-                logger.info(f"开始筛选指定测试章节: {test_designation_chunk_flag}")
-                designation_test_chunk = self._designation_test_chunks(clearned_chunks, test_designation_chunk_flag)
-
-                if not designation_test_chunk:
-                    # 指定了测试章节但未找到,返回空列表
-                    logger.warning(f"未找到包含关键字 '{test_designation_chunk_flag}' 的测试章节,建议去除前后符号(如《》())使用简洁关键词")
-                    review_chunks = []
-                else:
-                    # 找到指定测试章节
-                    logger.info(f"找到 {len(designation_test_chunk)} 个指定测试章节")
-                    review_chunks = designation_test_chunk
-            else:
-                # 未指定测试章节,使用正常筛选流程
-                logger.info(f"未指定测试章节,使用正常筛选流程")
-                review_chunks = self._filter_review_units(clearned_chunks)
-
-            total_units = len(review_chunks)
-            logger.info(f"最终审查单元数量: {total_units}")
-
-            # 【修复 3】: 任务开始前,清理 Redis 中的旧计数,防止进度条计算错误
-            try:
-                task_id = state.get("callback_task_id", "")
-                if task_id:
-                    redis_client = await RedisConnectionFactory.get_connection()
-                    completed_key = f"ai_review:overall_task_progress:{task_id}:completed"
-                    await redis_client.delete(completed_key)
-                    logger.info(f"已清理旧进度缓存: {completed_key}")
-            except Exception as e:
-                logger.warning(f"清理进度缓存失败 (不影响主流程): {str(e)}")
-            return review_chunks, total_units
-        except Exception as e:
-            logger.error(f"准备审查单元失败: {str(e)}")
-            raise
-
-    def _remove_basis_chunks(self, chunks: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
-        """
-        筛除编制依据章节的chunks
-
-        Args:
-            chunks: 所有章节chunks列表
-
-        Returns:
-            List[Dict[str, Any]]: 筛除编制依据章节后的chunks列表
-
-        Note:
-            根据 chapter_classification 字段筛选,排除值为 "basis" 的章节
-        """
-        try:
-            filtered_chunks = []
-            removed_count = 0
-            logger.info(f"开始筛除编制依据章节")
-            for chunk in chunks:
-                # 检查章节分类字段
-                chapter_classification = chunk.get('chapter_classification', '')
-                
-                # 保留非编制依据章节
-                if chapter_classification != 'basis':
-                    logger.info(f"保留非编制依据章节,当前章节: {chapter_classification}")
-                    filtered_chunks.append(chunk)
-                else:
-                    removed_count += 1
-                    logger.debug(f"筛除编制依据章节: {chunk.get('section_label', '未知章节')}")
-
-            logger.info(f"编制依据章节筛除完成: 共筛除 {removed_count} 个章节, 保留 {len(filtered_chunks)} 个章节")
-
-            return filtered_chunks
-
-        except Exception as e:
-            logger.error(f"筛除编制依据章节失败: {str(e)}")
-            # 出错时返回原始列表
-            return chunks
-    def _designation_test_chunks(self, chunks: List[Dict[str, Any]],test_designation_chunk_flag:str) -> List[Dict[str, Any]]:
-        """筛选设计测试章节
-        
-        Args:
-            chunks: 所有章节chunks列表
-
-        Returns:
-            List[Dict[str, Any]]: 筛选后的chunks列表
-
-        Note:
-            根据 chapter_classification 字段筛选,排除值为 "designation_test" 的章节
-
-        Raises:
-            Exception: 筛选失败
-        
-        """
-        try: 
-            designation_chunks = []
-            filtered_count = 0
-
-            logger.info(f"开始筛选设计测试章节")
-            for chunk in chunks:
-                content = chunk.get('content', '')
-                section_label = chunk.get('section_label', '未知章节')
-                logger.info(f"正在处理章节: {section_label}")
-                if test_designation_chunk_flag in content or test_designation_chunk_flag in section_label:
-                    logger.info(f"已命中指定测试章节: {chunk.get('section_label', '未知章节')}")
-                    designation_chunks.append(chunk)
-                else:
-                    filtered_count += 1
-                    logger.debug(f"跳过章节: {chunk.get('section_label', '未知章节')}")
-                if not designation_chunks:
-                    logger.info(f"未找到指定测试章节,请修改关键字尝试!")
-
-            return designation_chunks
-                     
-        except Exception as e:
-            logger.error(f"筛选设计测试章节失败: {str(e)}")
-            # 出错时返回原始列表
-            return chunks
-
-    async def _review_single_unit(self, unit_content: Dict[str, Any], unit_index: int,
-                                  total_units: int, state: AIReviewState) -> ReviewResult:
-        """
-        审查单个单元的核心业务逻辑
-
-        Args:
-            unit_content: 单元内容
-            unit_index: 单元索引
-            total_units: 总单元数
-            state: AI审查状态
-
-        Returns:
-            ReviewResult: 单元审查结果
-        """
-        try:
-
-            # 构建Trace ID
-            trace_id_idx = f"({state['callback_task_id']}-{unit_index})"
-
-            # 获取section_label用于stage_name
-            section_label = unit_content.get('section_label', f'第{unit_index + 1}部分')
-            page = unit_content.get('page', '')
-            review_location_label = f"第{page}页:{section_label}"
-            stage_name = f"AI审查:{section_label}"
-            #logger.info(f"test_review_location_label:{trace_id_idx}: {review_location_label}")
-            review_tasks = [
-                asyncio.create_task(
-                    asyncio.wait_for(
-                        self.ai_review_engine.basic_compliance_check(trace_id_idx, unit_content, review_location_label,state,stage_name),
-                        timeout=REVIEW_TIMEOUT
-                    )
-                ),
-                asyncio.create_task(
-                    asyncio.wait_for(
-                        self.ai_review_engine.technical_compliance_check(trace_id_idx, unit_content,review_location_label,state,stage_name),
-                        timeout=REVIEW_TIMEOUT
-                    )
-                ),
-            ]
-
-            # 使用 asyncio.gather 保证结果顺序,并提供超时控制
-            # 整体超时 = 两个任务的超时之和 + 缓冲时间
-            total_timeout = REVIEW_TIMEOUT * len(review_tasks) + 10
-
-            try:
-                # 使用 gather 确保结果顺序与任务顺序一致
-                review_results = await asyncio.wait_for(
-                    asyncio.gather(*review_tasks, return_exceptions=True),
-                    timeout=total_timeout
-                )
-            except asyncio.TimeoutError:
-                logger.error(f"[工作流] 审查任务整体超时: trace_id={trace_id_idx}")
-                # 填充超时结果
-                review_results = [
-                    Exception(f"基础审查超时({REVIEW_TIMEOUT}秒)"),
-                    Exception(f"技术审查超时({REVIEW_TIMEOUT}秒)")
-                ]
-
-            # 处理异常结果(gather 已经将异常作为结果返回)
-            basic_result = review_results[0] if not isinstance(review_results[0], Exception) else {"error": str(review_results[0])}
-            technical_result = review_results[1] if not isinstance(review_results[1], Exception) else {"error": str(review_results[1])}
-
-            # RAG检查已注释,提供空结果
-            rag_result = {"error": "RAG check disabled"}
-
-            # 计算总体风险等级
-            inter_tool = InterTool()
-            overall_risk = inter_tool._calculate_overall_risk(basic_result, technical_result, rag_result)
-
-            
-            return ReviewResult(
-                unit_index=unit_index,
-                unit_content=unit_content,
-                basic_compliance=basic_result,
-                technical_compliance=technical_result,
-                rag_enhanced=rag_result,
-                overall_risk=overall_risk
-            )
-
-        except asyncio.TimeoutError:
-            logger.error(f"审查单元 {unit_index} 超时")
-            return ReviewResult(
-                unit_index=unit_index,
-                unit_content=unit_content,
-                basic_compliance={"error": f"审查超时({REVIEW_TIMEOUT}秒)"},
-                technical_compliance={"error": f"审查超时({REVIEW_TIMEOUT}秒)"},
-                rag_enhanced={"error": f"审查超时({REVIEW_TIMEOUT}秒)"},
-                overall_risk="error"
-            )
-        except Exception as e:
-            logger.error(f"审查单元 {unit_index} 失败: {str(e)}")
-            return ReviewResult(
-                unit_index=unit_index,
-                unit_content=unit_content,
-                basic_compliance={"error": str(e)},
-                technical_compliance={"error": str(e)},
-                rag_enhanced={"error": str(e)},
-                overall_risk="error"
-            )
-
-    async def _send_start_review_progress(self, state: AIReviewState, total_units: int = None, review_type : str =None) -> None:
-        """
-        发送开始审查的进度更新
-
-        Args:
-            state: AI审查状态
-            total_units: 总审查单元数
-        """
-
-
-        try:
-            
-
-            if state["progress_manager"]:
-                if  review_type is "outline":
-                    await state["progress_manager"].update_stage_progress(
-                        callback_task_id=state["callback_task_id"],
-                        stage_name="AI审查",
-                        current=0,
-                        status="processing",
-                        message=f"开始大纲审查",
-                        event_type="processing"
-                    )
-                # elif  review_type is "prpe_basis":
-                #     await state["progress_manager"].update_stage_progress(
-                #         callback_task_id=state["callback_task_id"],
-                #         stage_name="AI审查",
-                #         current=0,
-                #         total=total_units,
-                #         status="processing",
-                #         message=f"开始编制依据审查",
-                #         event_type="processing"
-                #     )
-                else:
-                    await state["progress_manager"].update_stage_progress(
-                        callback_task_id=state["callback_task_id"],
-                        stage_name="AI审查",
-                        current=0,
-                        status="processing",
-                        message=f"开始核心审查,共 {total_units} 个审查单元",
-                        event_type="processing"
-                    )
-        except Exception as e:
-            logger.warning(f"发送开始进度更新失败: {str(e)}")
-
-    async def _send_unit_review_progress(self, state: AIReviewState, unit_index: int,
-                                            total_units: int, section_label: str,
-                                            issues: List[Dict], current: int) -> None:
-        """
-        发送单元审查详细信息 - 强制串行并统一进度值
-        """
-        async with self.message_lock:
-            try:
-                # 1. 计算问题数量
-                issues_count = 0
-                if isinstance(issues, list) and issues:
-                    issues_count = sum(
-                        1 for issue in issues
-                        for issue_data in issue.values()
-                        for review_item in issue_data.get("review_lists", [])
-                        if review_item.get("exist_issue", False)
-                    )
-
-                real_current = await self._send_unit_overall_progress(
-                    state, unit_index, total_units, section_label, issues_count
-                )
-                
-
-                final_current = real_current if real_current is not None else current
-
-                await asyncio.sleep(0.05)
-
-                # 3. 发送单元详情 (Unit Review)
-                if isinstance(issues, list) and issues and state["progress_manager"]:
-                    stage_name = f"AI审查:{section_label}"
-
-                    await state["progress_manager"].update_stage_progress(
-                        callback_task_id=state["callback_task_id"],
-                        stage_name=stage_name,
-                        current=final_current,  # 【关键】使用与 Flag 事件完全一致的进度值
-                        status="unit_review_update",
-                        message=f"发现{issues_count}个问题: {section_label}",
-                        issues=issues,
-                        user_id=state.get("user_id", ""),
-                        overall_task_status="processing",
-                        event_type="unit_review"
-                    )
-
-                    # 再次微小延迟,确保 Clear 不会吞掉 Review
-                    await asyncio.sleep(0.02)
-
-                    # 清空当前issues
-                    await state["progress_manager"].update_stage_progress(
-                        callback_task_id=state["callback_task_id"],
-                        issues=['clear']
-                    )
-
-            except Exception as e:
-                logger.warning(f"发送单元审查详情失败: {str(e)}")
-
-    async def _send_unit_overall_progress(self, state: AIReviewState, unit_index: int,
-                                           total_units: int, section_label: str,
-                                           issues_count: int = None) -> Optional[int]:
-        """
-        发送单元完成进度更新 - 返回计算出的实时进度
-        Returns:
-            int: 基于 Redis 统计的实时进度百分比
-        """
-        current_percent = None
-        try:
-            task_id = state.get("callback_task_id", "")
-            redis_client = None
-            try:
-                redis_client = await RedisConnectionFactory.get_connection()
-            except Exception as e:
-                logger.warning(f"Redis连接失败: {str(e)}")
-
-            completed_count = 0
-            
-            if redis_client and task_id:
-                completed_key = f"ai_review:overall_task_progress:{task_id}:completed"
-                # 原子操作
-                await redis_client.sadd(completed_key, str(unit_index))
-                await redis_client.expire(completed_key, 3600)
-                completed_count = await redis_client.scard(completed_key)
-                
-                # 计算进度
-                current_percent = int((completed_count / total_units) * 100)
-            else:
-                # 降级方案
-                completed_count = unit_index + 1
-                current_percent = int((completed_count / total_units) * 100)
-
-            # 构建消息
-            if issues_count is not None and issues_count > 0:
-                message = f"已完成第 {completed_count}/{total_units} 个单元: {section_label}(已发现{issues_count}个问题)"
-            else:
-                message = f"已完成第 {completed_count}/{total_units} 个单元: {section_label}"
-
-            logger.info(f"进度更新: {current_percent}% - {message}")
-
-            if state["progress_manager"]:
-                await state["progress_manager"].update_stage_progress(
-                    callback_task_id=state["callback_task_id"],
-                    stage_name="AI审查",
-                    current=current_percent,
-                    status="processing",
-                    message=message,
-                    user_id=state.get("user_id", ""),
-                    overall_task_status="processing",
-                    event_type="processing_flag"
-                )
-            
-            return current_percent
-
-        except Exception as e:
-            logger.warning(f"发送单元完成进度更新失败: {str(e)}")
-            # 发生异常时,尝试返回一个基于 index 的估算值
-            try:
-                return int(((unit_index + 1) / total_units) * 100)
-            except:
-                return 0
-
-    def _filter_review_units(self, chunks: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
-        """
-        根据配置筛选要审查的单元
-
-        Args:
-            chunks: 所有审查单元
-
-        Returns:
-            List[Dict[str, Any]]: 筛选后的审查单元
-
-        Note:
-            根据max_review_units和review_mode配置来筛选审查单元
-        """
-        if not self.max_review_units or self.max_review_units >= len(chunks):
-            # 如果没有限制或限制数量大于等于总数,返回所有单元
-            return chunks
-
-        if self.review_mode == "first":
-            # 返回前N个单元
-            return chunks[:self.max_review_units]
-        elif self.review_mode == "random":
-            # 随机选择N个单元
-            return random.sample(chunks, self.max_review_units)
-        else:
-            # 默认返回所有审查单元
-            return chunks
-

+ 1 - 1
foundation/observability/logger/loggering.py

@@ -31,7 +31,7 @@ class CompatibleLogger(logging.Logger):
                  log_format=None, datefmt=None):
                  log_format=None, datefmt=None):
         # 初始化父类
         # 初始化父类
         super().__init__(name)
         super().__init__(name)
-        self.setLevel(logging.DEBUG)  # 设置logger自身为最低级别
+        self.setLevel(logging.DEBUG) # 设置logger自身为最低级别
 
 
         # 存储配置
         # 存储配置
         self.log_dir = log_dir
         self.log_dir = log_dir

+ 132 - 6
views/construction_review/launch_review.py

@@ -79,9 +79,13 @@ class LaunchReviewRequest(BaseModel):
         ...,
         ...,
         description="倾向性审查角色,暂定为 default_role"
         description="倾向性审查角色,暂定为 default_role"
     )
     )
-    review_config: List[str] = Field(
-        ...,
-        description="审查配置列表,包含的项为启用状态"
+    review_config: Optional[List[str]] = Field(
+        None,
+        description="审查配置列表,包含的项为启用状态(审查维度枚举值)。与review_item_config互斥"
+    )
+    review_item_config: Optional[List[str]] = Field(
+        None,
+        description="审查项配置列表,格式为「章节code_审查维度」(如 basis_sensitive_word_check)。与review_config互斥"
     )
     )
     project_plan_type: str = Field(
     project_plan_type: str = Field(
         ...,
         ...,
@@ -89,7 +93,7 @@ class LaunchReviewRequest(BaseModel):
     )
     )
 
 
     test_designation_chunk_flag: Optional[str] = Field(  # 标注为可选字符串
     test_designation_chunk_flag: Optional[str] = Field(  # 标注为可选字符串
-        None,  
+        None,
         description="测试定位标志符,用于指定特定审查片段(可选字段)"
         description="测试定位标志符,用于指定特定审查片段(可选字段)"
     )
     )
 
 
@@ -127,6 +131,114 @@ def validate_review_config(review_config: List[str]) -> None:
     if unsupported_items:
     if unsupported_items:
         raise LaunchReviewErrors.enum_type_invalid()
         raise LaunchReviewErrors.enum_type_invalid()
 
 
+def validate_review_config_mutually_exclusive(
+    review_config: Optional[List[str]],
+    review_item_config: Optional[List[str]]
+) -> None:
+    """
+    验证 review_config 与 review_item_config 互斥性
+
+    规则:
+    1. 两者不能同时提供(互斥)
+    2. 两者不能同时为空(必须提供其中一个)
+
+    Args:
+        review_config: 审查配置列表(审查维度枚举值)
+        review_item_config: 审查项配置列表(章节_审查维度格式)
+
+    Raises:
+        HTTPException: 当两者同时提供或同时为空时抛出异常
+    """
+    # 判断字段是否存在(只判断是否为 None,不判断长度)
+    has_review_config = review_config is not None
+    has_review_item_config = review_item_config is not None
+
+    # 情况1:两者都提供了 - 互斥错误(包括空列表的情况)
+    if has_review_config and has_review_item_config:
+        raise LaunchReviewErrors.mutually_exclusive_config()
+
+    # 情况2:两者都为空 - 必须提供一个
+    if not has_review_config and not has_review_item_config:
+        raise LaunchReviewErrors.review_config_required()
+
+    logger.info(f"审查配置验证通过: 使用{'review_config' if has_review_config else 'review_item_config'}")
+
+def validate_review_item_config(review_item_config: List[str]) -> None:
+    """
+    验证审查项配置参数合法性
+    核心规则:每个传入值必须是「chapter_code_review_dimension」格式(如 basis_sensitive_word_check)
+    :param review_item_config: 配置列表(元素为待验证的组合字符串)
+    :raises ValueError: 配置为空、格式错误或包含不支持的code时抛出异常
+    """
+    # 1. 非空校验
+    if not review_item_config:
+        raise LaunchReviewErrors.enum_type_cannot_be_null()
+
+    # 2. 去重检查
+    if len(review_item_config) != len(set(review_item_config)):
+        # 找出重复的项
+        seen = set()
+        duplicates = []
+        for item in review_item_config:
+            if item in seen:
+                duplicates.append(item)
+            seen.add(item)
+        raise LaunchReviewErrors.duplicate_review_items(list(set(duplicates)))
+
+    # 定义支持的基础枚举值
+    supported = {
+        "chapter_code": {
+            "catalogue", "basis", "overview", "plan", "technology",
+            "safety", "quality", "environment", "management",
+            "acceptance", "other"
+        },
+        "review_dimensions": {
+            "sensitive_word_check", "semantic_logic_check", "completeness_check",
+            "timeliness_check", "reference_check", "sensitive_check",
+            "non_parameter_compliance_check", "parameter_compliance_check"
+        }
+    }
+
+    # 按规则校验每个配置项
+    invalid_format = []  # 格式错误(不是下划线连接的两个部分)
+    invalid_chapter = []  # 章节code不支持
+    invalid_review = []   # 审查项code不支持
+    catalogue_invalid = []  # 目录章节使用了非完整性审查
+
+    for item in review_item_config:
+        # 3. 校验格式:必须至少包含一个下划线(按第一个下划线分割)
+        parts = item.split("_", 1)  # maxsplit=1,只按第一个下划线分割
+        if len(parts) != 2:
+            invalid_format.append(item)
+            continue
+
+        chapter_code, review_dim = parts
+        # 4. 校验章节code是否支持
+        if chapter_code not in supported["chapter_code"]:
+            invalid_chapter.append(chapter_code)
+            continue  # 章节不支持时不继续检查审查项
+
+        # 5. 特殊规则:目录章节只能使用完整性审查
+        if chapter_code == "catalogue" and review_dim != "completeness_check":
+            catalogue_invalid.append(item)
+            continue  # 目录章节违反规则时不继续检查
+
+        # 6. 校验审查项code是否支持
+        if review_dim not in supported["review_dimensions"]:
+            invalid_review.append(review_dim)
+
+    # 批量抛出错误(按优先级:格式错误 → 重复 → 章节错误 → 目录特殊规则 → 审查项错误)
+    if invalid_format:
+        raise LaunchReviewErrors.invalid_review_item_format(invalid_format)
+    if invalid_chapter:
+        raise LaunchReviewErrors.invalid_chapter_code(list(set(invalid_chapter)))  # 去重
+    if catalogue_invalid:
+        raise LaunchReviewErrors.catalogue_completeness_only(catalogue_invalid)
+    if invalid_review:
+        raise LaunchReviewErrors.invalid_review_dimension(list(set(invalid_review)))  # 去重
+
+
+
 def validate_project_plan_type(project_plan_type: str) -> None:
 def validate_project_plan_type(project_plan_type: str) -> None:
     """验证工程方案类型"""
     """验证工程方案类型"""
     # 当前支持的工程方案类型
     # 当前支持的工程方案类型
@@ -192,6 +304,7 @@ async def launch_review_sse(request_data: LaunchReviewRequest):
     TraceContext.set_trace_id(callback_task_id)
     TraceContext.set_trace_id(callback_task_id)
     user_id = request_data.user_id
     user_id = request_data.user_id
     review_config = request_data.review_config
     review_config = request_data.review_config
+    review_item_config = request_data.review_item_config
     project_plan_type = request_data.project_plan_type
     project_plan_type = request_data.project_plan_type
     tendency_review_role = request_data.tendency_review_role
     tendency_review_role = request_data.tendency_review_role
     test_designation_chunk_flag = request_data.test_designation_chunk_flag
     test_designation_chunk_flag = request_data.test_designation_chunk_flag
@@ -201,8 +314,20 @@ async def launch_review_sse(request_data: LaunchReviewRequest):
     # 验证用户标识
     # 验证用户标识
     validate_user_id(user_id)
     validate_user_id(user_id)
 
 
-    # 验证审查配置
-    validate_review_config(review_config)
+    # 验证审查配置互斥性(优先验证互斥关系)
+    validate_review_config_mutually_exclusive(review_config, review_item_config)
+
+    # 根据提供的配置类型进行相应验证(只判断是否为 None)
+    if review_config is not None:
+        # 使用 review_config 时的验证
+        if len(review_config) == 0:
+            raise LaunchReviewErrors.review_config_required()  # 提供了空列表,提示必须提供有效值
+        validate_review_config(review_config)
+    elif review_item_config is not None:
+        # 使用 review_item_config 时的验证
+        if len(review_item_config) == 0:
+            raise LaunchReviewErrors.review_config_required()  # 提供了空列表,提示必须提供有效值
+        validate_review_item_config(review_item_config)
 
 
     # 验证工程方案类型
     # 验证工程方案类型
     validate_project_plan_type(project_plan_type)
     validate_project_plan_type(project_plan_type)
@@ -278,6 +403,7 @@ async def launch_review_sse(request_data: LaunchReviewRequest):
                 file_info.update({
                 file_info.update({
                     'user_id': user_id,
                     'user_id': user_id,
                     'review_config': review_config,
                     'review_config': review_config,
+                    'review_item_config': review_item_config,
                     'project_plan_type': project_plan_type,
                     'project_plan_type': project_plan_type,
                     'tendency_review_role': tendency_review_role,
                     'tendency_review_role': tendency_review_role,
                     'test_designation_chunk_flag': test_designation_chunk_flag,
                     'test_designation_chunk_flag': test_designation_chunk_flag,

+ 98 - 0
views/construction_review/schemas/error_schemas.py

@@ -179,6 +179,55 @@ class ErrorCodes:
         "status_code": 400
         "status_code": 400
     }
     }
 
 
+    QDSC015 = {
+        "code": "QDSC015",
+        "error_type": "INVALID_REVIEW_ITEM_FORMAT",
+        "message": "审查项配置格式错误,必须为「chapter_code_review_dimension」格式",
+        "status_code": 400
+    }
+
+    QDSC016 = {
+        "code": "QDSC016",
+        "error_type": "INVALID_CHAPTER_CODE",
+        "message": "章节code不支持",
+        "status_code": 400
+    }
+
+    QDSC017 = {
+        "code": "QDSC017",
+        "error_type": "INVALID_REVIEW_DIMENSION",
+        "message": "审查项code不支持",
+        "status_code": 400
+    }
+
+    QDSC018 = {
+        "code": "QDSC018",
+        "error_type": "MUTUALLY_EXCLUSIVE_CONFIG",
+        "message": "review_config与review_item_config互斥,只能提供其中一个",
+        "status_code": 400
+    }
+
+    QDSC019 = {
+        "code": "QDSC019",
+        "error_type": "REVIEW_CONFIG_REQUIRED",
+        "message": "必须提供review_config或review_item_config中的其中一个",
+        "status_code": 400
+    }
+
+    QDSC020 = {
+        "code": "QDSC020",
+        "error_type": "DUPLICATE_REVIEW_ITEMS",
+        "message": "审查项配置中存在重复项",
+        "status_code": 400
+    }
+
+    QDSC021 = {
+        "code": "QDSC021",
+        "error_type": "CATALOGUE_COMPLETENESS_ONLY",
+        "message": "目录章节仅支持完整性审查",
+        "status_code": 400
+    }
+
 
 
     # 审查结果接口错误码 (SCJG001-SCJG008)
     # 审查结果接口错误码 (SCJG001-SCJG008)
     SCJG001 = {
     SCJG001 = {
@@ -420,6 +469,55 @@ class LaunchReviewErrors:
         logger.error(ErrorCodes.QDSC014)
         logger.error(ErrorCodes.QDSC014)
         return create_http_exception(ErrorCodes.QDSC014)
         return create_http_exception(ErrorCodes.QDSC014)
 
 
+    @staticmethod
+    def invalid_review_item_format(invalid_items: list = None):
+        """审查项配置格式错误"""
+        logger.error(f"审查项配置格式错误: {invalid_items}")
+        message = f"审查项配置格式错误,必须为「chapter_code_review_dimension」格式(如 basis_sensitive_word_check)。无效项: {invalid_items}"
+        return create_http_exception(ErrorCodes.QDSC015, message)
+
+    @staticmethod
+    def invalid_chapter_code(invalid_codes: list = None):
+        """章节code不支持"""
+        logger.error(f"章节code不支持: {invalid_codes}")
+        message = f"章节code不支持。无效的章节code: {invalid_codes}"
+        return create_http_exception(ErrorCodes.QDSC016, message)
+
+    @staticmethod
+    def invalid_review_dimension(invalid_dimensions: list = None):
+        """审查项code不支持"""
+        logger.error(f"审查项code不支持: {invalid_dimensions}")
+        message = f"审查项code不支持。无效的审查项code: {invalid_dimensions}"
+        return create_http_exception(ErrorCodes.QDSC017, message)
+
+    @staticmethod
+    def mutually_exclusive_config():
+        """review_config与review_item_config互斥"""
+        logger.error("review_config与review_item_config同时提供,但两者互斥")
+        message = "参数错误:review_config(审查维度列表)与review_item_config(章节_审查维度列表)互斥,只能提供其中一个,不能同时提供。"
+        return create_http_exception(ErrorCodes.QDSC018, message)
+
+    @staticmethod
+    def review_config_required():
+        """必须提供review_config或review_item_config"""
+        logger.error("未提供review_config或review_item_config")
+        message = "参数错误:必须提供review_config(审查维度列表)或review_item_config(章节_审查维度列表)中的其中一个。"
+        return create_http_exception(ErrorCodes.QDSC019, message)
+
+    @staticmethod
+    def duplicate_review_items(duplicates: list = None):
+        """审查项配置中存在重复项"""
+        logger.error(f"审查项配置中存在重复项: {duplicates}")
+        message = f"参数错误:review_item_config 中存在重复的审查项。重复项: {duplicates}"
+        return create_http_exception(ErrorCodes.QDSC020, message)
+
+    @staticmethod
+    def catalogue_completeness_only(invalid_items: list = None):
+        """目录章节仅支持完整性审查"""
+        logger.error(f"目录章节使用了非完整性审查: {invalid_items}")
+        message = f"参数错误:目录章节(catalogue)仅支持完整性审查(completeness_check)。无效配置项: {invalid_items}"
+        return create_http_exception(ErrorCodes.QDSC021, message)
+
 
 
 class ReviewResultsErrors:
 class ReviewResultsErrors:
     """审查结果接口错误"""
     """审查结果接口错误"""