feat: Add DefaultEmbeddingFunction for local vector embeddings

- Added @chroma-core/default-embed dependency for local embeddings
- Updated ChromaSync to use DefaultEmbeddingFunction with collections
- Added isServerReachable() async method for reliable server detection
- Fixed start() to detect and reuse existing Chroma servers
- Updated build script to externalize native ONNX binaries
- Added runtime dependency to plugin/package.json

The embedding function uses all-MiniLM-L6-v2 model locally via ONNX,
eliminating need for external embedding API calls.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
bigphoot
2026-01-23 23:28:38 -08:00
committed by bigphoot
parent 70d6ac9daf
commit 2c304eafad
5 changed files with 62 additions and 9 deletions
+1
View File
@@ -86,6 +86,7 @@
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.1.76",
"@chroma-core/default-embed": "^0.1.9",
"@modelcontextprotocol/sdk": "^1.25.1",
"ansi-to-html": "^0.7.2",
"chromadb": "^3.2.2",
+3 -1
View File
@@ -4,7 +4,9 @@
"private": true,
"description": "Runtime dependencies for claude-mem bundled hooks",
"type": "module",
"dependencies": {},
"dependencies": {
"@chroma-core/default-embed": "^0.1.9"
},
"engines": {
"node": ">=18.0.0",
"bun": ">=1.0.0"
+9 -3
View File
@@ -58,7 +58,10 @@ async function buildHooks() {
private: true,
description: 'Runtime dependencies for claude-mem bundled hooks',
type: 'module',
dependencies: {},
dependencies: {
// Chroma embedding function with native ONNX binaries (can't be bundled)
'@chroma-core/default-embed': '^0.1.9'
},
engines: {
node: '>=18.0.0',
bun: '>=1.0.0'
@@ -94,9 +97,12 @@ async function buildHooks() {
logLevel: 'error', // Suppress warnings (import.meta warning is benign)
external: [
'bun:sqlite',
// Optional chromadb embedding providers (not needed for HTTP client mode)
// Optional chromadb embedding providers
'cohere-ai',
'ollama'
'ollama',
// Default embedding function with native binaries
'@chroma-core/default-embed',
'onnxruntime-node'
],
define: {
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
+40 -1
View File
@@ -48,6 +48,7 @@ export class ChromaServerManager {
/**
* Start the Chroma HTTP server
* Spawns `npx chroma run` as a background process
* If a server is already running (from previous worker), reuses it
*/
async start(): Promise<void> {
if (this.ready || this.starting) {
@@ -57,6 +58,24 @@ export class ChromaServerManager {
});
return;
}
// Check if a server is already running (from previous worker or manual start)
try {
const response = await fetch(
`http://${this.config.host}:${this.config.port}/api/v2/heartbeat`
);
if (response.ok) {
logger.info('CHROMA_SERVER', 'Existing server detected, reusing', {
host: this.config.host,
port: this.config.port
});
this.ready = true;
return;
}
} catch {
// No server running, proceed to start one
}
this.starting = true;
// Cross-platform: use npx.cmd on Windows
@@ -159,9 +178,29 @@ export class ChromaServerManager {
/**
* Check if the server is running and ready
* Returns true if we manage the process OR if a server is responding
*/
isRunning(): boolean {
return this.ready && this.serverProcess !== null;
return this.ready;
}
/**
* Async check if server is running by pinging heartbeat
* Use this when you need to verify server is actually reachable
*/
async isServerReachable(): Promise<boolean> {
try {
const response = await fetch(
`http://${this.config.host}:${this.config.port}/api/v2/heartbeat`
);
if (response.ok) {
this.ready = true;
return true;
}
} catch {
// Server not reachable
}
return false;
}
/**
+9 -4
View File
@@ -13,6 +13,7 @@
*/
import { ChromaClient, Collection } from 'chromadb';
import { DefaultEmbeddingFunction } from '@chroma-core/default-embed';
import { ParsedObservation, ParsedSummary } from '../../sdk/parser.js';
import { SessionStore } from '../sqlite/SessionStore.js';
import { logger } from '../../utils/logger.js';
@@ -108,11 +109,12 @@ export class ChromaSync {
const port = parseInt(settings.CLAUDE_MEM_CHROMA_PORT || '8000', 10);
const ssl = settings.CLAUDE_MEM_CHROMA_SSL === 'true';
// In local mode, verify server is running
// In local mode, verify server is reachable
if (mode === 'local') {
const serverManager = ChromaServerManager.getInstance();
if (!serverManager.isRunning()) {
throw new Error('Chroma server not running. Ensure worker started correctly.');
const reachable = await serverManager.isServerReachable();
if (!reachable) {
throw new Error('Chroma server not reachable. Ensure worker started correctly.');
}
}
@@ -159,8 +161,11 @@ export class ChromaSync {
try {
// getOrCreateCollection handles both cases
// Use DefaultEmbeddingFunction for local embeddings (all-MiniLM-L6-v2)
const embeddingFunction = new DefaultEmbeddingFunction();
this.collection = await this.chromaClient.getOrCreateCollection({
name: this.collectionName
name: this.collectionName,
embeddingFunction
});
logger.debug('CHROMA_SYNC', 'Collection ready', { collection: this.collectionName });