kulono / multi-model-verifier
blob · multi_model_verifier/audit.py · python
multi_model_verifier/audit.pypython
1"""Consensus types for multi-model verification.23ModelAnswer / VerificationAudit / compare_answers extracted from the4tical-code ortools core so this package has zero external dependencies.5"""6from collections import Counter7from dataclasses import dataclass, field8from typing import Dict, List, Optional9import time101112@dataclass13class ModelAnswer:14 """One model's reply in a multi-model comparison."""15 model_id: str16 provider_name: str17 content: str18 tool_calls: Optional[List[Dict]] = None19 latency_seconds: float = 0.020 error: Optional[str] = None212223@dataclass24class VerificationAudit:25 """All answers + consensus analysis."""26 prompt: str27 answers: List[ModelAnswer] = field(default_factory=list)28 consensus: str = ""29 divergence_score: float = 0.0 # 0.0 unanimous, 1.0 fully divergent30 recommendations: List[str] = field(default_factory=list)31 timestamp: float = field(default_factory=time.time)3233 def unanimous(self) -> bool:34 contents = [a.content.strip() for a in self.answers if not a.error]35 if len(contents) < 2:36 return False37 return len(set(contents)) == 13839 def error_count(self) -> int:40 return sum(1 for a in self.answers if a.error)4142 def best_answer(self) -> Optional[ModelAnswer]:43 ok = [a for a in self.answers if not a.error]44 return min(ok, key=lambda a: a.latency_seconds) if ok else None4546 def to_summary(self) -> str:47 lines = [48 "=== Verification Audit ===",49 f"Prompt: {self.prompt[:100]}",50 f"Models: {len(self.answers)} Errors: {self.error_count()}",51 f"Unanimous: {self.unanimous()} Divergence: {self.divergence_score:.2f}",52 f"Consensus: {self.consensus[:200]}",53 "Per-model:",54 ]55 for a in self.answers:56 status = f"ERROR {a.error[:80]}" if a.error else f"{a.latency_seconds:.1f}s"57 lines.append(f" {a.model_id} ({a.provider_name}): {status}")58 if not a.error:59 lines.append(f" {a.content[:200]}")60 if self.recommendations:61 lines.append("Recommendations:")62 lines.extend(f" - {r}" for r in self.recommendations)63 return "\n".join(lines)646566def compare_answers(answers: List[ModelAnswer]) -> VerificationAudit:67 """Produce a consensus audit from a list of model answers."""68 ok = [a for a in answers if not a.error]69 audit = VerificationAudit(prompt="", answers=list(answers))7071 if not ok:72 audit.consensus = "All models failed."73 audit.divergence_score = 1.074 audit.recommendations.append("Check provider health - all models returned errors.")75 return audit7677 if len(ok) == 1:78 audit.consensus = ok[0].content79 audit.divergence_score = 0.080 return audit8182 contents = [a.content.strip().lower() for a in ok if a.content]83 unique = set(contents)84 if len(unique) == 1:85 audit.consensus = ok[0].content86 audit.divergence_score = 0.087 return audit8889 audit.divergence_score = (len(unique) - 1) / len(ok)90 majority_content, _ = Counter(contents).most_common(1)[0]91 audit.consensus = ok[contents.index(majority_content)].content9293 if audit.divergence_score > 0.5:94 audit.recommendations.append("High divergence - verify manually before acting.")95 elif audit.divergence_score > 0.3:96 audit.recommendations.append("Models disagree - add extra verification.")97 return audit98
100%