Skip to main content

systemprompt_models/services/ai/
config.rs

1//! AI service configuration loaded from profile YAML.
2//!
3//! [`AiConfig`] is the top-level AI *policy* block: the default provider, the
4//! per-provider policy map (keyed by registry provider name), sampling, MCP
5//! discovery, and history retention. Upstream connectivity lives in the profile
6//! `providers` registry, not here. The nested [`ResilienceSettings`] is the
7//! per-dependency timeout/retry/circuit-breaker policy applied to outbound
8//! provider and MCP calls.
9
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13use super::model::AiProviderConfig;
14
15#[derive(Debug, Clone, Default, Serialize, Deserialize)]
16pub struct AiConfig {
17    #[serde(default)]
18    pub default_provider: String,
19
20    #[serde(default)]
21    pub default_max_output_tokens: Option<u32>,
22
23    #[serde(default)]
24    pub sampling: SamplingConfig,
25
26    #[serde(default)]
27    pub providers: HashMap<String, AiProviderConfig>,
28
29    #[serde(default)]
30    pub mcp: McpConfig,
31
32    #[serde(default)]
33    pub history: HistoryConfig,
34}
35
36#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
37pub struct SamplingConfig {
38    #[serde(default)]
39    pub enable_smart_routing: bool,
40
41    #[serde(default)]
42    pub fallback_enabled: bool,
43}
44
45#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
46pub struct McpConfig {
47    #[serde(default)]
48    pub auto_discover: bool,
49
50    /// Resilience policy applied to outbound MCP tool RPCs (timeouts, retry,
51    /// circuit breaker, bulkhead).
52    #[serde(default = "default_mcp_resilience")]
53    pub resilience: ResilienceSettings,
54}
55
56impl Default for McpConfig {
57    fn default() -> Self {
58        Self {
59            auto_discover: false,
60            resilience: default_mcp_resilience(),
61        }
62    }
63}
64
65/// MCP defaults: tool RPCs are bounded at 30s rather than the 60s AI default.
66fn default_mcp_resilience() -> ResilienceSettings {
67    ResilienceSettings {
68        request_timeout_ms: 30_000,
69        connect_timeout_ms: 5_000,
70        ..ResilienceSettings::default()
71    }
72}
73
74/// Per-dependency resilience policy: timeouts, retry, circuit breaker,
75/// bulkhead.
76///
77/// Plain serde data loaded from profile config (all values in milliseconds or
78/// counts). Translated into the runtime form consumed by the resilience
79/// primitives in `systemprompt-database`.
80#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
81pub struct ResilienceSettings {
82    /// Timeout for a single (non-streaming) attempt.
83    #[serde(default = "default_request_timeout")]
84    pub request_timeout_ms: u64,
85
86    /// Connection-establishment timeout.
87    #[serde(default = "default_resilience_connect_timeout")]
88    pub connect_timeout_ms: u64,
89
90    /// Maximum gap between two chunks of a streaming response.
91    #[serde(default = "default_stream_idle_timeout")]
92    pub stream_idle_timeout_ms: u64,
93
94    /// Maximum attempts including the first. `1` disables retries.
95    #[serde(default = "default_retry_attempts")]
96    pub retry_attempts: u32,
97
98    /// Backoff before the first retry; doubles each subsequent attempt.
99    #[serde(default = "default_retry_base_delay")]
100    pub retry_base_delay_ms: u64,
101
102    /// Upper bound on a single backoff delay.
103    #[serde(default = "default_retry_max_delay")]
104    pub retry_max_delay_ms: u64,
105
106    /// Consecutive failures that trip the circuit breaker open.
107    #[serde(default = "default_breaker_threshold")]
108    pub breaker_failure_threshold: u32,
109
110    /// How long the breaker stays open before allowing a half-open probe.
111    #[serde(default = "default_breaker_cooldown")]
112    pub breaker_open_cooldown_ms: u64,
113
114    /// Concurrent probes admitted while the breaker is half-open.
115    #[serde(default = "default_half_open_probes")]
116    pub breaker_half_open_probes: u32,
117
118    /// Maximum in-flight calls to the dependency; further calls fast-fail.
119    #[serde(default = "default_max_concurrent")]
120    pub max_concurrent: usize,
121}
122
123impl Default for ResilienceSettings {
124    fn default() -> Self {
125        Self {
126            request_timeout_ms: default_request_timeout(),
127            connect_timeout_ms: default_resilience_connect_timeout(),
128            stream_idle_timeout_ms: default_stream_idle_timeout(),
129            retry_attempts: default_retry_attempts(),
130            retry_base_delay_ms: default_retry_base_delay(),
131            retry_max_delay_ms: default_retry_max_delay(),
132            breaker_failure_threshold: default_breaker_threshold(),
133            breaker_open_cooldown_ms: default_breaker_cooldown(),
134            breaker_half_open_probes: default_half_open_probes(),
135            max_concurrent: default_max_concurrent(),
136        }
137    }
138}
139
140const fn default_request_timeout() -> u64 {
141    60_000
142}
143
144const fn default_resilience_connect_timeout() -> u64 {
145    10_000
146}
147
148const fn default_stream_idle_timeout() -> u64 {
149    60_000
150}
151
152const fn default_retry_attempts() -> u32 {
153    3
154}
155
156const fn default_retry_base_delay() -> u64 {
157    200
158}
159
160const fn default_retry_max_delay() -> u64 {
161    10_000
162}
163
164const fn default_breaker_threshold() -> u32 {
165    5
166}
167
168const fn default_breaker_cooldown() -> u64 {
169    30_000
170}
171
172const fn default_half_open_probes() -> u32 {
173    1
174}
175
176const fn default_max_concurrent() -> usize {
177    16
178}
179
180#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
181pub struct HistoryConfig {
182    #[serde(default = "default_retention_days")]
183    pub retention_days: u32,
184
185    #[serde(default)]
186    pub log_tool_executions: bool,
187}
188
189impl Default for HistoryConfig {
190    fn default() -> Self {
191        Self {
192            retention_days: default_retention_days(),
193            log_tool_executions: false,
194        }
195    }
196}
197
198const fn default_retention_days() -> u32 {
199    30
200}