Skip to main content

vv_llm/
types.rs

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}