|
|
@@ -53,6 +53,7 @@ from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
|
|
|
from foundation.observability.logger.loggering import server_logger as logger
|
|
|
from foundation.infrastructure.cache.redis_connection import RedisConnectionFactory
|
|
|
from ..component import AIReviewEngine
|
|
|
+from ..component.reviewers.utils.inter_tool import InterTool
|
|
|
|
|
|
# 常量定义
|
|
|
DEFAULT_SLICE_START_INDEX = 30
|
|
|
@@ -291,7 +292,7 @@ class AIReviewWorkflow:
|
|
|
# 构建符合格式的review_results
|
|
|
review_results = {
|
|
|
"callback_task_id": state["callback_task_id"],
|
|
|
- "file_name": state.get("file_name", ""), # 从state中获取文件名
|
|
|
+ "file_name": state.get("file_name", ""),
|
|
|
"user_id": state["user_id"],
|
|
|
"current": 100,
|
|
|
"stage_name": "完整审查结果",
|
|
|
@@ -445,8 +446,41 @@ class AIReviewWorkflow:
|
|
|
|
|
|
def _get_workflow_graph(self):
|
|
|
"""获取工作流图(可视化输出)"""
|
|
|
- grandalf_graph = self.graph.get_graph()
|
|
|
- grandalf_graph.print_ascii()
|
|
|
+ try:
|
|
|
+ grandalf_graph = self.graph.get_graph()
|
|
|
+ grandalf_graph.print_ascii()
|
|
|
+ self._save_workflow_graph(grandalf_graph)
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.warning(f"生成工作流图失败: {str(e)}")
|
|
|
+
|
|
|
+ def _save_workflow_graph(self, graph):
|
|
|
+ """保存工作流图到temp文件夹"""
|
|
|
+ try:
|
|
|
+ # 创建temp目录
|
|
|
+ temp_dir = "temp"
|
|
|
+
|
|
|
+ file_path = os.path.join(temp_dir, f"ai_review_workflow_graph.png")
|
|
|
+
|
|
|
+ png_data = graph.draw_mermaid_png()
|
|
|
+
|
|
|
+ # 保存到文件
|
|
|
+ with open(file_path, "wb") as f:
|
|
|
+ f.write(png_data)
|
|
|
+
|
|
|
+ logger.info(f"工作流图已保存到: {file_path}")
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ try:
|
|
|
+
|
|
|
+ ascii_file = os.path.join("temp", f"ai_review_workflow_graph.txt")
|
|
|
+ with open(ascii_file, 'w', encoding='utf-8') as f:
|
|
|
+ graph.print_ascii(file=f)
|
|
|
+ logger.info(f"工作流ASCII图已保存到: {ascii_file}")
|
|
|
+ except Exception as ascii_error:
|
|
|
+ logger.warning(f"保存ASCII图也失败: {str(ascii_error)}")
|
|
|
+
|
|
|
+ logger.warning(f"保存工作流图PNG失败: {str(e)}")
|
|
|
|
|
|
async def _get_status(self) -> dict:
|
|
|
"""获取工作流状态"""
|
|
|
@@ -470,8 +504,8 @@ class AIReviewCoreFun:
|
|
|
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()
|
|
|
|
|
|
|
|
|
async def _execute_concurrent_reviews(self, review_chunks: List[Dict[str, Any]],
|
|
|
@@ -503,7 +537,7 @@ class AIReviewCoreFun:
|
|
|
section_label = unit_content.get('section_label', f'第{unit_index + 1}部分')
|
|
|
logger.info(f"section_label: {section_label}")
|
|
|
# 格式化issues以获取问题数量
|
|
|
- issues = self._format_review_results_to_issues(
|
|
|
+ issues = self.inter_tool._format_review_results_to_issues(
|
|
|
state["callback_task_id"],
|
|
|
unit_index,
|
|
|
f"第{unit_content.get('page', '')}页:{section_label}",
|
|
|
@@ -591,27 +625,14 @@ class AIReviewCoreFun:
|
|
|
section_label = unit_content.get('section_label', f'第{unit_index + 1}部分')
|
|
|
page = unit_content.get('page', '')
|
|
|
review_location_label = f"第{page}页:{section_label}"
|
|
|
-
|
|
|
- # 设置review_location_label到AIReviewEngine
|
|
|
- self.ai_review_engine.set_review_location_label(review_location_label)
|
|
|
- logger.info(f"review_location_label: {review_location_label}")
|
|
|
- stage_name = f"AI审查:{section_label}"
|
|
|
-
|
|
|
- # 方法内部进度计算(基于当前处理的单元)
|
|
|
- current_progress = int((unit_index / total_units) * 100)
|
|
|
-
|
|
|
- # 构建进度消息
|
|
|
- #message = f"正在处理第 {unit_index + 1}/{total_units} 个单元: {section_label}"
|
|
|
- #logger.info(f"开始处理单元: {unit_index + 1}/{total_units} - {section_label}")
|
|
|
-
|
|
|
- # 并发执行各种原子化审查方法,添加超时控制
|
|
|
+ logger.info(f"test_review_location_label:{trace_id_idx}: {review_location_label}")
|
|
|
review_tasks = [
|
|
|
asyncio.wait_for(
|
|
|
- self.ai_review_engine.basic_compliance_check(trace_id_idx, unit_content, stage_name, state, current_progress),
|
|
|
+ self.ai_review_engine.basic_compliance_check(trace_id_idx, unit_content, review_location_label),
|
|
|
timeout=REVIEW_TIMEOUT
|
|
|
),
|
|
|
asyncio.wait_for(
|
|
|
- self.ai_review_engine.technical_compliance_check(trace_id_idx, unit_content, stage_name, state, current_progress),
|
|
|
+ self.ai_review_engine.technical_compliance_check(trace_id_idx, unit_content,review_location_label),
|
|
|
timeout=REVIEW_TIMEOUT
|
|
|
),
|
|
|
]
|
|
|
@@ -808,268 +829,6 @@ class AIReviewCoreFun:
|
|
|
except:
|
|
|
return 0
|
|
|
|
|
|
- def _format_review_results_to_issues(self,callback_task_id: str, unit_index: int, review_location_label: str,
|
|
|
- unit_content: Dict[str, Any], basic_result: Dict[str, Any],
|
|
|
- technical_result: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
|
- """
|
|
|
- 将审查结果格式化为issues结构
|
|
|
-
|
|
|
- Args:
|
|
|
- callback_task_id: 回调任务ID,用于生成唯一issue_id
|
|
|
- unit_index: 单元索引,用于生成唯一issue_id
|
|
|
- review_location_label: 审查位置标签,如"第3页:第一章"
|
|
|
- unit_content: 单元内容,包含原始文本等信息
|
|
|
- basic_result: 基础合规性审查结果,包含各项检查结果
|
|
|
- technical_result: 技术性审查结果,包含技术标准检查结果
|
|
|
-
|
|
|
- Returns:
|
|
|
- List[Dict]: 格式化后的issues列表,每个issue包含:
|
|
|
- - issue_id: 唯一标识符,格式为"{callback_task_id}-{risk_level}-{unit_index}"
|
|
|
- - metadata: 元数据,包含审查位置和原始内容
|
|
|
- - risk_summary: 风险摘要,包含最高风险等级和问题数量统计
|
|
|
- - review_lists: 详细审查问题列表
|
|
|
-
|
|
|
- Note:
|
|
|
- 自动跳过overall_score字段,提取所有检查项的详细结果
|
|
|
- 支持风险等级统计和最高风险等级确定
|
|
|
- """
|
|
|
- issues = []
|
|
|
- review_lists = []
|
|
|
- risk_count = {"high": 0, "medium": 0, "low": 0}
|
|
|
- max_risk_level = "low"
|
|
|
-
|
|
|
- # 合并所有审查结果
|
|
|
- all_results = {}
|
|
|
- if basic_result:
|
|
|
- all_results.update(basic_result)
|
|
|
- if technical_result:
|
|
|
- all_results.update(technical_result)
|
|
|
-
|
|
|
- logger.info(f"开始格式化审查结果,合并后结果: {list(all_results.keys())}")
|
|
|
-
|
|
|
- for check_key, check_result in all_results.items():
|
|
|
- logger.info(f"处理检查项: {check_key}, 结果类型: {type(check_result)}")
|
|
|
-
|
|
|
- if check_key == 'overall_score': # 跳过分数字段
|
|
|
- logger.info(f"跳过分数字段: {check_key}")
|
|
|
- continue
|
|
|
-
|
|
|
- if check_result and "details" in check_result and "response" in check_result["details"]:
|
|
|
- response = check_result["details"]["response"]
|
|
|
- check_name = check_result["details"].get("name", check_key)
|
|
|
- logger.info(f"解析检查项 {check_name} 的响应,长度: {len(response)}")
|
|
|
- check_issues = self._parse_ai_review_response(response, check_name)
|
|
|
- review_lists.extend(check_issues)
|
|
|
- else:
|
|
|
- logger.warning(f"检查项 {check_key} 格式不符合要求,缺少details或response字段")
|
|
|
- logger.warning(f"check_result内容: {check_result}")
|
|
|
-
|
|
|
- # 统计风险等级
|
|
|
- for issue in review_lists:
|
|
|
- risk_level = issue.get("risk_info", {}).get("risk_level", "low")
|
|
|
- if risk_level in risk_count:
|
|
|
- risk_count[risk_level] += 1
|
|
|
-
|
|
|
- # 确定最高风险等级
|
|
|
- if risk_count["high"] > 0:
|
|
|
- max_risk_level = "high"
|
|
|
- elif risk_count["medium"] > 0:
|
|
|
- max_risk_level = "medium"
|
|
|
-
|
|
|
- # 如果有审查结果,创建issue
|
|
|
- if review_lists:
|
|
|
- issue_id = f"{callback_task_id}-{max_risk_level if max_risk_level else '0'}-{unit_index}"
|
|
|
- issue = {
|
|
|
- issue_id: {
|
|
|
- "metadata": {
|
|
|
- "review_location_label": review_location_label,
|
|
|
- "original_content": unit_content.get('content', '')
|
|
|
- },
|
|
|
- "risk_summary": {
|
|
|
- "max_risk_level": max_risk_level if max_risk_level else '0',
|
|
|
- "risk_count": risk_count if risk_count else 0
|
|
|
- },
|
|
|
- "review_lists": review_lists
|
|
|
- }
|
|
|
- }
|
|
|
- issues.append(issue)
|
|
|
-
|
|
|
- return issues
|
|
|
-
|
|
|
- def _parse_ai_review_response(self,response: str, check_name: str) -> List[Dict[str, Any]]:
|
|
|
- """
|
|
|
- 解析AI审查的JSON格式响应
|
|
|
-
|
|
|
- Args:
|
|
|
- response: AI审查响应内容
|
|
|
- check_name: 检查项名称(如"词句语法检查")
|
|
|
-
|
|
|
- Returns:
|
|
|
- List[Dict]: 解析后的审查结果列表,包含check_item、check_result、exist_issue、risk_info等字段
|
|
|
-
|
|
|
- Note:
|
|
|
- 支持识别"无明显问题"等关键词,自动设置风险等级
|
|
|
- 解析新的英文键名JSON格式: issue_point, location, suggestion, reason, risk_level
|
|
|
- """
|
|
|
- review_lists = []
|
|
|
-
|
|
|
- try:
|
|
|
- if "无明显问题" in response or "无问题" in response or "符合要求" in response:
|
|
|
- review_lists.append({
|
|
|
- "check_item": check_name,
|
|
|
- "check_result": "无明显问题",
|
|
|
- "exist_issue": False,
|
|
|
- "risk_info": {"risk_level": "low"}
|
|
|
- })
|
|
|
- return review_lists
|
|
|
-
|
|
|
- # 尝试解析JSON格式响应
|
|
|
-
|
|
|
- # 首先检查响应是否直接被{}包裹
|
|
|
- response_stripped = response.strip()
|
|
|
- json_data = None
|
|
|
-
|
|
|
- if (response_stripped.startswith('{') and response_stripped.endswith('}')) or \
|
|
|
- (response_stripped.startswith('{\n') and response_stripped.endswith('\n}')):
|
|
|
- # 响应被{}包裹,直接解析
|
|
|
- try:
|
|
|
- json_data = json.loads(response_stripped)
|
|
|
- except json.JSONDecodeError:
|
|
|
- logger.warning(f"直接JSON解析失败,尝试查找JSON代码块")
|
|
|
-
|
|
|
- # 如果直接解析失败,查找JSON代码块
|
|
|
- if json_data is None:
|
|
|
- # 改进的正则表达式:正确处理多行JSON和转义字符
|
|
|
- json_pattern = r'```json\s*(\[[\s\S]*?\]|\{[\s\S]*?\})\s*```'
|
|
|
- json_matches = re.findall(json_pattern, response)
|
|
|
-
|
|
|
- # 如果改进的正则仍然匹配不到,使用更宽松的模式
|
|
|
- if not json_matches:
|
|
|
- json_pattern = r'```json\s*(.*?)\s*```'
|
|
|
- json_matches = re.findall(json_pattern, response, re.DOTALL)
|
|
|
-
|
|
|
- else:
|
|
|
- json_matches = [response_stripped] # 使用整个响应作为JSON
|
|
|
-
|
|
|
- # 如果所有方法都失败,尝试最后的JSON提取策略
|
|
|
- if not json_matches:
|
|
|
- logger.warning("标准JSON提取失败,尝试智能JSON提取策略")
|
|
|
-
|
|
|
- # 策略1: 查找被JSON数组/对象包围的内容
|
|
|
- array_pattern = r'\[\s*\{.*?\}\s*\]'
|
|
|
- object_pattern = r'\{[^{}]*"issue_point"[^{}]*\}'
|
|
|
-
|
|
|
- array_matches = re.findall(array_pattern, response, re.DOTALL)
|
|
|
- if array_matches:
|
|
|
- json_matches = array_matches
|
|
|
- logger.info(f"数组模式匹配成功,找到 {len(array_matches)} 个JSON数组")
|
|
|
- else:
|
|
|
- object_matches = re.findall(object_pattern, response, re.DOTALL)
|
|
|
- if object_matches:
|
|
|
- json_matches = object_matches
|
|
|
- logger.info(f"对象模式匹配成功,找到 {len(object_matches)} 个JSON对象")
|
|
|
-
|
|
|
- # 最后的备选方案:查找包含关键JSON字段的内容
|
|
|
- if not json_matches:
|
|
|
- logger.warning("所有JSON提取策略失败,查找包含关键字段的内容")
|
|
|
- key_pattern = r'[^`]*["\']issue_point["\'][^`]*["\']location["\'][^`]*'
|
|
|
- key_matches = re.findall(key_pattern, response, re.DOTALL)
|
|
|
- if key_matches:
|
|
|
- # 尝试将匹配内容包装为有效JSON
|
|
|
- for match in key_matches:
|
|
|
- # 简单包装,可能需要更复杂的逻辑
|
|
|
- wrapped_match = f'[{match}]' if not match.strip().startswith('[') else match
|
|
|
- json_matches.append(wrapped_match)
|
|
|
- logger.info(f"关键字段模式匹配成功,生成 {len(json_matches)} 个JSON候选")
|
|
|
-
|
|
|
- if json_matches:
|
|
|
- logger.info(f"成功匹配到 {len(json_matches)} 个JSON代码块")
|
|
|
- # 解析找到的JSON
|
|
|
- for i, json_str in enumerate(json_matches):
|
|
|
- try:
|
|
|
- # 如果是直接解析的JSON,不需要重新解析
|
|
|
- if json_str == response_stripped and json_data is not None:
|
|
|
- issue_data = json_data
|
|
|
- logger.debug(f"使用直接解析的JSON数据 (第{i+1}个)")
|
|
|
- else:
|
|
|
- # 清理JSON字符串
|
|
|
- json_str = json_str.strip()
|
|
|
- if not json_str:
|
|
|
- logger.warning(f"第{i+1}个JSON代码块为空,跳过")
|
|
|
- continue
|
|
|
-
|
|
|
- logger.debug(f"尝试解析第{i+1}个JSON: {json_str[:100]}...")
|
|
|
- # 解析JSON
|
|
|
- issue_data = json.loads(json_str)
|
|
|
- logger.info(f"第{i+1}个JSON解析成功,包含 {len(issue_data) if isinstance(issue_data, (list, dict)) else 0} 个元素")
|
|
|
-
|
|
|
- risk_level = issue_data.get("risk_level", "")
|
|
|
-
|
|
|
- # 确定风险等级
|
|
|
- if not risk_level:
|
|
|
- risk_level = "medium" # 默认中等风险
|
|
|
- else:
|
|
|
- risk_level = risk_level.lower()
|
|
|
- if "高" in risk_level or "high" in risk_level:
|
|
|
- risk_level = "high"
|
|
|
- elif "中" in risk_level or "medium" in risk_level:
|
|
|
- risk_level = "medium"
|
|
|
- else:
|
|
|
- risk_level = "low"
|
|
|
-
|
|
|
- # 直接存储JSON字段
|
|
|
- review_lists.append({
|
|
|
- "check_item": check_name,
|
|
|
- "check_result": issue_data, # 直接存储原始JSON数据
|
|
|
- "exist_issue": True,
|
|
|
- "risk_info": {"risk_level": risk_level}
|
|
|
- })
|
|
|
-
|
|
|
- except json.JSONDecodeError as je:
|
|
|
- logger.warning(f"JSON解析失败: {str(je)}, JSON内容: {json_str[:100]}...")
|
|
|
- # 如果JSON解析失败,回退到文本解析
|
|
|
- continue
|
|
|
-
|
|
|
- # 如果没有成功解析到JSON,使用旧的文本解析方法
|
|
|
- if not review_lists:
|
|
|
- lines = response.split('\n')
|
|
|
- risk_level = "medium" # 默认中等风险
|
|
|
-
|
|
|
- # 扫描提取风险等级(支持中英文)
|
|
|
- for line in lines:
|
|
|
- line = line.strip()
|
|
|
- if line.startswith('**风险等级**:') or line.startswith('**risk_level**:'):
|
|
|
- if ':' in line:
|
|
|
- extracted_risk = line.split(':', 1)[1].strip().lower()
|
|
|
- elif ':' in line:
|
|
|
- extracted_risk = line.split(':', 1)[1].strip().lower()
|
|
|
-
|
|
|
- if "高" in extracted_risk or "high" in extracted_risk:
|
|
|
- risk_level = "high"
|
|
|
- elif "中" in extracted_risk or "medium" in extracted_risk:
|
|
|
- risk_level = "medium"
|
|
|
- else:
|
|
|
- risk_level = "low"
|
|
|
- break
|
|
|
-
|
|
|
- review_lists.append({
|
|
|
- "check_item": check_name,
|
|
|
- "check_result": response, # 存储完整响应作为备选
|
|
|
- "exist_issue": True,
|
|
|
- "risk_info": {"risk_level": risk_level}
|
|
|
- })
|
|
|
-
|
|
|
- except Exception as e:
|
|
|
- logger.error(f"解析AI审查响应失败: {str(e)}")
|
|
|
- review_lists.append({
|
|
|
- "check_item": check_name,
|
|
|
- "check_result": response,
|
|
|
- "exist_issue": True,
|
|
|
- "risk_info": {"risk_level": "low"}
|
|
|
- })
|
|
|
-
|
|
|
- return review_lists
|
|
|
-
|
|
|
def _filter_review_units(self, chunks: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
|
"""
|
|
|
根据配置筛选要审查的单元
|
|
|
@@ -1081,145 +840,19 @@ class AIReviewCoreFun:
|
|
|
List[Dict[str, Any]]: 筛选后的审查单元
|
|
|
|
|
|
Note:
|
|
|
- 使用预设的起始索引进行切片,确保不会跳过前面的重要内容
|
|
|
+ 根据max_review_units和review_mode配置来筛选审查单元
|
|
|
"""
|
|
|
- if self.max_review_units is None or self.review_mode == "all":
|
|
|
+ if not self.max_review_units or self.max_review_units >= len(chunks):
|
|
|
+ # 如果没有限制或限制数量大于等于总数,返回所有单元
|
|
|
return chunks
|
|
|
|
|
|
- # 验证原始chunks不为空
|
|
|
- if not chunks:
|
|
|
- logger.warning("没有可用的审查单元")
|
|
|
- return []
|
|
|
-
|
|
|
- # 安全的切片操作,考虑边界情况
|
|
|
- # 使用预设的起始索引进行切片,避免跳过前面的内容
|
|
|
- start_index = min(DEFAULT_SLICE_START_INDEX, len(chunks) - 1) # 确保start_index不超过数组边界
|
|
|
- chunks = chunks[start_index:]
|
|
|
-
|
|
|
- # 再次验证切片后的结果
|
|
|
- if not chunks:
|
|
|
- logger.warning(f"从索引{start_index}切片后没有可用的审查单元")
|
|
|
- return []
|
|
|
-
|
|
|
- total_chunks = len(chunks)
|
|
|
- actual_review_count = min(self.max_review_units, total_chunks)
|
|
|
-
|
|
|
- logger.info(f"审查模式: {self.review_mode}, 总单元数: {total_chunks}, 实际审查数: {actual_review_count}")
|
|
|
-
|
|
|
if self.review_mode == "first":
|
|
|
- # 取前N个
|
|
|
- return chunks[:actual_review_count]
|
|
|
+ # 返回前N个单元
|
|
|
+ return chunks[:self.max_review_units]
|
|
|
elif self.review_mode == "random":
|
|
|
- # 随机取N个
|
|
|
- return random.sample(chunks, actual_review_count)
|
|
|
+ # 随机选择N个单元
|
|
|
+ return random.sample(chunks, self.max_review_units)
|
|
|
else:
|
|
|
- # 默认取前N个
|
|
|
- return chunks[:actual_review_count]
|
|
|
-
|
|
|
-
|
|
|
-class InterTool:
|
|
|
- """AI审查工具类 - 负责辅助计算和结果处理"""
|
|
|
-
|
|
|
- def _calculate_overall_risk(self, basic_result: Dict, technical_result: Dict, rag_result: Dict) -> str:
|
|
|
- """
|
|
|
- 计算总体风险等级
|
|
|
-
|
|
|
- Args:
|
|
|
- basic_result: 基础合规性审查结果,包含overall_score字段
|
|
|
- technical_result: 技术性审查结果,包含overall_score字段
|
|
|
- rag_result: RAG增强审查结果(当前未使用)
|
|
|
-
|
|
|
- Returns:
|
|
|
- str: 风险等级 ("low", "medium", "high")
|
|
|
-
|
|
|
- Note:
|
|
|
- 风险等级计算逻辑:
|
|
|
- - low: 基础和技术审查都达到90分以上
|
|
|
- - medium: 基础和技术审查都达到70分以上
|
|
|
- - high: 其他情况
|
|
|
- 异常情况下返回默认风险等级medium
|
|
|
- """
|
|
|
- try:
|
|
|
- # 基于各种审查结果计算风险等级
|
|
|
- # 风险等级计算逻辑:基础和技术审查都达到90分以上为低风险,70分以上为中风险,否则为高风险
|
|
|
- basic_score = basic_result.get('overall_score', 0)
|
|
|
- technical_score = technical_result.get('overall_score', 0)
|
|
|
-
|
|
|
- if basic_score >= 90 and technical_score >= 90:
|
|
|
- return "low"
|
|
|
- elif basic_score >= 70 and technical_score >= 70:
|
|
|
- return "medium"
|
|
|
- else:
|
|
|
- return "high"
|
|
|
- except (KeyError, TypeError, ValueError) as e:
|
|
|
- logger.warning(f"风险等级计算异常: {str(e)},使用默认风险等级")
|
|
|
- return DEFAULT_RISK_LEVEL
|
|
|
-
|
|
|
- def _aggregate_results(self, successful_results: List[List[Dict[str, Any]]]) -> Dict[str, Any]:
|
|
|
- """
|
|
|
- 汇总审查结果(issues格式)
|
|
|
-
|
|
|
- Args:
|
|
|
- successful_results: 成功的审查结果列表(issues格式),每个单元返回一个issues列表
|
|
|
-
|
|
|
- Returns:
|
|
|
- Dict[str, Any]: 汇总后的统计信息,包含以下字段:
|
|
|
- - risk_stats: 各风险等级的数量统计 {"low": 0, "medium": 0, "high": 0}
|
|
|
- - total_reviewed: 成功审查的总数量
|
|
|
- - total_issues: 总问题数量
|
|
|
-
|
|
|
- Note:
|
|
|
- 当输入为空时返回空字典,异常时记录错误并返回空字典
|
|
|
- """
|
|
|
- try:
|
|
|
- if not successful_results:
|
|
|
- return {}
|
|
|
-
|
|
|
- # 计算风险等级统计和问题总数
|
|
|
- risk_stats = {"low": 0, "medium": 0, "high": 0}
|
|
|
- total_issues = 0
|
|
|
-
|
|
|
- for unit_issues in successful_results:
|
|
|
- # 每个unit_issues是一个issues列表
|
|
|
- if unit_issues and isinstance(unit_issues, list):
|
|
|
- total_issues += len(unit_issues)
|
|
|
-
|
|
|
- # 统计每个issue中的风险等级
|
|
|
- for issue in unit_issues:
|
|
|
- if isinstance(issue, dict):
|
|
|
- # issue格式: {issue_id: {risk_summary: {...}}}
|
|
|
- for issue_data in issue.values():
|
|
|
- risk_summary = issue_data.get('risk_summary', {})
|
|
|
- max_risk = risk_summary.get('max_risk_level', '0')
|
|
|
-
|
|
|
- if max_risk in risk_stats:
|
|
|
- risk_stats[max_risk] += 1
|
|
|
- elif max_risk == '0':
|
|
|
- risk_stats['low'] += 1 # 无风险视为低风险
|
|
|
-
|
|
|
- return {
|
|
|
- 'risk_stats': risk_stats,
|
|
|
- 'total_reviewed': len(successful_results),
|
|
|
- 'total_issues': total_issues
|
|
|
- }
|
|
|
- except (ZeroDivisionError, KeyError, TypeError) as e:
|
|
|
- logger.error(f"结果汇总失败: {str(e)}")
|
|
|
- return {}
|
|
|
-
|
|
|
- def _check_ai_review_result(self, state: AIReviewState) -> str:
|
|
|
- """
|
|
|
- 检查AI审查结果状态
|
|
|
-
|
|
|
- Args:
|
|
|
- state: AI审查工作流状态
|
|
|
-
|
|
|
- Returns:
|
|
|
- str: 状态标识 ("success" 或 "error")
|
|
|
-
|
|
|
- Note:
|
|
|
- 根据状态中是否存在错误信息来判断审查是否成功
|
|
|
- """
|
|
|
- if state.get("error_message"):
|
|
|
- return "error"
|
|
|
- return "success"
|
|
|
+ # 默认返回前N个单元
|
|
|
+ return chunks[:self.max_review_units]
|
|
|
|