mirror of
https://github.com/geoffsee/open-gsio.git
synced 2025-09-08 22:56:46 +00:00
- Add cache refresh mechanism for providers in ChatService
- Implemented tests to validate caching logic based on provider changes - Enhanced caching logic to include a provider signature for more precise cache validation
This commit is contained in:
@@ -37,6 +37,18 @@ vi.mock('../../lib/handleStreamData', () => ({
|
|||||||
default: vi.fn().mockReturnValue(() => {}),
|
default: vi.fn().mockReturnValue(() => {}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Mock ProviderRepository
|
||||||
|
vi.mock('@open-gsio/ai/providers/_ProviderRepository.ts', () => {
|
||||||
|
return {
|
||||||
|
ProviderRepository: class {
|
||||||
|
constructor() {}
|
||||||
|
getProviders() {
|
||||||
|
return [{ name: 'openai', key: 'test-key', endpoint: 'https://api.openai.com/v1' }];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('ChatService', () => {
|
describe('ChatService', () => {
|
||||||
let chatService: any;
|
let chatService: any;
|
||||||
let mockEnv: any;
|
let mockEnv: any;
|
||||||
@@ -221,6 +233,105 @@ describe('ChatService', () => {
|
|||||||
Response.json = originalResponseJson;
|
Response.json = originalResponseJson;
|
||||||
localService.getSupportedModels = originalGetSupportedModels;
|
localService.getSupportedModels = originalGetSupportedModels;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should test the cache refresh mechanism when providers change', async () => {
|
||||||
|
// This test verifies that the cache is refreshed when providers change
|
||||||
|
// and that the cache is used when providers haven't changed.
|
||||||
|
|
||||||
|
// Mock data for the first scenario (cache hit)
|
||||||
|
const cachedModels = [
|
||||||
|
{ id: 'model-1', provider: 'openai' },
|
||||||
|
{ id: 'model-2', provider: 'openai' },
|
||||||
|
];
|
||||||
|
const providersSignature = JSON.stringify(['openai']);
|
||||||
|
|
||||||
|
// Mock KV_STORAGE for the first scenario (cache hit)
|
||||||
|
const mockKVStorage = {
|
||||||
|
get: vi.fn().mockImplementation(key => {
|
||||||
|
if (key === 'supportedModels') return Promise.resolve(JSON.stringify(cachedModels));
|
||||||
|
if (key === 'providersSignature') return Promise.resolve(providersSignature);
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}),
|
||||||
|
put: vi.fn().mockResolvedValue(undefined),
|
||||||
|
};
|
||||||
|
|
||||||
|
// The ProviderRepository is already mocked at the top of the file
|
||||||
|
|
||||||
|
// Create a service instance with the mocked environment
|
||||||
|
const service = ChatService.create({
|
||||||
|
maxTokens: 2000,
|
||||||
|
systemPrompt: 'You are a helpful assistant.',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up the environment with the mocked KV_STORAGE
|
||||||
|
service.setEnv({
|
||||||
|
...mockEnv,
|
||||||
|
KV_STORAGE: mockKVStorage,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scenario 1: Cache hit - providers haven't changed
|
||||||
|
const response1 = await service.getSupportedModels();
|
||||||
|
const data1 = await response1.json();
|
||||||
|
|
||||||
|
// Verify the cache was used
|
||||||
|
expect(mockKVStorage.get).toHaveBeenCalledWith('supportedModels');
|
||||||
|
expect(mockKVStorage.get).toHaveBeenCalledWith('providersSignature');
|
||||||
|
expect(data1).toEqual(cachedModels);
|
||||||
|
expect(mockKVStorage.put).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Reset the mock calls for the next scenario
|
||||||
|
vi.clearAllMocks();
|
||||||
|
|
||||||
|
// Scenario 2: Cache miss - providers have changed
|
||||||
|
// Update the mock to return a different providers signature
|
||||||
|
mockKVStorage.get.mockImplementation(key => {
|
||||||
|
if (key === 'supportedModels') {
|
||||||
|
return Promise.resolve(JSON.stringify(cachedModels));
|
||||||
|
}
|
||||||
|
if (key === 'providersSignature') {
|
||||||
|
// Different signature
|
||||||
|
return Promise.resolve(JSON.stringify(['openai', 'anthropic']));
|
||||||
|
}
|
||||||
|
return Promise.resolve(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock the provider models fetching to avoid actual API calls
|
||||||
|
const mockModels = [
|
||||||
|
{ id: 'new-model-1', provider: 'openai' },
|
||||||
|
{ id: 'new-model-2', provider: 'openai' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Mock OpenAI instance for the second scenario
|
||||||
|
const mockOpenAIInstance = {
|
||||||
|
models: {
|
||||||
|
list: vi.fn().mockResolvedValue({
|
||||||
|
data: mockModels,
|
||||||
|
}),
|
||||||
|
retrieve: vi.fn().mockImplementation(id => {
|
||||||
|
return Promise.resolve({ id, provider: 'openai' });
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the OpenAI mock
|
||||||
|
vi.mocked(OpenAI).mockImplementation(() => mockOpenAIInstance as any);
|
||||||
|
|
||||||
|
// Call getSupportedModels again
|
||||||
|
const response2 = await service.getSupportedModels();
|
||||||
|
|
||||||
|
// Verify the cache was refreshed
|
||||||
|
expect(mockKVStorage.get).toHaveBeenCalledWith('supportedModels');
|
||||||
|
expect(mockKVStorage.get).toHaveBeenCalledWith('providersSignature');
|
||||||
|
expect(mockKVStorage.put).toHaveBeenCalledTimes(2); // Called twice: once for models, once for signature
|
||||||
|
expect(mockKVStorage.put).toHaveBeenCalledWith('supportedModels', expect.any(String), {
|
||||||
|
expirationTtl: 60 * 60 * 24,
|
||||||
|
});
|
||||||
|
expect(mockKVStorage.put).toHaveBeenCalledWith('providersSignature', expect.any(String), {
|
||||||
|
expirationTtl: 60 * 60 * 24,
|
||||||
|
});
|
||||||
|
|
||||||
|
// No need to restore mocks as we're using vi.mock at the module level
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Fix this test suite
|
// TODO: Fix this test suite
|
||||||
|
@@ -118,11 +118,19 @@ const ChatService = types
|
|||||||
|
|
||||||
const useCache = true;
|
const useCache = true;
|
||||||
|
|
||||||
|
// Create a signature of the current providers
|
||||||
|
const providerRepo = new ProviderRepository(self.env);
|
||||||
|
const providers = providerRepo.getProviders();
|
||||||
|
const currentProvidersSignature = JSON.stringify(providers.map(p => p.name).sort());
|
||||||
|
|
||||||
if (useCache) {
|
if (useCache) {
|
||||||
// ----- 1. Try cached value ---------------------------------------------
|
// ----- 1. Try cached value ---------------------------------------------
|
||||||
try {
|
try {
|
||||||
const cached = yield self.env.KV_STORAGE.get('supportedModels');
|
const cached = yield self.env.KV_STORAGE.get('supportedModels');
|
||||||
if (cached) {
|
const cachedSignature = yield self.env.KV_STORAGE.get('providersSignature');
|
||||||
|
|
||||||
|
// Check if cache exists and providers haven't changed
|
||||||
|
if (cached && cachedSignature && cachedSignature === currentProvidersSignature) {
|
||||||
const parsed = JSON.parse(cached as string);
|
const parsed = JSON.parse(cached as string);
|
||||||
if (Array.isArray(parsed) && parsed.length > 0) {
|
if (Array.isArray(parsed) && parsed.length > 0) {
|
||||||
logger.info('Cache hit – returning supportedModels from KV');
|
logger.info('Cache hit – returning supportedModels from KV');
|
||||||
@@ -130,6 +138,11 @@ const ChatService = types
|
|||||||
}
|
}
|
||||||
logger.warn('Cache entry malformed – refreshing');
|
logger.warn('Cache entry malformed – refreshing');
|
||||||
throw new Error('Malformed cache entry');
|
throw new Error('Malformed cache entry');
|
||||||
|
} else if (
|
||||||
|
cached &&
|
||||||
|
(!cachedSignature || cachedSignature !== currentProvidersSignature)
|
||||||
|
) {
|
||||||
|
logger.info('Providers changed – refreshing cache');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn('Error reading/parsing supportedModels cache', err);
|
logger.warn('Error reading/parsing supportedModels cache', err);
|
||||||
@@ -137,8 +150,6 @@ const ChatService = types
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----- 2. Build fresh list ---------------------------------------------
|
// ----- 2. Build fresh list ---------------------------------------------
|
||||||
const providerRepo = new ProviderRepository(self.env);
|
|
||||||
const providers = providerRepo.getProviders();
|
|
||||||
|
|
||||||
const providerModels = new Map<string, any[]>();
|
const providerModels = new Map<string, any[]>();
|
||||||
const modelMeta = new Map<string, any>();
|
const modelMeta = new Map<string, any>();
|
||||||
@@ -195,11 +206,20 @@ const ChatService = types
|
|||||||
|
|
||||||
// ----- 4. Cache fresh list ---------------------------------------------
|
// ----- 4. Cache fresh list ---------------------------------------------
|
||||||
try {
|
try {
|
||||||
|
// Store the models
|
||||||
yield self.env.KV_STORAGE.put(
|
yield self.env.KV_STORAGE.put(
|
||||||
'supportedModels',
|
'supportedModels',
|
||||||
JSON.stringify(resultArr),
|
JSON.stringify(resultArr),
|
||||||
{ expirationTtl: 60 * 60 * 24 }, // 24
|
{ expirationTtl: 60 * 60 * 24 }, // 24 hours
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Store the providers signature
|
||||||
|
yield self.env.KV_STORAGE.put(
|
||||||
|
'providersSignature',
|
||||||
|
currentProvidersSignature,
|
||||||
|
{ expirationTtl: 60 * 60 * 24 }, // 24 hours
|
||||||
|
);
|
||||||
|
|
||||||
logger.info('supportedModels cache refreshed');
|
logger.info('supportedModels cache refreshed');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('KV put failed for supportedModels', err);
|
logger.error('KV put failed for supportedModels', err);
|
||||||
|
Reference in New Issue
Block a user