zeph_core/quality/
config.rs1use serde::{Deserialize, Serialize};
7use thiserror::Error;
8use zeph_config::providers::ProviderName;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
12#[serde(rename_all = "snake_case")]
13#[non_exhaustive]
14pub enum TriggerPolicy {
15 #[default]
17 HasRetrieval,
18 Always,
20 Manual,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct QualityConfig {
36 #[serde(default)]
38 pub self_check: bool,
39
40 #[serde(default)]
45 pub proposer_provider: ProviderName,
46
47 #[serde(default)]
52 pub checker_provider: ProviderName,
53
54 #[serde(default)]
56 pub trigger: TriggerPolicy,
57
58 #[serde(default = "default_min_evidence")]
62 pub min_evidence: f32,
63
64 #[serde(default)]
67 pub async_run: bool,
68
69 #[serde(default = "default_latency_budget_ms")]
71 pub latency_budget_ms: u64,
72
73 #[serde(default = "default_per_call_timeout_ms")]
75 pub per_call_timeout_ms: u64,
76
77 #[serde(default = "default_max_assertions")]
79 pub max_assertions: usize,
80
81 #[serde(default = "default_max_response_chars")]
83 pub max_response_chars: usize,
84
85 #[serde(default = "default_cache_disabled_for_checker")]
87 pub cache_disabled_for_checker: bool,
88
89 #[serde(default = "default_flag_marker")]
91 pub flag_marker: String,
92}
93
94fn default_min_evidence() -> f32 {
95 0.6
96}
97fn default_latency_budget_ms() -> u64 {
98 4_000
99}
100fn default_per_call_timeout_ms() -> u64 {
101 2_000
102}
103fn default_max_assertions() -> usize {
104 12
105}
106fn default_max_response_chars() -> usize {
107 8_000
108}
109fn default_cache_disabled_for_checker() -> bool {
110 true
111}
112fn default_flag_marker() -> String {
113 "[verify]".into()
114}
115
116impl Default for QualityConfig {
117 fn default() -> Self {
118 Self {
119 self_check: false,
120 proposer_provider: ProviderName::default(),
121 checker_provider: ProviderName::default(),
122 trigger: TriggerPolicy::default(),
123 min_evidence: default_min_evidence(),
124 async_run: false,
125 latency_budget_ms: default_latency_budget_ms(),
126 per_call_timeout_ms: default_per_call_timeout_ms(),
127 max_assertions: default_max_assertions(),
128 max_response_chars: default_max_response_chars(),
129 cache_disabled_for_checker: default_cache_disabled_for_checker(),
130 flag_marker: default_flag_marker(),
131 }
132 }
133}
134
135#[derive(Debug, Error)]
137#[non_exhaustive]
138pub enum QualityConfigError {
139 #[error("per_call_timeout_ms ({per_call}) × 2 must be ≤ latency_budget_ms ({budget})")]
140 TimeoutExceedsBudget { per_call: u64, budget: u64 },
141 #[error("min_evidence must be in 0.0..=1.0, got {0}")]
142 InvalidMinEvidence(f32),
143}
144
145impl QualityConfig {
146 pub fn validate(&self) -> Result<(), QualityConfigError> {
153 if 2 * self.per_call_timeout_ms > self.latency_budget_ms {
154 return Err(QualityConfigError::TimeoutExceedsBudget {
155 per_call: self.per_call_timeout_ms,
156 budget: self.latency_budget_ms,
157 });
158 }
159 if !(0.0..=1.0).contains(&self.min_evidence) {
160 return Err(QualityConfigError::InvalidMinEvidence(self.min_evidence));
161 }
162 Ok(())
163 }
164}
165
166impl From<&zeph_config::QualityConfig> for QualityConfig {
167 fn from(c: &zeph_config::QualityConfig) -> Self {
168 Self {
169 self_check: c.self_check,
170 proposer_provider: c.proposer_provider.clone(),
171 checker_provider: c.checker_provider.clone(),
172 trigger: match c.trigger {
173 zeph_config::TriggerPolicy::Always => TriggerPolicy::Always,
174 zeph_config::TriggerPolicy::Manual => TriggerPolicy::Manual,
175 _ => TriggerPolicy::HasRetrieval,
176 },
177 min_evidence: c.min_evidence,
178 async_run: c.async_run,
179 latency_budget_ms: c.latency_budget_ms,
180 per_call_timeout_ms: c.per_call_timeout_ms,
181 max_assertions: c.max_assertions,
182 max_response_chars: c.max_response_chars,
183 cache_disabled_for_checker: c.cache_disabled_for_checker,
184 flag_marker: c.flag_marker.clone(),
185 }
186 }
187}