Skip to main content

apollo/providers/
ollama.rs

1//! Ollama provider — local model support via Ollama API.
2
3use async_trait::async_trait;
4use serde_json::Value;
5
6use super::retry::send_with_retry;
7use super::traits::*;
8use crate::text::truncate_chars;
9
10pub struct OllamaProvider {
11    base_url: String,
12}
13
14impl OllamaProvider {
15    pub fn new(base_url: impl Into<String>) -> Self {
16        Self {
17            base_url: base_url.into(),
18        }
19    }
20
21    fn build_request_body(&self, request: &ChatRequest<'_>) -> Value {
22        let messages: Vec<Value> = request
23            .messages
24            .iter()
25            .map(|m| serde_json::json!({ "role": &m.role, "content": &m.content }))
26            .collect();
27
28        serde_json::json!({
29            "model": request.model,
30            "messages": messages,
31            "stream": false,
32            "options": {
33                "temperature": request.temperature,
34            }
35        })
36    }
37
38    fn parse_response(&self, data: Value) -> ChatResponse {
39        let text = data["message"]["content"].as_str().map(String::from);
40
41        ChatResponse {
42            text,
43            tool_calls: vec![],
44            usage: None,
45        }
46    }
47}
48
49impl Default for OllamaProvider {
50    fn default() -> Self {
51        Self::new("http://localhost:11434")
52    }
53}
54
55#[async_trait]
56impl Provider for OllamaProvider {
57    fn name(&self) -> &str {
58        "ollama"
59    }
60
61    fn capabilities(&self) -> ProviderCapabilities {
62        ProviderCapabilities {
63            native_tools: false, // Most Ollama models don't support native tools
64            streaming: true,
65            vision: false,
66            max_context: 32_000,
67            native_web_search: false,
68        }
69    }
70
71    async fn chat(&self, request: &ChatRequest<'_>) -> anyhow::Result<ChatResponse> {
72        let client = crate::http::shared();
73        let body = self.build_request_body(request);
74
75        let resp = send_with_retry(
76            client
77                .post(format!("{}/api/chat", self.base_url))
78                .json(&body),
79            self.name(),
80        )
81        .await?;
82
83        if !resp.status().is_success() {
84            let text = resp.text().await.unwrap_or_default();
85            anyhow::bail!("Ollama error: {}", truncate_chars(&text, 200));
86        }
87
88        let data: Value = resp.json().await?;
89        Ok(self.parse_response(data))
90    }
91}