增强AI服务的JSON解析能力,添加对jsonrepair库的支持以处理不完整或格式错误的JSON,改进字段提取逻辑以允许部分字段为空,提升数据提取的容错性和准确性。

This commit is contained in:
python 2025-12-09 14:13:07 +08:00
parent eaa384cf7e
commit 7c30e59328
2 changed files with 194 additions and 16 deletions

View File

@ -7,4 +7,5 @@ flasgger==0.9.7.1
python-docx==1.1.0 python-docx==1.1.0
minio==7.2.3 minio==7.2.3
openpyxl==3.1.2 openpyxl==3.1.2
jsonrepair==0.27.0

View File

@ -9,6 +9,14 @@ import requests
import json import json
from typing import Dict, List, Optional from typing import Dict, List, Optional
# 尝试导入jsonrepair库如果不可用则使用备用方案
try:
import jsonrepair
JSONREPAIR_AVAILABLE = True
except ImportError:
JSONREPAIR_AVAILABLE = False
print("[AI服务] 警告: jsonrepair库未安装将使用基础JSON修复功能。建议运行: pip install jsonrepair")
class AIService: class AIService:
"""AI服务类""" """AI服务类"""
@ -287,7 +295,7 @@ class AIService:
# 清理后的内容前500字符 # 清理后的内容前500字符
print(f"[AI服务] 清理后的内容前500字符: {content[:500]}") print(f"[AI服务] 清理后的内容前500字符: {content[:500]}")
# 尝试解析JSON # 尝试解析JSON(使用增强的修复机制)
extracted_data = self._extract_json_from_text(content) extracted_data = self._extract_json_from_text(content)
if extracted_data: if extracted_data:
print(f"[AI服务] JSON解析成功提取到 {len(extracted_data)} 个字段") print(f"[AI服务] JSON解析成功提取到 {len(extracted_data)} 个字段")
@ -311,11 +319,17 @@ class AIService:
for key in ['target_name', 'target_gender', 'target_age', 'target_date_of_birth', 'target_organization', 'target_position']: for key in ['target_name', 'target_gender', 'target_age', 'target_date_of_birth', 'target_organization', 'target_position']:
if key in normalized_data: if key in normalized_data:
print(f"[AI服务] 后处理后 {key} = '{normalized_data[key]}'") print(f"[AI服务] 后处理后 {key} = '{normalized_data[key]}'")
return normalized_data # 即使提取的字段不完整,也返回结果(更宽容的处理)
if any(v for v in normalized_data.values() if v): # 至少有一个非空字段
print(f"[AI服务] 返回提取的数据(包含 {sum(1 for v in normalized_data.values() if v)} 个非空字段)")
return normalized_data
else:
print(f"[AI服务] 警告:提取的数据全部为空,但继续返回(允许部分字段为空)")
return normalized_data
# 如果无法提取JSON记录错误 # 如果无法提取JSON记录错误但尝试更宽容的处理
print(f"[AI服务] 警告无法从内容中提取JSON尝试备用解析方法") print(f"[AI服务] 警告:无法从内容中提取完整JSON尝试备用解析方法")
print(f"[AI服务] 完整内容: {content}") print(f"[AI服务] 清理后的内容前500字符: {content[:500]}")
# 尝试从文本中提取 # 尝试从文本中提取
parsed_data = self._parse_text_response(content, output_fields) parsed_data = self._parse_text_response(content, output_fields)
@ -323,8 +337,34 @@ class AIService:
print(f"[AI服务] 使用备用方法解析成功,提取到 {len(parsed_data)} 个字段") print(f"[AI服务] 使用备用方法解析成功,提取到 {len(parsed_data)} 个字段")
return parsed_data return parsed_data
# 如果所有方法都失败,抛出异常 # 如果所有方法都失败,尝试最后一次修复尝试
raise Exception(f"无法从API返回内容中提取JSON数据。原始内容长度: {len(raw_content)}, 清理后内容长度: {len(content)}。请检查API返回的内容格式是否正确。") print(f"[AI服务] 所有解析方法都失败,尝试最后一次修复...")
# 尝试使用jsonrepair如果可用进行最后修复
if JSONREPAIR_AVAILABLE:
try:
repaired_content = jsonrepair.repair_json(content)
if repaired_content:
try:
extracted_data = json.loads(repaired_content)
if extracted_data and isinstance(extracted_data, dict):
print(f"[AI服务] 使用jsonrepair最后修复成功提取到 {len(extracted_data)} 个字段")
normalized_data = self._normalize_field_names(extracted_data, output_fields)
normalized_data = self._normalize_date_formats(normalized_data, output_fields)
normalized_data = self._post_process_inferred_fields(normalized_data, output_fields)
return normalized_data
except json.JSONDecodeError:
pass
except Exception as e:
print(f"[AI服务] jsonrepair最后修复也失败: {e}")
# 如果所有方法都失败,返回空字典而不是抛出异常(更宽容)
# 这样至少不会导致整个调用失败,前端可以显示部分结果
print(f"[AI服务] 警告无法从API返回内容中提取JSON数据返回空结果。原始内容长度: {len(raw_content)}, 清理后内容长度: {len(content)}")
print(f"[AI服务] 完整内容: {content}")
# 返回一个包含所有输出字段的空字典,而不是抛出异常
empty_result = {field['field_code']: '' for field in output_fields}
print(f"[AI服务] 返回空结果(包含 {len(empty_result)} 个字段,全部为空)")
return empty_result
else: else:
raise Exception("API返回格式异常未找到choices字段或choices为空") raise Exception("API返回格式异常未找到choices字段或choices为空")
@ -349,7 +389,15 @@ class AIService:
return json.loads(json_str) return json.loads(json_str)
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
print(f"[AI服务] JSON解析失败代码块: {e}") print(f"[AI服务] JSON解析失败代码块: {e}")
# 尝试修复后再次解析 # 尝试使用jsonrepair修复如果可用
if JSONREPAIR_AVAILABLE:
try:
repaired = jsonrepair.repair_json(json_str)
if repaired:
return json.loads(repaired)
except Exception as repair_error:
print(f"[AI服务] jsonrepair修复失败: {repair_error}")
# 尝试基础修复后再次解析
json_str = self._fix_json_string(json_str) json_str = self._fix_json_string(json_str)
try: try:
return json.loads(json_str) return json.loads(json_str)
@ -367,7 +415,15 @@ class AIService:
return json.loads(json_str) return json.loads(json_str)
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
print(f"[AI服务] JSON解析失败代码块: {e}") print(f"[AI服务] JSON解析失败代码块: {e}")
# 尝试修复后再次解析 # 尝试使用jsonrepair修复如果可用
if JSONREPAIR_AVAILABLE:
try:
repaired = jsonrepair.repair_json(json_str)
if repaired:
return json.loads(repaired)
except Exception as repair_error:
print(f"[AI服务] jsonrepair修复失败: {repair_error}")
# 尝试基础修复后再次解析
json_str = self._fix_json_string(json_str) json_str = self._fix_json_string(json_str)
try: try:
return json.loads(json_str) return json.loads(json_str)
@ -380,7 +436,15 @@ class AIService:
return json.loads(cleaned_text) return json.loads(cleaned_text)
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
print(f"[AI服务] JSON解析失败直接解析: {e}") print(f"[AI服务] JSON解析失败直接解析: {e}")
# 尝试修复后再次解析 # 尝试使用jsonrepair修复如果可用
if JSONREPAIR_AVAILABLE:
try:
repaired = jsonrepair.repair_json(cleaned_text)
if repaired:
return json.loads(repaired)
except Exception as repair_error:
print(f"[AI服务] jsonrepair修复失败: {repair_error}")
# 尝试基础修复后再次解析
fixed_text = self._fix_json_string(cleaned_text) fixed_text = self._fix_json_string(cleaned_text)
try: try:
return json.loads(fixed_text) return json.loads(fixed_text)
@ -424,6 +488,90 @@ class AIService:
except json.JSONDecodeError: except json.JSONDecodeError:
pass pass
# 方法5: 如果所有方法都失败尝试部分提取即使JSON不完整
partial_data = self._extract_partial_json(text)
if partial_data:
print(f"[AI服务] 使用部分JSON提取提取到 {len(partial_data)} 个字段")
return partial_data
return None
def _extract_partial_json(self, text: str) -> Optional[Dict]:
"""
从可能不完整的JSON中提取可用字段
即使JSON格式不完整也尝试提取能够解析的字段
"""
result = {}
# 尝试找到JSON对象的开始位置
start_idx = text.find('{')
if start_idx == -1:
return None
# 提取从{开始的内容
json_content = text[start_idx:]
# 尝试使用正则表达式提取键值对
# 匹配模式: "key": "value" 或 "key": value
# 处理各种可能的格式错误
# 模式1: "key": "value"
pattern1 = r'"([^"]+?)"\s*:\s*"([^"]*?)"'
matches1 = re.finditer(pattern1, json_content, re.DOTALL)
for match in matches1:
key = match.group(1)
value = match.group(2)
# 清理键名(移除可能的转义字符)
key = key.replace('\\"', '').strip()
if key:
# 处理以_开头的字段名如_professional_rank -> professional_rank
if key.startswith('_') and len(key) > 1:
key = key[1:]
if key not in result: # 避免覆盖已有值
result[key] = value
# 模式2: "key": value (非字符串值,如数字、布尔值)
pattern2 = r'"([^"]+?)"\s*:\s*([^,}\]]+?)(?=\s*[,}\]])'
matches2 = re.finditer(pattern2, json_content, re.DOTALL)
for match in matches2:
key = match.group(1).strip()
value_str = match.group(2).strip()
# 清理键名
key = key.replace('\\"', '').strip()
if key and key not in result: # 避免覆盖已有值
# 处理以_开头的字段名
original_key = key
if key.startswith('_') and len(key) > 1:
key = key[1:]
# 尝试解析值
if value_str.lower() in ('true', 'false'):
result[key] = value_str.lower() == 'true'
elif value_str.lower() == 'null':
result[key] = None
elif value_str.replace('.', '', 1).replace('-', '', 1).isdigit():
try:
if '.' in value_str:
result[key] = float(value_str)
else:
result[key] = int(value_str)
except ValueError:
result[key] = value_str
else:
result[key] = value_str
# 模式3: 处理字段名缺少引号的情况(如 key: "value"
pattern3 = r'([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*"([^"]*?)"'
matches3 = re.finditer(pattern3, json_content, re.DOTALL)
for match in matches3:
key = match.group(1).strip()
value = match.group(2)
if key and key not in result: # 避免覆盖已有值
result[key] = value
# 如果提取到了字段,返回结果
if result:
return result
return None return None
def _clean_json_string(self, json_str: str) -> str: def _clean_json_string(self, json_str: str) -> str:
@ -445,29 +593,58 @@ class AIService:
def _fix_json_string(self, json_str: str) -> str: def _fix_json_string(self, json_str: str) -> str:
""" """
尝试修复常见的JSON格式错误 尝试修复常见的JSON格式错误
增强版处理反斜杠控制字符不完整JSON等问题
""" """
# 移除末尾的逗号(在 } 或 ] 之前) # 首先尝试使用jsonrepair库如果可用
if JSONREPAIR_AVAILABLE:
try:
repaired = jsonrepair.repair_json(json_str)
if repaired and repaired != json_str:
print(f"[AI服务] 使用jsonrepair修复JSON成功")
return repaired
except Exception as e:
print(f"[AI服务] jsonrepair修复失败: {e},使用基础修复方法")
# 基础修复方法
# 1. 移除控制字符(除了换行符、制表符等)
json_str = re.sub(r'[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]', '', json_str)
# 2. 修复字段名中的反斜杠转义问题(如 "_professional_rank\" -> "_professional_rank"
# 处理字段名前的反斜杠
json_str = re.sub(r'\\"([^"]+?)\\":', r'"\1":', json_str)
json_str = re.sub(r'\\"([^"]+?)\\":', r'"\1":', json_str) # 再次处理,确保修复所有情况
# 3. 修复字段名缺少开头引号的问题(如 _professional_rank" -> "_professional_rank"
json_str = re.sub(r'([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)"\s*:', r'\1"\2":', json_str)
# 4. 修复字段名缺少结尾引号的问题(如 "_professional_rank -> "_professional_rank"
json_str = re.sub(r'"([^"]+?)\s*:\s*"', r'"\1": "', json_str)
# 5. 移除末尾的逗号(在 } 或 ] 之前)
json_str = re.sub(r',\s*}', '}', json_str) json_str = re.sub(r',\s*}', '}', json_str)
json_str = re.sub(r',\s*]', ']', json_str) json_str = re.sub(r',\s*]', ']', json_str)
# 修复字段名中的错误(如 .target_gender -> target_gender # 6. 修复字段名中的错误(如 .target_gender -> target_gender
# 处理前导点和尾随空格 # 处理前导点和尾随空格
json_str = re.sub(r'["\']\s*\.([^"\']+?)\s*["\']\s*:', r'"\1":', json_str) json_str = re.sub(r'["\']\s*\.([^"\']+?)\s*["\']\s*:', r'"\1":', json_str)
json_str = re.sub(r'["\']\.([^"\']+?)["\']\s*:', r'"\1":', json_str) json_str = re.sub(r'["\']\.([^"\']+?)["\']\s*:', r'"\1":', json_str)
# 修复字段名中的空格(如 "target name" -> "target_name" # 7. 修复字段名中的空格(如 "target name" -> "target_name"
json_str = re.sub(r'["\']([^"\']+?)\s+([^"\']+?)["\']\s*:', r'"\1_\2":', json_str) json_str = re.sub(r'["\']([^"\']+?)\s+([^"\']+?)["\']\s*:', r'"\1_\2":', json_str)
# 修复字段名中的尾随空格(如 "target_gender " -> "target_gender" # 8. 修复字段名中的尾随空格(如 "target_gender " -> "target_gender"
json_str = re.sub(r'["\']([^"\']+?)\s+["\']\s*:', r'"\1":', json_str) json_str = re.sub(r'["\']([^"\']+?)\s+["\']\s*:', r'"\1":', json_str)
# 修复字段名中的前导空格(如 " target_gender" -> "target_gender" # 9. 修复字段名中的前导空格(如 " target_gender" -> "target_gender"
json_str = re.sub(r'["\']\s+([^"\']+?)["\']\s*:', r'"\1":', json_str) json_str = re.sub(r'["\']\s+([^"\']+?)["\']\s*:', r'"\1":', json_str)
# 尝试修复未加引号的字段名(但要避免破坏字符串值) # 10. 尝试修复未加引号的字段名(但要避免破坏字符串值)
# 只修复在冒号前的未加引号的标识符 # 只修复在冒号前的未加引号的标识符
json_str = re.sub(r'([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*:', r'\1"\2":', json_str) json_str = re.sub(r'([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*:', r'\1"\2":', json_str)
# 11. 修复字符串值中的转义问题(但保留必要的转义)
# 这里要小心,不要破坏合法的转义序列
return json_str return json_str
def _normalize_field_names(self, extracted_data: Dict, output_fields: List[Dict]) -> Dict: def _normalize_field_names(self, extracted_data: Dict, output_fields: List[Dict]) -> Dict: