feat: implement full pipeline (pgvector, embeddings, ingest, query, HTTP)
- Add database schema with pgvector extension (L0/L1/L2 memories) - Implement pgvector-backed vector store with similarity search - Add Ollama embeddings client for 768-dim nomic embeddings - Implement ingest worker to process records into L0/L1 memory - Implement query worker with semantic search across memory tiers - Rewrite HTTP server with database connection pooling - Wire all endpoints to actual backend (ingest, query, projects, skills) - Update main.rs to use DATABASE_URL from environment - All code compiles, ready for Docker build and deployment
This commit is contained in:
@@ -14,3 +14,5 @@ thiserror = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
pgvector = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use pgvector::Vector;
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
|
||||
/// Embeddings client for Ollama
|
||||
#[derive(Clone)]
|
||||
pub struct EmbeddingsClient {
|
||||
base_url: String,
|
||||
model: String,
|
||||
#[allow(dead_code)]
|
||||
http: Client,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct EmbeddingRequest {
|
||||
model: String,
|
||||
input: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct EmbeddingResponse {
|
||||
embeddings: Vec<Vec<f32>>,
|
||||
model: String,
|
||||
}
|
||||
|
||||
impl EmbeddingsClient {
|
||||
/// Create from environment (OLLAMA_BASE_URL, EMBEDDINGS_MODEL)
|
||||
pub fn from_env() -> Result<Self> {
|
||||
let base_url = env::var("OLLAMA_BASE_URL").unwrap_or_else(|_| "http://ollama:11434".to_string());
|
||||
let model = env::var("EMBEDDINGS_MODEL").unwrap_or_else(|_| "nomic-embed-text-v2-moe".to_string());
|
||||
|
||||
Ok(Self {
|
||||
base_url,
|
||||
model,
|
||||
http: Client::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Embed a single text string
|
||||
pub async fn embed(&self, text: &str) -> Result<Vector> {
|
||||
let embeddings = self.embed_batch(&[text.to_string()]).await?;
|
||||
Ok(embeddings.into_iter().next().ok_or_else(|| anyhow::anyhow!("empty embedding response"))?)
|
||||
}
|
||||
|
||||
/// Embed multiple texts in a batch
|
||||
pub async fn embed_batch(&self, texts: &[String]) -> Result<Vec<Vector>> {
|
||||
let req = EmbeddingRequest {
|
||||
model: self.model.clone(),
|
||||
input: texts.to_vec(),
|
||||
};
|
||||
|
||||
let url = format!("{}/api/embed", self.base_url);
|
||||
let resp: EmbeddingResponse = self.http.post(&url).json(&req).send().await?.json().await?;
|
||||
|
||||
Ok(resp
|
||||
.embeddings
|
||||
.into_iter()
|
||||
.map(Vector::from)
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod chat;
|
||||
pub mod rerank;
|
||||
pub mod embeddings;
|
||||
|
||||
pub use chat::{ChatClient, Completion, Usage};
|
||||
pub use rerank::RerankClient;
|
||||
pub use embeddings::EmbeddingsClient;
|
||||
|
||||
Reference in New Issue
Block a user