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 stream: Option<bool>,
163}
164
165#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
166pub struct ChatRequest {
167 pub model: String,
168 pub messages: Vec<Message>,
169 #[serde(default)]
170 pub options: ChatRequestOptions,
171 #[serde(default, skip_serializing_if = "Vec::is_empty")]
172 pub tools: Vec<ChatTool>,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
174 pub tool_choice: Option<String>,
175 #[serde(default, skip_serializing_if = "is_empty_extra_body")]
176 pub extra_body: serde_json::Value,
177}
178
179impl ChatRequest {
180 pub fn new(model: impl Into<String>, messages: Vec<Message>) -> Self {
181 Self {
182 model: model.into(),
183 messages,
184 options: ChatRequestOptions::default(),
185 tools: Vec::new(),
186 tool_choice: None,
187 extra_body: serde_json::Value::Null,
188 }
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193pub struct ChatTool {
194 pub name: String,
195 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub description: Option<String>,
197 pub parameters: serde_json::Value,
198 #[serde(default, skip_serializing_if = "Option::is_none")]
199 pub cache_control: Option<serde_json::Value>,
200}
201
202impl ChatTool {
203 pub fn function(
204 name: impl Into<String>,
205 description: impl Into<String>,
206 parameters: serde_json::Value,
207 ) -> Self {
208 Self {
209 name: name.into(),
210 description: Some(description.into()),
211 parameters,
212 cache_control: None,
213 }
214 }
215
216 pub fn with_cache_control(mut self, cache_control: serde_json::Value) -> Self {
217 self.cache_control = Some(cache_control);
218 self
219 }
220}
221
222#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
223pub struct ToolCall {
224 pub id: String,
225 pub name: String,
226 pub arguments: String,
227 #[serde(default, skip_serializing_if = "Option::is_none")]
228 pub index: Option<usize>,
229 #[serde(default, skip_serializing_if = "Option::is_none")]
230 pub extra_content: Option<serde_json::Value>,
231}
232
233impl ToolCall {
234 pub fn function(
235 id: impl Into<String>,
236 name: impl Into<String>,
237 arguments: impl Into<String>,
238 ) -> Self {
239 Self {
240 id: id.into(),
241 name: name.into(),
242 arguments: arguments.into(),
243 index: None,
244 extra_content: None,
245 }
246 }
247}
248
249#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
250pub struct ChatResponse {
251 pub id: String,
252 pub model: String,
253 pub content: String,
254 #[serde(default, skip_serializing_if = "Vec::is_empty")]
255 pub tool_calls: Vec<ToolCall>,
256 #[serde(default, skip_serializing_if = "Option::is_none")]
257 pub reasoning_content: Option<String>,
258 #[serde(default, skip_serializing_if = "Option::is_none")]
259 pub usage: Option<ChatUsage>,
260}
261
262#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
263pub struct ChatStreamDelta {
264 #[serde(default, skip_serializing_if = "String::is_empty")]
265 pub content: String,
266 #[serde(default, skip_serializing_if = "String::is_empty")]
267 pub reasoning_content: String,
268 #[serde(default, skip_serializing_if = "Vec::is_empty")]
269 pub tool_calls: Vec<ToolCall>,
270 #[serde(default, skip_serializing_if = "Option::is_none")]
271 pub usage: Option<ChatUsage>,
272 #[serde(default, skip_serializing_if = "Option::is_none")]
273 pub raw_content: Option<serde_json::Value>,
274 #[serde(default, skip_serializing_if = "is_false")]
275 pub done: bool,
276}
277
278#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
279pub struct ChatUsage {
280 #[serde(default, skip_serializing_if = "Option::is_none")]
281 pub prompt_tokens: Option<u32>,
282 #[serde(default, skip_serializing_if = "Option::is_none")]
283 pub completion_tokens: Option<u32>,
284 #[serde(default, skip_serializing_if = "Option::is_none")]
285 pub total_tokens: Option<u32>,
286}
287
288fn is_false(value: &bool) -> bool {
289 !*value
290}
291
292fn is_empty_extra_body(value: &serde_json::Value) -> bool {
293 match value {
294 serde_json::Value::Null => true,
295 serde_json::Value::Object(object) => object.is_empty(),
296 _ => false,
297 }
298}
299
300#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
301pub struct EmbeddingResponse {
302 pub model: String,
303 pub data: Vec<EmbeddingData>,
304}
305
306#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
307pub struct EmbeddingData {
308 pub index: u32,
309 pub embedding: Vec<f32>,
310}
311
312#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
313pub struct RerankResponse {
314 pub results: Vec<RerankResult>,
315}
316
317#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
318pub struct RerankResult {
319 pub index: usize,
320 pub relevance_score: f32,
321}
322
323#[derive(Debug, thiserror::Error)]
324pub enum VvLlmError {
325 #[error("configuration error: {0}")]
326 Configuration(String),
327 #[error("model not found: backend={backend} model={model}")]
328 ModelNotFound { backend: String, model: String },
329 #[error("endpoint not found: {0}")]
330 EndpointNotFound(String),
331 #[error("serialization error: {0}")]
332 Serialization(#[from] serde_json::Error),
333 #[error("http error: {0}")]
334 Http(String),
335 #[error("provider error: {0}")]
336 Provider(String),
337}