kulono / multi-model-verifier
blob · multi_model_verifier/audit.py · python
← filesrepo
multi_model_verifier/audit.pypython
1"""Consensus types for multi-model verification.
2
3ModelAnswer / VerificationAudit / compare_answers extracted from the
4tical-code ortools core so this package has zero external dependencies.
5"""
6from collections import Counter
7from dataclasses import dataclass, field
8from typing import Dict, List, Optional
9import time
10
11
12@dataclass
13class ModelAnswer:
14 """One model's reply in a multi-model comparison."""
15 model_id: str
16 provider_name: str
17 content: str
18 tool_calls: Optional[List[Dict]] = None
19 latency_seconds: float = 0.0
20 error: Optional[str] = None
21
22
23@dataclass
24class VerificationAudit:
25 """All answers + consensus analysis."""
26 prompt: str
27 answers: List[ModelAnswer] = field(default_factory=list)
28 consensus: str = ""
29 divergence_score: float = 0.0 # 0.0 unanimous, 1.0 fully divergent
30 recommendations: List[str] = field(default_factory=list)
31 timestamp: float = field(default_factory=time.time)
32
33 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 False
37 return len(set(contents)) == 1
38
39 def error_count(self) -> int:
40 return sum(1 for a in self.answers if a.error)
41
42 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 None
45
46 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)
64
65
66def 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))
70
71 if not ok:
72 audit.consensus = "All models failed."
73 audit.divergence_score = 1.0
74 audit.recommendations.append("Check provider health - all models returned errors.")
75 return audit
76
77 if len(ok) == 1:
78 audit.consensus = ok[0].content
79 audit.divergence_score = 0.0
80 return audit
81
82 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].content
86 audit.divergence_score = 0.0
87 return audit
88
89 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)].content
92
93 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 audit
98
100%