"""Shared helpers for building prompts and (for MockLLMProvider) recovering the structured evidence a prompt was built from, without a real model call. Every analysis task embeds its evidence as a fenced JSON block via `build_user_prompt`, so this stays consistent across all six tasks and lets the mock provider parse it back out deterministically. """ from __future__ import annotations import json from typing import Any _EVIDENCE_FENCE_START = "```json evidence" _EVIDENCE_FENCE_END = "```" def build_user_prompt(instructions: str, evidence: dict[str, Any]) -> str: evidence_json = json.dumps(evidence, indent=2, default=str) return ( f"{instructions}\n\n" "Evidence (only use what is provided here - never invent facts not present):\n" f"{_EVIDENCE_FENCE_START}\n{evidence_json}\n{_EVIDENCE_FENCE_END}" ) def extract_evidence_block(user_prompt: str) -> dict[str, Any]: """Recovers the evidence dict embedded by `build_user_prompt`. Used only by MockLLMProvider, which has no model to actually read the prompt.""" start = user_prompt.find(_EVIDENCE_FENCE_START) if start == -1: return {} start += len(_EVIDENCE_FENCE_START) end = user_prompt.find(_EVIDENCE_FENCE_END, start) if end == -1: return {} try: return json.loads(user_prompt[start:end].strip()) except json.JSONDecodeError: return {}