# Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca """Provider-to-role assignment for the three committees (all_anthropic, all_openai, mixed_random). ``mixed_random`` draws a balanced, seeded permutation per property (seed = global seed + tx_id) so that the assignment is reproducible and recorded in the transcript. """ from __future__ import annotations import hashlib import random from ..config import SEED, load_config COMMITTEES = ("all_anthropic", "all_openai", "mixed_random") def pair_for_tier(tier: str | None = None) -> dict[str, str]: cfg = load_config("models") tier = tier or cfg.get("default_tier", "mid") pairs = cfg["cost_matched_pairs"] if tier not in pairs: raise KeyError(f"tier {tier!r} not in configs/models.yaml cost_matched_pairs") return dict(pairs[tier]) def committee_models(committee: str, tier: str | None = None) -> list[str]: pair = pair_for_tier(tier) if committee == "all_anthropic": return [pair["anthropic"]] if committee == "all_openai": return [pair["openai"]] if committee == "mixed_random": return [pair["anthropic"], pair["openai"]] raise KeyError(f"unknown committee {committee!r} (expected one of {COMMITTEES})") def assign(roles: list[str], committee: str, seed: int = SEED, tx_id: str = "", tier: str | None = None) -> dict[str, str]: """role id -> model id. Deterministic given (roles, committee, seed, tx_id, tier).""" pair = pair_for_tier(tier) if committee == "all_anthropic": return {r: pair["anthropic"] for r in roles} if committee == "all_openai": return {r: pair["openai"] for r in roles} if committee != "mixed_random": raise KeyError(f"unknown committee {committee!r} (expected one of {COMMITTEES})") n = len(roles) vendors = ["anthropic", "openai"] * (n // 2 + 1) vendors = vendors[:n] # balanced: ceil(n/2) anthropic, floor(n/2) openai before shuffling rng = random.Random(int(hashlib.sha256(f"{seed}|{tx_id}".encode()).hexdigest()[:16], 16)) rng.shuffle(vendors) if n % 2 == 1 and rng.random() < 0.5: # the odd slot goes to either vendor with equal probability vendors = ["openai" if v == "anthropic" else "anthropic" for v in vendors] return {r: pair[v] for r, v in zip(roles, vendors)}