Integrate semantic cache with ChromaDB URL
This commit is contained in:
@@ -32,6 +32,13 @@ const mockSettings: PluginSettings = {
|
||||
vaultSearchLimit: 3,
|
||||
maxMessageHistory: 50,
|
||||
lastIndexTime: 0,
|
||||
cacheConfig: {
|
||||
enabled: false,
|
||||
similarityThreshold: 0.9,
|
||||
collectionName: 'test-cache',
|
||||
embeddingModel: 'nomic-embed-text',
|
||||
chromaUrl: 'http://localhost:8000',
|
||||
},
|
||||
};
|
||||
|
||||
describe('ChatView', () => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { OllamaMessage, OllamaTool, CacheConfig } from '../src/types';
|
||||
const mockInitialize = jest.fn().mockResolvedValue(undefined);
|
||||
const mockGetCache = jest.fn().mockResolvedValue(null);
|
||||
const mockSetCache = jest.fn().mockResolvedValue(undefined);
|
||||
const mockClearCache = jest.fn().mockResolvedValue(undefined);
|
||||
|
||||
// Mock the semantic cache service BEFORE importing OllamaClient
|
||||
jest.mock('../src/semantic-cache', () => ({
|
||||
@@ -13,6 +14,7 @@ jest.mock('../src/semantic-cache', () => ({
|
||||
initialize: mockInitialize,
|
||||
getCache: mockGetCache,
|
||||
setCache: mockSetCache,
|
||||
clearCache: mockClearCache,
|
||||
})),
|
||||
}));
|
||||
|
||||
@@ -68,6 +70,7 @@ describe('OllamaClient with Semantic Cache', () => {
|
||||
similarityThreshold: 0.85,
|
||||
collectionName: 'test_cache',
|
||||
embeddingModel: 'nomic-embed-text',
|
||||
chromaUrl: 'http://localhost:8000',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -101,6 +104,7 @@ describe('OllamaClient with Semantic Cache', () => {
|
||||
});
|
||||
|
||||
it('should not create cache service when no config provided', () => {
|
||||
jest.clearAllMocks(); // Reset the call recorded by beforeEach before checking
|
||||
new OllamaClient('http://localhost:11434', 'llama3', mockFetch);
|
||||
|
||||
expect(SemanticCacheService).not.toHaveBeenCalled();
|
||||
@@ -350,19 +354,8 @@ describe('OllamaClient with Semantic Cache', () => {
|
||||
// Mock cache service to throw an error
|
||||
mockGetCache.mockRejectedValueOnce(new Error('Cache error'));
|
||||
|
||||
const mockResponse = {
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
message: {
|
||||
content: 'LLM response after cache failure',
|
||||
},
|
||||
}),
|
||||
};
|
||||
mockFetch.mockResolvedValueOnce(mockResponse);
|
||||
|
||||
// The chat method does not handle cache errors, so it should propagate
|
||||
// However, the client should still be usable
|
||||
// Note: fetch is never reached because the cache throws first.
|
||||
// The chat method does not handle cache errors, so it should propagate.
|
||||
await expect(client.chat(mockMessages)).rejects.toThrow('Cache error');
|
||||
});
|
||||
|
||||
@@ -414,6 +407,18 @@ describe('OllamaClient with Semantic Cache', () => {
|
||||
expect(mockGetCache).toHaveBeenCalledWith('Second question');
|
||||
});
|
||||
|
||||
it('should call clearCache on the cache service', async () => {
|
||||
await client.clearCache();
|
||||
expect(mockClearCache).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not throw when clearCache is called without a cache service', async () => {
|
||||
const noCacheClient = new OllamaClient('http://localhost:11434', 'llama3', mockFetch);
|
||||
jest.clearAllMocks();
|
||||
await expect(noCacheClient.clearCache()).resolves.toBeUndefined();
|
||||
expect(mockClearCache).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should skip cache when no user message found', async () => {
|
||||
const onlyAssistantMessages: OllamaMessage[] = [
|
||||
{ role: 'system', content: 'You are helpful.' },
|
||||
|
||||
@@ -7,12 +7,20 @@ import { CacheConfig } from '../src/types';
|
||||
jest.mock('chromadb', () => ({
|
||||
ChromaClient: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
getOrCreateCollection: jest.fn().mockResolvedValue({
|
||||
getOrCreateCollection: jest.fn().mockReturnValue({
|
||||
query: jest.fn(),
|
||||
add: jest.fn(),
|
||||
upsert: jest.fn(),
|
||||
}),
|
||||
deleteCollection: jest.fn(),
|
||||
};
|
||||
}),
|
||||
IncludeEnum: {
|
||||
Documents: 'documents',
|
||||
Embeddings: 'embeddings',
|
||||
Metadatas: 'metadatas',
|
||||
Distances: 'distances',
|
||||
},
|
||||
}));
|
||||
|
||||
// Now import SemanticCacheService after mocking
|
||||
@@ -21,10 +29,12 @@ import { SemanticCacheService } from '../src/semantic-cache';
|
||||
jest.spyOn(global, 'fetch').mockImplementation(jest.fn());
|
||||
|
||||
const mockChromaClient = {
|
||||
getOrCreateCollection: jest.fn().mockResolvedValue({
|
||||
getOrCreateCollection: jest.fn().mockReturnValue({
|
||||
query: jest.fn(),
|
||||
add: jest.fn(),
|
||||
upsert: jest.fn(),
|
||||
}),
|
||||
deleteCollection: jest.fn(),
|
||||
};
|
||||
|
||||
// Set up mock instance
|
||||
@@ -44,6 +54,7 @@ describe('SemanticCacheService', () => {
|
||||
similarityThreshold: 0.85,
|
||||
collectionName: 'test_cache',
|
||||
embeddingModel: 'nomic-embed-text',
|
||||
chromaUrl: 'http://localhost:8000',
|
||||
};
|
||||
|
||||
service = new SemanticCacheService('http://localhost:11434', config);
|
||||
@@ -211,32 +222,30 @@ describe('SemanticCacheService', () => {
|
||||
json: () => Promise.resolve({ embedding: [0.1, 0.2, 0.3] }),
|
||||
});
|
||||
|
||||
// crypto.randomUUID mock
|
||||
const mockUuid = 'mock-uuid-123' as any;
|
||||
jest.spyOn(crypto, 'randomUUID').mockReturnValue(mockUuid);
|
||||
|
||||
await service.setCache('test prompt', 'test response');
|
||||
|
||||
const mockCollection = mockChromaClient.getOrCreateCollection();
|
||||
expect(mockCollection.add).toHaveBeenCalledWith({
|
||||
ids: [mockUuid],
|
||||
embeddings: [[0.1, 0.2, 0.3]],
|
||||
metadatas: [{ fullResponse: 'test response' }],
|
||||
});
|
||||
expect(mockCollection.upsert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
ids: [expect.any(String)],
|
||||
embeddings: [[0.1, 0.2, 0.3]],
|
||||
metadatas: [{ fullResponse: 'test response' }],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add entry when prompt is empty', async () => {
|
||||
await service.setCache(' ', 'test response');
|
||||
|
||||
expect(mockFetch).not.toHaveBeenCalled();
|
||||
expect(mockChromaClient.getOrCreateCollection().add).not.toHaveBeenCalled();
|
||||
expect(mockChromaClient.getOrCreateCollection().upsert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not add entry when response is empty', async () => {
|
||||
await service.setCache('test prompt', ' ');
|
||||
|
||||
expect(mockFetch).not.toHaveBeenCalled();
|
||||
expect(mockChromaClient.getOrCreateCollection().add).not.toHaveBeenCalled();
|
||||
expect(mockChromaClient.getOrCreateCollection().upsert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not add entry when cache is disabled', async () => {
|
||||
@@ -255,13 +264,33 @@ describe('SemanticCacheService', () => {
|
||||
json: () => Promise.resolve({ embedding: [0.1, 0.2, 0.3] }),
|
||||
});
|
||||
|
||||
const mockUuid = 'mock-uuid-456' as any;
|
||||
jest.spyOn(crypto, 'randomUUID').mockReturnValue(mockUuid);
|
||||
|
||||
mockChromaClient.getOrCreateCollection().add.mockRejectedValueOnce(new Error('Add failed'));
|
||||
mockChromaClient
|
||||
.getOrCreateCollection()
|
||||
.upsert.mockRejectedValueOnce(new Error('Add failed'));
|
||||
|
||||
// Should not throw
|
||||
await expect(service.setCache('test prompt', 'test response')).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearCache', () => {
|
||||
beforeEach(async () => {
|
||||
await service.initialize();
|
||||
mockChromaClient.deleteCollection.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it('should delete the collection and re-initialize', async () => {
|
||||
await service.clearCache();
|
||||
|
||||
expect(mockChromaClient.deleteCollection).toHaveBeenCalledWith({ name: 'test_cache' });
|
||||
// getOrCreateCollection called once in beforeEach initialize, once in clearCache re-init
|
||||
expect(mockChromaClient.getOrCreateCollection).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should propagate errors from deleteCollection', async () => {
|
||||
mockChromaClient.deleteCollection.mockRejectedValueOnce(new Error('Delete failed'));
|
||||
|
||||
await expect(service.clearCache()).rejects.toThrow('Delete failed');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user