1use crate::chat::Tool;
21use crate::client::ModelClient;
22use crate::error::{OllamaError, Result};
23use serde::{Deserialize, Serialize};
24use std::collections::HashMap;
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ChatMessage {
31 pub role: String,
32 pub content: serde_json::Value,
33}
34
35impl ChatMessage {
36 pub fn new(role: impl Into<String>, content: impl Into<serde_json::Value>) -> Self {
37 Self {
38 role: role.into(),
39 content: content.into(),
40 }
41 }
42
43 pub fn user(content: impl Into<serde_json::Value>) -> Self {
44 Self::new("user", content)
45 }
46
47 pub fn assistant(content: impl Into<serde_json::Value>) -> Self {
48 Self::new("assistant", content)
49 }
50
51 pub fn system(content: impl Into<serde_json::Value>) -> Self {
52 Self::new("system", content)
53 }
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize, Default)]
58pub struct ChatCompletionsRequest {
59 pub model: String,
60 pub messages: Vec<ChatMessage>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub frequency_penalty: Option<f32>,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub presence_penalty: Option<f32>,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub response_format: Option<serde_json::Value>,
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub seed: Option<i32>,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub stop: Option<Vec<String>>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub stream: Option<bool>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub stream_options: Option<StreamOptions>,
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub temperature: Option<f32>,
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub top_p: Option<f32>,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub max_tokens: Option<u32>,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub tools: Option<Vec<Tool>>,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub reasoning_effort: Option<String>,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub reasoning: Option<serde_json::Value>,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub tool_choice: Option<serde_json::Value>,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub logit_bias: Option<HashMap<String, f32>>,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub user: Option<String>,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub n: Option<u32>,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct StreamOptions {
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub include_usage: Option<bool>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct Choice {
107 pub index: u32,
108 pub message: ChatMessage,
109 pub finish_reason: String,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct Usage {
115 pub prompt_tokens: u32,
116 pub completion_tokens: u32,
117 pub total_tokens: u32,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct ChatCompletionsResponse {
123 pub id: String,
124 pub choices: Vec<Choice>,
125 pub created: u64,
126 pub model: String,
127 pub usage: Usage,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
134#[serde(untagged)]
135pub enum OpenAIEmbeddingsInput {
136 Single(String),
137 Multiple(Vec<String>),
138 Tokens(Vec<u32>),
139 TokenArrays(Vec<Vec<u32>>),
140}
141
142impl Default for OpenAIEmbeddingsInput {
143 fn default() -> Self {
144 Self::Single(String::new())
145 }
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize, Default)]
150pub struct OpenAIEmbeddingsRequest {
151 pub model: String,
152 pub input: OpenAIEmbeddingsInput,
153 #[serde(skip_serializing_if = "Option::is_none")]
154 pub encoding_format: Option<String>,
155 #[serde(skip_serializing_if = "Option::is_none")]
156 pub dimensions: Option<u32>,
157 #[serde(skip_serializing_if = "Option::is_none")]
158 pub user: Option<String>,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct OpenAIEmbedding {
164 pub embedding: Vec<f32>,
165 pub index: u32,
166 pub object: String,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct OpenAIEmbeddingsResponse {
172 pub data: Vec<OpenAIEmbedding>,
173 pub model: String,
174 pub usage: Usage,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize, Default)]
181pub struct ResponsesRequest {
182 pub model: String,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub input: Option<String>,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub instructions: Option<String>,
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub tools: Option<Vec<serde_json::Value>>,
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub stream: Option<bool>,
191 #[serde(skip_serializing_if = "Option::is_none")]
192 pub temperature: Option<f32>,
193 #[serde(skip_serializing_if = "Option::is_none")]
194 pub top_p: Option<f32>,
195 #[serde(skip_serializing_if = "Option::is_none")]
196 pub max_output_tokens: Option<u32>,
197 #[serde(skip_serializing_if = "Option::is_none")]
198 pub previous_response_id: Option<String>,
199 #[serde(skip_serializing_if = "Option::is_none")]
200 pub conversation: Option<Vec<serde_json::Value>>,
201 #[serde(skip_serializing_if = "Option::is_none")]
202 pub truncation: Option<String>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct ResponsesResponse {
208 pub output: String,
209 pub done: bool,
210 pub model: String,
211 pub done_reason: String,
212 pub tool_calls: Vec<serde_json::Value>,
213 pub prompt_evals: u32,
214 pub eval_count: u32,
215 pub total_duration: u64,
216 pub load_duration: u64,
217 pub prompt_eval_duration: u64,
218 pub eval_duration: u64,
219 pub output_eval_count: u32,
220 pub output_eval_duration: u64,
221}
222
223impl ModelClient {
224 pub async fn chat_completions(
254 &self,
255 request: ChatCompletionsRequest,
256 ) -> Result<ChatCompletionsResponse> {
257 let url = self
258 .base_url
259 .join("v1/chat/completions")
260 .map_err(OllamaError::UrlError)?;
261 let response = self
262 .client
263 .post(url)
264 .json(&request)
265 .send()
266 .await
267 .map_err(OllamaError::RequestError)?;
268
269 self.handle_response(response, Some(&request.model)).await
270 }
271
272 pub async fn openai_embeddings(
302 &self,
303 request: OpenAIEmbeddingsRequest,
304 ) -> Result<OpenAIEmbeddingsResponse> {
305 let url = self
306 .base_url
307 .join("v1/embeddings")
308 .map_err(OllamaError::UrlError)?;
309 let response = self
310 .client
311 .post(url)
312 .json(&request)
313 .send()
314 .await
315 .map_err(OllamaError::RequestError)?;
316
317 self.handle_response(response, Some(&request.model)).await
318 }
319
320 pub async fn responses(&self, request: ResponsesRequest) -> Result<ResponsesResponse> {
350 let url = self
351 .base_url
352 .join("v1/responses")
353 .map_err(OllamaError::UrlError)?;
354 let response = self
355 .client
356 .post(url)
357 .json(&request)
358 .send()
359 .await
360 .map_err(OllamaError::RequestError)?;
361
362 self.handle_response(response, Some(&request.model)).await
363 }
364}