starweaver_context/config/
model.rs1use std::collections::BTreeSet;
2
3use serde::{Deserialize, Serialize};
4
5use super::PerThousandRatio;
6
7#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
9#[serde(rename_all = "snake_case")]
10pub enum ModelCapability {
11 Vision,
13 VideoUnderstanding,
15 DocumentUnderstanding,
17 AudioUnderstanding,
19 ImageUrl,
21 VideoUrl,
23 ReasoningRequired,
25 ReasoningForeignUnsupported,
27}
28
29#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
31pub struct ModelConfig {
32 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub context_window: Option<u64>,
35 #[serde(default = "default_proactive_context_management_threshold")]
37 pub proactive_context_management_threshold: Option<PerThousandRatio>,
38 #[serde(default = "default_compact_threshold")]
40 pub compact_threshold: PerThousandRatio,
41 #[serde(default = "default_cold_start_trim_seconds")]
43 pub cold_start_trim_seconds: u64,
44 #[serde(default)]
46 pub stream_resume_on_error: bool,
47 #[serde(default = "default_stream_resume_max_attempts")]
49 pub stream_resume_max_attempts: usize,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub stream_resume_prompt: Option<String>,
53 #[serde(default = "default_model_max_images")]
55 pub max_images: usize,
56 #[serde(default = "default_model_max_videos")]
58 pub max_videos: usize,
59 #[serde(default = "default_true")]
61 pub support_gif: bool,
62 #[serde(default = "default_model_max_image_bytes")]
67 pub max_image_bytes: usize,
68 #[serde(default = "default_model_max_image_dimension")]
73 pub max_image_dimension: usize,
74 #[serde(default = "default_true")]
76 pub split_large_images: bool,
77 #[serde(default = "default_image_split_max_height")]
79 pub image_split_max_height: usize,
80 #[serde(default = "default_image_split_overlap")]
82 pub image_split_overlap: usize,
83 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
85 pub capabilities: BTreeSet<ModelCapability>,
86}
87
88impl Default for ModelConfig {
89 fn default() -> Self {
90 Self {
91 context_window: None,
92 proactive_context_management_threshold: default_proactive_context_management_threshold(
93 ),
94 compact_threshold: default_compact_threshold(),
95 cold_start_trim_seconds: default_cold_start_trim_seconds(),
96 stream_resume_on_error: false,
97 stream_resume_max_attempts: default_stream_resume_max_attempts(),
98 stream_resume_prompt: None,
99 max_images: default_model_max_images(),
100 max_videos: default_model_max_videos(),
101 support_gif: true,
102 max_image_bytes: default_model_max_image_bytes(),
103 max_image_dimension: default_model_max_image_dimension(),
104 split_large_images: true,
105 image_split_max_height: default_image_split_max_height(),
106 image_split_overlap: default_image_split_overlap(),
107 capabilities: BTreeSet::new(),
108 }
109 }
110}
111
112impl ModelConfig {
113 #[must_use]
115 pub fn is_default(&self) -> bool {
116 self == &Self::default()
117 }
118
119 #[must_use]
121 pub const fn has_runtime_context_window(&self) -> bool {
122 self.context_window.is_some()
123 }
124
125 #[must_use]
127 pub fn has_capability(&self, capability: &ModelCapability) -> bool {
128 self.capabilities.contains(capability)
129 }
130
131 #[must_use]
133 pub fn has_vision(&self) -> bool {
134 self.has_capability(&ModelCapability::Vision)
135 }
136
137 #[must_use]
139 pub fn has_video_understanding(&self) -> bool {
140 self.has_capability(&ModelCapability::VideoUnderstanding)
141 }
142
143 #[must_use]
145 pub fn has_audio_understanding(&self) -> bool {
146 self.has_capability(&ModelCapability::AudioUnderstanding)
147 }
148
149 #[must_use]
151 pub fn has_document_understanding(&self) -> bool {
152 self.has_capability(&ModelCapability::DocumentUnderstanding)
153 }
154
155 pub fn merge_from(&mut self, other: Self) {
157 if other.context_window.is_some() {
158 self.context_window = other.context_window;
159 }
160 self.proactive_context_management_threshold = other.proactive_context_management_threshold;
161 self.compact_threshold = other.compact_threshold;
162 self.cold_start_trim_seconds = other.cold_start_trim_seconds;
163 self.stream_resume_on_error = other.stream_resume_on_error;
164 self.stream_resume_max_attempts = other.stream_resume_max_attempts;
165 if other.stream_resume_prompt.is_some() {
166 self.stream_resume_prompt = other.stream_resume_prompt;
167 }
168 self.max_images = other.max_images;
169 self.max_videos = other.max_videos;
170 self.support_gif = other.support_gif;
171 self.max_image_bytes = other.max_image_bytes;
172 self.max_image_dimension = other.max_image_dimension;
173 self.split_large_images = other.split_large_images;
174 self.image_split_max_height = other.image_split_max_height;
175 self.image_split_overlap = other.image_split_overlap;
176 if !other.capabilities.is_empty() {
177 self.capabilities = other.capabilities;
178 }
179 }
180}
181
182const fn default_true() -> bool {
183 true
184}
185
186#[allow(clippy::unnecessary_wraps)]
187const fn default_proactive_context_management_threshold() -> Option<PerThousandRatio> {
188 Some(PerThousandRatio::from_per_thousand(650))
189}
190
191const fn default_compact_threshold() -> PerThousandRatio {
192 PerThousandRatio::from_per_thousand(900)
193}
194
195const fn default_cold_start_trim_seconds() -> u64 {
196 3600
197}
198
199const fn default_stream_resume_max_attempts() -> usize {
200 3
201}
202
203const fn default_model_max_images() -> usize {
204 20
205}
206
207const fn default_model_max_videos() -> usize {
208 1
209}
210
211const fn default_model_max_image_bytes() -> usize {
212 5 * 1024 * 1024
213}
214
215const fn default_model_max_image_dimension() -> usize {
216 8000
217}
218
219const fn default_image_split_max_height() -> usize {
220 4096
221}
222
223const fn default_image_split_overlap() -> usize {
224 50
225}