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