增强AI服务的JSON解析能力,添加对jsonrepair库的支持以处理不完整或格式错误的JSON,改进字段提取逻辑以允许部分字段为空,提升数据提取的容错性和准确性。
This commit is contained in:
parent
eaa384cf7e
commit
7c30e59328
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user