Skip to main content

zeph_config/
quality.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Configuration for the MARCH self-check quality pipeline.
5//!
6//! Add a `[quality]` section to `config.toml`:
7//!
8//! ```toml
9//! [quality]
10//! self_check = true
11//! trigger = "has_retrieval"
12//! async_run = false
13//! ```
14
15use serde::{Deserialize, Serialize};
16
17/// When to trigger the self-check pipeline.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
19#[serde(rename_all = "snake_case")]
20pub enum TriggerPolicy {
21    /// Run only when the turn has retrieved context.
22    #[default]
23    HasRetrieval,
24    /// Always run regardless of retrieved context.
25    Always,
26    /// Never run automatically.
27    Manual,
28}
29
30/// Configuration for the MARCH self-check quality pipeline.
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct QualityConfig {
33    /// Enable post-response self-check pipeline.
34    #[serde(default)]
35    pub self_check: bool,
36
37    /// Advisory: preferred provider for the Proposer role (MVP: no-op).
38    #[serde(default)]
39    pub proposer_provider: String,
40
41    /// Advisory: preferred provider for the Checker role (MVP: no-op).
42    #[serde(default)]
43    pub checker_provider: String,
44
45    /// When to trigger the pipeline.
46    #[serde(default)]
47    pub trigger: TriggerPolicy,
48
49    /// Minimum evidence strength to avoid flagging an assertion (0.0–1.0).
50    #[serde(default = "default_min_evidence")]
51    pub min_evidence: f32,
52
53    /// If `false` (default), pipeline blocks response until done.
54    #[serde(default)]
55    pub async_run: bool,
56
57    /// Hard ceiling on total pipeline latency in milliseconds.
58    #[serde(default = "default_latency_budget_ms")]
59    pub latency_budget_ms: u64,
60
61    /// Per-LLM-call timeout in milliseconds.
62    #[serde(default = "default_per_call_timeout_ms")]
63    pub per_call_timeout_ms: u64,
64
65    /// Maximum assertions to extract from one response.
66    #[serde(default = "default_max_assertions")]
67    pub max_assertions: usize,
68
69    /// Skip pipeline when response exceeds this many characters.
70    #[serde(default = "default_max_response_chars")]
71    pub max_response_chars: usize,
72
73    /// Suppress prompt-cache emission on Checker provider.
74    #[serde(default = "default_cache_disabled_for_checker")]
75    pub cache_disabled_for_checker: bool,
76
77    /// Marker appended to response when issues are flagged.
78    #[serde(default = "default_flag_marker")]
79    pub flag_marker: String,
80}
81
82fn default_min_evidence() -> f32 {
83    0.6
84}
85fn default_latency_budget_ms() -> u64 {
86    4_000
87}
88fn default_per_call_timeout_ms() -> u64 {
89    2_000
90}
91fn default_max_assertions() -> usize {
92    12
93}
94fn default_max_response_chars() -> usize {
95    8_000
96}
97fn default_cache_disabled_for_checker() -> bool {
98    true
99}
100fn default_flag_marker() -> String {
101    "[verify]".into()
102}
103
104impl Default for QualityConfig {
105    fn default() -> Self {
106        Self {
107            self_check: false,
108            proposer_provider: String::new(),
109            checker_provider: String::new(),
110            trigger: TriggerPolicy::default(),
111            min_evidence: default_min_evidence(),
112            async_run: false,
113            latency_budget_ms: default_latency_budget_ms(),
114            per_call_timeout_ms: default_per_call_timeout_ms(),
115            max_assertions: default_max_assertions(),
116            max_response_chars: default_max_response_chars(),
117            cache_disabled_for_checker: default_cache_disabled_for_checker(),
118            flag_marker: default_flag_marker(),
119        }
120    }
121}