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        }
68    }
69
70    async fn chat(&self, request: &ChatRequest<'_>) -> anyhow::Result<ChatResponse> {
71        let client = reqwest::Client::new();
72        let body = self.build_request_body(request);
73
74        let resp = send_with_retry(
75            client
76                .post(format!("{}/api/chat", self.base_url))
77                .json(&body),
78            self.name(),
79        )
80        .await?;
81
82        if !resp.status().is_success() {
83            let text = resp.text().await.unwrap_or_default();
84            anyhow::bail!("Ollama error: {}", truncate_chars(&text, 200));
85        }
86
87        let data: Value = resp.json().await?;
88        Ok(self.parse_response(data))
89    }
90}