from __future__ import annotations import json import re from collections.abc import Callable from pydantic import ValidationError from core.schemas import SimulationResult RetryFn = Callable[[str, str], str] def extract_json(raw: str) -> str: stripped = raw.strip() fence_match = re.search(r"```(?:json)?\s*(.*?)\s*```", stripped, re.DOTALL | re.IGNORECASE) if fence_match: return fence_match.group(1).strip() start = stripped.find("{") end = stripped.rfind("}") if start == -1 or end == -1 or end < start: raise json.JSONDecodeError("No JSON object found", stripped, 0) return stripped[start : end + 1] def validate_result( raw_json: str, persona_id: str, retry_fn: RetryFn | None = None, max_retries: int = 2, ) -> SimulationResult | None: raw = raw_json for attempt in range(max_retries + 1): try: return SimulationResult.model_validate_json(extract_json(raw)) except (json.JSONDecodeError, ValidationError) as exc: if retry_fn is None or attempt >= max_retries: return None raw = retry_fn(persona_id, str(exc)) return None def validate_swarm( raw_results: list[tuple[str, str]], retry_fn: RetryFn | None = None, ) -> tuple[list[SimulationResult], int]: valid: list[SimulationResult] = [] failures = 0 for persona_id, raw in raw_results: result = validate_result(raw, persona_id, retry_fn) if result is None: failures += 1 else: valid.append(result) return valid, failures