rig_core/providers/ollama/
extension.rs1use serde::{Deserialize, Serialize};
18use serde_json::Value;
19
20use crate::completion::{ExtensionOptions, ProviderExtension, ReplyExtras};
21use crate::message::Api;
22
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
25pub struct OllamaExt;
26
27impl ProviderExtension for OllamaExt {
28 const PROVIDER: &'static str = super::PROVIDER_NAME;
29 type Options = OllamaOptions;
30 type Extras = OllamaExtras;
31}
32
33#[non_exhaustive]
35#[derive(Clone, Debug, Default, PartialEq, Serialize)]
36pub struct OllamaOptions {
37 #[serde(rename = "*")]
39 pub shared: OllamaShared,
40 #[serde(rename = "ollama.chat")]
42 pub chat: OllamaNative,
43}
44
45impl ExtensionOptions for OllamaOptions {
46 type Ext = OllamaExt;
47
48 fn unsupported(
51 &self,
52 target: &dyn crate::completion::ReplayTarget,
53 _request: &crate::completion::CompletionRequest,
54 ) -> Vec<(&'static str, String)> {
55 let messages = Api::from_static(crate::providers::anthropic::wire::MESSAGES_API);
56 match self.shared.keep_alive {
57 Some(_) if target.api() == messages => vec![(
58 "keep_alive",
59 "Ollama's Anthropic-compatible API does not read it".to_owned(),
60 )],
61 _ => Vec::new(),
62 }
63 }
64}
65
66impl OllamaOptions {
67 pub fn keep_alive(mut self, keep_alive: KeepAlive) -> Self {
70 self.shared.keep_alive = Some(keep_alive);
71 self
72 }
73
74 pub fn num_ctx(mut self, num_ctx: u32) -> Self {
76 self.chat.options.num_ctx = Some(num_ctx);
77 self
78 }
79
80 pub fn num_keep(mut self, num_keep: u32) -> Self {
83 self.chat.options.num_keep = Some(num_keep);
84 self
85 }
86
87 pub fn top_k(mut self, top_k: u32) -> Self {
89 self.chat.options.top_k = Some(top_k);
90 self
91 }
92
93 pub fn min_p(mut self, min_p: f64) -> Self {
96 self.chat.options.min_p = Some(min_p);
97 self
98 }
99
100 pub fn repeat_penalty(mut self, penalty: f64) -> Self {
102 self.chat.options.repeat_penalty = Some(penalty);
103 self
104 }
105
106 pub fn repeat_last_n(mut self, last_n: i32) -> Self {
109 self.chat.options.repeat_last_n = Some(last_n);
110 self
111 }
112
113 pub fn num_gpu(mut self, num_gpu: i32) -> Self {
115 self.chat.options.num_gpu = Some(num_gpu);
116 self
117 }
118
119 pub fn num_thread(mut self, num_thread: u32) -> Self {
121 self.chat.options.num_thread = Some(num_thread);
122 self
123 }
124
125 pub fn logprobs(mut self, logprobs: bool) -> Self {
128 self.chat.logprobs = Some(logprobs);
129 self
130 }
131
132 pub fn top_logprobs(mut self, top_logprobs: u32) -> Self {
135 self.chat.top_logprobs = Some(top_logprobs);
136 self
137 }
138}
139
140#[non_exhaustive]
142#[derive(Clone, Debug, Default, PartialEq, Serialize)]
143pub struct OllamaShared {
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub keep_alive: Option<KeepAlive>,
147}
148
149#[non_exhaustive]
151#[derive(Clone, Debug, PartialEq, Serialize)]
152#[serde(untagged)]
153pub enum KeepAlive {
154 Duration(String),
157 Seconds(i64),
160}
161
162impl KeepAlive {
163 pub fn duration(duration: impl Into<String>) -> Self {
165 Self::Duration(duration.into())
166 }
167
168 pub fn seconds(seconds: i64) -> Self {
170 Self::Seconds(seconds)
171 }
172}
173
174#[non_exhaustive]
176#[derive(Clone, Debug, Default, PartialEq, Serialize)]
177pub struct OllamaNative {
178 #[serde(skip_serializing_if = "ModelOptions::is_empty")]
181 pub options: ModelOptions,
182 #[serde(skip_serializing_if = "Option::is_none")]
184 pub logprobs: Option<bool>,
185 #[serde(skip_serializing_if = "Option::is_none")]
187 pub top_logprobs: Option<u32>,
188}
189
190#[non_exhaustive]
194#[derive(Clone, Debug, Default, PartialEq, Serialize)]
195pub struct ModelOptions {
196 #[serde(skip_serializing_if = "Option::is_none")]
198 pub num_ctx: Option<u32>,
199 #[serde(skip_serializing_if = "Option::is_none")]
201 pub num_keep: Option<u32>,
202 #[serde(skip_serializing_if = "Option::is_none")]
204 pub top_k: Option<u32>,
205 #[serde(skip_serializing_if = "Option::is_none")]
207 pub min_p: Option<f64>,
208 #[serde(skip_serializing_if = "Option::is_none")]
210 pub repeat_penalty: Option<f64>,
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub repeat_last_n: Option<i32>,
214 #[serde(skip_serializing_if = "Option::is_none")]
216 pub num_gpu: Option<i32>,
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub num_thread: Option<u32>,
220}
221
222impl ModelOptions {
223 pub fn is_empty(&self) -> bool {
225 self == &Self::default()
226 }
227}
228
229#[non_exhaustive]
233#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
234#[serde(default)]
235pub struct OllamaExtras {
236 pub model: Option<String>,
238 pub created_at: Option<String>,
240 pub done_reason: Option<String>,
242 pub total_duration: Option<u64>,
244 pub load_duration: Option<u64>,
246 pub prompt_eval_duration: Option<u64>,
248 pub eval_duration: Option<u64>,
250 pub prompt_eval_count: Option<u64>,
252 pub prompt_eval_cached_count: Option<u64>,
254 pub eval_count: Option<u64>,
256 pub logprobs: Option<Vec<Logprob>>,
258}
259
260#[non_exhaustive]
262#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
263#[serde(default)]
264pub struct Logprob {
265 pub token: Option<String>,
267 pub logprob: Option<f64>,
269 pub bytes: Option<Vec<u8>>,
271 pub top_logprobs: Option<Vec<Logprob>>,
274}
275
276impl ReplyExtras for OllamaExtras {
277 fn from_reply(_api: &Api, raw: &Value) -> Result<Self, serde_json::Error> {
278 Self::deserialize(raw)
279 }
280}
281
282#[cfg(test)]
283mod tests;