evalstate/diffusers-pr-api
0
1from __future__ import annotations2 3import hashlib4import json5from dataclasses import dataclass6from pathlib import Path7from typing import Any8 9from slop_farmer.data.parquet_io import read_json, write_json10 11HYBRID_REVIEW_CACHE_MANIFEST_FILENAME = "hybrid-review-cache-manifest.json"12HYBRID_REVIEW_CACHE_ENTRIES_FILENAME = "hybrid-review-cache.jsonl"13HYBRID_REVIEW_CACHE_SCHEMA_VERSION = "1.0"14PREPARED_REVIEW_UNIT_SCHEMA_VERSION = "1.0"15 16 17def _canonical_json_bytes(data: Any) -> bytes:18 return json.dumps(19 data,20 ensure_ascii=False,21 separators=(",", ":"),22 sort_keys=True,23 ).encode("utf-8")24 25 26def _normalize_review_item_for_hash(item: dict[str, Any]) -> dict[str, Any]:27 normalized = dict(item)28 filenames = normalized.get("filenames")29 if filenames is not None:30 normalized["filenames"] = sorted(str(filename) for filename in filenames)31 explicit_issue_targets = normalized.get("explicit_issue_targets")32 if explicit_issue_targets is not None:33 normalized["explicit_issue_targets"] = sorted(34 int(target) for target in explicit_issue_targets35 )36 return normalized37 38 39def _normalize_soft_pair_for_hash(pair: dict[str, Any]) -> dict[str, Any]:40 normalized = dict(pair)41 evidence_types = normalized.get("evidence_types")42 if evidence_types is not None:43 normalized["evidence_types"] = sorted(str(value) for value in evidence_types)44 shared_targets = normalized.get("shared_targets")45 if shared_targets is not None:46 normalized["shared_targets"] = sorted(int(target) for target in shared_targets)47 shared_filenames = normalized.get("shared_filenames")48 if shared_filenames is not None:49 normalized["shared_filenames"] = sorted(str(filename) for filename in shared_filenames)50 return normalized51 52 53def _normalize_prepared_review_unit_for_hash(54 prepared_review_unit: dict[str, Any],55) -> dict[str, Any]:56 normalized = dict(prepared_review_unit)57 packet = dict(normalized.get("packet") or {})58 packet["nodes"] = sorted(str(node) for node in packet.get("nodes") or [])59 packet["items"] = sorted(60 (_normalize_review_item_for_hash(dict(item)) for item in packet.get("items") or []),61 key=lambda item: str(item.get("node_id") or ""),62 )63 packet["pair_evidence"] = {64 str(pair): sorted(str(value) for value in values)65 for pair, values in sorted(dict(packet.get("pair_evidence") or {}).items())66 }67 packet["soft_pairs"] = sorted(68 (_normalize_soft_pair_for_hash(dict(pair)) for pair in packet.get("soft_pairs") or []),69 key=lambda pair: (70 str(pair.get("left") or ""),71 str(pair.get("right") or ""),72 ),73 )74 normalized["packet"] = packet75 return normalized76 77 78@dataclass(frozen=True, slots=True)79class HybridReviewSettingsFingerprint:80 llm_max_input_tokens: int81 llm_max_nodes_per_packet: int82 llm_max_soft_pairs_per_packet: int83 llm_max_diff_chars_per_item: int84 llm_max_filenames_per_item: int85 llm_skip_evaluator_above_tokens: int86 llm_overflow_policy: str87 88 @property89 def value(self) -> str:90 return hashlib.sha256(_canonical_json_bytes(self.to_json())).hexdigest()91 92 def to_json(self) -> dict[str, Any]:93 return {94 "llm_max_input_tokens": self.llm_max_input_tokens,95 "llm_max_nodes_per_packet": self.llm_max_nodes_per_packet,96 "llm_max_soft_pairs_per_packet": self.llm_max_soft_pairs_per_packet,97 "llm_max_diff_chars_per_item": self.llm_max_diff_chars_per_item,98 "llm_max_filenames_per_item": self.llm_max_filenames_per_item,99 "llm_skip_evaluator_above_tokens": self.llm_skip_evaluator_above_tokens,100 "llm_overflow_policy": self.llm_overflow_policy,101 }102 103 @classmethod104 def from_json(cls, payload: dict[str, Any]) -> HybridReviewSettingsFingerprint:105 return cls(106 llm_max_input_tokens=int(payload["llm_max_input_tokens"]),107 llm_max_nodes_per_packet=int(payload["llm_max_nodes_per_packet"]),108 llm_max_soft_pairs_per_packet=int(payload["llm_max_soft_pairs_per_packet"]),109 llm_max_diff_chars_per_item=int(payload["llm_max_diff_chars_per_item"]),110 llm_max_filenames_per_item=int(payload["llm_max_filenames_per_item"]),111 llm_skip_evaluator_above_tokens=int(payload["llm_skip_evaluator_above_tokens"]),112 llm_overflow_policy=str(payload["llm_overflow_policy"]),113 )114 115 116@dataclass(frozen=True, slots=True)117class HybridReviewCacheManifest:118 cache_schema_version: str119 prepared_review_unit_schema_version: str120 analyst_prompt_version: str121 evaluator_prompt_version: str122 hybrid_review_settings: HybridReviewSettingsFingerprint123 124 @property125 def hybrid_review_settings_fingerprint(self) -> str:126 return self.hybrid_review_settings.value127 128 def to_json(self) -> dict[str, Any]:129 return {130 "cache_schema_version": self.cache_schema_version,131 "prepared_review_unit_schema_version": self.prepared_review_unit_schema_version,132 "analyst_prompt_version": self.analyst_prompt_version,133 "evaluator_prompt_version": self.evaluator_prompt_version,134 "hybrid_review_settings": self.hybrid_review_settings.to_json(),135 "hybrid_review_settings_fingerprint": self.hybrid_review_settings_fingerprint,136 }137 138 @classmethod139 def from_json(cls, payload: dict[str, Any]) -> HybridReviewCacheManifest:140 return cls(141 cache_schema_version=str(payload["cache_schema_version"]),142 prepared_review_unit_schema_version=str(payload["prepared_review_unit_schema_version"]),143 analyst_prompt_version=str(payload["analyst_prompt_version"]),144 evaluator_prompt_version=str(payload["evaluator_prompt_version"]),145 hybrid_review_settings=HybridReviewSettingsFingerprint.from_json(146 payload["hybrid_review_settings"]147 ),148 )149 150 151@dataclass(frozen=True, slots=True)152class HybridReviewCacheKey:153 cache_schema_version: str154 prepared_review_unit_schema_version: str155 analyst_prompt_version: str156 evaluator_prompt_version: str157 hybrid_review_settings_fingerprint: str158 model: str159 prepared_review_unit_hash: str160 161 def to_json(self) -> dict[str, Any]:162 return {163 "cache_schema_version": self.cache_schema_version,164 "prepared_review_unit_schema_version": self.prepared_review_unit_schema_version,165 "analyst_prompt_version": self.analyst_prompt_version,166 "evaluator_prompt_version": self.evaluator_prompt_version,167 "hybrid_review_settings_fingerprint": self.hybrid_review_settings_fingerprint,168 "model": self.model,169 "prepared_review_unit_hash": self.prepared_review_unit_hash,170 }171 172 @classmethod173 def from_json(cls, payload: dict[str, Any]) -> HybridReviewCacheKey:174 return cls(175 cache_schema_version=str(payload["cache_schema_version"]),176 prepared_review_unit_schema_version=str(payload["prepared_review_unit_schema_version"]),177 analyst_prompt_version=str(payload["analyst_prompt_version"]),178 evaluator_prompt_version=str(payload["evaluator_prompt_version"]),179 hybrid_review_settings_fingerprint=str(payload["hybrid_review_settings_fingerprint"]),180 model=str(payload["model"]),181 prepared_review_unit_hash=str(payload["prepared_review_unit_hash"]),182 )183 184 185@dataclass(frozen=True, slots=True)186class HybridReviewCacheEntry:187 key: HybridReviewCacheKey188 result: dict[str, Any]189 cached_at: str190 nodes: tuple[str, ...] = ()191 soft_pairs: tuple[str, ...] = ()192 budget: dict[str, int] | None = None193 split: bool = False194 trimmed: bool = False195 aggressively_trimmed: bool = False196 197 def to_json(self) -> dict[str, Any]:198 return {199 "key": self.key.to_json(),200 "result": self.result,201 "cached_at": self.cached_at,202 "nodes": list(self.nodes),203 "soft_pairs": list(self.soft_pairs),204 "budget": self.budget,205 "split": self.split,206 "trimmed": self.trimmed,207 "aggressively_trimmed": self.aggressively_trimmed,208 }209 210 @classmethod211 def from_json(cls, payload: dict[str, Any]) -> HybridReviewCacheEntry:212 return cls(213 key=HybridReviewCacheKey.from_json(payload["key"]),214 result=dict(payload["result"]),215 cached_at=str(payload["cached_at"]),216 nodes=tuple(str(node) for node in payload.get("nodes") or []),217 soft_pairs=tuple(str(pair) for pair in payload.get("soft_pairs") or []),218 budget=(219 None220 if payload.get("budget") is None221 else {str(key): int(value) for key, value in dict(payload["budget"]).items()}222 ),223 split=bool(payload.get("split", False)),224 trimmed=bool(payload.get("trimmed", False)),225 aggressively_trimmed=bool(payload.get("aggressively_trimmed", False)),226 )227 228 229def prepared_review_unit_hash(prepared_review_unit: dict[str, Any]) -> str:230 normalized = _normalize_prepared_review_unit_for_hash(prepared_review_unit)231 return hashlib.sha256(_canonical_json_bytes(normalized)).hexdigest()232 233 234def build_hybrid_review_cache_key(235 *,236 manifest: HybridReviewCacheManifest,237 model: str,238 prepared_review_unit: dict[str, Any],239) -> HybridReviewCacheKey:240 return HybridReviewCacheKey(241 cache_schema_version=manifest.cache_schema_version,242 prepared_review_unit_schema_version=manifest.prepared_review_unit_schema_version,243 analyst_prompt_version=manifest.analyst_prompt_version,244 evaluator_prompt_version=manifest.evaluator_prompt_version,245 hybrid_review_settings_fingerprint=manifest.hybrid_review_settings_fingerprint,246 model=model,247 prepared_review_unit_hash=prepared_review_unit_hash(prepared_review_unit),248 )249 250 251def hybrid_review_cache_dir(snapshot_dir: Path) -> Path:252 return snapshot_dir / "analysis-state"253 254 255class HybridReviewCacheStore:256 def __init__(257 self,258 cache_dir: Path,259 manifest: HybridReviewCacheManifest,260 *,261 enabled: bool = True,262 ) -> None:263 self.cache_dir = cache_dir264 self.manifest = manifest265 self.enabled = enabled266 self.invalidation_reason: str | None = None267 self._entries: dict[HybridReviewCacheKey, HybridReviewCacheEntry] = {}268 self._needs_reset = False269 if self.enabled:270 self._load()271 272 @property273 def manifest_path(self) -> Path:274 return self.cache_dir / HYBRID_REVIEW_CACHE_MANIFEST_FILENAME275 276 @property277 def entries_path(self) -> Path:278 return self.cache_dir / HYBRID_REVIEW_CACHE_ENTRIES_FILENAME279 280 @property281 def has_entries(self) -> bool:282 return bool(self._entries)283 284 def get(self, key: HybridReviewCacheKey) -> HybridReviewCacheEntry | None:285 if not self.enabled:286 return None287 return self._entries.get(key)288 289 def put(self, entry: HybridReviewCacheEntry) -> None:290 if not self.enabled or entry.key in self._entries:291 return292 self._prepare_for_write()293 with self.entries_path.open("a", encoding="utf-8") as handle:294 handle.write(json.dumps(entry.to_json(), sort_keys=True) + "\n")295 self._entries[entry.key] = entry296 297 def _load(self) -> None:298 if not self.manifest_path.exists():299 if self.entries_path.exists():300 self.invalidation_reason = "missing_manifest"301 self._needs_reset = True302 return303 try:304 existing_manifest = HybridReviewCacheManifest.from_json(read_json(self.manifest_path))305 except Exception:306 self.invalidation_reason = "invalid_manifest"307 self._needs_reset = True308 return309 if existing_manifest != self.manifest:310 self.invalidation_reason = "manifest_mismatch"311 self._needs_reset = True312 return313 if not self.entries_path.exists():314 return315 try:316 with self.entries_path.open("r", encoding="utf-8") as handle:317 for line in handle:318 line = line.strip()319 if not line:320 continue321 entry = HybridReviewCacheEntry.from_json(json.loads(line))322 self._entries[entry.key] = entry323 except Exception:324 self._entries.clear()325 self.invalidation_reason = "invalid_entries"326 self._needs_reset = True327 328 def _prepare_for_write(self) -> None:329 self.cache_dir.mkdir(parents=True, exist_ok=True)330 if self._needs_reset:331 self.entries_path.write_text("", encoding="utf-8")332 elif not self.entries_path.exists():333 self.entries_path.touch()334 if self._needs_reset or not self.manifest_path.exists():335 write_json(self.manifest.to_json(), self.manifest_path)336 self._needs_reset = False337 