ferrin_core/middleware/builtin/
default_settings.rs1use ferrin_spec::BoxFuture;
4use ferrin_spec::CallOptions;
5use ferrin_spec::Headers;
6use ferrin_spec::JsonObject;
7use ferrin_spec::JsonValue;
8use ferrin_spec::ProviderOptions;
9use ferrin_spec::ToolChoice;
10use ferrin_spec::ToolDefinition;
11use ferrin_spec::error::ProviderError;
12use ferrin_spec::language_model::ResponseFormat;
13
14use crate::middleware::LanguageModelMiddleware;
15use crate::middleware::MiddlewareContext;
16
17#[derive(Debug, Clone, Default)]
23pub struct CallDefaults {
24 pub max_output_tokens: Option<u32>,
26 pub temperature: Option<f64>,
28 pub stop_sequences: Option<Vec<String>>,
30 pub top_p: Option<f64>,
32 pub top_k: Option<u32>,
34 pub presence_penalty: Option<f64>,
36 pub frequency_penalty: Option<f64>,
38 pub response_format: Option<ResponseFormat>,
40 pub seed: Option<u64>,
42 pub tools: Vec<ToolDefinition>,
44 pub tool_choice: Option<ToolChoice>,
46 pub headers: Headers,
48 pub provider_options: ProviderOptions,
50}
51
52#[derive(Debug, Clone)]
54pub struct DefaultSettings {
55 defaults: CallDefaults,
56}
57
58#[must_use]
60pub fn default_settings(defaults: CallDefaults) -> DefaultSettings {
61 DefaultSettings { defaults }
62}
63
64impl DefaultSettings {
65 #[must_use]
67 pub fn apply(&self, mut options: CallOptions) -> CallOptions {
68 let defaults = &self.defaults;
69 macro_rules! fill {
70 ($($field:ident),* $(,)?) => {
71 $(
72 if options.$field.is_none() {
73 options.$field = defaults.$field.clone();
74 }
75 )*
76 };
77 }
78 fill!(
79 max_output_tokens,
80 temperature,
81 stop_sequences,
82 top_p,
83 top_k,
84 presence_penalty,
85 frequency_penalty,
86 response_format,
87 seed,
88 tool_choice,
89 );
90 if options.tools.is_empty() && !defaults.tools.is_empty() {
91 options.tools = defaults.tools.clone();
92 }
93 if !defaults.headers.is_empty() {
94 let mut headers = defaults.headers.clone();
95 headers.merge(&options.headers);
96 options.headers = headers;
97 }
98 if !defaults.provider_options.is_empty() {
99 options.provider_options =
100 merge_provider_options(&defaults.provider_options, options.provider_options);
101 }
102 options
103 }
104}
105
106impl LanguageModelMiddleware for DefaultSettings {
107 fn transform_params<'a>(
108 &'a self,
109 options: CallOptions,
110 _ctx: MiddlewareContext<'a>,
111 ) -> BoxFuture<'a, Result<CallOptions, ProviderError>> {
112 let options = self.apply(options);
113 Box::pin(async move { Ok(options) })
114 }
115}
116
117fn merge_provider_options(base: &ProviderOptions, overrides: ProviderOptions) -> ProviderOptions {
118 let mut merged = base.clone();
119 for (provider, options) in overrides {
120 match merged.remove(&provider) {
121 Some(existing) => {
122 merged.insert(provider, merge_json_objects(&existing, options));
123 }
124 None => {
125 merged.insert(provider, options);
126 }
127 }
128 }
129 merged
130}
131
132#[must_use]
136pub fn merge_json_objects(base: &JsonObject, overrides: JsonObject) -> JsonObject {
137 let mut merged = base.clone();
138 for (key, value) in overrides {
139 match (merged.remove(&key), value) {
140 (Some(JsonValue::Object(existing)), JsonValue::Object(incoming)) => {
141 merged.insert(
142 key,
143 JsonValue::Object(merge_json_objects(&existing, incoming)),
144 );
145 }
146 (_, value) => {
147 merged.insert(key, value);
148 }
149 }
150 }
151 merged
152}