Skip to main content

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}