fix: cherry-pick improvements from feature/semantic_caching

- semantic-cache.ts: use upsert instead of add to avoid duplicate-ID errors
- semantic-cache.ts: add crypto.randomUUID() fallback for constrained runtimes
- ollama-client.ts: JSON.stringify non-string Ollama errors to avoid [object Object]
- tests: update semantic-cache mocks and assertions for upsert
This commit is contained in:
2026-05-19 18:02:23 +02:00
parent 378642152e
commit d31c989423
3 changed files with 28 additions and 9 deletions
+15 -5
View File
@@ -196,7 +196,9 @@ export class OllamaClient {
}
if (parsed.error) {
throw new Error(`Ollama error: ${parsed.error}`);
const errorMsg =
typeof parsed.error === 'string' ? parsed.error : JSON.stringify(parsed.error);
throw new Error(`Ollama error: ${errorMsg}`);
}
yield this.normalizeMessage(parsed.message);
@@ -216,7 +218,9 @@ export class OllamaClient {
}
if (parsed?.error) {
throw new Error(`Ollama error: ${parsed.error}`);
const errorMsg =
typeof parsed.error === 'string' ? parsed.error : JSON.stringify(parsed.error);
throw new Error(`Ollama error: ${errorMsg}`);
}
if (parsed?.message) {
@@ -226,7 +230,10 @@ export class OllamaClient {
} catch (error) {
if (retryCount < this.maxRetries && this.isRetryableError(error, controller)) {
const errorMessage = error instanceof Error ? error.message : String(error);
Logger.warn(`Retrying after error (attempt ${retryCount + 1}): ${errorMessage}`, 'ollama-client');
Logger.warn(
`Retrying after error (attempt ${retryCount + 1}): ${errorMessage}`,
'ollama-client'
);
await new Promise((resolve) => setTimeout(resolve, 10 * Math.pow(2, retryCount)));
yield* this.streamChatWithRetry(messages, tools, retryCount + 1);
} else {
@@ -267,7 +274,7 @@ export class OllamaClient {
throw new ApiError(`Ollama API error: ${response.status}`, response.status);
}
const data = await response.json() as unknown;
const data = (await response.json()) as unknown;
if (!this.isChatResponse(data)) {
return this.normalizeMessage();
}
@@ -275,7 +282,10 @@ export class OllamaClient {
} catch (error) {
if (retryCount < this.maxRetries && this.isRetryableError(error, controller)) {
const errorMessage = error instanceof Error ? error.message : String(error);
Logger.warn(`Retrying after error (attempt ${retryCount + 1}): ${errorMessage}`, 'ollama-client');
Logger.warn(
`Retrying after error (attempt ${retryCount + 1}): ${errorMessage}`,
'ollama-client'
);
await new Promise((resolve) => setTimeout(resolve, 10 * Math.pow(2, retryCount)));
return this.chatWithRetry(messages, tools, retryCount + 1);
} else {
+10 -2
View File
@@ -62,12 +62,20 @@ export class SemanticCacheService {
}
}
private static generateId(): string {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
// Fallback for environments without crypto.randomUUID
return 'cache_' + Date.now() + '_' + Math.random().toString(36).substring(2, 11);
}
async setCache(query: string, response: string): Promise<void> {
if (!this.config.enabled || !this.collection) return;
try {
await this.collection.add({
ids: [crypto.randomUUID()],
await this.collection.upsert({
ids: [SemanticCacheService.generateId()],
documents: [response],
embeddings: await this.generateEmbedding(query),
metadatas: [{ source: 'ollama' }],
+3 -2
View File
@@ -10,6 +10,7 @@ jest.mock('chromadb', () => ({
getOrCreateCollection: jest.fn().mockResolvedValue({
query: jest.fn(),
add: jest.fn(),
upsert: jest.fn(),
reset: jest.fn(),
}),
deleteCollection: jest.fn(),
@@ -135,13 +136,13 @@ describe('SemanticCacheService', () => {
await disabledCacheService.setCache('test query', 'test response');
expect(mockCollection.add).not.toHaveBeenCalled();
expect(mockCollection.upsert).not.toHaveBeenCalled();
});
it('should add content to cache', async () => {
await cacheService.setCache('test query', 'test response');
expect(mockCollection.add).toHaveBeenCalled();
expect(mockCollection.upsert).toHaveBeenCalled();
});
});