llm_json.py 1.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. """LLM 回复 JSON 解析工具。"""
  2. from __future__ import annotations
  3. import json
  4. import re
  5. from typing import Any
  6. from app.gap_script_demand.exceptions import GapScriptDemandError
  7. def strip_markdown_fence(text: str) -> str:
  8. raw = text.strip()
  9. if not raw.startswith("```"):
  10. return raw
  11. raw = re.sub(r"^```(?:json)?\s*", "", raw, flags=re.IGNORECASE)
  12. raw = re.sub(r"\s*```\s*$", "", raw)
  13. return raw.strip()
  14. def extract_json_object(text: str) -> dict[str, Any]:
  15. """从 LLM 回复中提取第一个 JSON 对象(容忍尾部多余文字)。"""
  16. raw = strip_markdown_fence(text)
  17. decoder = json.JSONDecoder()
  18. def _try_decode(candidate: str) -> dict[str, Any] | None:
  19. stripped = candidate.strip()
  20. if not stripped:
  21. return None
  22. try:
  23. parsed = json.loads(stripped)
  24. if isinstance(parsed, dict):
  25. return parsed
  26. except json.JSONDecodeError:
  27. pass
  28. try:
  29. parsed, _end = decoder.raw_decode(stripped)
  30. if isinstance(parsed, dict):
  31. return parsed
  32. except json.JSONDecodeError:
  33. pass
  34. return None
  35. direct = _try_decode(raw)
  36. if direct is not None:
  37. return direct
  38. start = 0
  39. while True:
  40. start = raw.find("{", start)
  41. if start == -1:
  42. break
  43. decoded = _try_decode(raw[start:])
  44. if decoded is not None:
  45. return decoded
  46. start += 1
  47. raise GapScriptDemandError("llm output is not json object")