feat: store arena leaderboards in relational SQLite tables and JOIN with models

This commit is contained in:
2026-10-16 15:34:50 +00:00
parent d8fa0af825
commit 296fa9c7ad
+189 -46
View File
@@ -35,15 +35,52 @@ def get_db() -> sqlite3.Connection:
def init_db(): def init_db():
db = get_db() db = get_db()
db.executescript(""" db.executescript("""
CREATE TABLE IF NOT EXISTS arena_text_leaderboard (
rank INTEGER,
model_name TEXT PRIMARY KEY,
norm_name TEXT NOT NULL,
clean_name TEXT NOT NULL,
provider TEXT,
score TEXT,
preliminary INTEGER,
rank_spread TEXT,
votes INTEGER,
price TEXT,
context TEXT,
fetched_at TEXT
);
CREATE TABLE IF NOT EXISTS arena_agent_leaderboard (
rank INTEGER,
model_name TEXT PRIMARY KEY,
norm_name TEXT NOT NULL,
clean_name TEXT NOT NULL,
provider TEXT,
net_improvement TEXT,
confirmed_success TEXT,
praise_vs_complaint TEXT,
steerability TEXT,
bash_recovery TEXT,
tool_hallucination TEXT,
sessions INTEGER,
cost_per_task TEXT,
output_tokens_per_task TEXT,
price TEXT,
fetched_at TEXT
);
CREATE TABLE IF NOT EXISTS models ( CREATE TABLE IF NOT EXISTS models (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
provider TEXT NOT NULL, provider TEXT NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
arena_text_score TEXT, raw_base TEXT NOT NULL,
arena_text_rank INTEGER, norm_base TEXT NOT NULL,
arena_agent_score TEXT, clean_base TEXT NOT NULL,
arena_agent_rank INTEGER, text_model_match TEXT,
updated_at TEXT agent_model_match TEXT,
updated_at TEXT,
FOREIGN KEY(text_model_match) REFERENCES arena_text_leaderboard(model_name),
FOREIGN KEY(agent_model_match) REFERENCES arena_agent_leaderboard(model_name)
); );
CREATE TABLE IF NOT EXISTS checks ( CREATE TABLE IF NOT EXISTS checks (
@@ -56,26 +93,97 @@ def init_db():
CREATE INDEX IF NOT EXISTS idx_checks_model ON checks(model_id); CREATE INDEX IF NOT EXISTS idx_checks_model ON checks(model_id);
CREATE INDEX IF NOT EXISTS idx_checks_time ON checks(checked_at); CREATE INDEX IF NOT EXISTS idx_checks_time ON checks(checked_at);
CREATE INDEX IF NOT EXISTS idx_at_norm ON arena_text_leaderboard(norm_name);
CREATE INDEX IF NOT EXISTS idx_ag_norm ON arena_agent_leaderboard(norm_name);
""") """)
db.commit() db.commit()
db.close() db.close()
# ─── Arena data loading ────────────────────────────────────────────────────── # ─── Arena data ingestion into DB ───────────────────────────────────────────
def load_arena_cache(path: str) -> dict[str, dict]: def ingest_arena_tables(db: sqlite3.Connection):
"""Возвращает dict {normalized_model_name: {rank, score}} из кеш-файла арены.""" """Импортирует сырые JSON кеши арены прямо в реляционные таблицы SQLite."""
if not os.path.exists(path): now = datetime.now(timezone.utc).isoformat()
return {}
try: if os.path.exists(ARENA_CACHE_TEXT):
with open(path) as f: try:
data = json.load(f) with open(ARENA_CACHE_TEXT) as f:
result = {} tdata = json.load(f)
for m in data.get("models", []): for m in tdata.get("models", []):
result[m["model"].lower()] = m raw_m = m["model"]
return result m_only = raw_m.split(" · ")[0]
except Exception: m_only = re.sub(r"\s+(Google|Anthropic|OpenAI|Meta|Nvidia|Mistral|DeepSeek|Alibaba)$", "", m_only, flags=re.I)
return {} clean = re.sub(r"\s*\([^)]+\)", "", m_only)
db.execute("""
INSERT INTO arena_text_leaderboard (
rank, model_name, norm_name, clean_name, provider, score,
preliminary, rank_spread, votes, price, context, fetched_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(model_name) DO UPDATE SET
rank = excluded.rank,
norm_name = excluded.norm_name,
clean_name = excluded.clean_name,
provider = excluded.provider,
score = excluded.score,
preliminary = excluded.preliminary,
rank_spread = excluded.rank_spread,
votes = excluded.votes,
price = excluded.price,
context = excluded.context,
fetched_at = excluded.fetched_at
""", (
m.get("rank"), raw_m, _norm(m_only), _norm(clean),
m.get("provider"), m.get("score"), int(bool(m.get("preliminary"))),
m.get("rank_spread"), m.get("votes"), m.get("price"), m.get("context"), now
))
except Exception as e:
print(f"[ingest] text leaderboard error: {e}")
if os.path.exists(ARENA_CACHE_AGENT):
try:
with open(ARENA_CACHE_AGENT) as f:
adata = json.load(f)
for m in adata.get("models", []):
raw_m = m["model"]
m_only = raw_m.split(" · ")[0]
m_only = re.sub(r"\s+(Google|Anthropic|OpenAI|Meta|Nvidia|Mistral|DeepSeek|Alibaba)$", "", m_only, flags=re.I)
clean = re.sub(r"\s*\([^)]+\)", "", m_only)
db.execute("""
INSERT INTO arena_agent_leaderboard (
rank, model_name, norm_name, clean_name, provider, net_improvement,
confirmed_success, praise_vs_complaint, steerability, bash_recovery,
tool_hallucination, sessions, cost_per_task, output_tokens_per_task, price, fetched_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(model_name) DO UPDATE SET
rank = excluded.rank,
norm_name = excluded.norm_name,
clean_name = excluded.clean_name,
provider = excluded.provider,
net_improvement = excluded.net_improvement,
confirmed_success = excluded.confirmed_success,
praise_vs_complaint = excluded.praise_vs_complaint,
steerability = excluded.steerability,
bash_recovery = excluded.bash_recovery,
tool_hallucination = excluded.tool_hallucination,
sessions = excluded.sessions,
cost_per_task = excluded.cost_per_task,
output_tokens_per_task = excluded.output_tokens_per_task,
price = excluded.price,
fetched_at = excluded.fetched_at
""", (
m.get("rank"), raw_m, _norm(m_only), _norm(clean),
m.get("provider"), m.get("net_improvement"), m.get("confirmed_success"),
m.get("praise_vs_complaint"), m.get("steerability"), m.get("bash_recovery"),
m.get("tool_hallucination"), m.get("sessions"), m.get("cost_per_task"),
m.get("output_tokens_per_task"), m.get("price"), now
))
except Exception as e:
print(f"[ingest] agent leaderboard error: {e}")
db.commit()
# ─── OmniRoute helpers ──────────────────────────────────────────────────────── # ─── OmniRoute helpers ────────────────────────────────────────────────────────
@@ -153,17 +261,39 @@ def arena_match(model_id: str, arena: dict[str, dict]) -> Optional[dict]:
# ─── model sync ────────────────────────────────────────────────────────────── # ─── model sync ──────────────────────────────────────────────────────────────
def sync_models(): def sync_models():
"""Обновить список моделей в DB из OmniRoute + арены.""" """Обновить список моделей в DB из OmniRoute + найти связи с таблицами арены."""
db = get_db()
ingest_arena_tables(db)
try: try:
omni = fetch_omni_models() omni = fetch_omni_models()
except Exception as e: except Exception as e:
print(f"[sync] OmniRoute error: {e}") print(f"[sync] OmniRoute error: {e}")
db.close()
return return
arena_text = load_arena_cache(ARENA_CACHE_TEXT) # Загружаем нормализованные имена из таблиц арены для сопоставления
arena_agent = load_arena_cache(ARENA_CACHE_AGENT) text_rows = db.execute("SELECT model_name, norm_name, clean_name FROM arena_text_leaderboard").fetchall()
agent_rows = db.execute("SELECT model_name, norm_name, clean_name FROM arena_agent_leaderboard").fetchall()
def find_match(base: str, cleaned: str, rows) -> Optional[str]:
bn = _norm(base)
for r in rows:
if r["norm_name"] == bn or r["clean_name"] == bn:
return r["model_name"]
cn = _norm(cleaned)
for r in rows:
if r["clean_name"] == cn:
return r["model_name"]
for candidate in (base, cleaned):
gk = _norm_keep(candidate)
if len(gk) >= 10 and (re.search(r"\d", gk) or "-" in gk):
for r in rows:
am = _norm_keep(r["model_name"])
if gk == am or am.endswith("-" + gk) or ("-" + gk) in am or (gk in am and len(gk) >= 12):
return r["model_name"]
return None
db = get_db()
now = datetime.now(timezone.utc).isoformat() now = datetime.now(timezone.utc).isoformat()
for m in omni: for m in omni:
@@ -175,36 +305,31 @@ def sync_models():
continue continue
provider = mid.split("/")[0] provider = mid.split("/")[0]
name = mid raw = mid.split("/")[-1].lower()
base = re.sub(r"-(it|preview)(:[^:]*)?$", "", raw)
base = re.sub(r"(:[^:]*)$", "", base)
cleaned = re.sub(r"-(thinking|tiered|disabled|low|medium|high|xhigh|max|ultra|mini)", "", base)
cleaned = re.sub(r"-(thinking|tiered|disabled|low|medium|high|xhigh|max|ultra|mini)", "", cleaned)
ta = arena_match(mid, arena_text) text_match = find_match(base, cleaned, text_rows)
ga = arena_match(mid, arena_agent) agent_match = find_match(base, cleaned, agent_rows)
# у agent-борда главная метрика — net_improvement, а не score
agent_score = ga.get("net_improvement") or ga.get("score") if ga else None
db.execute(""" db.execute("""
INSERT INTO models (id, provider, name, arena_text_score, arena_text_rank, INSERT INTO models (id, provider, name, raw_base, norm_base, clean_base,
arena_agent_score, arena_agent_rank, updated_at) text_model_match, agent_model_match, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET ON CONFLICT(id) DO UPDATE SET
arena_text_score = excluded.arena_text_score, text_model_match = excluded.text_model_match,
arena_text_rank = excluded.arena_text_rank, agent_model_match = excluded.agent_model_match,
arena_agent_score = excluded.arena_agent_score,
arena_agent_rank = excluded.arena_agent_rank,
updated_at = excluded.updated_at updated_at = excluded.updated_at
""", ( """, (
mid, provider, name, mid, provider, mid, base, _norm(base), _norm(cleaned),
ta["score"] if ta else None, text_match, agent_match, now
ta["rank"] if ta else None,
agent_score,
ga["rank"] if ga else None,
now,
)) ))
db.commit() db.commit()
db.close() db.close()
print(f"[sync] synced {len(omni)} models at {now}") print(f"[sync] synced {len(omni)} models at {now} with SQL foreign keys")
# ─── Stats helpers ──────────────────────────────────────────────────────────── # ─── Stats helpers ────────────────────────────────────────────────────────────
@@ -257,13 +382,29 @@ def api_models(
sort: str = Query("arena_text_rank"), sort: str = Query("arena_text_rank"),
): ):
db = get_db() db = get_db()
q = "SELECT * FROM models WHERE 1=1" # SQL JOIN таблиц арены с моделями OmniRoute!
q = """
SELECT
m.id,
m.provider,
m.name,
t.score as arena_text_score,
t.rank as arena_text_rank,
a.net_improvement as arena_agent_score,
a.rank as arena_agent_rank,
t.votes as arena_text_votes,
t.price as arena_text_price
FROM models m
LEFT JOIN arena_text_leaderboard t ON m.text_model_match = t.model_name
LEFT JOIN arena_agent_leaderboard a ON m.agent_model_match = a.model_name
WHERE 1=1
"""
params: list = [] params: list = []
if provider: if provider:
q += " AND provider = ?" q += " AND m.provider = ?"
params.append(provider) params.append(provider)
if arena_only: if arena_only:
q += " AND arena_text_score IS NOT NULL" q += " AND t.score IS NOT NULL"
rows = db.execute(q, params).fetchall() rows = db.execute(q, params).fetchall()
@@ -279,6 +420,8 @@ def api_models(
"arena_text_rank": r["arena_text_rank"], "arena_text_rank": r["arena_text_rank"],
"arena_agent_score": r["arena_agent_score"], "arena_agent_score": r["arena_agent_score"],
"arena_agent_rank": r["arena_agent_rank"], "arena_agent_rank": r["arena_agent_rank"],
"arena_text_votes": r["arena_text_votes"],
"arena_text_price": r["arena_text_price"],
**stats, **stats,
}) })