1use serde::{Deserialize, Serialize};
11
12fn default_chat_template() -> String {
13 "chatml".into()
14}
15
16fn default_temperature() -> f64 {
17 0.7
18}
19
20fn default_max_tokens() -> usize {
21 2048
22}
23
24fn default_seed() -> u64 {
25 42
26}
27
28fn default_repeat_penalty() -> f32 {
29 1.1
30}
31
32fn default_repeat_last_n() -> usize {
33 64
34}
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
40#[serde(rename_all = "lowercase")]
41pub enum CandleSource {
42 #[default]
45 Huggingface,
46 Local,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
56#[serde(rename_all = "lowercase")]
57pub enum CandleDevice {
58 #[default]
60 Cpu,
61 Cuda,
63 Metal,
65 Auto,
70}
71
72#[derive(Deserialize, Serialize)]
79pub struct CandleConfig {
80 #[serde(default)]
81 pub source: CandleSource,
82 #[serde(default)]
83 pub local_path: String,
84 #[serde(default)]
85 pub filename: Option<String>,
86 #[serde(default = "default_chat_template")]
87 pub chat_template: String,
88 #[serde(default)]
89 pub device: CandleDevice,
90 #[serde(default)]
91 pub embedding_repo: Option<String>,
92 #[serde(default)]
96 pub hf_token: Option<String>,
97 #[serde(default)]
98 pub generation: GenerationParams,
99 #[serde(default = "default_inference_timeout_secs")]
108 pub inference_timeout_secs: u64,
109}
110
111fn default_inference_timeout_secs() -> u64 {
112 120
113}
114
115impl std::fmt::Debug for CandleConfig {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 f.debug_struct("CandleConfig")
118 .field("source", &self.source)
119 .field("local_path", &self.local_path)
120 .field("filename", &self.filename)
121 .field("chat_template", &self.chat_template)
122 .field("device", &self.device)
123 .field("embedding_repo", &self.embedding_repo)
124 .field("hf_token", &self.hf_token.as_ref().map(|_| "[REDACTED]"))
125 .field("generation", &self.generation)
126 .field("inference_timeout_secs", &self.inference_timeout_secs)
127 .finish()
128 }
129}
130
131#[derive(Debug, Clone, Deserialize, Serialize)]
135pub struct GenerationParams {
136 #[serde(default = "default_temperature")]
138 pub temperature: f64,
139 #[serde(default)]
142 pub top_p: Option<f64>,
143 #[serde(default)]
146 pub top_k: Option<usize>,
147 #[serde(default = "default_max_tokens")]
150 pub max_tokens: usize,
151 #[serde(default = "default_seed")]
153 pub seed: u64,
154 #[serde(default = "default_repeat_penalty")]
156 pub repeat_penalty: f32,
157 #[serde(default = "default_repeat_last_n")]
159 pub repeat_last_n: usize,
160}
161
162pub const MAX_TOKENS_CAP: usize = 32768;
164
165impl GenerationParams {
166 #[must_use]
177 pub fn capped_max_tokens(&self) -> usize {
178 self.max_tokens.min(MAX_TOKENS_CAP)
179 }
180}
181
182impl Default for GenerationParams {
183 fn default() -> Self {
184 Self {
185 temperature: default_temperature(),
186 top_p: None,
187 top_k: None,
188 max_tokens: default_max_tokens(),
189 seed: default_seed(),
190 repeat_penalty: default_repeat_penalty(),
191 repeat_last_n: default_repeat_last_n(),
192 }
193 }
194}
195#[derive(Clone, Deserialize, Serialize)]
198pub struct CandleInlineConfig {
199 #[serde(default)]
200 pub source: CandleSource,
201 #[serde(default)]
202 pub local_path: String,
203 #[serde(default)]
204 pub filename: Option<String>,
205 #[serde(default)]
210 pub chat_model_sha256: Option<String>,
211 #[serde(default = "default_chat_template")]
212 pub chat_template: String,
213 #[serde(default)]
214 pub device: CandleDevice,
215 #[serde(default)]
216 pub embedding_repo: Option<String>,
217 #[serde(default)]
221 pub embedding_model_sha256: Option<String>,
222 #[serde(default)]
224 pub hf_token: Option<String>,
225 #[serde(default)]
226 pub generation: GenerationParams,
227 #[serde(default = "default_inference_timeout_secs")]
232 pub inference_timeout_secs: u64,
233}
234
235impl Default for CandleInlineConfig {
236 fn default() -> Self {
237 Self {
238 source: CandleSource::default(),
239 local_path: String::new(),
240 filename: None,
241 chat_model_sha256: None,
242 chat_template: default_chat_template(),
243 device: CandleDevice::default(),
244 embedding_repo: None,
245 embedding_model_sha256: None,
246 hf_token: None,
247 generation: GenerationParams::default(),
248 inference_timeout_secs: default_inference_timeout_secs(),
249 }
250 }
251}
252
253impl std::fmt::Debug for CandleInlineConfig {
254 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255 f.debug_struct("CandleInlineConfig")
256 .field("source", &self.source)
257 .field("local_path", &self.local_path)
258 .field("filename", &self.filename)
259 .field("chat_model_sha256", &self.chat_model_sha256)
260 .field("chat_template", &self.chat_template)
261 .field("device", &self.device)
262 .field("embedding_repo", &self.embedding_repo)
263 .field("embedding_model_sha256", &self.embedding_model_sha256)
264 .field("hf_token", &self.hf_token.as_ref().map(|_| "[REDACTED]"))
265 .field("generation", &self.generation)
266 .field("inference_timeout_secs", &self.inference_timeout_secs)
267 .finish()
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn candle_config_debug_redacts_hf_token() {
277 let cfg = CandleConfig {
278 source: CandleSource::default(),
279 local_path: String::new(),
280 filename: None,
281 chat_template: default_chat_template(),
282 device: CandleDevice::default(),
283 embedding_repo: None,
284 hf_token: Some("hf_SUPERSECRET".to_owned()),
285 generation: GenerationParams::default(),
286 inference_timeout_secs: default_inference_timeout_secs(),
287 };
288 let dbg = format!("{cfg:?}");
289 assert!(!dbg.contains("hf_SUPERSECRET"));
290 assert!(dbg.contains("[REDACTED]"));
291 }
292
293 #[test]
294 fn candle_config_debug_none_hf_token() {
295 let cfg = CandleConfig {
296 source: CandleSource::default(),
297 local_path: String::new(),
298 filename: None,
299 chat_template: default_chat_template(),
300 device: CandleDevice::default(),
301 embedding_repo: None,
302 hf_token: None,
303 generation: GenerationParams::default(),
304 inference_timeout_secs: default_inference_timeout_secs(),
305 };
306 let dbg = format!("{cfg:?}");
307 assert!(!dbg.contains("[REDACTED]"));
308 assert!(dbg.contains("hf_token: None"));
309 }
310
311 #[test]
312 fn candle_inline_config_debug_redacts_hf_token() {
313 let cfg = CandleInlineConfig {
314 hf_token: Some("hf_SUPERSECRET".to_owned()),
315 ..CandleInlineConfig::default()
316 };
317 let dbg = format!("{cfg:?}");
318 assert!(!dbg.contains("hf_SUPERSECRET"));
319 assert!(dbg.contains("[REDACTED]"));
320 }
321
322 #[test]
323 fn candle_inline_config_debug_none_hf_token() {
324 let cfg = CandleInlineConfig::default();
325 let dbg = format!("{cfg:?}");
326 assert!(!dbg.contains("[REDACTED]"));
327 assert!(dbg.contains("hf_token: None"));
328 }
329}