"""LLM 回复 JSON 解析工具。""" from __future__ import annotations import json import re from typing import Any from app.gap_script_demand.exceptions import GapScriptDemandError def strip_markdown_fence(text: str) -> str: raw = text.strip() if not raw.startswith("```"): return raw raw = re.sub(r"^```(?:json)?\s*", "", raw, flags=re.IGNORECASE) raw = re.sub(r"\s*```\s*$", "", raw) return raw.strip() def extract_json_object(text: str) -> dict[str, Any]: """从 LLM 回复中提取第一个 JSON 对象(容忍尾部多余文字)。""" raw = strip_markdown_fence(text) decoder = json.JSONDecoder() def _try_decode(candidate: str) -> dict[str, Any] | None: stripped = candidate.strip() if not stripped: return None try: parsed = json.loads(stripped) if isinstance(parsed, dict): return parsed except json.JSONDecodeError: pass try: parsed, _end = decoder.raw_decode(stripped) if isinstance(parsed, dict): return parsed except json.JSONDecodeError: pass return None direct = _try_decode(raw) if direct is not None: return direct start = 0 while True: start = raw.find("{", start) if start == -1: break decoded = _try_decode(raw[start:]) if decoded is not None: return decoded start += 1 raise GapScriptDemandError("llm output is not json object")