Skip to main content

systemprompt_models/services/ai/
config.rs

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