mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(memory): default embeddings to cloud (Voyage), opt in for local Ollama (#1508)
This commit is contained in:
@@ -592,8 +592,9 @@ impl Agent {
|
||||
&config.workspace_dir,
|
||||
));
|
||||
|
||||
let memory: Arc<dyn Memory> = Arc::from(memory::create_memory_with_storage_and_routes(
|
||||
let memory: Arc<dyn Memory> = Arc::from(memory::create_memory_with_local_ai(
|
||||
&config.memory,
|
||||
&config.local_ai,
|
||||
&config.embedding_routes,
|
||||
Some(&config.storage.provider.config),
|
||||
&config.workspace_dir,
|
||||
|
||||
@@ -184,8 +184,10 @@ pub async fn start_channels(config: Config) -> Result<()> {
|
||||
.clone()
|
||||
.unwrap_or_else(|| crate::openhuman::config::DEFAULT_MODEL.into());
|
||||
let temperature = config.default_temperature;
|
||||
let mem: Arc<dyn Memory> = Arc::from(memory::create_memory_with_storage(
|
||||
let mem: Arc<dyn Memory> = Arc::from(memory::create_memory_with_local_ai(
|
||||
&config.memory,
|
||||
&config.local_ai,
|
||||
&[],
|
||||
Some(&config.storage.provider.config),
|
||||
&config.workspace_dir,
|
||||
)?);
|
||||
|
||||
@@ -48,13 +48,20 @@ pub struct MemoryConfig {
|
||||
}
|
||||
|
||||
fn default_embedding_provider() -> String {
|
||||
"ollama".into()
|
||||
// Default to the OpenHuman backend (Voyage-backed `embedding-v1`) so a
|
||||
// fresh install works without requiring a local Ollama daemon. Users
|
||||
// who want fully-local embeddings can flip this to "ollama" in
|
||||
// `config.toml` or enable `local_ai.usage.embeddings = true`, which is
|
||||
// wired into the memory factory via [`LocalAiConfig::use_local_for_embeddings`].
|
||||
"cloud".into()
|
||||
}
|
||||
fn default_embedding_model() -> String {
|
||||
"nomic-embed-text:latest".into()
|
||||
// Keep this in sync with `embeddings::cloud::DEFAULT_CLOUD_EMBEDDING_MODEL`.
|
||||
"embedding-v1".into()
|
||||
}
|
||||
fn default_embedding_dims() -> usize {
|
||||
768
|
||||
// Keep this in sync with `embeddings::cloud::DEFAULT_CLOUD_EMBEDDING_DIMENSIONS`.
|
||||
1024
|
||||
}
|
||||
fn default_min_relevance_score() -> f64 {
|
||||
0.4
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
//! Cloud embedding provider — routes through the OpenHuman backend's
|
||||
//! `POST /openai/v1/embeddings` surface (Voyage-backed) using the same
|
||||
//! session JWT that the `OpenHumanBackendProvider` chat path uses.
|
||||
//!
|
||||
//! This is the default embedder for a fresh install. The local Ollama path
|
||||
//! stays available, but the user has to explicitly opt in (either by setting
|
||||
//! `memory.embedding_provider = "ollama"` in `config.toml`, or by enabling
|
||||
//! the local-AI runtime with `local_ai.usage.embeddings = true`).
|
||||
//!
|
||||
//! The JWT and API URL are resolved per call so a session refresh between
|
||||
//! embed batches is picked up transparently — matching
|
||||
//! [`crate::openhuman::providers::openhuman_backend::OpenHumanBackendProvider`].
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use super::openai::OpenAiEmbedding;
|
||||
use super::EmbeddingProvider;
|
||||
use crate::api::config::effective_api_url;
|
||||
use crate::openhuman::credentials::{AuthService, APP_SESSION_PROVIDER};
|
||||
|
||||
/// Default cloud embedding model — backed by `voyage-3.5` (1024 dims) on the
|
||||
/// OpenHuman backend. See `tinyhumansai/backend#746`.
|
||||
pub const DEFAULT_CLOUD_EMBEDDING_MODEL: &str = "embedding-v1";
|
||||
|
||||
/// Default output dimensionality for [`DEFAULT_CLOUD_EMBEDDING_MODEL`].
|
||||
pub const DEFAULT_CLOUD_EMBEDDING_DIMENSIONS: usize = 1024;
|
||||
|
||||
/// OpenHuman-backend-backed embedding provider.
|
||||
pub struct OpenHumanCloudEmbedding {
|
||||
api_url: Option<String>,
|
||||
openhuman_dir: Option<PathBuf>,
|
||||
secrets_encrypt: bool,
|
||||
model: String,
|
||||
dims: usize,
|
||||
}
|
||||
|
||||
impl OpenHumanCloudEmbedding {
|
||||
/// Construct a cloud embedder. `api_url` and `openhuman_dir` are looked up
|
||||
/// per request; pass `None` to fall back to the runtime defaults
|
||||
/// ([`effective_api_url`] / `~/.openhuman`).
|
||||
pub fn new(
|
||||
api_url: Option<String>,
|
||||
openhuman_dir: Option<PathBuf>,
|
||||
secrets_encrypt: bool,
|
||||
model: impl Into<String>,
|
||||
dims: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
api_url: api_url
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty()),
|
||||
openhuman_dir,
|
||||
secrets_encrypt,
|
||||
model: model.into(),
|
||||
dims,
|
||||
}
|
||||
}
|
||||
|
||||
fn state_dir(&self) -> PathBuf {
|
||||
self.openhuman_dir.clone().unwrap_or_else(|| {
|
||||
directories::UserDirs::new()
|
||||
.map(|d| d.home_dir().join(".openhuman"))
|
||||
.unwrap_or_else(|| PathBuf::from(".openhuman"))
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_bearer(&self) -> anyhow::Result<String> {
|
||||
let auth = AuthService::new(&self.state_dir(), self.secrets_encrypt);
|
||||
if let Some(t) = auth
|
||||
.get_provider_bearer_token(APP_SESSION_PROVIDER, None)?
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
{
|
||||
return Ok(t);
|
||||
}
|
||||
anyhow::bail!(
|
||||
"No backend session for cloud embeddings: log in to OpenHuman, or set \
|
||||
memory.embedding_provider to \"ollama\" / \"none\" in config.toml"
|
||||
)
|
||||
}
|
||||
|
||||
fn base_url(&self) -> String {
|
||||
let u = effective_api_url(&self.api_url);
|
||||
format!("{}/openai/v1", u.trim_end_matches('/'))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EmbeddingProvider for OpenHumanCloudEmbedding {
|
||||
fn name(&self) -> &str {
|
||||
"cloud"
|
||||
}
|
||||
|
||||
fn dimensions(&self) -> usize {
|
||||
self.dims
|
||||
}
|
||||
|
||||
async fn embed(&self, texts: &[&str]) -> anyhow::Result<Vec<Vec<f32>>> {
|
||||
if texts.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let token = self.resolve_bearer()?;
|
||||
let inner = OpenAiEmbedding::new(&self.base_url(), &token, &self.model, self.dims);
|
||||
inner.embed(texts).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn name_and_dimensions() {
|
||||
let p = OpenHumanCloudEmbedding::new(
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
DEFAULT_CLOUD_EMBEDDING_MODEL,
|
||||
DEFAULT_CLOUD_EMBEDDING_DIMENSIONS,
|
||||
);
|
||||
assert_eq!(p.name(), "cloud");
|
||||
assert_eq!(p.dimensions(), DEFAULT_CLOUD_EMBEDDING_DIMENSIONS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base_url_appends_openai_v1() {
|
||||
let p = OpenHumanCloudEmbedding::new(
|
||||
Some("https://api.openhuman.example/".into()),
|
||||
None,
|
||||
true,
|
||||
DEFAULT_CLOUD_EMBEDDING_MODEL,
|
||||
DEFAULT_CLOUD_EMBEDDING_DIMENSIONS,
|
||||
);
|
||||
assert_eq!(p.base_url(), "https://api.openhuman.example/openai/v1");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn embed_empty_returns_empty_without_auth() {
|
||||
// Empty input should short-circuit *before* hitting the AuthService —
|
||||
// otherwise the no-op path would spuriously fail in unauthenticated
|
||||
// contexts (e.g. ingestion of an empty chunk batch).
|
||||
let p = OpenHumanCloudEmbedding::new(
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
DEFAULT_CLOUD_EMBEDDING_MODEL,
|
||||
DEFAULT_CLOUD_EMBEDDING_DIMENSIONS,
|
||||
);
|
||||
assert!(p.embed(&[]).await.unwrap().is_empty());
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,17 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::cloud::{
|
||||
OpenHumanCloudEmbedding, DEFAULT_CLOUD_EMBEDDING_DIMENSIONS, DEFAULT_CLOUD_EMBEDDING_MODEL,
|
||||
};
|
||||
use super::provider_trait::EmbeddingProvider;
|
||||
use super::{NoopEmbedding, OllamaEmbedding, OpenAiEmbedding};
|
||||
|
||||
/// Creates an embedding provider based on the specified name and configuration.
|
||||
///
|
||||
/// Supported provider names:
|
||||
/// - `"ollama"` → local Ollama server (default, preferred)
|
||||
/// - `"cloud"` → OpenHuman backend (Voyage-backed) — default, preferred
|
||||
/// - `"ollama"` → local Ollama server (opt-in for offline-only installs)
|
||||
/// - `"openai"` → OpenAI API
|
||||
/// - `"custom:<url>"` → OpenAI-compatible endpoint
|
||||
/// - `"none"` → no-op (keyword-only search, no embeddings)
|
||||
@@ -22,6 +26,9 @@ pub fn create_embedding_provider(
|
||||
dims: usize,
|
||||
) -> anyhow::Result<Box<dyn EmbeddingProvider>> {
|
||||
match provider {
|
||||
"cloud" => Ok(Box::new(OpenHumanCloudEmbedding::new(
|
||||
None, None, true, model, dims,
|
||||
))),
|
||||
"ollama" => Ok(Box::new(OllamaEmbedding::new("", model, dims))),
|
||||
"openai" => Ok(Box::new(OpenAiEmbedding::new(
|
||||
"https://api.openai.com",
|
||||
@@ -36,12 +43,28 @@ pub fn create_embedding_provider(
|
||||
"none" => Ok(Box::new(NoopEmbedding)),
|
||||
unknown => Err(anyhow::anyhow!(
|
||||
"unknown embedding provider: \"{unknown}\". \
|
||||
Supported: \"ollama\", \"openai\", \"custom:<url>\", \"none\""
|
||||
Supported: \"cloud\", \"ollama\", \"openai\", \"custom:<url>\", \"none\""
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the default local embedding provider (Ollama-backed).
|
||||
/// Returns the default embedding provider — cloud (OpenHuman backend, Voyage).
|
||||
///
|
||||
/// The cloud embedder lazily resolves the session JWT and API URL on each
|
||||
/// call, so this can be constructed before login completes; the first
|
||||
/// `embed()` will fail with a clear message if the user is unauthenticated.
|
||||
pub fn default_embedding_provider() -> Arc<dyn EmbeddingProvider> {
|
||||
Arc::new(OpenHumanCloudEmbedding::new(
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
DEFAULT_CLOUD_EMBEDDING_MODEL,
|
||||
DEFAULT_CLOUD_EMBEDDING_DIMENSIONS,
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns the local Ollama-backed embedding provider. Only used when the
|
||||
/// caller has explicitly opted into local-only embeddings.
|
||||
pub fn default_local_embedding_provider() -> Arc<dyn EmbeddingProvider> {
|
||||
Arc::new(OllamaEmbedding::default())
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
//!
|
||||
//! Converts text into numerical vectors for semantic search. Providers:
|
||||
//!
|
||||
//! - **Ollama** (default): Delegates to a local Ollama server — handles model
|
||||
//! management, quantization, and GPU acceleration out of the box.
|
||||
//! - **Cloud** (default): Routes through the OpenHuman backend's
|
||||
//! `POST /openai/v1/embeddings` (Voyage-backed). The recommended path —
|
||||
//! works on a fresh install without requiring a local Ollama daemon.
|
||||
//! - **Ollama**: Local Ollama server. Opt-in for offline-only setups
|
||||
//! (set `memory.embedding_provider = "ollama"` or enable
|
||||
//! `local_ai.usage.embeddings`).
|
||||
//! - **OpenAI**: Cloud-based embeddings via the OpenAI API or compatible endpoints.
|
||||
//! - **Noop**: A fallback provider for keyword-only search.
|
||||
|
||||
pub mod cloud;
|
||||
mod factory;
|
||||
pub mod noop;
|
||||
pub mod ollama;
|
||||
@@ -14,7 +19,12 @@ pub mod openai;
|
||||
mod provider_trait;
|
||||
pub mod store;
|
||||
|
||||
pub use factory::{create_embedding_provider, default_local_embedding_provider};
|
||||
pub use cloud::{
|
||||
OpenHumanCloudEmbedding, DEFAULT_CLOUD_EMBEDDING_DIMENSIONS, DEFAULT_CLOUD_EMBEDDING_MODEL,
|
||||
};
|
||||
pub use factory::{
|
||||
create_embedding_provider, default_embedding_provider, default_local_embedding_provider,
|
||||
};
|
||||
pub use noop::NoopEmbedding;
|
||||
pub use ollama::{OllamaEmbedding, DEFAULT_OLLAMA_DIMENSIONS, DEFAULT_OLLAMA_MODEL};
|
||||
pub use openai::OpenAiEmbedding;
|
||||
@@ -125,8 +135,27 @@ mod tests {
|
||||
.contains("fastembed"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_cloud() {
|
||||
let p = create_embedding_provider(
|
||||
"cloud",
|
||||
DEFAULT_CLOUD_EMBEDDING_MODEL,
|
||||
DEFAULT_CLOUD_EMBEDDING_DIMENSIONS,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(p.name(), "cloud");
|
||||
assert_eq!(p.dimensions(), DEFAULT_CLOUD_EMBEDDING_DIMENSIONS);
|
||||
}
|
||||
|
||||
// ── Default provider ─────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn default_provider_uses_cloud() {
|
||||
let p = default_embedding_provider();
|
||||
assert_eq!(p.name(), "cloud");
|
||||
assert_eq!(p.dimensions(), DEFAULT_CLOUD_EMBEDDING_DIMENSIONS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_local_provider_uses_ollama() {
|
||||
let p = default_local_embedding_provider();
|
||||
|
||||
@@ -32,10 +32,11 @@ pub use schemas::{
|
||||
all_registered_controllers as all_memory_registered_controllers,
|
||||
};
|
||||
pub use store::{
|
||||
create_memory, create_memory_for_migration, create_memory_with_storage,
|
||||
create_memory_with_storage_and_routes, effective_memory_backend_name, MemoryClient,
|
||||
MemoryClientRef, MemoryItemKind, MemoryState, NamespaceDocumentInput, NamespaceMemoryHit,
|
||||
NamespaceQueryResult, NamespaceRetrievalContext, RetrievalScoreBreakdown, UnifiedMemory,
|
||||
create_memory, create_memory_for_migration, create_memory_with_local_ai,
|
||||
create_memory_with_storage, create_memory_with_storage_and_routes,
|
||||
effective_embedding_settings, effective_memory_backend_name, MemoryClient, MemoryClientRef,
|
||||
MemoryItemKind, MemoryState, NamespaceDocumentInput, NamespaceMemoryHit, NamespaceQueryResult,
|
||||
NamespaceRetrievalContext, RetrievalScoreBreakdown, UnifiedMemory,
|
||||
};
|
||||
pub use sync_status::{
|
||||
all_memory_sync_status_controller_schemas, all_memory_sync_status_registered_controllers,
|
||||
|
||||
@@ -31,11 +31,21 @@ pub type MemoryClientRef = Arc<MemoryClient>;
|
||||
/// be initialized.
|
||||
pub struct MemoryState(pub std::sync::Mutex<Option<MemoryClientRef>>);
|
||||
|
||||
/// Local-only memory client backed by SQLite in the user's workspace directory.
|
||||
/// SQLite-backed memory client rooted at the user's workspace directory.
|
||||
///
|
||||
/// All memory storage and retrieval happens on-device; there is no remote sync.
|
||||
/// Remote/cloud memory sync is a future consideration — until then the memory
|
||||
/// subsystem operates entirely locally via [`UnifiedMemory`].
|
||||
/// Storage (documents, vectors, graph) remains on-device via [`UnifiedMemory`].
|
||||
/// Embedding generation is delegated to whichever provider the
|
||||
/// [`MemoryConfig.embedding_provider`](crate::openhuman::config::MemoryConfig)
|
||||
/// resolves to — cloud (OpenHuman backend, the default returned by
|
||||
/// [`crate::openhuman::embeddings::default_embedding_provider`]) or local Ollama
|
||||
/// when explicitly opted into. The cloud embedder resolves its session JWT
|
||||
/// lazily, so an unauthenticated session will surface as a clear error on the
|
||||
/// first `embed` call rather than at client construction.
|
||||
///
|
||||
/// Callers that need a non-default embedder should construct the underlying
|
||||
/// store via [`crate::openhuman::memory::create_memory_with_storage_and_routes`]
|
||||
/// (or [`crate::openhuman::memory::create_memory_with_local_ai`]) with the
|
||||
/// appropriate `MemoryConfig.embedding_provider`.
|
||||
#[derive(Clone)]
|
||||
pub struct MemoryClient {
|
||||
/// The underlying memory implementation.
|
||||
@@ -94,8 +104,14 @@ impl MemoryClient {
|
||||
std::fs::create_dir_all(&workspace_dir)
|
||||
.map_err(|e| format!("Create workspace dir {}: {e}", workspace_dir.display()))?;
|
||||
|
||||
// Initialize the default local embedding provider (Ollama).
|
||||
let embedder: Arc<dyn EmbeddingProvider> = embeddings::default_local_embedding_provider();
|
||||
// Default to cloud embeddings (OpenHuman backend, Voyage-backed). The
|
||||
// cloud embedder is lazy: JWT + API URL are resolved per call, so an
|
||||
// unauthenticated session produces a clear error on first embed rather
|
||||
// than blocking client construction. Callers that need the local
|
||||
// Ollama path should build their memory store via
|
||||
// `create_memory_with_storage_and_routes` with the appropriate
|
||||
// `MemoryConfig.embedding_provider`.
|
||||
let embedder: Arc<dyn EmbeddingProvider> = embeddings::default_embedding_provider();
|
||||
|
||||
// Create the underlying UnifiedMemory instance.
|
||||
let memory =
|
||||
|
||||
@@ -11,11 +11,49 @@
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::openhuman::config::{EmbeddingRouteConfig, MemoryConfig, StorageProviderConfig};
|
||||
use crate::openhuman::embeddings::{self, EmbeddingProvider};
|
||||
use crate::openhuman::config::{
|
||||
EmbeddingRouteConfig, LocalAiConfig, MemoryConfig, StorageProviderConfig,
|
||||
};
|
||||
use crate::openhuman::embeddings::{
|
||||
self, EmbeddingProvider, DEFAULT_OLLAMA_DIMENSIONS, DEFAULT_OLLAMA_MODEL,
|
||||
};
|
||||
use crate::openhuman::memory::store::unified::UnifiedMemory;
|
||||
use crate::openhuman::memory::traits::Memory;
|
||||
|
||||
/// Returns the effective `(provider, model, dimensions)` triple for the
|
||||
/// embedding backend.
|
||||
///
|
||||
/// The user-facing default is `"cloud"` (OpenHuman backend, Voyage-backed) so
|
||||
/// fresh installs work without a local Ollama daemon. When the user has
|
||||
/// explicitly opted into local AI for embeddings —
|
||||
/// [`LocalAiConfig::use_local_for_embeddings`] — we route through the local
|
||||
/// Ollama embedder regardless of what `memory.embedding_provider` says, since
|
||||
/// that toggle is a stronger statement of intent than the per-section default.
|
||||
pub fn effective_embedding_settings(
|
||||
memory: &MemoryConfig,
|
||||
local_ai: Option<&LocalAiConfig>,
|
||||
) -> (String, String, usize) {
|
||||
if local_ai
|
||||
.map(LocalAiConfig::use_local_for_embeddings)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
// Trim once and reuse — the emptiness check and the final model
|
||||
// string must agree, otherwise a value like " bge-m3 " would pass
|
||||
// through to Ollama with surrounding whitespace and 404.
|
||||
let model = local_ai
|
||||
.map(|c| c.embedding_model_id.trim())
|
||||
.filter(|m| !m.is_empty())
|
||||
.unwrap_or(DEFAULT_OLLAMA_MODEL)
|
||||
.to_string();
|
||||
return ("ollama".to_string(), model, DEFAULT_OLLAMA_DIMENSIONS);
|
||||
}
|
||||
(
|
||||
memory.embedding_provider.clone(),
|
||||
memory.embedding_model.clone(),
|
||||
memory.embedding_dimensions,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the effective name of the memory backend being used.
|
||||
///
|
||||
/// Currently, this always returns "namespace" as the unified memory system
|
||||
@@ -41,27 +79,85 @@ pub fn create_memory_with_storage(
|
||||
storage_provider: Option<&StorageProviderConfig>,
|
||||
workspace_dir: &Path,
|
||||
) -> anyhow::Result<Box<dyn Memory>> {
|
||||
create_memory_with_storage_and_routes(config, &[], storage_provider, workspace_dir)
|
||||
create_memory_full(config, &[], storage_provider, None, workspace_dir)
|
||||
}
|
||||
|
||||
/// Create a memory instance honoring both the `memory` and `local_ai` sections.
|
||||
///
|
||||
/// Used by top-level entry points (agent harness, channels runtime) that have
|
||||
/// the full `Config` in scope and want the local-AI opt-in to flip the
|
||||
/// embedder to Ollama.
|
||||
pub fn create_memory_with_local_ai(
|
||||
memory: &MemoryConfig,
|
||||
local_ai: &LocalAiConfig,
|
||||
embedding_routes: &[EmbeddingRouteConfig],
|
||||
storage_provider: Option<&StorageProviderConfig>,
|
||||
workspace_dir: &Path,
|
||||
) -> anyhow::Result<Box<dyn Memory>> {
|
||||
create_memory_full(
|
||||
memory,
|
||||
embedding_routes,
|
||||
storage_provider,
|
||||
Some(local_ai),
|
||||
workspace_dir,
|
||||
)
|
||||
}
|
||||
|
||||
/// Back-compat wrapper preserved for existing call sites that don't have a
|
||||
/// `LocalAiConfig` to pass. The local-AI opt-in is not honored on this path —
|
||||
/// use [`create_memory_with_local_ai`] when both sections are available.
|
||||
pub fn create_memory_with_storage_and_routes(
|
||||
config: &MemoryConfig,
|
||||
embedding_routes: &[EmbeddingRouteConfig],
|
||||
storage_provider: Option<&StorageProviderConfig>,
|
||||
workspace_dir: &Path,
|
||||
) -> anyhow::Result<Box<dyn Memory>> {
|
||||
create_memory_full(
|
||||
config,
|
||||
embedding_routes,
|
||||
storage_provider,
|
||||
None,
|
||||
workspace_dir,
|
||||
)
|
||||
}
|
||||
|
||||
/// The most comprehensive factory function for creating a memory instance.
|
||||
///
|
||||
/// This function initializes the embedding provider and then creates a
|
||||
/// `UnifiedMemory` instance.
|
||||
pub fn create_memory_with_storage_and_routes(
|
||||
fn create_memory_full(
|
||||
config: &MemoryConfig,
|
||||
_embedding_routes: &[EmbeddingRouteConfig],
|
||||
_storage_provider: Option<&StorageProviderConfig>,
|
||||
local_ai: Option<&LocalAiConfig>,
|
||||
workspace_dir: &Path,
|
||||
) -> anyhow::Result<Box<dyn Memory>> {
|
||||
// 1. Create the embedding provider based on config (Local vs Remote).
|
||||
let embedder: Arc<dyn EmbeddingProvider> = Arc::from(embeddings::create_embedding_provider(
|
||||
&config.embedding_provider,
|
||||
&config.embedding_model,
|
||||
config.embedding_dimensions,
|
||||
)?);
|
||||
// 1. Resolve the effective (provider, model, dims) — local-AI opt-in
|
||||
// overrides the per-section default when both are present.
|
||||
let (provider, model, dims) = effective_embedding_settings(config, local_ai);
|
||||
log::debug!(
|
||||
"[memory::factory] effective embedding settings: provider={} model={} dims={} (local_ai_opt_in={})",
|
||||
provider,
|
||||
model,
|
||||
dims,
|
||||
local_ai
|
||||
.map(LocalAiConfig::use_local_for_embeddings)
|
||||
.unwrap_or(false),
|
||||
);
|
||||
|
||||
// 2. Instantiate UnifiedMemory which handles SQLite and vector storage.
|
||||
// 2. Create the embedding provider.
|
||||
let embedder: Arc<dyn EmbeddingProvider> = Arc::from(
|
||||
embeddings::create_embedding_provider(&provider, &model, dims).inspect_err(|err| {
|
||||
log::warn!(
|
||||
"[memory::factory] create_embedding_provider failed provider={} model={} dims={}: {err}",
|
||||
provider,
|
||||
model,
|
||||
dims,
|
||||
);
|
||||
})?,
|
||||
);
|
||||
|
||||
// 3. Instantiate UnifiedMemory which handles SQLite and vector storage.
|
||||
let mem = UnifiedMemory::new(workspace_dir, embedder, config.sqlite_open_timeout_secs)?;
|
||||
Ok(Box::new(mem))
|
||||
}
|
||||
|
||||
@@ -25,8 +25,9 @@ mod memory_trait;
|
||||
|
||||
pub use client::{MemoryClient, MemoryClientRef, MemoryState};
|
||||
pub use factories::{
|
||||
create_memory, create_memory_for_migration, create_memory_with_storage,
|
||||
create_memory_with_storage_and_routes, effective_memory_backend_name,
|
||||
create_memory, create_memory_for_migration, create_memory_with_local_ai,
|
||||
create_memory_with_storage, create_memory_with_storage_and_routes,
|
||||
effective_embedding_settings, effective_memory_backend_name,
|
||||
};
|
||||
pub use types::{
|
||||
GraphRelationRecord, MemoryItemKind, MemoryKvRecord, NamespaceDocumentInput,
|
||||
|
||||
Reference in New Issue
Block a user