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 { text: String },
86 ImageUrl { url: String },
87}
88
89#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
90pub struct Message {
91 pub role: MessageRole,
92 pub content: Vec<MessageContent>,
93 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub name: Option<String>,
95 #[serde(default, skip_serializing_if = "Option::is_none")]
96 pub tool_call_id: Option<String>,
97 #[serde(default, skip_serializing_if = "Vec::is_empty")]
98 pub tool_calls: Vec<ToolCall>,
99}
100
101impl Message {
102 pub fn text(role: MessageRole, text: impl Into<String>) -> Self {
103 Self {
104 role,
105 content: vec![MessageContent::Text { text: text.into() }],
106 name: None,
107 tool_call_id: None,
108 tool_calls: Vec::new(),
109 }
110 }
111
112 pub fn text_content(&self) -> Option<String> {
113 let mut parts = Vec::new();
114 for content in &self.content {
115 if let MessageContent::Text { text } = content {
116 parts.push(text.as_str());
117 }
118 }
119 if parts.is_empty() {
120 None
121 } else {
122 Some(parts.join("\n"))
123 }
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
128pub struct ChatRequestOptions {
129 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub temperature: Option<f32>,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
132 pub max_tokens: Option<u32>,
133 #[serde(default, skip_serializing_if = "Option::is_none")]
134 pub stream: Option<bool>,
135}
136
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138pub struct ChatRequest {
139 pub model: String,
140 pub messages: Vec<Message>,
141 #[serde(default)]
142 pub options: ChatRequestOptions,
143 #[serde(default, skip_serializing_if = "Vec::is_empty")]
144 pub tools: Vec<ChatTool>,
145 #[serde(default, skip_serializing_if = "Option::is_none")]
146 pub tool_choice: Option<String>,
147}
148
149impl ChatRequest {
150 pub fn new(model: impl Into<String>, messages: Vec<Message>) -> Self {
151 Self {
152 model: model.into(),
153 messages,
154 options: ChatRequestOptions::default(),
155 tools: Vec::new(),
156 tool_choice: None,
157 }
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
162pub struct ChatTool {
163 pub name: String,
164 #[serde(default, skip_serializing_if = "Option::is_none")]
165 pub description: Option<String>,
166 pub parameters: serde_json::Value,
167}
168
169impl ChatTool {
170 pub fn function(
171 name: impl Into<String>,
172 description: impl Into<String>,
173 parameters: serde_json::Value,
174 ) -> Self {
175 Self {
176 name: name.into(),
177 description: Some(description.into()),
178 parameters,
179 }
180 }
181}
182
183#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
184pub struct ToolCall {
185 pub id: String,
186 pub name: String,
187 pub arguments: String,
188}
189
190impl ToolCall {
191 pub fn function(
192 id: impl Into<String>,
193 name: impl Into<String>,
194 arguments: impl Into<String>,
195 ) -> Self {
196 Self {
197 id: id.into(),
198 name: name.into(),
199 arguments: arguments.into(),
200 }
201 }
202}
203
204#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
205pub struct ChatResponse {
206 pub id: String,
207 pub model: String,
208 pub content: String,
209 #[serde(default, skip_serializing_if = "Vec::is_empty")]
210 pub tool_calls: Vec<ToolCall>,
211 #[serde(default, skip_serializing_if = "Option::is_none")]
212 pub usage: Option<ChatUsage>,
213}
214
215#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
216pub struct ChatStreamDelta {
217 #[serde(default, skip_serializing_if = "String::is_empty")]
218 pub content: String,
219 #[serde(default, skip_serializing_if = "String::is_empty")]
220 pub reasoning_content: String,
221 #[serde(default, skip_serializing_if = "Vec::is_empty")]
222 pub tool_calls: Vec<ToolCall>,
223 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub usage: Option<ChatUsage>,
225 #[serde(default, skip_serializing_if = "Option::is_none")]
226 pub raw_content: Option<serde_json::Value>,
227 #[serde(default, skip_serializing_if = "is_false")]
228 pub done: bool,
229}
230
231#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
232pub struct ChatUsage {
233 #[serde(default, skip_serializing_if = "Option::is_none")]
234 pub prompt_tokens: Option<u32>,
235 #[serde(default, skip_serializing_if = "Option::is_none")]
236 pub completion_tokens: Option<u32>,
237 #[serde(default, skip_serializing_if = "Option::is_none")]
238 pub total_tokens: Option<u32>,
239}
240
241fn is_false(value: &bool) -> bool {
242 !*value
243}
244
245#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
246pub struct EmbeddingResponse {
247 pub model: String,
248 pub data: Vec<EmbeddingData>,
249}
250
251#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
252pub struct EmbeddingData {
253 pub index: u32,
254 pub embedding: Vec<f32>,
255}
256
257#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
258pub struct RerankResponse {
259 pub results: Vec<RerankResult>,
260}
261
262#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
263pub struct RerankResult {
264 pub index: usize,
265 pub relevance_score: f32,
266}
267
268#[derive(Debug, thiserror::Error)]
269pub enum VvLlmError {
270 #[error("configuration error: {0}")]
271 Configuration(String),
272 #[error("model not found: backend={backend} model={model}")]
273 ModelNotFound { backend: String, model: String },
274 #[error("endpoint not found: {0}")]
275 EndpointNotFound(String),
276 #[error("serialization error: {0}")]
277 Serialization(#[from] serde_json::Error),
278 #[error("http error: {0}")]
279 Http(String),
280 #[error("provider error: {0}")]
281 Provider(String),
282}