mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
feat(memory_tree): switch embed model to bge-m3 (1024-dim, 8K context) (#1174)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
644c5c8bd3
commit
b8113e96a5
@@ -85,7 +85,7 @@ mod tests {
|
||||
fn ollama_chosen_when_endpoint_and_model_set() {
|
||||
let (_tmp, mut cfg) = test_config();
|
||||
cfg.memory_tree.embedding_endpoint = Some("http://localhost:11434".into());
|
||||
cfg.memory_tree.embedding_model = Some("nomic-embed-text".into());
|
||||
cfg.memory_tree.embedding_model = Some("bge-m3".into());
|
||||
cfg.memory_tree.embedding_timeout_ms = Some(5000);
|
||||
let e = build_embedder_from_config(&cfg).expect("Ollama path should build");
|
||||
assert_eq!(e.name(), "ollama");
|
||||
|
||||
@@ -2,15 +2,24 @@
|
||||
//!
|
||||
//! Produces a fixed-dimension vector per chunk / summary so retrieval can
|
||||
//! rerank candidates by semantic similarity. Phase 4's default backend is a
|
||||
//! local [Ollama](https://ollama.com) endpoint running `nomic-embed-text`;
|
||||
//! local [Ollama](https://ollama.com) endpoint running `bge-m3`;
|
||||
//! tests use the deterministic [`InertEmbedder`] so no network is required.
|
||||
//!
|
||||
//! Dimension is hard-coded at [`EMBEDDING_DIM`] (768) — matches the
|
||||
//! nomic-embed-text output and keeps the blob layout on `mem_tree_chunks` /
|
||||
//! Dimension is hard-coded at [`EMBEDDING_DIM`] (1024) — matches the
|
||||
//! bge-m3 output and keeps the blob layout on `mem_tree_chunks` /
|
||||
//! `mem_tree_summaries` consistent across providers. Mixing dimensions
|
||||
//! mid-run would corrupt cosine comparisons; we catch that at the trait
|
||||
//! level rather than deferring to retrieval-time diagnostics.
|
||||
//!
|
||||
//! NOTE: bge-m3 replaces the prior `nomic-embed-text` (768-dim, 2048
|
||||
//! token context). Migration was driven by nomic's hard 2048-token
|
||||
//! context cap causing long-chunk embed failures (chunker estimates
|
||||
//! undercount BERT-WordPiece tokens by ~1.5-2× for HTML-derived
|
||||
//! markdown, so 1500 chunker-tokens routinely exceed nomic's cap).
|
||||
//! bge-m3 has a native 8192-token context. Existing `embedding` blobs
|
||||
//! from the 768-dim era are invalid against the new dimension and
|
||||
//! must be wiped or re-embedded.
|
||||
//!
|
||||
//! Write-time semantics: ingest + seal call [`Embedder::embed`] **before**
|
||||
//! persisting the new row, so a provider error cascades into "don't write
|
||||
//! this row". Legacy rows from Phases 1-3 predate embeddings and read back
|
||||
@@ -30,11 +39,11 @@ pub use ollama::OllamaEmbedder;
|
||||
|
||||
/// Embedding dimensionality used across the memory tree.
|
||||
///
|
||||
/// Hard-coded to match `nomic-embed-text`; swapping providers requires a
|
||||
/// matching dimension or the trait's post-call validation will bail. Any
|
||||
/// change to this constant breaks on-disk compatibility with existing
|
||||
/// Hard-coded to match `bge-m3`; swapping providers requires a matching
|
||||
/// dimension or the trait's post-call validation will bail. Any change
|
||||
/// to this constant breaks on-disk compatibility with existing
|
||||
/// `mem_tree_chunks.embedding` / `mem_tree_summaries.embedding` blobs.
|
||||
pub const EMBEDDING_DIM: usize = 768;
|
||||
pub const EMBEDDING_DIM: usize = 1024;
|
||||
|
||||
/// Trait backing all Phase 4 embedders. Implementations MUST produce
|
||||
/// exactly [`EMBEDDING_DIM`] floats per call — callers that persist the
|
||||
@@ -206,9 +215,12 @@ mod tests {
|
||||
#[test]
|
||||
fn unpack_wrong_dim_errors() {
|
||||
// Correct byte multiple, but wrong float count.
|
||||
let bad = vec![0u8; 16]; // 4 floats, expected 768
|
||||
let bad = vec![0u8; 16]; // 4 floats, expected EMBEDDING_DIM (1024)
|
||||
let err = unpack_embedding(&bad).unwrap_err().to_string();
|
||||
assert!(err.contains("expected 768"), "got {err}");
|
||||
assert!(
|
||||
err.contains(&format!("expected {EMBEDDING_DIM}")),
|
||||
"got {err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//! Ollama-backed embedder for Phase 4 (#710).
|
||||
//!
|
||||
//! Posts `{model, prompt}` to `{endpoint}/api/embeddings` and expects
|
||||
//! Posts `{model, prompt, options: {num_ctx}}` to
|
||||
//! `{endpoint}/api/embeddings` and expects
|
||||
//! `{"embedding": [f32; EMBEDDING_DIM]}` back. Designed for a local
|
||||
//! `ollama serve` hosting `nomic-embed-text`.
|
||||
//! `ollama serve` hosting `bge-m3`.
|
||||
//!
|
||||
//! This is intentionally a tiny HTTP client — no retry, no pool caching,
|
||||
//! no streaming. Phase 4 wants the simplest thing that works so we can
|
||||
@@ -22,8 +23,10 @@ use super::{Embedder, EMBEDDING_DIM};
|
||||
/// `local_ai` subsystem and the Ollama defaults.
|
||||
pub const DEFAULT_ENDPOINT: &str = "http://localhost:11434";
|
||||
|
||||
/// Default embedding model — must output exactly [`EMBEDDING_DIM`] dims.
|
||||
pub const DEFAULT_MODEL: &str = "nomic-embed-text";
|
||||
/// Default embedding model — must output exactly [`EMBEDDING_DIM`]
|
||||
/// (1024) dims. `bge-m3` is a multilingual BERT-family encoder with
|
||||
/// native 8192-token context and 1024-dim output.
|
||||
pub const DEFAULT_MODEL: &str = "bge-m3";
|
||||
|
||||
/// Default request timeout. Ollama's first-use latency is a few hundred
|
||||
/// ms on a warm model; 10s absorbs a cold-model load on commodity
|
||||
@@ -99,10 +102,28 @@ impl OllamaEmbedder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Override Ollama's per-model `num_ctx` default. Ollama loads
|
||||
/// embedding models with `num_ctx = 4096` (or whatever default the
|
||||
/// model's modelfile carries) unless the request explicitly asks for
|
||||
/// more. `bge-m3` natively supports 8192 tokens, and chunker-token
|
||||
/// counts undercount BERT-WordPiece tokens by ~1.5-2× for HTML-derived
|
||||
/// markdown — so a 1500-chunker-token chunk routinely produces 2500+
|
||||
/// real tokens at embed time. Asking for 8192 unconditionally avoids
|
||||
/// silent prompt truncation; on models that natively support less,
|
||||
/// Ollama clamps `num_ctx` to the model's actual maximum, so this is
|
||||
/// safe to over-request.
|
||||
const EMBED_NUM_CTX: u32 = 8192;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct EmbedRequest<'a> {
|
||||
model: &'a str,
|
||||
prompt: &'a str,
|
||||
options: EmbedOptions,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct EmbedOptions {
|
||||
num_ctx: u32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -127,6 +148,9 @@ impl Embedder for OllamaEmbedder {
|
||||
let req = EmbedRequest {
|
||||
model: &self.model,
|
||||
prompt: text,
|
||||
options: EmbedOptions {
|
||||
num_ctx: EMBED_NUM_CTX,
|
||||
},
|
||||
};
|
||||
let resp = self
|
||||
.client
|
||||
@@ -223,8 +247,9 @@ mod tests {
|
||||
post(move |Json(body): Json<serde_json::Value>| {
|
||||
let v = v_clone.clone();
|
||||
async move {
|
||||
assert_eq!(body["model"], "nomic-embed-text");
|
||||
assert_eq!(body["model"], "bge-m3");
|
||||
assert_eq!(body["prompt"], "hello world");
|
||||
assert_eq!(body["options"]["num_ctx"], 8192);
|
||||
Json(serde_json::json!({ "embedding": v }))
|
||||
}
|
||||
}),
|
||||
@@ -262,7 +287,7 @@ mod tests {
|
||||
let e = OllamaEmbedder::new(url, String::new(), 0);
|
||||
let err = e.embed("hi").await.unwrap_err().to_string();
|
||||
assert!(err.contains("3 dims"), "msg: {err}");
|
||||
assert!(err.contains("expected 768"), "msg: {err}");
|
||||
assert!(err.contains("expected 1024"), "msg: {err}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user