1use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7use super::auth::EndpointAuthConfig;
8use crate::llm::ReasoningEffort;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "kebab-case")]
13pub enum EmbeddingAdapter {
14 Tei,
16 OpenAi,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(deny_unknown_fields)]
23pub struct EmbeddingConfig {
24 pub base_url: String,
25 #[serde(flatten)]
26 pub auth: EndpointAuthConfig,
27 pub adapter: EmbeddingAdapter,
28 pub model: String,
30 #[serde(default)]
32 pub document_model: Option<String>,
33 #[serde(default = "default_embedding_context_tokens")]
35 pub context_tokens: u32,
36}
37
38impl EmbeddingConfig {
39 #[must_use]
41 pub fn document_model(&self) -> &str {
42 self.document_model.as_deref().unwrap_or(&self.model)
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "kebab-case")]
49pub enum RerankAdapter {
50 Tei,
52 Minimal,
54 Cohere,
56 Jina,
58}
59
60#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(rename_all = "kebab-case")]
63pub enum RerankScoreScale {
64 #[default]
66 Normalized,
67 Logits,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73#[serde(deny_unknown_fields)]
74pub struct RerankConfig {
75 pub base_url: String,
76 #[serde(flatten)]
77 pub auth: EndpointAuthConfig,
78 pub adapter: RerankAdapter,
79 pub model: String,
80 #[serde(default)]
81 pub score_scale: RerankScoreScale,
82 #[serde(default = "default_rerank_truncate")]
84 pub truncate: bool,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
89#[serde(rename_all = "kebab-case")]
90pub enum ChatAdapter {
91 #[default]
93 OpenAi,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98#[serde(deny_unknown_fields)]
99pub struct ChatExpansionConfig {
100 pub base_url: String,
101 #[serde(flatten)]
102 pub auth: EndpointAuthConfig,
103 #[serde(default)]
104 pub adapter: ChatAdapter,
105 pub model: String,
106 #[serde(default = "default_chat_context_tokens")]
107 pub context_tokens: u32,
108 #[serde(default)]
109 pub max_output_tokens: Option<u32>,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
116#[serde(deny_unknown_fields)]
117pub struct ChatAskConfig {
118 #[serde(default)]
119 pub base_url: Option<String>,
120 #[serde(flatten)]
121 pub auth: EndpointAuthConfig,
122 #[serde(default)]
123 pub adapter: Option<ChatAdapter>,
124 #[serde(default)]
125 pub model: Option<String>,
126 #[serde(default = "default_ask_context_tokens")]
127 pub context_tokens: u32,
128 #[serde(default = "default_ask_max_output_tokens")]
129 pub max_output_tokens: u32,
130 #[serde(default)]
131 pub planning_enable_thinking: Option<bool>,
132 #[serde(default)]
133 pub synthesis_enable_thinking: Option<bool>,
134 #[serde(default)]
135 pub planning_reasoning_effort: Option<ReasoningEffort>,
136 #[serde(default)]
137 pub synthesis_reasoning_effort: Option<ReasoningEffort>,
138 #[serde(default)]
139 pub planning_chat_template_kwargs: Option<BTreeMap<String, serde_json::Value>>,
140 #[serde(default)]
141 pub synthesis_chat_template_kwargs: Option<BTreeMap<String, serde_json::Value>>,
142}
143
144impl ChatAskConfig {
145 #[must_use]
147 pub fn resolved_base_url<'a>(&'a self, expansion: &'a ChatExpansionConfig) -> &'a str {
148 self.base_url
149 .as_deref()
150 .filter(|url| !url.is_empty())
151 .unwrap_or(expansion.base_url.as_str())
152 }
153
154 #[must_use]
156 pub fn resolved_model<'a>(&'a self, expansion: &'a ChatExpansionConfig) -> &'a str {
157 self.model
158 .as_deref()
159 .filter(|model| !model.is_empty())
160 .unwrap_or(expansion.model.as_str())
161 }
162
163 #[must_use]
165 pub fn resolved_adapter(&self, expansion: &ChatExpansionConfig) -> ChatAdapter {
166 self.adapter.unwrap_or(expansion.adapter)
167 }
168
169 #[must_use]
171 pub fn resolved_auth(&self, expansion: &ChatExpansionConfig) -> EndpointAuthConfig {
172 EndpointAuthConfig {
173 credential: self
174 .auth
175 .credential
176 .clone()
177 .or_else(|| expansion.auth.credential.clone()),
178 api_key: self
179 .auth
180 .api_key
181 .clone()
182 .or_else(|| expansion.auth.api_key.clone()),
183 api_key_env: self
184 .auth
185 .api_key_env
186 .clone()
187 .or_else(|| expansion.auth.api_key_env.clone()),
188 extra_headers: merge_headers(&expansion.auth.extra_headers, &self.auth.extra_headers),
189 }
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
195#[serde(deny_unknown_fields)]
196pub struct ChatSection {
197 pub expansion: ChatExpansionConfig,
198 #[serde(default)]
199 pub ask: ChatAskConfig,
200}
201
202#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
204#[serde(deny_unknown_fields)]
205pub struct McpConfig {
206 #[serde(default)]
208 pub hooks: McpHooksConfig,
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
213#[serde(deny_unknown_fields)]
214pub struct McpHooksConfig {
215 #[serde(default = "default_recall_deadline_ms")]
217 pub recall_deadline_ms: u64,
218}
219
220impl Default for McpHooksConfig {
221 fn default() -> Self {
222 Self {
223 recall_deadline_ms: default_recall_deadline_ms(),
224 }
225 }
226}
227
228fn merge_headers(
229 base: &BTreeMap<String, String>,
230 override_headers: &BTreeMap<String, String>,
231) -> BTreeMap<String, String> {
232 let mut merged = base.clone();
233 merged.extend(override_headers.iter().map(|(k, v)| (k.clone(), v.clone())));
234 merged
235}
236
237const fn default_embedding_context_tokens() -> u32 {
238 512
239}
240
241const fn default_rerank_truncate() -> bool {
242 true
243}
244
245const fn default_chat_context_tokens() -> u32 {
246 32_768
247}
248
249const fn default_ask_context_tokens() -> u32 {
250 65_536
251}
252
253const fn default_ask_max_output_tokens() -> u32 {
254 2_048
255}
256
257const fn default_recall_deadline_ms() -> u64 {
258 20_000
259}