utils.py 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. import json
  2. import re
  3. def parse_json_from_response(content):
  4. """
  5. Attempts to parse JSON from a string, handling code blocks if present.
  6. Also handles common LLM JSON errors like unescaped quotes.
  7. """
  8. try:
  9. content = content.strip()
  10. # 1. Try to extract JSON from markdown code blocks
  11. json_match = re.search(r"```(?:json)?\s*([\s\S]*?)\s*```", content)
  12. if json_match:
  13. content = json_match.group(1)
  14. else:
  15. # 2. If no code blocks, try to find the first outer-most JSON object or array
  16. # Find the first '{' or '['
  17. start_idx = -1
  18. end_idx = -1
  19. stack = []
  20. for i, char in enumerate(content):
  21. if char in '{[':
  22. if start_idx == -1:
  23. start_idx = i
  24. stack.append(char)
  25. elif char in '}]':
  26. if stack:
  27. last = stack[-1]
  28. if (last == '{' and char == '}') or (last == '[' and char == ']'):
  29. stack.pop()
  30. if not stack:
  31. end_idx = i + 1
  32. break
  33. if start_idx != -1 and end_idx != -1:
  34. content = content[start_idx:end_idx]
  35. return json.loads(content)
  36. except json.JSONDecodeError as e:
  37. print(f"Standard JSON parse failed: {e}. Attempting cleanup...")
  38. try:
  39. import dirtyjson
  40. return dirtyjson.loads(content)
  41. except Exception:
  42. pass
  43. # Cleanup: remove trailing commas, comments
  44. try:
  45. # Remove single-line comments // ...
  46. content = re.sub(r'//.*', '', content)
  47. # Remove trailing commas before } or ]
  48. content = re.sub(r',(\s*[}\]])', r'\1', content)
  49. return json.loads(content)
  50. except Exception:
  51. pass
  52. print(f"Failed to parse JSON content: {content[:200]}...")
  53. return None