gglib_core/ports/benchmark.rs
1//! Benchmark repository port definition.
2//!
3//! Defines the interface for persisting benchmark runs and results.
4//! Implementations live in `gglib-db`; this trait contains only domain types.
5
6use async_trait::async_trait;
7
8use super::RepositoryError;
9use crate::domain::benchmark::agentic::AgenticEvalReport;
10use crate::domain::{
11 BenchmarkRun, BenchmarkRunType, ModelBenchmarkSummary, ModelCompareResult, ModelPerfResult,
12 TuneCandidateResult,
13};
14
15/// Repository interface for benchmark persistence.
16///
17/// Implementations are responsible for:
18/// - Creating and updating run records
19/// - Storing per-model compare and perf results
20/// - Upserting `model_benchmark_summaries` in the same transaction as each
21/// result save, so the model list query always has fresh summary data
22/// without extra round-trips
23#[async_trait]
24pub trait BenchmarkRepositoryPort: Send + Sync {
25 /// Create a new benchmark run record in `Running` status.
26 ///
27 /// Returns the auto-assigned run ID.
28 async fn create_run(
29 &self,
30 run_type: BenchmarkRunType,
31 model_ids: &[i64],
32 prompt_text: Option<&str>,
33 system_prompt: Option<&str>,
34 config_json: Option<&str>,
35 ) -> Result<i64, RepositoryError>;
36
37 /// Mark a run as `Complete` and record the completion timestamp.
38 async fn complete_run(&self, run_id: i64) -> Result<(), RepositoryError>;
39
40 /// Mark a run as `Failed` and record the error message.
41 async fn fail_run(&self, run_id: i64, error: &str) -> Result<(), RepositoryError>;
42
43 /// Persist a compare result and upsert the model's benchmark summary.
44 ///
45 /// Both the result INSERT and the summary upsert happen in the same
46 /// database transaction to keep the denormalised summary consistent.
47 ///
48 /// Returns the auto-assigned result ID.
49 async fn save_compare_result(
50 &self,
51 result: &ModelCompareResult,
52 run_id: i64,
53 ) -> Result<i64, RepositoryError>;
54
55 /// Persist a perf result and upsert the model's benchmark summary.
56 ///
57 /// Both the result INSERT and the summary upsert happen in the same
58 /// database transaction to keep the denormalised summary consistent.
59 ///
60 /// Returns the auto-assigned result ID.
61 async fn save_perf_result(
62 &self,
63 result: &ModelPerfResult,
64 run_id: i64,
65 ) -> Result<i64, RepositoryError>;
66
67 /// List benchmark runs, most recent first.
68 async fn list_runs(
69 &self,
70 limit: i64,
71 offset: i64,
72 ) -> Result<Vec<BenchmarkRun>, RepositoryError>;
73
74 /// Get a single benchmark run by ID.
75 async fn get_run(&self, run_id: i64) -> Result<Option<BenchmarkRun>, RepositoryError>;
76
77 /// Get compare results for one model, most recent first.
78 async fn get_model_compare_history(
79 &self,
80 model_id: i64,
81 limit: i64,
82 ) -> Result<Vec<ModelCompareResult>, RepositoryError>;
83
84 /// Get perf results for one model, most recent first.
85 async fn get_model_perf_history(
86 &self,
87 model_id: i64,
88 limit: i64,
89 ) -> Result<Vec<ModelPerfResult>, RepositoryError>;
90
91 /// Get the denormalised benchmark summary for one model.
92 ///
93 /// Returns `None` if no benchmark has been run for this model.
94 async fn get_model_summary(
95 &self,
96 model_id: i64,
97 ) -> Result<Option<ModelBenchmarkSummary>, RepositoryError>;
98
99 /// Persist one tune candidate's result.
100 ///
101 /// Unlike compare/perf, tune results do not upsert
102 /// `model_benchmark_summaries` — a candidate's `composite_score` is only
103 /// meaningful relative to the other candidates in the same run, so it is
104 /// not a useful cross-run "best" figure to denormalise.
105 ///
106 /// Returns the auto-assigned result ID.
107 async fn save_tune_result(
108 &self,
109 result: &TuneCandidateResult,
110 run_id: i64,
111 model_id: i64,
112 ) -> Result<i64, RepositoryError>;
113
114 /// Get tune candidate results for one model, most recent first.
115 async fn get_model_tune_history(
116 &self,
117 model_id: i64,
118 limit: i64,
119 ) -> Result<Vec<TuneCandidateResult>, RepositoryError>;
120
121 /// Every candidate of one tune run, in insertion order — the input the
122 /// apply gate (`tune::apply::evaluate_apply`) judges.
123 async fn get_tune_results(
124 &self,
125 run_id: i64,
126 ) -> Result<Vec<TuneCandidateResult>, RepositoryError>;
127
128 /// Record the apply decision taken from this run — a JSON-serialized
129 /// `tune::apply::ApplyRecord`, stored so "measured by a tune sweep" on
130 /// the model can always be traced to the numbers that licensed it.
131 async fn mark_run_applied(
132 &self,
133 run_id: i64,
134 applied_json: &str,
135 ) -> Result<(), RepositoryError>;
136
137 /// Persist one raw-vs-gglib A/B report.
138 ///
139 /// Like tune results, this does not upsert `model_benchmark_summaries`:
140 /// the interesting figure is the *difference* between two arms of the same
141 /// run, which is not a cross-run "best" worth denormalising onto the model.
142 ///
143 /// Returns the auto-assigned result ID.
144 async fn save_agentic_result(
145 &self,
146 report: &AgenticEvalReport,
147 run_id: i64,
148 model_id: i64,
149 ) -> Result<i64, RepositoryError>;
150
151 /// Get raw-vs-gglib A/B reports for one model, most recent first.
152 async fn get_model_agentic_history(
153 &self,
154 model_id: i64,
155 limit: i64,
156 ) -> Result<Vec<AgenticEvalReport>, RepositoryError>;
157}