ferrin_core/prompt/
call_settings.rs1use ferrin_spec::CallOptions;
4use ferrin_spec::Headers;
5use ferrin_spec::ProviderOptions;
6use ferrin_spec::ReasoningEffort;
7
8use crate::error::Error;
9
10#[derive(Debug, Clone, Default, PartialEq)]
15pub struct CallSettings {
16 pub max_output_tokens: Option<u32>,
18 pub temperature: Option<f64>,
20 pub top_p: Option<f64>,
22 pub top_k: Option<u32>,
24 pub presence_penalty: Option<f64>,
26 pub frequency_penalty: Option<f64>,
28 pub stop_sequences: Option<Vec<String>>,
30 pub seed: Option<u64>,
32 pub reasoning: ReasoningEffort,
34 pub headers: Headers,
36 pub provider_options: ProviderOptions,
38}
39
40impl CallSettings {
41 pub fn validate(&self) -> Result<(), Error> {
47 if self.max_output_tokens == Some(0) {
48 return Err(Error::invalid_argument(
49 "max_output_tokens",
50 "must be at least 1",
51 ));
52 }
53 for (name, value) in [
54 ("temperature", self.temperature),
55 ("top_p", self.top_p),
56 ("presence_penalty", self.presence_penalty),
57 ("frequency_penalty", self.frequency_penalty),
58 ] {
59 if let Some(value) = value
60 && !value.is_finite()
61 {
62 return Err(Error::invalid_argument(name, "must be a finite number"));
63 }
64 }
65 Ok(())
66 }
67
68 pub fn apply(&self, options: &mut CallOptions) {
71 options.max_output_tokens = self.max_output_tokens;
72 options.temperature = self.temperature;
73 options.top_p = self.top_p;
74 options.top_k = self.top_k;
75 options.presence_penalty = self.presence_penalty;
76 options.frequency_penalty = self.frequency_penalty;
77 options.stop_sequences = self.stop_sequences.clone();
78 options.seed = self.seed;
79 options.reasoning = self.reasoning;
80 options.headers.merge(&self.headers);
81 for (key, value) in &self.provider_options {
82 options
83 .provider_options
84 .entry(key.clone())
85 .or_default()
86 .extend(value.clone());
87 }
88 }
89
90 pub fn merge(&mut self, other: &CallSettings) {
93 if other.max_output_tokens.is_some() {
94 self.max_output_tokens = other.max_output_tokens;
95 }
96 if other.temperature.is_some() {
97 self.temperature = other.temperature;
98 }
99 if other.top_p.is_some() {
100 self.top_p = other.top_p;
101 }
102 if other.top_k.is_some() {
103 self.top_k = other.top_k;
104 }
105 if other.presence_penalty.is_some() {
106 self.presence_penalty = other.presence_penalty;
107 }
108 if other.frequency_penalty.is_some() {
109 self.frequency_penalty = other.frequency_penalty;
110 }
111 if other.stop_sequences.is_some() {
112 self.stop_sequences.clone_from(&other.stop_sequences);
113 }
114 if other.seed.is_some() {
115 self.seed = other.seed;
116 }
117 if other.reasoning != ReasoningEffort::default() {
118 self.reasoning = other.reasoning;
119 }
120 self.headers.merge(&other.headers);
121 for (key, value) in &other.provider_options {
122 self.provider_options
123 .entry(key.clone())
124 .or_default()
125 .extend(value.clone());
126 }
127 }
128}