1use std::collections::HashMap;
5
6use serde::{Deserialize, Deserializer, Serialize};
7
8use crate::pagination::HasId;
9
10#[derive(Debug, Clone, PartialEq)]
16pub enum AutoOr<T> {
17 Auto,
19 Value(T),
21}
22
23impl<T: Serialize> Serialize for AutoOr<T> {
24 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
25 where
26 S: serde::Serializer,
27 {
28 match self {
29 AutoOr::Auto => serializer.serialize_str("auto"),
30 AutoOr::Value(value) => value.serialize(serializer),
31 }
32 }
33}
34
35impl<'de, T: Deserialize<'de>> Deserialize<'de> for AutoOr<T> {
36 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
37 where
38 D: Deserializer<'de>,
39 {
40 let value = serde_json::Value::deserialize(deserializer)?;
41 if value.as_str() == Some("auto") {
42 Ok(AutoOr::Auto)
43 } else {
44 T::deserialize(value)
45 .map(AutoOr::Value)
46 .map_err(serde::de::Error::custom)
47 }
48 }
49}
50
51#[derive(Debug, Clone, Default, Serialize, Deserialize)]
58pub struct Hyperparameters {
59 #[serde(default, skip_serializing_if = "Option::is_none")]
60 pub batch_size: Option<AutoOr<i64>>,
61 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub learning_rate_multiplier: Option<AutoOr<f64>>,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
64 pub n_epochs: Option<AutoOr<i64>>,
65}
66
67#[derive(Debug, Clone, Default, Serialize, Deserialize)]
69pub struct DpoHyperparameters {
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub batch_size: Option<AutoOr<i64>>,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub beta: Option<AutoOr<f64>>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub learning_rate_multiplier: Option<AutoOr<f64>>,
76 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub n_epochs: Option<AutoOr<i64>>,
78}
79
80#[derive(Debug, Clone, Default, Serialize, Deserialize)]
82pub struct ReinforcementHyperparameters {
83 #[serde(default, skip_serializing_if = "Option::is_none")]
84 pub batch_size: Option<AutoOr<i64>>,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub compute_multiplier: Option<AutoOr<f64>>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub eval_interval: Option<AutoOr<i64>>,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub eval_samples: Option<AutoOr<i64>>,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub learning_rate_multiplier: Option<AutoOr<f64>>,
93 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub n_epochs: Option<AutoOr<i64>>,
95 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub reasoning_effort: Option<String>,
98}
99
100#[derive(Debug, Clone, Default, Serialize, Deserialize)]
106pub struct SupervisedMethod {
107 #[serde(default, skip_serializing_if = "Option::is_none")]
108 pub hyperparameters: Option<Hyperparameters>,
109}
110
111#[derive(Debug, Clone, Default, Serialize, Deserialize)]
113pub struct DpoMethod {
114 #[serde(default, skip_serializing_if = "Option::is_none")]
115 pub hyperparameters: Option<DpoHyperparameters>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct ReinforcementMethod {
122 pub grader: serde_json::Value,
123 #[serde(default, skip_serializing_if = "Option::is_none")]
124 pub hyperparameters: Option<ReinforcementHyperparameters>,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct FineTuningMethod {
130 #[serde(rename = "type")]
132 pub method_type: String,
133 #[serde(default, skip_serializing_if = "Option::is_none")]
134 pub supervised: Option<SupervisedMethod>,
135 #[serde(default, skip_serializing_if = "Option::is_none")]
136 pub dpo: Option<DpoMethod>,
137 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub reinforcement: Option<ReinforcementMethod>,
139}
140
141impl FineTuningMethod {
142 pub fn supervised(config: SupervisedMethod) -> Self {
144 Self {
145 method_type: "supervised".into(),
146 supervised: Some(config),
147 dpo: None,
148 reinforcement: None,
149 }
150 }
151
152 pub fn dpo(config: DpoMethod) -> Self {
154 Self {
155 method_type: "dpo".into(),
156 supervised: None,
157 dpo: Some(config),
158 reinforcement: None,
159 }
160 }
161
162 pub fn reinforcement(config: ReinforcementMethod) -> Self {
164 Self {
165 method_type: "reinforcement".into(),
166 supervised: None,
167 dpo: None,
168 reinforcement: Some(config),
169 }
170 }
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct WandbIntegration {
180 pub project: String,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
182 pub name: Option<String>,
183 #[serde(default, skip_serializing_if = "Option::is_none")]
184 pub entity: Option<String>,
185 #[serde(default, skip_serializing_if = "Option::is_none")]
186 pub tags: Option<Vec<String>>,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct Integration {
192 #[serde(rename = "type")]
194 pub integration_type: String,
195 pub wandb: WandbIntegration,
196}
197
198impl Integration {
199 pub fn wandb(project: impl Into<String>) -> Self {
201 Self {
202 integration_type: "wandb".into(),
203 wandb: WandbIntegration {
204 project: project.into(),
205 name: None,
206 entity: None,
207 tags: None,
208 },
209 }
210 }
211}
212
213#[derive(Debug, Clone, Serialize)]
220pub struct FineTuningJobRequest {
221 pub model: String,
222 pub training_file: String,
223 #[serde(skip_serializing_if = "Option::is_none")]
224 pub suffix: Option<String>,
225 #[serde(skip_serializing_if = "Option::is_none")]
226 pub validation_file: Option<String>,
227 #[serde(skip_serializing_if = "Option::is_none")]
228 pub seed: Option<i64>,
229 #[serde(skip_serializing_if = "Option::is_none")]
230 pub metadata: Option<HashMap<String, String>>,
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub hyperparameters: Option<Hyperparameters>,
234 #[serde(skip_serializing_if = "Option::is_none")]
235 pub method: Option<FineTuningMethod>,
236 #[serde(skip_serializing_if = "Option::is_none")]
237 pub integrations: Option<Vec<Integration>>,
238}
239
240impl FineTuningJobRequest {
241 pub fn new(model: impl Into<String>, training_file: impl Into<String>) -> Self {
243 Self {
244 model: model.into(),
245 training_file: training_file.into(),
246 suffix: None,
247 validation_file: None,
248 seed: None,
249 metadata: None,
250 hyperparameters: None,
251 method: None,
252 integrations: None,
253 }
254 }
255
256 pub fn suffix(mut self, suffix: impl Into<String>) -> Self {
257 self.suffix = Some(suffix.into());
258 self
259 }
260
261 pub fn validation_file(mut self, validation_file: impl Into<String>) -> Self {
262 self.validation_file = Some(validation_file.into());
263 self
264 }
265
266 pub fn seed(mut self, seed: i64) -> Self {
267 self.seed = Some(seed);
268 self
269 }
270
271 pub fn metadata(mut self, metadata: HashMap<String, String>) -> Self {
272 self.metadata = Some(metadata);
273 self
274 }
275
276 pub fn hyperparameters(mut self, hyperparameters: Hyperparameters) -> Self {
277 self.hyperparameters = Some(hyperparameters);
278 self
279 }
280
281 pub fn method(mut self, method: FineTuningMethod) -> Self {
282 self.method = Some(method);
283 self
284 }
285
286 pub fn integrations(mut self, integrations: Vec<Integration>) -> Self {
287 self.integrations = Some(integrations);
288 self
289 }
290}
291
292#[derive(Debug, Clone, Default)]
298pub struct FineTuningJobListParams {
299 pub after: Option<String>,
300 pub limit: Option<u32>,
301 pub metadata: Option<HashMap<String, String>>,
303}
304
305impl FineTuningJobListParams {
306 pub(crate) fn to_query(&self) -> Vec<(String, String)> {
307 let mut query =
308 crate::pagination::cursor_query(self.after.as_deref(), None, self.limit, None);
309 if let Some(metadata) = &self.metadata {
310 for (key, value) in metadata {
311 query.push((format!("metadata[{key}]"), value.clone()));
312 }
313 }
314 query
315 }
316}
317
318#[derive(Debug, Clone, Default)]
320pub struct FineTuningPageParams {
321 pub after: Option<String>,
322 pub limit: Option<u32>,
323}
324
325impl FineTuningPageParams {
326 pub(crate) fn to_query(&self) -> Vec<(String, String)> {
327 crate::pagination::cursor_query(self.after.as_deref(), None, self.limit, None)
328 }
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
337#[non_exhaustive]
338pub struct FineTuningJobError {
339 #[serde(default)]
340 pub code: Option<String>,
341 #[serde(default)]
342 pub message: Option<String>,
343 #[serde(default)]
344 pub param: Option<String>,
345}
346
347#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
349#[serde(rename_all = "snake_case")]
350pub enum FineTuningJobStatus {
351 ValidatingFiles,
352 Queued,
353 Running,
354 Succeeded,
355 Failed,
356 Cancelled,
357 #[serde(other)]
359 Unknown,
360}
361
362#[derive(Debug, Clone, Serialize, Deserialize)]
364#[non_exhaustive]
365pub struct FineTuningJob {
366 pub id: String,
367 #[serde(default)]
368 pub created_at: i64,
369 #[serde(default)]
370 pub error: Option<FineTuningJobError>,
371 #[serde(default)]
372 pub fine_tuned_model: Option<String>,
373 #[serde(default)]
374 pub finished_at: Option<i64>,
375 #[serde(default)]
376 pub hyperparameters: Option<Hyperparameters>,
377 #[serde(default)]
378 pub model: String,
379 #[serde(default)]
380 pub object: String,
381 #[serde(default)]
382 pub organization_id: Option<String>,
383 #[serde(default)]
384 pub result_files: Vec<String>,
385 #[serde(default)]
386 pub seed: Option<i64>,
387 #[serde(default)]
388 pub status: Option<FineTuningJobStatus>,
389 #[serde(default)]
390 pub trained_tokens: Option<i64>,
391 #[serde(default)]
392 pub training_file: String,
393 #[serde(default)]
394 pub validation_file: Option<String>,
395 #[serde(default)]
396 pub estimated_finish: Option<i64>,
397 #[serde(default)]
398 pub integrations: Option<Vec<serde_json::Value>>,
399 #[serde(default)]
400 pub metadata: Option<HashMap<String, String>>,
401 #[serde(default)]
402 pub method: Option<FineTuningMethod>,
403}
404
405impl HasId for FineTuningJob {
406 fn id(&self) -> Option<&str> {
407 Some(&self.id)
408 }
409}
410
411#[derive(Debug, Clone, Serialize, Deserialize)]
413#[non_exhaustive]
414pub struct FineTuningJobEvent {
415 pub id: String,
416 #[serde(default)]
417 pub created_at: i64,
418 #[serde(default)]
420 pub level: Option<String>,
421 #[serde(default)]
422 pub message: Option<String>,
423 #[serde(default)]
424 pub object: String,
425 #[serde(default, rename = "type")]
427 pub event_type: Option<String>,
428 #[serde(default)]
429 pub data: Option<serde_json::Value>,
430}
431
432impl HasId for FineTuningJobEvent {
433 fn id(&self) -> Option<&str> {
434 Some(&self.id)
435 }
436}
437
438#[derive(Debug, Clone, Default, Serialize, Deserialize)]
440#[non_exhaustive]
441pub struct FineTuningJobCheckpointMetrics {
442 #[serde(default)]
443 pub full_valid_loss: Option<f64>,
444 #[serde(default)]
445 pub full_valid_mean_token_accuracy: Option<f64>,
446 #[serde(default)]
447 pub step: Option<f64>,
448 #[serde(default)]
449 pub train_loss: Option<f64>,
450 #[serde(default)]
451 pub train_mean_token_accuracy: Option<f64>,
452 #[serde(default)]
453 pub valid_loss: Option<f64>,
454 #[serde(default)]
455 pub valid_mean_token_accuracy: Option<f64>,
456}
457
458#[derive(Debug, Clone, Serialize, Deserialize)]
460#[non_exhaustive]
461pub struct FineTuningJobCheckpoint {
462 pub id: String,
463 #[serde(default)]
464 pub created_at: i64,
465 #[serde(default)]
466 pub fine_tuned_model_checkpoint: String,
467 #[serde(default)]
468 pub fine_tuning_job_id: String,
469 #[serde(default)]
470 pub metrics: Option<FineTuningJobCheckpointMetrics>,
471 #[serde(default)]
472 pub object: String,
473 #[serde(default)]
474 pub step_number: i64,
475}
476
477impl HasId for FineTuningJobCheckpoint {
478 fn id(&self) -> Option<&str> {
479 Some(&self.id)
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486
487 #[test]
488 fn auto_or_serializes_auto_as_string() {
489 assert_eq!(
490 serde_json::to_value(AutoOr::<i64>::Auto).unwrap(),
491 serde_json::json!("auto")
492 );
493 }
494
495 #[test]
496 fn auto_or_serializes_value_as_number() {
497 assert_eq!(
498 serde_json::to_value(AutoOr::Value(3_i64)).unwrap(),
499 serde_json::json!(3)
500 );
501 assert_eq!(
502 serde_json::to_value(AutoOr::Value(0.5_f64)).unwrap(),
503 serde_json::json!(0.5)
504 );
505 }
506
507 #[test]
508 fn auto_or_deserializes_auto_and_number() {
509 let auto: AutoOr<i64> = serde_json::from_value(serde_json::json!("auto")).unwrap();
510 assert_eq!(auto, AutoOr::Auto);
511 let value: AutoOr<i64> = serde_json::from_value(serde_json::json!(4)).unwrap();
512 assert_eq!(value, AutoOr::Value(4));
513 let float: AutoOr<f64> = serde_json::from_value(serde_json::json!(0.25)).unwrap();
514 assert_eq!(float, AutoOr::Value(0.25));
515 }
516
517 #[test]
518 fn status_unknown_falls_back() {
519 let status: FineTuningJobStatus =
520 serde_json::from_value(serde_json::json!("brand_new_status")).unwrap();
521 assert_eq!(status, FineTuningJobStatus::Unknown);
522 let running: FineTuningJobStatus =
523 serde_json::from_value(serde_json::json!("running")).unwrap();
524 assert_eq!(running, FineTuningJobStatus::Running);
525 }
526}