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;
9use crate::middleware::builtin::merge_provider_options;
10
11#[derive(Debug, Clone, Default, PartialEq)]
16pub struct CallSettings {
17 pub max_output_tokens: Option<u32>,
19 pub temperature: Option<f64>,
21 pub top_p: Option<f64>,
23 pub top_k: Option<u32>,
25 pub presence_penalty: Option<f64>,
27 pub frequency_penalty: Option<f64>,
29 pub stop_sequences: Option<Vec<String>>,
31 pub seed: Option<u64>,
33 pub reasoning: ReasoningEffort,
35 pub headers: Headers,
37 pub provider_options: ProviderOptions,
39}
40
41impl CallSettings {
42 pub fn validate(&self) -> Result<(), Error> {
48 if self.max_output_tokens == Some(0) {
49 return Err(Error::invalid_argument(
50 "max_output_tokens",
51 "must be at least 1",
52 ));
53 }
54 for (name, value) in [
55 ("temperature", self.temperature),
56 ("top_p", self.top_p),
57 ("presence_penalty", self.presence_penalty),
58 ("frequency_penalty", self.frequency_penalty),
59 ] {
60 if let Some(value) = value
61 && !value.is_finite()
62 {
63 return Err(Error::invalid_argument(name, "must be a finite number"));
64 }
65 }
66 Ok(())
67 }
68
69 pub fn apply(&self, options: &mut CallOptions) {
72 options.max_output_tokens = self.max_output_tokens;
73 options.temperature = self.temperature;
74 options.top_p = self.top_p;
75 options.top_k = self.top_k;
76 options.presence_penalty = self.presence_penalty;
77 options.frequency_penalty = self.frequency_penalty;
78 options.stop_sequences = self.stop_sequences.clone();
79 options.seed = self.seed;
80 options.reasoning = self.reasoning;
81 options.headers.merge(&self.headers);
82 options.provider_options =
83 merge_provider_options(&options.provider_options, self.provider_options.clone());
84 }
85
86 pub fn merge(&mut self, other: &CallSettings) {
89 if other.max_output_tokens.is_some() {
90 self.max_output_tokens = other.max_output_tokens;
91 }
92 if other.temperature.is_some() {
93 self.temperature = other.temperature;
94 }
95 if other.top_p.is_some() {
96 self.top_p = other.top_p;
97 }
98 if other.top_k.is_some() {
99 self.top_k = other.top_k;
100 }
101 if other.presence_penalty.is_some() {
102 self.presence_penalty = other.presence_penalty;
103 }
104 if other.frequency_penalty.is_some() {
105 self.frequency_penalty = other.frequency_penalty;
106 }
107 if other.stop_sequences.is_some() {
108 self.stop_sequences.clone_from(&other.stop_sequences);
109 }
110 if other.seed.is_some() {
111 self.seed = other.seed;
112 }
113 if other.reasoning != ReasoningEffort::default() {
114 self.reasoning = other.reasoning;
115 }
116 self.headers.merge(&other.headers);
117 self.provider_options =
118 merge_provider_options(&self.provider_options, other.provider_options.clone());
119 }
120}