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
117pub(super) fn merge_provider_options(
118 base: &ProviderOptions,
119 overrides: ProviderOptions,
120) -> ProviderOptions {
121 let mut merged = base.clone();
122 for (provider, options) in overrides {
123 match merged.remove(&provider) {
124 Some(existing) => {
125 merged.insert(provider, merge_json_objects(&existing, options));
126 }
127 None => {
128 merged.insert(provider, options);
129 }
130 }
131 }
132 merged
133}
134
135#[must_use]
139pub fn merge_json_objects(base: &JsonObject, overrides: JsonObject) -> JsonObject {
140 let mut merged = base.clone();
141 for (key, value) in overrides {
142 match (merged.remove(&key), value) {
143 (Some(JsonValue::Object(existing)), JsonValue::Object(incoming)) => {
144 merged.insert(
145 key,
146 JsonValue::Object(merge_json_objects(&existing, incoming)),
147 );
148 }
149 (_, value) => {
150 merged.insert(key, value);
151 }
152 }
153 }
154 merged
155}