fix: improve JSON parsing robustness in chat_json and plan_outline fallback
- Add multi-strategy JSON parsing in llm_client.chat_json (control char removal, nested JSON extraction, single-quote fix, markdown code block handling) - Add ValueError-specific handler in plan_outline to extract JSON from error messages before falling back
This commit is contained in:
parent
db5b958e50
commit
632ed64829
|
|
@ -1392,6 +1392,43 @@ class ReportAgent:
|
|||
logger.info(t('report.outlinePlanDone', count=len(sections)))
|
||||
return outline
|
||||
|
||||
except ValueError as e:
|
||||
# JSON 解析失败,尝试用更宽松的方式提取 JSON
|
||||
logger.warning(t('report.outlinePlanFailed', error=str(e)))
|
||||
try:
|
||||
# 尝试从错误消息中提取 JSON
|
||||
error_msg = str(e)
|
||||
json_start = error_msg.find('{')
|
||||
json_end = error_msg.rfind('}') + 1
|
||||
if json_start >= 0 and json_end > json_start:
|
||||
json_str = error_msg[json_start:json_end]
|
||||
response = json.loads(json_str)
|
||||
sections = []
|
||||
for section_data in response.get("sections", []):
|
||||
sections.append(ReportSection(
|
||||
title=section_data.get("title", ""),
|
||||
content=""
|
||||
))
|
||||
outline = ReportOutline(
|
||||
title=response.get("title", "模拟分析报告"),
|
||||
summary=response.get("summary", ""),
|
||||
sections=sections
|
||||
)
|
||||
outline = self._ensure_prediction_outline(outline)
|
||||
logger.info(t('report.outlinePlanDone', count=len(sections)))
|
||||
return outline
|
||||
except Exception:
|
||||
pass
|
||||
# Fallback 默认大纲
|
||||
return self._ensure_prediction_outline(ReportOutline(
|
||||
title="未来预测报告",
|
||||
summary="基于模拟预测的未来趋势与风险分析",
|
||||
sections=[
|
||||
ReportSection(title="预测场景与核心发现"),
|
||||
ReportSection(title="人群行为预测分析"),
|
||||
ReportSection(title="趋势展望与风险提示")
|
||||
]
|
||||
))
|
||||
except Exception as e:
|
||||
logger.error(t('report.outlinePlanFailed', error=str(e)))
|
||||
# 返回默认大纲(3个章节,作为fallback)
|
||||
|
|
|
|||
|
|
@ -101,8 +101,51 @@ class LLMClient:
|
|||
cleaned_response = re.sub(r'\n?```\s*$', '', cleaned_response)
|
||||
cleaned_response = cleaned_response.strip()
|
||||
|
||||
# 尝试直接解析
|
||||
try:
|
||||
return json.loads(cleaned_response)
|
||||
except json.JSONDecodeError:
|
||||
raise ValueError(f"LLM返回的JSON格式无效: {cleaned_response}")
|
||||
pass
|
||||
|
||||
# 策略2: 尝试提取 JSON 对象(可能嵌套在其他文本中)
|
||||
json_patterns = [
|
||||
r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', # 简单嵌套
|
||||
r'(\{"[^"]*"\s*:\s*)', # JSON 开始位置
|
||||
]
|
||||
for pattern in json_patterns:
|
||||
match = re.search(pattern, cleaned_response, re.DOTALL)
|
||||
if match:
|
||||
try:
|
||||
# 尝试从匹配位置开始解析
|
||||
candidate = match.group(0) if match.group(0).startswith('{') else match.group(1)
|
||||
# 向后扩展直到找到完整对象
|
||||
for end in range(len(candidate), len(cleaned_response) + 1):
|
||||
try:
|
||||
result = json.loads(cleaned_response[match.start():end])
|
||||
return result
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 策略3: 尝试修复常见 JSON 问题
|
||||
fixed = cleaned_response
|
||||
# 移除控制字符
|
||||
fixed = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', fixed)
|
||||
# 修复单引号
|
||||
fixed = re.sub(r"'", '"', fixed)
|
||||
try:
|
||||
return json.loads(fixed)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
# 策略4: 查找 ```json ... ``` 块
|
||||
code_block_match = re.search(r'```json\s*\n?(.*?)\n?```', cleaned_response, re.DOTALL | re.IGNORECASE)
|
||||
if code_block_match:
|
||||
try:
|
||||
return json.loads(code_block_match.group(1).strip())
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
raise ValueError(f"LLM返回的JSON格式无效: {cleaned_response[:500]}")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue