Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

### Fixed

- **`uteke import` works on a store with no embedding backend (#1166)** - the import asked the embedder for every row and failed on the first one when no backend was configured (`open_with_backend(.., None)`), unlike `remember`, which stores such a row keyword-only. The import now does the same: no vector and no duplicate check, the rows are found by keyword search, and `uteke repair --reembed` adds the vectors once a backend exists.
- **A namespace called `all` no longer shares recall-cache entries with queries across all namespaces** - the recall cache keyed an all-namespaces query as the namespace `"all"`, so a cached cross-namespace result could be returned to a caller scoped to a namespace named `all` (and the reverse). The key is now the namespace itself, absent for an all-namespaces query. Found by the CI review of the #1519 move.
- **`uteke-mcp` honours `[embedding]` and `[vector]` and the `UTEKE_EMBEDDING_*` variables (#1535, #1537 step 2)** - the MCP binary opened the store with the built-in ONNX backend and default settings, so with `backend = "openai"` or `"ollama"` (or a custom `dims`) configured it silently embedded with ONNX while the CLI and the server used the configured backend, writing vectors of another model into the same index. It now resolves the embedder with the same single resolution as the CLI and the server (`uteke_core::embedding_config`): `[embedding]` and `[vector]` from the global file, the project file with its untrusted keys stripped, `UTEKE_EMBEDDING_BACKEND` (trimmed, blank = unset) and the other `UTEKE_EMBEDDING_*` variables over the file. An unreachable backend is reported in the tool result (`Embedding NOT written: ...`) instead of silently using ONNX, and an unsupported backend is a config error at start. With nothing configured nothing changes. Agreed with cloud in uteke-cloud#254 (cloud has a fixed server-side embedder; no objection). Checked with binaries from `develop` and from the branch on the same configs.
- **The CLI no longer sends the ONNX model name to an `openai` or `ollama` endpoint (#1537, step 1)** - with `[embedding] backend = "openai"` and no `model`, `uteke` sent `model: "embeddinggemma-q4"` to the endpoint (a real OpenAI rejects it) while `uteke-serve` on the same file sent the backend's default, `text-embedding-3-small`. The CLI's `model` now defaults to empty, like the server's, so an unset model is the backend's default everywhere (same with `UTEKE_EMBEDDING_BACKEND` set in the environment). Checked with binaries from `develop` and from the branch against a mock embeddings endpoint.
Expand Down
25 changes: 23 additions & 2 deletions crates/uteke-core/src/import_export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,18 @@ impl crate::Uteke {
let redactions = prepared.redactions;

// Re-embed the content with retry (consistent with remember path, #1005).
let embedding = self.embed(&entry.content)?;
// A store with no embedding backend (#1166) imports FTS5-only, as
// `remember` stores there: no vector, so no duplicate check either.
let embedding = if self.embedder_backend.is_empty() {
Vec::new()
} else {
self.embed(&entry.content)?
};

// Dedup check: skip if cosine >= 0.95 to existing memory (#1005).
if let Some(existing_id) = self.check_duplicate(&embedding, namespace)? {
if !embedding.is_empty()
&& let Some(existing_id) = self.check_duplicate(&embedding, namespace)?
{
deduped += 1;
// The stored memory is the one that belongs in the rooms (#1588).
room_links.apply(self, &existing_id, &rooms, &link_author);
Expand Down Expand Up @@ -549,4 +557,17 @@ mod room_link_tests {
let json = serde_json::to_value(&r).unwrap();
assert!(json.get("room_links").is_none() && json.get("rooms_missing").is_none());
}

/// #1166: a store opened without an embedding backend imports FTS5-only
/// instead of failing on the first row.
#[test]
fn a_store_without_an_embedder_imports_keyword_only() {
let u = crate::Uteke::open_with_backend(":memory:", None).unwrap();
let file = "{\"content\":\"alpha beta gamma\"}\n{\"content\":\"delta epsilon zeta\"}";
let r = u.import(file, None).unwrap();
assert_eq!((r.imported, r.skipped), (2, 0), "{r:?}");
assert_eq!(u.count(None).unwrap(), 2);
let found = u.search("alpha", 5, None, None).unwrap();
assert_eq!(found.len(), 1, "the rows are keyword-searchable");
}
}
Loading