// llmindex.io — Prisma schema: models, eval items, responses, duels, score runs // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // License: Proprietary — © Simon-Pierre Boucher, all rights reserved generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // LLMs synced daily from OpenRouter /models (slug, pricing → cost_per_1k_items). model Model { id String @id @default(cuid()) slug String @unique // OpenRouter slug, e.g. "anthropic/claude-..." name String provider String contextLength Int? @map("context_length") // USD per 1M tokens (converted from OpenRouter per-token pricing at sync time) promptPricePerM Float? @map("prompt_price_per_m") completionPricePerM Float? @map("completion_price_per_m") active Boolean @default(true) ranked Boolean @default(false) // curated subset shown on the index vision Boolean @default(false) // accepts image input (vision_ocr domain) syncedAt DateTime @map("synced_at") createdAt DateTime @default(now()) @map("created_at") responses ModelResponse[] scores Score[] duelsAsA PairwiseDuel[] @relation("duel_model_a") duelsAsB PairwiseDuel[] @relation("duel_model_b") @@map("models") } // Instantiated evaluation items. answer_key/rubric are SERVER-SIDE ONLY: // they must never be selected by web app queries or public API serializers. model EvalItem { id String @id @default(cuid()) templateId String @map("template_id") // versioned template id from packages/items domain String prompt String answerKey String @map("answer_key") rubric String? perturbSeed String? @map("perturb_seed") // seed used by the perturbation engine isAnchor Boolean @default(false) @map("is_anchor") // fixed longitudinal subset (≤20% of a run) irtA Float? @map("irt_a") // 2PL discrimination (from last fit) irtB Float? @map("irt_b") // 2PL difficulty (logits, from last fit) status String @default("active") // active | flagged_for_retirement | retired createdAt DateTime @default(now()) @map("created_at") responses ModelResponse[] duels PairwiseDuel[] @@index([domain, status]) @@map("eval_items") } // One row per batch (kind=eval_batch|duel_batch) or per index fit (kind=index_fit). // Historical runs are immutable; every displayed score traces to one of these. model ScoreRun { id String @id @default(cuid()) indexVersion String @map("index_version") // INDEX_VERSION from packages/scoring kind String // eval_batch | duel_batch | index_fit | demo_seed status String @default("pending") // pending | running | complete | degraded | failed itemSetHash String @map("item_set_hash") // sha256 over ordered item ids + perturb seeds modelSet Json @map("model_set") // array of model slugs in the run fitDiagnostics Json? @map("fit_diagnostics") // convergence, judge agreement, bias rates… notes String? createdAt DateTime @default(now()) @map("created_at") completedAt DateTime? @map("completed_at") responses ModelResponse[] duels PairwiseDuel[] scores Score[] @@index([kind, status, createdAt]) @@map("score_runs") } // Full audit trail: a ranking without stored raw responses is invalid. model ModelResponse { id String @id @default(cuid()) runId String @map("run_id") modelId String @map("model_id") itemId String @map("item_id") sampleIndex Int @default(0) @map("sample_index") // >0 for consistency k-samples requestParams Json @map("request_params") // exact temperature, max_tokens, seed… rawResponse Json @map("raw_response") answerExtracted String? @map("answer_extracted") correct Boolean? confidence Float? // model-reported confidence in [0,1] → calibration tokensIn Int? @map("tokens_in") tokensOut Int? @map("tokens_out") latencyMs Int? @map("latency_ms") costUsd Float? @map("cost_usd") error String? createdAt DateTime @default(now()) @map("created_at") run ScoreRun @relation(fields: [runId], references: [id]) model Model @relation(fields: [modelId], references: [id]) item EvalItem @relation(fields: [itemId], references: [id]) @@unique([runId, modelId, itemId, sampleIndex]) @@index([modelId, createdAt]) @@map("model_responses") } // LLM-judged pairwise duels feeding the Bradley-Terry layer. model PairwiseDuel { id String @id @default(cuid()) runId String @map("run_id") domain String itemId String @map("item_id") modelAId String @map("model_a_id") modelBId String @map("model_b_id") judgeSlug String @map("judge_slug") // never a model judging its own duel positionSwapped Boolean @default(false) @map("position_swapped") winner String // "a" | "b" | "tie" rawJudgment Json @map("raw_judgment") createdAt DateTime @default(now()) @map("created_at") run ScoreRun @relation(fields: [runId], references: [id]) item EvalItem @relation(fields: [itemId], references: [id]) modelA Model @relation("duel_model_a", fields: [modelAId], references: [id]) modelB Model @relation("duel_model_b", fields: [modelBId], references: [id]) @@index([runId, domain]) @@map("pairwise_duels") } // Final scores. domain="global" for the Global Index; otherwise one of // the domains in packages/scoring/src/domains.ts. Always with CI bounds. model Score { id String @id @default(cuid()) runId String @map("run_id") modelId String @map("model_id") domain String // "global" | code | math | reasoning | … score Float // 0–1000 scoreLow Float @map("score_low") scoreHigh Float @map("score_high") // { accuracy_irt, consistency, calibration, contamination_delta, latency_p50, cost_per_1k_items } subMetrics Json? @map("sub_metrics") run ScoreRun @relation(fields: [runId], references: [id]) model Model @relation(fields: [modelId], references: [id]) @@unique([runId, modelId, domain]) @@index([domain]) @@map("scores") }