| import json |
| import re |
| from typing import Any, TypeVar |
|
|
| from pydantic import BaseModel, ValidationError |
|
|
| T = TypeVar("T", bound=BaseModel) |
|
|
|
|
| def safe_json_loads(text: str) -> dict[str, Any]: |
| stripped = text.strip() |
|
|
| |
| try: |
| payload = json.loads(stripped) |
| if isinstance(payload, dict): |
| return payload |
| except json.JSONDecodeError: |
| pass |
|
|
| |
| fence_match = re.search(r"```(?:json)?\s*\n?(.*?)\n?```", stripped, re.DOTALL) |
| if fence_match: |
| try: |
| payload = json.loads(fence_match.group(1).strip()) |
| if isinstance(payload, dict): |
| return payload |
| except json.JSONDecodeError: |
| pass |
|
|
| |
| brace_start = stripped.find("{") |
| if brace_start != -1: |
| brace_end = stripped.rfind("}") |
| if brace_end > brace_start: |
| try: |
| payload = json.loads(stripped[brace_start : brace_end + 1]) |
| if isinstance(payload, dict): |
| return payload |
| except json.JSONDecodeError: |
| pass |
|
|
| raise ValueError("Response is not valid JSON — tried direct parse, markdown fences, and brace extraction.") |
|
|
|
|
| def validate_json_plan(payload: dict[str, Any], model: type[T]) -> T: |
| try: |
| return model.model_validate(payload) |
| except ValidationError as exc: |
| raise ValueError("Invalid JSON analysis plan.") from exc |
|
|