engram/
embedding_ollama.rs1use crate::embedding::EmbeddingProvider;
7use crate::store::MemoryError;
8use async_trait::async_trait;
9use serde::{Deserialize, Serialize};
10
11#[derive(Serialize)]
16struct EmbedRequest<'a> {
17 model: &'a str,
18 input: Vec<&'a str>,
19}
20
21#[derive(Deserialize)]
22struct EmbedResponse {
23 embeddings: Vec<Vec<f32>>,
24}
25
26pub struct OllamaEmbeddingProvider {
48 base_url: String,
49 model: String,
50 dims: usize,
51 client: reqwest::Client,
52}
53
54impl OllamaEmbeddingProvider {
55 pub fn new() -> Self {
60 Self::with_config("http://localhost:11434", "nomic-embed-text", 768)
61 }
62
63 pub fn with_config(base_url: &str, model: &str, dims: usize) -> Self {
65 Self {
66 base_url: base_url.trim_end_matches('/').to_string(),
67 model: model.to_string(),
68 dims,
69 client: reqwest::Client::new(),
70 }
71 }
72}
73
74impl Default for OllamaEmbeddingProvider {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80#[async_trait]
81impl EmbeddingProvider for OllamaEmbeddingProvider {
82 async fn embed(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>, MemoryError> {
83 let url = format!("{}/api/embed", self.base_url);
84 let body = EmbedRequest {
85 model: &self.model,
86 input: texts.to_vec(),
87 };
88
89 let response = self
90 .client
91 .post(&url)
92 .json(&body)
93 .send()
94 .await
95 .map_err(|e| MemoryError::Embedding(format!("Ollama request failed: {e}")))?;
96
97 if !response.status().is_success() {
98 let status = response.status();
99 let text = response
100 .text()
101 .await
102 .unwrap_or_else(|_| "<no body>".to_string());
103 return Err(MemoryError::Embedding(format!(
104 "Ollama returned {status}: {text}"
105 )));
106 }
107
108 let embed_response: EmbedResponse = response
109 .json()
110 .await
111 .map_err(|e| MemoryError::Embedding(format!("Failed to parse Ollama response: {e}")))?;
112
113 Ok(embed_response.embeddings)
114 }
115
116 fn dimensions(&self) -> usize {
117 self.dims
118 }
119}