apollo/providers/
ollama.rs1use 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, 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}