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