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:
duwanze 2026-06-15 10:13:53 +08:00
parent db5b958e50
commit 632ed64829
2 changed files with 81 additions and 1 deletions

View File

@ -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

View File

@ -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]}")