fix: resolve ESLint errors and align chromadb types with bundled client

- src/semantic-cache.ts: Replace require('chromadb') with static import and
  use proper ChromaClient/Collection types instead of any. Fix camelCase
  API parameters (queryEmbeddings, nResults) and wrap single embedding into
  Embedding[] for upsert. Fix clearCache to call client.reset() instead of
  collection.reset() (matches actual chromadb API).

- src/workflow-engine/workflow-engine.ts: Fix unnecessary escapes in regex,
  remove redundant 'as unknown' assertion, handle never type in template
  literal, and add type annotations to replace callback to satisfy
  no-unsafe-argument and no-base-to-string rules.

- tests/semantic-cache.test.ts: Update mocks to include client.reset() and
  adjust clearCache assertions to match new implementation.
This commit is contained in:
2026-05-19 20:44:27 +02:00
parent 97cc4ed5fe
commit 1ed2e39c3d
4 changed files with 4764 additions and 5258 deletions
+4730 -5238
View File
File diff suppressed because it is too large Load Diff
+14 -13
View File
@@ -1,13 +1,12 @@
// src/semantic-cache.ts // src/semantic-cache.ts
import { ChromaClient, Collection } from 'chromadb';
import { Logger } from './utils'; import { Logger } from './utils';
import { CacheConfig } from './types'; import { CacheConfig } from './types';
export class SemanticCacheService { export class SemanticCacheService {
// eslint-disable-next-line @typescript-eslint/no-explicit-any private client: ChromaClient | null = null;
private client: any | null = null; private collection: Collection | null = null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private collection: any | null = null;
private config: CacheConfig; private config: CacheConfig;
private ollamaURL: string; private ollamaURL: string;
@@ -20,8 +19,6 @@ export class SemanticCacheService {
if (!this.config.enabled) return; if (!this.config.enabled) return;
try { try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { ChromaClient } = require('chromadb');
const chromaURL = this.config.chromaURL || 'http://localhost:8000'; const chromaURL = this.config.chromaURL || 'http://localhost:8000';
this.client = new ChromaClient({ path: chromaURL }); this.client = new ChromaClient({ path: chromaURL });
this.collection = await this.client.getOrCreateCollection({ this.collection = await this.client.getOrCreateCollection({
@@ -42,13 +39,17 @@ export class SemanticCacheService {
try { try {
const results = await this.collection.query({ const results = await this.collection.query({
query_embeddings: await this.generateEmbedding(query), queryEmbeddings: [await this.generateEmbedding(query)],
n_results: 1, nResults: 1,
where: { source: 'ollama' }, where: { source: 'ollama' },
}); });
if (results.ids[0] && results.ids[0].length > 0) { if (results.ids[0] && results.ids[0].length > 0) {
if (results.distances[0] && results.distances[0][0] > this.config.similarityThreshold) { if (
results.distances &&
results.distances[0] &&
results.distances[0][0] > this.config.similarityThreshold
) {
return results.documents[0][0]; return results.documents[0][0];
} }
} }
@@ -76,7 +77,7 @@ export class SemanticCacheService {
await this.collection.upsert({ await this.collection.upsert({
ids: [SemanticCacheService.generateId()], ids: [SemanticCacheService.generateId()],
documents: [response], documents: [response],
embeddings: await this.generateEmbedding(query), embeddings: [await this.generateEmbedding(query)],
metadatas: [{ source: 'ollama' }], metadatas: [{ source: 'ollama' }],
}); });
} catch (error) { } catch (error) {
@@ -86,10 +87,10 @@ export class SemanticCacheService {
} }
async clearCache(): Promise<void> { async clearCache(): Promise<void> {
if (!this.config.enabled || !this.collection) return; if (!this.config.enabled || !this.client) return;
try { try {
await this.collection.reset(); await this.client.reset();
Logger.info('Semantic cache cleared', 'semantic-cache'); Logger.info('Semantic cache cleared', 'semantic-cache');
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error); const errorMessage = error instanceof Error ? error.message : String(error);
@@ -113,7 +114,7 @@ export class SemanticCacheService {
throw new Error(`Failed to generate embedding: ${response.status} ${response.statusText}`); throw new Error(`Failed to generate embedding: ${response.status} ${response.statusText}`);
} }
const data = await response.json(); const data = (await response.json()) as { embedding: number[] };
return data.embedding; return data.embedding;
} }
} }
+16 -4
View File
@@ -290,10 +290,10 @@ Rules:
// Extract JSON from the response (handle markdown code blocks) // Extract JSON from the response (handle markdown code blocks)
const jsonMatch = const jsonMatch =
content.match(/\```(?:json)?\s*([\s\S]*?)\```/) ?? content.match(/\{[\s\S]*\}/); content.match(/```(?:json)?\s*([\s\S]*?)```/) ?? content.match(/\{[\s\S]*\}/);
const jsonString = jsonMatch ? jsonMatch[1] : content; const jsonString = jsonMatch ? jsonMatch[1] : content;
const parsed = safeParseJson(jsonString) as unknown; const parsed = safeParseJson(jsonString);
if (!parsed || typeof parsed !== 'object') { if (!parsed || typeof parsed !== 'object') {
Logger.error('Invalid workflow JSON from LLM', 'workflow-engine'); Logger.error('Invalid workflow JSON from LLM', 'workflow-engine');
return null; return null;
@@ -354,7 +354,7 @@ Rules:
data = this.executeFormatStep(interpolatedConfig as FormatStepConfig, context); data = this.executeFormatStep(interpolatedConfig as FormatStepConfig, context);
break; break;
default: default:
throw new Error(`Unknown step type: ${step.type}`); throw new Error(`Unknown step type: ${String(step.type)}`);
} }
return { return {
@@ -502,7 +502,7 @@ Rules:
* Interpolate variables in a string. * Interpolate variables in a string.
*/ */
private interpolateString(input: string, variables: Map<string, unknown>): string { private interpolateString(input: string, variables: Map<string, unknown>): string {
return input.replace(VARIABLE_PATTERN, (_match, variablePath) => { return input.replace(VARIABLE_PATTERN, (_match: string, variablePath: string) => {
const value = this.resolveVariable(variablePath, variables); const value = this.resolveVariable(variablePath, variables);
if (value === undefined) { if (value === undefined) {
// Keep the original placeholder if variable not found // Keep the original placeholder if variable not found
@@ -515,6 +515,18 @@ Rules:
return JSON.stringify(value); return JSON.stringify(value);
} }
if (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean' ||
value === null ||
value === undefined
) {
return String(value);
}
// Fallback for symbols, functions, etc.
// eslint-disable-next-line @typescript-eslint/no-base-to-string
return String(value); return String(value);
}); });
} }
+4 -3
View File
@@ -14,6 +14,7 @@ jest.mock('chromadb', () => ({
reset: jest.fn(), reset: jest.fn(),
}), }),
deleteCollection: jest.fn(), deleteCollection: jest.fn(),
reset: jest.fn(),
}; };
}), }),
IncludeEnum: { IncludeEnum: {
@@ -154,13 +155,13 @@ describe('SemanticCacheService', () => {
await disabledCacheService.clearCache(); await disabledCacheService.clearCache();
expect(mockCollection.reset).not.toHaveBeenCalled(); expect(mockChromaClient.reset).not.toHaveBeenCalled();
}); });
it('should clear the cache collection', async () => { it('should clear the cache via the ChromaClient', async () => {
await cacheService.clearCache(); await cacheService.clearCache();
expect(mockCollection.reset).toHaveBeenCalled(); expect(mockChromaClient.reset).toHaveBeenCalled();
}); });
}); });
}); });