@vfarcic/dot-ai
Version:
Universal Kubernetes application deployment agent with CLI and MCP interfaces
199 lines (198 loc) • 6.47 kB
JavaScript
"use strict";
/**
* Embedding Service
*
* Optional semantic search enhancement for pattern matching.
* Gracefully falls back to keyword-only search when OpenAI API key is not available.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmbeddingService = exports.OpenAIEmbeddingProvider = void 0;
const openai_1 = __importDefault(require("openai"));
/**
* OpenAI Embedding Provider
* Optional provider for semantic search enhancement
*/
class OpenAIEmbeddingProvider {
client = null;
model;
dimensions;
available;
constructor(config = {}) {
const apiKey = config.apiKey || process.env.OPENAI_API_KEY;
this.model = config.model || 'text-embedding-3-small';
this.dimensions = config.dimensions || 1536; // text-embedding-3-small default
this.available = false;
if (apiKey) {
try {
this.client = new openai_1.default({ apiKey: apiKey });
this.available = true;
}
catch (error) {
// Client creation failed, remain unavailable
this.available = false;
this.client = null;
}
}
}
async generateEmbedding(text) {
if (!this.isAvailable()) {
throw new Error('OpenAI embedding provider not available');
}
if (!text || text.trim().length === 0) {
throw new Error('Text cannot be empty for embedding generation');
}
try {
const response = await this.client.embeddings.create({
model: this.model,
input: text.trim(),
encoding_format: 'float'
});
if (!response.data || response.data.length === 0) {
throw new Error('No embedding data returned from OpenAI API');
}
return response.data[0].embedding;
}
catch (error) {
if (error instanceof Error) {
throw new Error(`OpenAI embedding failed: ${error.message}`);
}
throw new Error(`OpenAI embedding failed: ${String(error)}`);
}
}
async generateEmbeddings(texts) {
if (!this.isAvailable()) {
throw new Error('OpenAI embedding provider not available');
}
if (!texts || texts.length === 0) {
return [];
}
const validTexts = texts
.map(t => t?.trim())
.filter(t => t && t.length > 0);
if (validTexts.length === 0) {
return [];
}
try {
const response = await this.client.embeddings.create({
model: this.model,
input: validTexts,
encoding_format: 'float'
});
return response.data.map(item => item.embedding);
}
catch (error) {
if (error instanceof Error) {
throw new Error(`OpenAI batch embedding failed: ${error.message}`);
}
throw new Error(`OpenAI batch embedding failed: ${String(error)}`);
}
}
isAvailable() {
return this.available && this.client !== null;
}
getDimensions() {
return this.dimensions;
}
getModel() {
return this.model;
}
}
exports.OpenAIEmbeddingProvider = OpenAIEmbeddingProvider;
/**
* Main Embedding Service
* Provides optional semantic search capabilities with graceful degradation
*/
class EmbeddingService {
provider;
constructor(config = {}) {
// Try to initialize OpenAI provider, but don't fail if unavailable
try {
const provider = new OpenAIEmbeddingProvider(config);
this.provider = provider.isAvailable() ? provider : null;
}
catch (error) {
this.provider = null;
}
}
/**
* Generate embedding for text (optional enhancement)
* Returns null if embeddings not available - caller should handle gracefully
*/
async generateEmbedding(text) {
if (!this.isAvailable()) {
return null;
}
try {
return await this.provider.generateEmbedding(text);
}
catch (error) {
// Log error but don't throw - allow graceful fallback
console.warn('Embedding generation failed, falling back to keyword search:', error);
return null;
}
}
/**
* Generate embeddings for multiple texts (optional enhancement)
* Returns empty array if embeddings not available
*/
async generateEmbeddings(texts) {
if (!this.isAvailable()) {
return [];
}
try {
return await this.provider.generateEmbeddings(texts);
}
catch (error) {
// Log error but don't throw - allow graceful fallback
console.warn('Batch embedding generation failed, falling back to keyword search:', error);
return [];
}
}
/**
* Check if semantic search is available
*/
isAvailable() {
return this.provider !== null && this.provider.isAvailable();
}
/**
* Get embedding dimensions (if available)
*/
getDimensions() {
return this.provider?.getDimensions() || 1536;
}
/**
* Get status information for debugging/logging
*/
getStatus() {
if (this.isAvailable()) {
const openaiProvider = this.provider;
return {
available: true,
provider: 'openai',
model: openaiProvider.getModel(),
dimensions: openaiProvider.getDimensions()
};
}
return {
available: false,
provider: null,
reason: 'OPENAI_API_KEY not set - using keyword-only pattern search'
};
}
/**
* Create searchable text from pattern data
*/
createPatternSearchText(pattern) {
return [
pattern.description,
...pattern.triggers,
pattern.rationale,
// Include resource types for better semantic matching
...pattern.suggestedResources.map(r => `kubernetes ${r.toLowerCase()}`)
].join(' ').trim();
}
}
exports.EmbeddingService = EmbeddingService;