fix(mcp_server): pass embedding_function= on collection reopen (#1299)
`mcp_server._get_collection` bypassed `ChromaBackend.get_collection` and called `client.get_collection` / `client.create_collection` without `embedding_function=`. ChromaDB 1.x does not persist the EF identity with the collection, so the MCP server's reopen silently bound chromadb's built-in `DefaultEmbeddingFunction` while the miner / Stop hook ingest path bound `mempalace.embedding.get_embedding_function()`. On bleeding-edge interpreters (python 3.14 + chromadb 1.5.x on Apple Silicon, per #1299) the default EF's lazy ONNX provider selection could SIGSEGV the host process on first `col.add()`, killing the MCP stdio server and leaving every subsequent tool call returning `Connection closed` until Claude Code was relaunched. Reads worked because `col.get(ids=...)` and metadata fetches don't invoke the EF; the auto-ingest path worked because mining routes through the backend abstraction. Diary writes were the consistent failure surface. Resolve the EF up front (matching `ChromaBackend._resolve_embedding_function`) and pass it into both reopen branches. Falls back to the chromadb default only if `mempalace.embedding.get_embedding_function` itself raises. Regression test patches the chromadb client class to capture `embedding_function=` on every `get_collection` / `create_collection` call from `_get_collection(create=True)` and `_get_collection()`, and fails if any call omits it. Follow-up to #1262 / #1289 (which fixed the metadata-mismatch SIGSEGV path); this addresses the EF-mismatch SIGSEGV path on the same surface.
This commit is contained in:
+20
-2
@@ -66,6 +66,7 @@ from .backends.chroma import ( # noqa: E402
|
||||
_pin_hnsw_threads,
|
||||
hnsw_capacity_status,
|
||||
)
|
||||
from .embedding import get_embedding_function # noqa: E402
|
||||
from .query_sanitizer import sanitize_query # noqa: E402
|
||||
from .searcher import search_memories # noqa: E402
|
||||
from .palace_graph import ( # noqa: E402
|
||||
@@ -278,6 +279,22 @@ def _get_collection(create=False):
|
||||
global _collection_cache, _metadata_cache, _metadata_cache_time
|
||||
try:
|
||||
client = _get_client()
|
||||
# ChromaDB 1.x does not persist the embedding function with the
|
||||
# collection, so a reader/writer that omits ``embedding_function=``
|
||||
# silently gets the chromadb-built-in default. On bleeding-edge
|
||||
# interpreters (#1299: python 3.14 + chromadb 1.5.x on Apple Silicon)
|
||||
# the default's lazy ONNX provider selection can SIGSEGV the host
|
||||
# process on first ``col.add()``. The miner / Stop hook ingest path
|
||||
# avoids this because it routes through ``ChromaBackend.get_collection``
|
||||
# which resolves the EF via ``mempalace.embedding.get_embedding_function``.
|
||||
# The MCP server bypassed that abstraction; mirror its behaviour so
|
||||
# ``tool_diary_write`` / ``tool_add_drawer`` get the same EF as mining.
|
||||
try:
|
||||
ef = get_embedding_function()
|
||||
except Exception:
|
||||
logger.exception("Failed to build embedding function; using chromadb default")
|
||||
ef = None
|
||||
ef_kwargs = {"embedding_function": ef} if ef is not None else {}
|
||||
if create:
|
||||
# hnsw:num_threads=1 disables ChromaDB's multi-threaded ParallelFor
|
||||
# HNSW insert path, which has a race in repairConnectionsForUpdate /
|
||||
@@ -292,7 +309,7 @@ def _get_collection(create=False):
|
||||
# below skips the metadata-comparison codepath for existing
|
||||
# collections, mirroring the backend-layer fix from #1262.
|
||||
try:
|
||||
raw = client.get_collection(_config.collection_name)
|
||||
raw = client.get_collection(_config.collection_name, **ef_kwargs)
|
||||
except _ChromaNotFoundError:
|
||||
raw = client.create_collection(
|
||||
_config.collection_name,
|
||||
@@ -301,13 +318,14 @@ def _get_collection(create=False):
|
||||
"hnsw:num_threads": 1,
|
||||
**_HNSW_BLOAT_GUARD,
|
||||
},
|
||||
**ef_kwargs,
|
||||
)
|
||||
_pin_hnsw_threads(raw)
|
||||
_collection_cache = ChromaCollection(raw)
|
||||
_metadata_cache = None
|
||||
_metadata_cache_time = 0
|
||||
elif _collection_cache is None:
|
||||
raw = client.get_collection(_config.collection_name)
|
||||
raw = client.get_collection(_config.collection_name, **ef_kwargs)
|
||||
_pin_hnsw_threads(raw)
|
||||
_collection_cache = ChromaCollection(raw)
|
||||
_metadata_cache = None
|
||||
|
||||
Reference in New Issue
Block a user