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:
@@ -86,6 +86,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.1.76",
|
"@anthropic-ai/claude-agent-sdk": "^0.1.76",
|
||||||
|
"@chroma-core/default-embed": "^0.1.9",
|
||||||
"@modelcontextprotocol/sdk": "^1.25.1",
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"chromadb": "^3.2.2",
|
"chromadb": "^3.2.2",
|
||||||
|
|||||||
+3
-1
@@ -4,7 +4,9 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"description": "Runtime dependencies for claude-mem bundled hooks",
|
"description": "Runtime dependencies for claude-mem bundled hooks",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {},
|
"dependencies": {
|
||||||
|
"@chroma-core/default-embed": "^0.1.9"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0",
|
"node": ">=18.0.0",
|
||||||
"bun": ">=1.0.0"
|
"bun": ">=1.0.0"
|
||||||
|
|||||||
@@ -58,7 +58,10 @@ async function buildHooks() {
|
|||||||
private: true,
|
private: true,
|
||||||
description: 'Runtime dependencies for claude-mem bundled hooks',
|
description: 'Runtime dependencies for claude-mem bundled hooks',
|
||||||
type: 'module',
|
type: 'module',
|
||||||
dependencies: {},
|
dependencies: {
|
||||||
|
// Chroma embedding function with native ONNX binaries (can't be bundled)
|
||||||
|
'@chroma-core/default-embed': '^0.1.9'
|
||||||
|
},
|
||||||
engines: {
|
engines: {
|
||||||
node: '>=18.0.0',
|
node: '>=18.0.0',
|
||||||
bun: '>=1.0.0'
|
bun: '>=1.0.0'
|
||||||
@@ -94,9 +97,12 @@ async function buildHooks() {
|
|||||||
logLevel: 'error', // Suppress warnings (import.meta warning is benign)
|
logLevel: 'error', // Suppress warnings (import.meta warning is benign)
|
||||||
external: [
|
external: [
|
||||||
'bun:sqlite',
|
'bun:sqlite',
|
||||||
// Optional chromadb embedding providers (not needed for HTTP client mode)
|
// Optional chromadb embedding providers
|
||||||
'cohere-ai',
|
'cohere-ai',
|
||||||
'ollama'
|
'ollama',
|
||||||
|
// Default embedding function with native binaries
|
||||||
|
'@chroma-core/default-embed',
|
||||||
|
'onnxruntime-node'
|
||||||
],
|
],
|
||||||
define: {
|
define: {
|
||||||
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
|
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export class ChromaServerManager {
|
|||||||
/**
|
/**
|
||||||
* Start the Chroma HTTP server
|
* Start the Chroma HTTP server
|
||||||
* Spawns `npx chroma run` as a background process
|
* Spawns `npx chroma run` as a background process
|
||||||
|
* If a server is already running (from previous worker), reuses it
|
||||||
*/
|
*/
|
||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
if (this.ready || this.starting) {
|
if (this.ready || this.starting) {
|
||||||
@@ -57,6 +58,24 @@ export class ChromaServerManager {
|
|||||||
});
|
});
|
||||||
return;
|
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;
|
this.starting = true;
|
||||||
|
|
||||||
// Cross-platform: use npx.cmd on Windows
|
// Cross-platform: use npx.cmd on Windows
|
||||||
@@ -159,9 +178,29 @@ export class ChromaServerManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the server is running and ready
|
* Check if the server is running and ready
|
||||||
|
* Returns true if we manage the process OR if a server is responding
|
||||||
*/
|
*/
|
||||||
isRunning(): boolean {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChromaClient, Collection } from 'chromadb';
|
import { ChromaClient, Collection } from 'chromadb';
|
||||||
|
import { DefaultEmbeddingFunction } from '@chroma-core/default-embed';
|
||||||
import { ParsedObservation, ParsedSummary } from '../../sdk/parser.js';
|
import { ParsedObservation, ParsedSummary } from '../../sdk/parser.js';
|
||||||
import { SessionStore } from '../sqlite/SessionStore.js';
|
import { SessionStore } from '../sqlite/SessionStore.js';
|
||||||
import { logger } from '../../utils/logger.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 port = parseInt(settings.CLAUDE_MEM_CHROMA_PORT || '8000', 10);
|
||||||
const ssl = settings.CLAUDE_MEM_CHROMA_SSL === 'true';
|
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') {
|
if (mode === 'local') {
|
||||||
const serverManager = ChromaServerManager.getInstance();
|
const serverManager = ChromaServerManager.getInstance();
|
||||||
if (!serverManager.isRunning()) {
|
const reachable = await serverManager.isServerReachable();
|
||||||
throw new Error('Chroma server not running. Ensure worker started correctly.');
|
if (!reachable) {
|
||||||
|
throw new Error('Chroma server not reachable. Ensure worker started correctly.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,8 +161,11 @@ export class ChromaSync {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// getOrCreateCollection handles both cases
|
// getOrCreateCollection handles both cases
|
||||||
|
// Use DefaultEmbeddingFunction for local embeddings (all-MiniLM-L6-v2)
|
||||||
|
const embeddingFunction = new DefaultEmbeddingFunction();
|
||||||
this.collection = await this.chromaClient.getOrCreateCollection({
|
this.collection = await this.chromaClient.getOrCreateCollection({
|
||||||
name: this.collectionName
|
name: this.collectionName,
|
||||||
|
embeddingFunction
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.debug('CHROMA_SYNC', 'Collection ready', { collection: this.collectionName });
|
logger.debug('CHROMA_SYNC', 'Collection ready', { collection: this.collectionName });
|
||||||
|
|||||||
Reference in New Issue
Block a user