1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
5#[serde(rename_all = "lowercase")]
6pub enum BackendType {
7 OpenAI,
8 ZhiPuAI,
9 MiniMax,
10 Moonshot,
11 Anthropic,
12 Mistral,
13 DeepSeek,
14 Qwen,
15 Groq,
16 Local,
17 Yi,
18 Gemini,
19 Baichuan,
20 StepFun,
21 XAI,
22 Xiaomi,
23 Ernie,
24}
25
26impl BackendType {
27 pub fn as_str(self) -> &'static str {
28 match self {
29 Self::OpenAI => "openai",
30 Self::ZhiPuAI => "zhipuai",
31 Self::MiniMax => "minimax",
32 Self::Moonshot => "moonshot",
33 Self::Anthropic => "anthropic",
34 Self::Mistral => "mistral",
35 Self::DeepSeek => "deepseek",
36 Self::Qwen => "qwen",
37 Self::Groq => "groq",
38 Self::Local => "local",
39 Self::Yi => "yi",
40 Self::Gemini => "gemini",
41 Self::Baichuan => "baichuan",
42 Self::StepFun => "stepfun",
43 Self::XAI => "xai",
44 Self::Xiaomi => "xiaomi",
45 Self::Ernie => "ernie",
46 }
47 }
48}
49
50impl fmt::Display for BackendType {
51 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
52 formatter.write_str(self.as_str())
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
57#[serde(rename_all = "lowercase")]
58pub enum MessageRole {
59 System,
60 User,
61 Assistant,
62 Tool,
63}
64
65impl MessageRole {
66 pub fn as_str(self) -> &'static str {
67 match self {
68 Self::System => "system",
69 Self::User => "user",
70 Self::Assistant => "assistant",
71 Self::Tool => "tool",
72 }
73 }
74}
75
76impl fmt::Display for MessageRole {
77 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78 formatter.write_str(self.as_str())
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
83#[serde(tag = "type", rename_all = "snake_case")]
84pub enum MessageContent {
85 Text {
86 text: String,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
88 cache_control: Option<serde_json::Value>,
89 },
90 ImageUrl {
91 url: String,
92 },
93}
94
95impl MessageContent {
96 pub fn text(text: impl Into<String>) -> Self {
97 Self::Text {
98 text: text.into(),
99 cache_control: None,
100 }
101 }
102
103 pub fn text_with_cache_control(
104 text: impl Into<String>,
105 cache_control: serde_json::Value,
106 ) -> Self {
107 Self::Text {
108 text: text.into(),
109 cache_control: Some(cache_control),
110 }
111 }
112}
113
114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub struct Message {
116 pub role: MessageRole,
117 pub content: Vec<MessageContent>,
118 #[serde(default, skip_serializing_if = "Option::is_none")]
119 pub name: Option<String>,
120 #[serde(default, skip_serializing_if = "Option::is_none")]
121 pub tool_call_id: Option<String>,
122 #[serde(default, skip_serializing_if = "Vec::is_empty")]
123 pub tool_calls: Vec<ToolCall>,
124 #[serde(default, skip_serializing_if = "Option::is_none")]
125 pub reasoning_content: Option<String>,
126}
127
128impl Message {
129 pub fn text(role: MessageRole, text: impl Into<String>) -> Self {
130 Self {
131 role,
132 content: vec![MessageContent::text(text)],
133 name: None,
134 tool_call_id: None,
135 tool_calls: Vec::new(),
136 reasoning_content: None,
137 }
138 }
139
140 pub fn text_content(&self) -> Option<String> {
141 let mut parts = Vec::new();
142 for content in &self.content {
143 if let MessageContent::Text { text, .. } = content {
144 parts.push(text.as_str());
145 }
146 }
147 if parts.is_empty() {
148 None
149 } else {
150 Some(parts.join("\n"))
151 }
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
156pub struct ChatRequestOptions {
157 #[serde(default, skip_serializing_if = "Option::is_none")]
158 pub temperature: Option<f32>,
159 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub max_tokens: Option<u32>,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub max_completion_tokens: Option<u32>,
163 #[serde(default, skip_serializing_if = "Option::is_none")]
164 pub stream: Option<bool>,
165 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub top_p: Option<f32>,
167 #[serde(default, skip_serializing_if = "Vec::is_empty")]
168 pub stop: Vec<String>,
169 #[serde(default, skip_serializing_if = "Option::is_none")]
170 pub response_format: Option<serde_json::Value>,
171 #[serde(default, skip_serializing_if = "Option::is_none")]
172 pub stream_options: Option<serde_json::Value>,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
174 pub audio: Option<serde_json::Value>,
175 #[serde(default, skip_serializing_if = "Option::is_none")]
176 pub frequency_penalty: Option<f32>,
177 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub logit_bias: Option<serde_json::Value>,
179 #[serde(default, skip_serializing_if = "Option::is_none")]
180 pub logprobs: Option<bool>,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
182 pub metadata: Option<serde_json::Value>,
183 #[serde(default, skip_serializing_if = "Option::is_none")]
184 pub modalities: Option<serde_json::Value>,
185 #[serde(default, skip_serializing_if = "Option::is_none")]
186 pub n: Option<u32>,
187 #[serde(default, skip_serializing_if = "Option::is_none")]
188 pub parallel_tool_calls: Option<bool>,
189 #[serde(default, skip_serializing_if = "Option::is_none")]
190 pub prediction: Option<serde_json::Value>,
191 #[serde(default, skip_serializing_if = "Option::is_none")]
192 pub presence_penalty: Option<f32>,
193 #[serde(default, skip_serializing_if = "Option::is_none")]
194 pub reasoning_effort: Option<String>,
195 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub seed: Option<i64>,
197 #[serde(default, skip_serializing_if = "Option::is_none")]
198 pub service_tier: Option<String>,
199 #[serde(default, skip_serializing_if = "Option::is_none")]
200 pub store: Option<bool>,
201 #[serde(default, skip_serializing_if = "Option::is_none")]
202 pub top_logprobs: Option<u32>,
203 #[serde(default, skip_serializing_if = "Option::is_none")]
204 pub user: Option<String>,
205}
206
207impl ChatRequestOptions {
208 pub fn has_openai_json_extensions(&self) -> bool {
209 self.max_completion_tokens.is_some()
210 || self.top_p.is_some()
211 || !self.stop.is_empty()
212 || self.response_format.is_some()
213 || self.stream_options.is_some()
214 || self.audio.is_some()
215 || self.frequency_penalty.is_some()
216 || self.logit_bias.is_some()
217 || self.logprobs.is_some()
218 || self.metadata.is_some()
219 || self.modalities.is_some()
220 || self.n.is_some()
221 || self.parallel_tool_calls.is_some()
222 || self.prediction.is_some()
223 || self.presence_penalty.is_some()
224 || self.reasoning_effort.is_some()
225 || self.seed.is_some()
226 || self.service_tier.is_some()
227 || self.store.is_some()
228 || self.top_logprobs.is_some()
229 || self.user.is_some()
230 }
231}
232
233#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
234pub struct ChatRequest {
235 pub model: String,
236 pub messages: Vec<Message>,
237 #[serde(default)]
238 pub options: ChatRequestOptions,
239 #[serde(default, skip_serializing_if = "Vec::is_empty")]
240 pub tools: Vec<ChatTool>,
241 #[serde(default, skip_serializing_if = "Option::is_none")]
242 pub tool_choice: Option<String>,
243 #[serde(default, skip_serializing_if = "is_empty_extra_body")]
244 pub extra_body: serde_json::Value,
245}
246
247impl ChatRequest {
248 pub fn new(model: impl Into<String>, messages: Vec<Message>) -> Self {
249 Self {
250 model: model.into(),
251 messages,
252 options: ChatRequestOptions::default(),
253 tools: Vec::new(),
254 tool_choice: None,
255 extra_body: serde_json::Value::Null,
256 }
257 }
258}
259
260#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
261pub struct ChatTool {
262 pub name: String,
263 #[serde(default, skip_serializing_if = "Option::is_none")]
264 pub description: Option<String>,
265 pub parameters: serde_json::Value,
266 #[serde(default, skip_serializing_if = "Option::is_none")]
267 pub cache_control: Option<serde_json::Value>,
268}
269
270impl ChatTool {
271 pub fn function(
272 name: impl Into<String>,
273 description: impl Into<String>,
274 parameters: serde_json::Value,
275 ) -> Self {
276 Self {
277 name: name.into(),
278 description: Some(description.into()),
279 parameters,
280 cache_control: None,
281 }
282 }
283
284 pub fn with_cache_control(mut self, cache_control: serde_json::Value) -> Self {
285 self.cache_control = Some(cache_control);
286 self
287 }
288}
289
290#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
291pub struct ToolCall {
292 pub id: String,
293 pub name: String,
294 pub arguments: String,
295 #[serde(default, skip_serializing_if = "Option::is_none")]
296 pub index: Option<usize>,
297 #[serde(default, skip_serializing_if = "Option::is_none")]
298 pub extra_content: Option<serde_json::Value>,
299}
300
301impl ToolCall {
302 pub fn function(
303 id: impl Into<String>,
304 name: impl Into<String>,
305 arguments: impl Into<String>,
306 ) -> Self {
307 Self {
308 id: id.into(),
309 name: name.into(),
310 arguments: arguments.into(),
311 index: None,
312 extra_content: None,
313 }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
318pub struct ChatResponse {
319 pub id: String,
320 pub model: String,
321 pub content: String,
322 #[serde(default, skip_serializing_if = "Vec::is_empty")]
323 pub tool_calls: Vec<ToolCall>,
324 #[serde(default, skip_serializing_if = "Option::is_none")]
325 pub reasoning_content: Option<String>,
326 #[serde(default, skip_serializing_if = "Option::is_none")]
327 pub usage: Option<ChatUsage>,
328}
329
330#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
331pub struct ChatStreamDelta {
332 #[serde(default, skip_serializing_if = "String::is_empty")]
333 pub content: String,
334 #[serde(default, skip_serializing_if = "String::is_empty")]
335 pub reasoning_content: String,
336 #[serde(default, skip_serializing_if = "Vec::is_empty")]
337 pub tool_calls: Vec<ToolCall>,
338 #[serde(default, skip_serializing_if = "Option::is_none")]
339 pub usage: Option<ChatUsage>,
340 #[serde(default, skip_serializing_if = "Option::is_none")]
341 pub raw_content: Option<serde_json::Value>,
342 #[serde(default, skip_serializing_if = "is_false")]
343 pub done: bool,
344}
345
346#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
347pub struct ChatUsage {
348 #[serde(default, skip_serializing_if = "Option::is_none")]
349 pub prompt_tokens: Option<u32>,
350 #[serde(default, skip_serializing_if = "Option::is_none")]
351 pub completion_tokens: Option<u32>,
352 #[serde(default, skip_serializing_if = "Option::is_none")]
353 pub total_tokens: Option<u32>,
354 #[serde(default, skip_serializing_if = "Option::is_none")]
355 pub input_tokens: Option<u32>,
356 #[serde(default, skip_serializing_if = "Option::is_none")]
357 pub output_tokens: Option<u32>,
358 #[serde(default, skip_serializing_if = "Option::is_none")]
359 pub cache_read_input_tokens: Option<u32>,
360 #[serde(default, skip_serializing_if = "Option::is_none")]
361 pub cache_creation_input_tokens: Option<u32>,
362 #[serde(default, skip_serializing_if = "Option::is_none")]
363 pub raw_usage: Option<serde_json::Value>,
364}
365
366fn is_false(value: &bool) -> bool {
367 !*value
368}
369
370fn is_empty_extra_body(value: &serde_json::Value) -> bool {
371 match value {
372 serde_json::Value::Null => true,
373 serde_json::Value::Object(object) => object.is_empty(),
374 _ => false,
375 }
376}
377
378#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
379pub struct EmbeddingResponse {
380 pub model: String,
381 pub data: Vec<EmbeddingData>,
382}
383
384#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
385pub struct EmbeddingData {
386 pub index: u32,
387 pub embedding: Vec<f32>,
388}
389
390#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
391pub struct RerankResponse {
392 pub results: Vec<RerankResult>,
393}
394
395#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
396pub struct RerankResult {
397 pub index: usize,
398 pub relevance_score: f32,
399}
400
401#[derive(Debug, thiserror::Error)]
402pub enum VvLlmError {
403 #[error("configuration error: {0}")]
404 Configuration(String),
405 #[error("model not found: backend={backend} model={model}")]
406 ModelNotFound { backend: String, model: String },
407 #[error("endpoint not found: {0}")]
408 EndpointNotFound(String),
409 #[error("serialization error: {0}")]
410 Serialization(#[from] serde_json::Error),
411 #[error("http error: {0}")]
412 Http(String),
413 #[error("provider error: {0}")]
414 Provider(String),
415}