feat: add heartbeat monitor and vector memory search (Tier 2)
Heartbeat: - HeartbeatMonitor with 5 checks: gateway, model, channels, memory, disk - Configurable interval, failure threshold, notification channel - Recovery notifications when health restores - 25 new tests Vector Memory Search: - EmbeddingProvider interface with OpenAI, Gemini, Ollama, LlamaCpp backends - SQLite-backed VectorStore with cosine similarity search - Text chunker with paragraph-aware splitting and overlap - HybridSearch merging keyword + vector results with configurable weight - Background indexer with dirty-namespace tracking - Graceful fallback to keyword search when embeddings unavailable - 51 new tests Config: automation.heartbeat + memory.embedding schema sections Total: 950 tests passing, all types clean
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
createEmbeddingProvider,
|
||||
OpenAIEmbeddingProvider,
|
||||
GeminiEmbeddingProvider,
|
||||
OllamaEmbeddingProvider,
|
||||
LlamaCppEmbeddingProvider,
|
||||
} from './embeddings.js';
|
||||
import type { EmbeddingConfig } from '../config/schema.js';
|
||||
|
||||
describe('createEmbeddingProvider', () => {
|
||||
const baseConfig: EmbeddingConfig = {
|
||||
enabled: true,
|
||||
provider: 'openai',
|
||||
model: 'text-embedding-3-small',
|
||||
chunk_size: 512,
|
||||
chunk_overlap: 50,
|
||||
top_k: 5,
|
||||
hybrid_weight: 0.7,
|
||||
};
|
||||
|
||||
it('creates OpenAI provider', () => {
|
||||
const provider = createEmbeddingProvider({ ...baseConfig, provider: 'openai' });
|
||||
expect(provider).toBeInstanceOf(OpenAIEmbeddingProvider);
|
||||
});
|
||||
|
||||
it('creates Gemini provider', () => {
|
||||
const provider = createEmbeddingProvider({ ...baseConfig, provider: 'gemini' });
|
||||
expect(provider).toBeInstanceOf(GeminiEmbeddingProvider);
|
||||
});
|
||||
|
||||
it('creates Ollama provider', () => {
|
||||
const provider = createEmbeddingProvider({ ...baseConfig, provider: 'ollama' });
|
||||
expect(provider).toBeInstanceOf(OllamaEmbeddingProvider);
|
||||
});
|
||||
|
||||
it('creates LlamaCpp provider', () => {
|
||||
const provider = createEmbeddingProvider({ ...baseConfig, provider: 'llamacpp' });
|
||||
expect(provider).toBeInstanceOf(LlamaCppEmbeddingProvider);
|
||||
});
|
||||
|
||||
it('throws on unknown provider', () => {
|
||||
expect(() => createEmbeddingProvider({ ...baseConfig, provider: 'unknown' as never })).toThrow('Unknown embedding provider');
|
||||
});
|
||||
});
|
||||
|
||||
describe('OpenAIEmbeddingProvider', () => {
|
||||
it('reports configured dimensions', () => {
|
||||
const config: EmbeddingConfig = {
|
||||
enabled: true,
|
||||
provider: 'openai',
|
||||
model: 'text-embedding-3-small',
|
||||
dimensions: 512,
|
||||
chunk_size: 512,
|
||||
chunk_overlap: 50,
|
||||
top_k: 5,
|
||||
hybrid_weight: 0.7,
|
||||
};
|
||||
const provider = new OpenAIEmbeddingProvider(config);
|
||||
expect(provider.dimensions).toBe(512);
|
||||
});
|
||||
|
||||
it('defaults to 1536 dimensions', () => {
|
||||
const config: EmbeddingConfig = {
|
||||
enabled: true,
|
||||
provider: 'openai',
|
||||
model: 'text-embedding-3-small',
|
||||
chunk_size: 512,
|
||||
chunk_overlap: 50,
|
||||
top_k: 5,
|
||||
hybrid_weight: 0.7,
|
||||
};
|
||||
const provider = new OpenAIEmbeddingProvider(config);
|
||||
expect(provider.dimensions).toBe(1536);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GeminiEmbeddingProvider', () => {
|
||||
it('reports configured dimensions', () => {
|
||||
const config: EmbeddingConfig = {
|
||||
enabled: true,
|
||||
provider: 'gemini',
|
||||
model: 'text-embedding-004',
|
||||
dimensions: 256,
|
||||
chunk_size: 512,
|
||||
chunk_overlap: 50,
|
||||
top_k: 5,
|
||||
hybrid_weight: 0.7,
|
||||
};
|
||||
const provider = new GeminiEmbeddingProvider(config);
|
||||
expect(provider.dimensions).toBe(256);
|
||||
});
|
||||
|
||||
it('defaults to 768 dimensions', () => {
|
||||
const config: EmbeddingConfig = {
|
||||
enabled: true,
|
||||
provider: 'gemini',
|
||||
model: 'text-embedding-004',
|
||||
chunk_size: 512,
|
||||
chunk_overlap: 50,
|
||||
top_k: 5,
|
||||
hybrid_weight: 0.7,
|
||||
};
|
||||
const provider = new GeminiEmbeddingProvider(config);
|
||||
expect(provider.dimensions).toBe(768);
|
||||
});
|
||||
});
|
||||
|
||||
describe('OllamaEmbeddingProvider', () => {
|
||||
it('reports configured dimensions', () => {
|
||||
const config: EmbeddingConfig = {
|
||||
enabled: true,
|
||||
provider: 'ollama',
|
||||
model: 'nomic-embed-text',
|
||||
dimensions: 384,
|
||||
endpoint: 'http://localhost:11434',
|
||||
chunk_size: 512,
|
||||
chunk_overlap: 50,
|
||||
top_k: 5,
|
||||
hybrid_weight: 0.7,
|
||||
};
|
||||
const provider = new OllamaEmbeddingProvider(config);
|
||||
expect(provider.dimensions).toBe(384);
|
||||
});
|
||||
});
|
||||
|
||||
describe('LlamaCppEmbeddingProvider', () => {
|
||||
it('reports configured dimensions', () => {
|
||||
const config: EmbeddingConfig = {
|
||||
enabled: true,
|
||||
provider: 'llamacpp',
|
||||
model: 'unused',
|
||||
dimensions: 768,
|
||||
endpoint: 'http://localhost:8080',
|
||||
chunk_size: 512,
|
||||
chunk_overlap: 50,
|
||||
top_k: 5,
|
||||
hybrid_weight: 0.7,
|
||||
};
|
||||
const provider = new LlamaCppEmbeddingProvider(config);
|
||||
expect(provider.dimensions).toBe(768);
|
||||
});
|
||||
|
||||
it('defaults endpoint to localhost:8080', () => {
|
||||
const config: EmbeddingConfig = {
|
||||
enabled: true,
|
||||
provider: 'llamacpp',
|
||||
model: 'unused',
|
||||
dimensions: 768,
|
||||
chunk_size: 512,
|
||||
chunk_overlap: 50,
|
||||
top_k: 5,
|
||||
hybrid_weight: 0.7,
|
||||
};
|
||||
// Provider should be constructable without endpoint
|
||||
const provider = new LlamaCppEmbeddingProvider(config);
|
||||
expect(provider.dimensions).toBe(768);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user