Skip to main content

opendev_http/adapters/
ollama.rs

1//! Ollama adapter.
2//!
3//! Ollama exposes an OpenAI-compatible Chat Completions API at
4//! `http://localhost:11434/v1/chat/completions`. This adapter:
5//! - Uses a local default base URL (no auth required)
6//! - Passes requests in standard Chat Completions format
7//! - Removes unsupported parameters
8//! - Handles minor response differences
9
10use serde_json::Value;
11
12const DEFAULT_API_URL: &str = "http://localhost:11434/v1/chat/completions";
13
14/// Adapter for the Ollama local inference server.
15///
16/// Ollama is OpenAI-compatible, so requests pass through with minimal changes.
17/// No authentication is required for local instances.
18#[derive(Debug, Clone)]
19pub struct OllamaAdapter {
20    api_url: String,
21}
22
23impl OllamaAdapter {
24    /// Create a new Ollama adapter with the default local URL.
25    pub fn new() -> Self {
26        Self {
27            api_url: DEFAULT_API_URL.to_string(),
28        }
29    }
30
31    /// Create with a custom API URL (e.g., remote Ollama instance).
32    pub fn with_url(url: impl Into<String>) -> Self {
33        Self {
34            api_url: url.into(),
35        }
36    }
37
38    /// Remove parameters that Ollama does not support.
39    fn clean_request(payload: &mut Value) {
40        if let Some(obj) = payload.as_object_mut() {
41            obj.remove("logprobs");
42            obj.remove("top_logprobs");
43            obj.remove("n");
44            obj.remove("frequency_penalty");
45            obj.remove("presence_penalty");
46            obj.remove("seed");
47            // Ollama doesn't support max_completion_tokens; convert to max_tokens
48            if let Some(val) = obj.remove("max_completion_tokens") {
49                obj.entry("max_tokens").or_insert(val);
50            }
51        }
52    }
53}
54
55impl Default for OllamaAdapter {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61#[async_trait::async_trait]
62impl super::base::ProviderAdapter for OllamaAdapter {
63    fn provider_name(&self) -> &str {
64        "ollama"
65    }
66
67    fn convert_request(&self, mut payload: Value) -> Value {
68        Self::clean_request(&mut payload);
69        // Strip internal reasoning effort field
70        payload
71            .as_object_mut()
72            .map(|obj| obj.remove("_reasoning_effort"));
73        payload
74    }
75
76    fn convert_response(&self, response: Value) -> Value {
77        // Ollama responses are in Chat Completions format
78        response
79    }
80
81    fn api_url(&self) -> &str {
82        &self.api_url
83    }
84}
85
86#[cfg(test)]
87#[path = "ollama_tests.rs"]
88mod tests;