use anyhow::{Context, Result};
use async_trait::async_trait;
use serde::Deserialize;
use super::{LlmService, Message};
const DEFAULT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(300);
pub struct OllamaLlm {
client: reqwest::Client,
base_url: String,
model: String,
}
impl OllamaLlm {
pub fn new(model: impl Into<String>) -> Self {
Self {
client: super::http_client(DEFAULT_TIMEOUT),
base_url: "http://localhost:11434".to_string(),
model: model.into(),
}
}
pub fn with_base_url(mut self, base_url: impl Into<String>) -> Self {
self.base_url = base_url.into();
self
}
pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self {
self.client = super::http_client(timeout);
self
}
}
#[derive(Deserialize)]
struct ChatResponse {
message: ResponseMessage,
}
#[derive(Deserialize)]
struct ResponseMessage {
content: String,
}
#[async_trait]
impl LlmService for OllamaLlm {
async fn submit_prompt(&self, messages: Vec<Message>) -> Result<String> {
let body = serde_json::json!({
"model": self.model,
"messages": messages,
"stream": false,
});
let url = format!("{}/api/chat", self.base_url);
let response = self
.client
.post(url)
.json(&body)
.send()
.await
.context("failed to call Ollama (is it running?)")?
.error_for_status()
.context("Ollama returned an error status")?;
let data: ChatResponse = response
.json()
.await
.context("failed to parse Ollama response")?;
Ok(data.message.content)
}
}