1use anyhow::{Result, bail};
8use compact_str::CompactString;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Serialize, Deserialize, Clone)]
14pub struct ProviderConfig {
15 pub model: CompactString,
19 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub api_key: Option<String>,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub base_url: Option<String>,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub loader: Option<Loader>,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub quantization: Option<QuantizationType>,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub chat_template: Option<String>,
35}
36
37impl ProviderConfig {
38 pub fn kind(&self) -> Result<ProviderKind> {
40 ProviderKind::from_model(&self.model)
41 }
42
43 pub fn validate(&self) -> Result<()> {
47 if self.model.is_empty() {
48 bail!("model is required");
49 }
50
51 let kind = self.kind()?;
52
53 match kind {
54 ProviderKind::Local => {
55 if self.api_key.is_some() {
56 bail!("local provider '{}' must not have api_key", self.model);
57 }
58 }
59 _ => {
60 if self.api_key.is_none() && self.base_url.is_none() {
63 bail!(
64 "remote provider '{}' requires api_key or base_url",
65 self.model
66 );
67 }
68 if self.loader.is_some() {
69 bail!(
70 "remote provider '{}' must not have loader field",
71 self.model
72 );
73 }
74 if self.quantization.is_some() {
75 bail!(
76 "remote provider '{}' must not have quantization field",
77 self.model
78 );
79 }
80 if self.chat_template.is_some() {
81 bail!(
82 "remote provider '{}' must not have chat_template field",
83 self.model
84 );
85 }
86 }
87 }
88
89 Ok(())
90 }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum ProviderKind {
98 DeepSeek,
99 OpenAI,
100 Claude,
101 Grok,
102 Qwen,
103 Kimi,
104 Local,
105}
106
107impl ProviderKind {
108 pub fn from_model(model: &str) -> Result<Self> {
115 if model.contains('/') {
116 return Ok(Self::Local);
117 }
118
119 let prefixes: &[(&[&str], ProviderKind)] = &[
120 (&["deepseek-"], ProviderKind::DeepSeek),
121 (&["gpt-", "o1-", "o3-", "o4-"], ProviderKind::OpenAI),
122 (&["claude-"], ProviderKind::Claude),
123 (&["grok-"], ProviderKind::Grok),
124 (&["qwen-", "qwq-"], ProviderKind::Qwen),
125 (&["kimi-", "moonshot-"], ProviderKind::Kimi),
126 ];
127
128 for (patterns, kind) in prefixes {
129 for prefix in *patterns {
130 if model.starts_with(prefix) {
131 return Ok(*kind);
132 }
133 }
134 }
135
136 bail!("unknown model prefix: '{model}' — cannot detect provider kind")
137 }
138
139 pub fn as_str(self) -> &'static str {
141 match self {
142 Self::DeepSeek => "deepseek",
143 Self::OpenAI => "openai",
144 Self::Claude => "claude",
145 Self::Grok => "grok",
146 Self::Qwen => "qwen",
147 Self::Kimi => "kimi",
148 Self::Local => "local",
149 }
150 }
151}
152
153#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default)]
157#[serde(rename_all = "snake_case")]
158pub enum Loader {
159 #[default]
161 Text,
162 Lora,
164 #[serde(rename = "xlora")]
166 XLora,
167 Gguf,
169 #[serde(rename = "gguf_lora")]
171 GgufLora,
172 #[serde(rename = "gguf_xlora")]
174 GgufXLora,
175 Vision,
177}
178
179#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
181pub enum QuantizationType {
182 #[serde(rename = "q4_0")]
183 Q4_0,
184 #[serde(rename = "q4_1")]
185 Q4_1,
186 #[serde(rename = "q5_0")]
187 Q5_0,
188 #[serde(rename = "q5_1")]
189 Q5_1,
190 #[serde(rename = "q8_0")]
191 Q8_0,
192 #[serde(rename = "q8_1")]
193 Q8_1,
194 #[serde(rename = "q2k")]
195 Q2K,
196 #[serde(rename = "q3k")]
197 Q3K,
198 #[serde(rename = "q4k")]
199 Q4K,
200 #[serde(rename = "q5k")]
201 Q5K,
202 #[serde(rename = "q6k")]
203 Q6K,
204 #[serde(rename = "q8k")]
205 Q8K,
206}
207
208#[cfg(feature = "local")]
209impl QuantizationType {
210 pub fn to_isq(self) -> mistralrs::IsqType {
212 match self {
213 Self::Q4_0 => mistralrs::IsqType::Q4_0,
214 Self::Q4_1 => mistralrs::IsqType::Q4_1,
215 Self::Q5_0 => mistralrs::IsqType::Q5_0,
216 Self::Q5_1 => mistralrs::IsqType::Q5_1,
217 Self::Q8_0 => mistralrs::IsqType::Q8_0,
218 Self::Q8_1 => mistralrs::IsqType::Q8_1,
219 Self::Q2K => mistralrs::IsqType::Q2K,
220 Self::Q3K => mistralrs::IsqType::Q3K,
221 Self::Q4K => mistralrs::IsqType::Q4K,
222 Self::Q5K => mistralrs::IsqType::Q5K,
223 Self::Q6K => mistralrs::IsqType::Q6K,
224 Self::Q8K => mistralrs::IsqType::Q8K,
225 }
226 }
227}