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