runifold_model/
runtime_profile.rs1use std::collections::BTreeMap;
2
3use serde_json::Value;
4
5use crate::{CircuitBreakerConfig, FeaturePolicy, ModelRetryPolicy, ResponseMode};
6
7pub trait RuntimeProfilePreset: Copy + Send + Sync + 'static {
9 fn apply(self, provider: ProviderRuntimeProfile) -> ProviderRuntimeProfile;
12}
13
14#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
16pub struct ProductionProfile;
17
18impl RuntimeProfilePreset for ProductionProfile {
19 fn apply(self, provider: ProviderRuntimeProfile) -> ProviderRuntimeProfile {
20 provider
21 }
22}
23
24#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
26pub struct InteractiveProfile;
27
28impl RuntimeProfilePreset for InteractiveProfile {
29 fn apply(self, provider: ProviderRuntimeProfile) -> ProviderRuntimeProfile {
30 provider.response_mode(ResponseMode::Streaming)
31 }
32}
33
34#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
36pub struct BatchProfile;
37
38impl RuntimeProfilePreset for BatchProfile {
39 fn apply(self, provider: ProviderRuntimeProfile) -> ProviderRuntimeProfile {
40 provider.response_mode(ResponseMode::Complete)
41 }
42}
43
44#[derive(Clone, Debug, Eq, PartialEq)]
51pub struct ProviderRuntimeProfile {
52 response_mode: ResponseMode,
53 feature_policy: FeaturePolicy,
54 retry_policy: ModelRetryPolicy,
55 circuit_breaker: CircuitBreakerConfig,
56 provider_options: BTreeMap<String, Value>,
57}
58
59impl Default for ProviderRuntimeProfile {
60 fn default() -> Self {
61 Self {
62 response_mode: ResponseMode::Streaming,
63 feature_policy: FeaturePolicy::Strict,
64 retry_policy: ModelRetryPolicy::default(),
65 circuit_breaker: CircuitBreakerConfig::default(),
66 provider_options: BTreeMap::new(),
67 }
68 }
69}
70
71impl ProviderRuntimeProfile {
72 pub fn conservative() -> Self {
74 Self::default()
75 }
76
77 #[must_use]
79 pub const fn response_mode(mut self, response_mode: ResponseMode) -> Self {
80 self.response_mode = response_mode;
81 self
82 }
83
84 #[must_use]
86 pub const fn feature_policy(mut self, feature_policy: FeaturePolicy) -> Self {
87 self.feature_policy = feature_policy;
88 self
89 }
90
91 #[must_use]
93 pub fn retry_policy(mut self, retry_policy: ModelRetryPolicy) -> Self {
94 self.retry_policy = retry_policy;
95 self
96 }
97
98 #[must_use]
100 pub fn circuit_breaker(mut self, circuit_breaker: CircuitBreakerConfig) -> Self {
101 self.circuit_breaker = circuit_breaker;
102 self
103 }
104
105 #[must_use]
107 pub fn provider_option(mut self, provider: impl Into<String>, options: Value) -> Self {
108 self.provider_options.insert(provider.into(), options);
109 self
110 }
111
112 pub const fn selected_response_mode(&self) -> ResponseMode {
114 self.response_mode
115 }
116
117 pub const fn selected_feature_policy(&self) -> FeaturePolicy {
119 self.feature_policy
120 }
121
122 pub const fn selected_retry_policy(&self) -> &ModelRetryPolicy {
124 &self.retry_policy
125 }
126
127 pub const fn selected_circuit_breaker(&self) -> &CircuitBreakerConfig {
129 &self.circuit_breaker
130 }
131
132 pub const fn provider_options(&self) -> &BTreeMap<String, Value> {
134 &self.provider_options
135 }
136
137 pub fn into_parts(
139 self,
140 ) -> (
141 ResponseMode,
142 FeaturePolicy,
143 ModelRetryPolicy,
144 CircuitBreakerConfig,
145 BTreeMap<String, Value>,
146 ) {
147 (
148 self.response_mode,
149 self.feature_policy,
150 self.retry_policy,
151 self.circuit_breaker,
152 self.provider_options,
153 )
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use crate::{
160 BatchProfile, FeaturePolicy, InteractiveProfile, ProductionProfile, ProviderRuntimeProfile,
161 ResponseMode, RuntimeProfilePreset,
162 };
163
164 #[test]
165 fn conservative_profile_is_fail_closed_and_provider_neutral() {
166 let profile = ProviderRuntimeProfile::conservative();
167
168 assert_eq!(profile.selected_response_mode(), ResponseMode::Streaming);
169 assert_eq!(profile.selected_feature_policy(), FeaturePolicy::Strict);
170 assert!(profile.provider_options().is_empty());
171 assert_eq!(profile.selected_retry_policy().max_attempts(), 3);
172 }
173
174 #[test]
175 fn workload_presets_preserve_provider_owned_options() {
176 let provider = ProviderRuntimeProfile::conservative()
177 .provider_option("provider", serde_json::json!({"safe": true}));
178
179 let production = ProductionProfile.apply(provider.clone());
180 let interactive = InteractiveProfile.apply(provider.clone());
181 let batch = BatchProfile.apply(provider);
182
183 assert_eq!(production.selected_response_mode(), ResponseMode::Streaming);
184 assert_eq!(
185 interactive.selected_response_mode(),
186 ResponseMode::Streaming
187 );
188 assert_eq!(batch.selected_response_mode(), ResponseMode::Complete);
189 assert_eq!(batch.provider_options()["provider"]["safe"], true);
190 }
191}