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 }
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}