@vfarcic/dot-ai
Version:
Universal Kubernetes application deployment agent with CLI and MCP interfaces
232 lines (231 loc) • 8.44 kB
JavaScript
"use strict";
/**
* Base Vector Service
*
* Generic vector operations that can be extended for different data types
* (patterns, capabilities, dependencies, etc.)
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseVectorService = void 0;
const vector_db_service_1 = require("./vector-db-service");
const embedding_service_1 = require("./embedding-service");
/**
* Abstract base class for vector-based data services
*/
class BaseVectorService {
vectorDB;
embeddingService;
collectionName;
constructor(collectionName, vectorDB, embeddingService) {
this.collectionName = collectionName;
this.vectorDB = vectorDB || new vector_db_service_1.VectorDBService({ collectionName });
this.embeddingService = embeddingService || new embedding_service_1.EmbeddingService();
}
/**
* Initialize the collection
*/
async initialize() {
// Use embedding dimensions if available, otherwise default to 1536 (OpenAI default)
const dimensions = this.embeddingService.isAvailable() ?
this.embeddingService.getDimensions() :
1536;
await this.vectorDB.initializeCollection(dimensions);
}
/**
* Health check for Vector DB connection
*/
async healthCheck() {
return await this.vectorDB.healthCheck();
}
/**
* Store data in Vector DB with optional semantic embedding
*/
async storeData(data) {
const searchText = this.createSearchText(data);
const id = this.extractId(data);
// Try to generate embedding if service is available
let embedding = null;
if (this.embeddingService.isAvailable()) {
try {
embedding = await this.embeddingService.generateEmbedding(searchText);
}
catch (error) {
// Log but don't fail - fall back to keyword-only storage
console.warn(`Failed to generate embedding for ${this.collectionName}, using keyword-only storage:`, error);
}
}
const document = {
id,
payload: {
...this.createPayload(data),
searchText: searchText,
hasEmbedding: embedding !== null
},
vector: embedding || undefined
};
await this.vectorDB.upsertDocument(document);
}
/**
* Search for data using hybrid semantic + keyword matching
*/
async searchData(query, options = {}) {
// Extract keywords for keyword search
const queryKeywords = this.extractKeywords(query);
if (queryKeywords.length === 0) {
return [];
}
const limit = options.limit || 10;
const scoreThreshold = options.scoreThreshold || 0.1;
// Try semantic search first if embeddings available
if (this.embeddingService.isAvailable()) {
try {
return await this.hybridSearch(query, queryKeywords, { limit, scoreThreshold });
}
catch (error) {
// Fall back to keyword-only search if semantic search fails
console.warn('Semantic search failed, falling back to keyword search:', error);
}
}
// Keyword-only search (fallback or when embeddings not available)
return await this.keywordOnlySearch(queryKeywords, { limit, scoreThreshold });
}
/**
* Get data by ID
*/
async getData(id) {
const document = await this.vectorDB.getDocument(id);
if (!document) {
return null;
}
const data = this.payloadToData(document.payload);
// Set the ID from the document
data.id = document.id;
return data;
}
/**
* Delete data by ID
*/
async deleteData(id) {
await this.vectorDB.deleteDocument(id);
}
/**
* Get all data (limited)
*/
async getAllData(limit) {
const documents = await this.vectorDB.getAllDocuments(limit);
return documents.map(doc => {
const data = this.payloadToData(doc.payload);
data.id = doc.id;
return data;
});
}
/**
* Get total count of data items
*/
async getDataCount() {
try {
const info = await this.vectorDB.getCollectionInfo();
return info.points_count || 0;
}
catch (error) {
// Fallback: get all and count
const data = await this.getAllData();
return data.length;
}
}
/**
* Get current search mode (semantic vs keyword-only)
*/
getSearchMode() {
const status = this.embeddingService.getStatus();
return {
semantic: status.available,
provider: status.provider || undefined,
reason: status.reason || (status.available ? 'Embedding service available' : undefined)
};
}
// Virtual methods that can be overridden by subclasses
extractKeywords(query) {
return query.toLowerCase().split(/\s+/).filter(word => word.length > 2);
}
/**
* Hybrid search combining semantic and keyword matching
*/
async hybridSearch(query, queryKeywords, options) {
// Generate query embedding
const queryEmbedding = await this.embeddingService.generateEmbedding(query);
if (!queryEmbedding) {
// Fall back to keyword search
return await this.keywordOnlySearch(queryKeywords, options);
}
// Semantic search using vector similarity
const semanticResults = await this.vectorDB.searchSimilar(queryEmbedding, {
limit: options.limit * 2, // Get more candidates for hybrid ranking
scoreThreshold: 0.5 // Lower threshold for semantic similarity
});
// Keyword search
const keywordResults = await this.vectorDB.searchByKeywords(queryKeywords, {
limit: options.limit * 2,
scoreThreshold: 0.1
});
// Combine and rank results
return this.combineHybridResults(semanticResults, keywordResults, queryKeywords, options);
}
/**
* Keyword-only search (fallback when embeddings not available)
*/
async keywordOnlySearch(queryKeywords, options) {
const keywordResults = await this.vectorDB.searchByKeywords(queryKeywords, options);
return keywordResults
.map(result => {
const data = this.payloadToData(result.payload);
data.id = result.id;
return {
data,
score: result.score,
matchType: 'keyword'
};
})
.filter(result => result.score >= options.scoreThreshold); // Apply score filtering
}
/**
* Combine semantic and keyword results with hybrid ranking
*/
combineHybridResults(semanticResults, keywordResults, queryKeywords, options) {
const combinedResults = new Map();
// Add semantic results
for (const result of semanticResults) {
const data = this.payloadToData(result.payload);
data.id = result.id;
combinedResults.set(result.id, {
data,
score: result.score * 0.7, // Weight semantic similarity
matchType: 'semantic'
});
}
// Add or boost keyword results
for (const result of keywordResults) {
const existing = combinedResults.get(result.id);
if (existing) {
// Boost score for hybrid match
existing.score = Math.max(existing.score, result.score * 0.8);
existing.matchType = 'hybrid';
}
else {
const data = this.payloadToData(result.payload);
data.id = result.id;
combinedResults.set(result.id, {
data,
score: result.score * 0.6, // Weight keyword matching
matchType: 'keyword'
});
}
}
// Sort by score and apply limits
return Array.from(combinedResults.values())
.filter(result => result.score >= options.scoreThreshold)
.sort((a, b) => b.score - a.score)
.slice(0, options.limit);
}
}
exports.BaseVectorService = BaseVectorService;