diff --git a/.env.example b/.env.example index db539be..b127600 100644 --- a/.env.example +++ b/.env.example @@ -1,17 +1,17 @@ # ============================================================================ -# ForensicPathways Environment Configuration +# ForensicPathways Environment Configuration - COMPLETE # ============================================================================ # Copy this file to .env and adjust the values below. -# Settings are ordered by likelihood of needing adjustment during setup. +# This file covers ALL environment variables used in the codebase. # ============================================================================ -# 1. CORE APPLICATION SETTINGS (REQUIRED - ADJUST FOR YOUR SETUP) +# 1. CORE APPLICATION SETTINGS (REQUIRED) # ============================================================================ # Your application's public URL (used for redirects and links) PUBLIC_BASE_URL=http://localhost:4321 -# Application environment (development, production, staging) +# Application environment NODE_ENV=development # Secret key for session encryption (CHANGE IN PRODUCTION!) @@ -22,19 +22,99 @@ AUTH_SECRET=your-secret-key-change-in-production-please # ============================================================================ # Main AI Analysis Service (for query processing and recommendations) -# Example uses Mistral AI - adjust endpoint/model as needed -AI_ANALYZER_ENDPOINT=https://api.mistral.ai/v1 -AI_ANALYZER_API_KEY=your-mistral-api-key-here -AI_ANALYZER_MODEL=mistral-small-latest +# Examples: http://localhost:11434 (Ollama), https://api.mistral.ai, https://api.openai.com +AI_ANALYZER_ENDPOINT=https://api.mistral.ai/v1/chat/completions +AI_ANALYZER_API_KEY= +AI_ANALYZER_MODEL=mistral/mistral-small-latest -# Vector Embeddings Service (for semantic search - can use same provider) +# Vector Embeddings Service (for semantic search) +# Leave API_KEY empty for Ollama, use actual key for cloud services AI_EMBEDDINGS_ENABLED=true AI_EMBEDDINGS_ENDPOINT=https://api.mistral.ai/v1/embeddings -AI_EMBEDDINGS_API_KEY=your-mistral-api-key-here +AI_EMBEDDINGS_API_KEY= AI_EMBEDDINGS_MODEL=mistral-embed # ============================================================================ -# 3. AUTHENTICATION (OPTIONAL - SET TO 'true' IF NEEDED) +# 3. AI PIPELINE CONFIGURATION (CONTEXT & PERFORMANCE TUNING) +# ============================================================================ + +# === SIMILARITY SEARCH STAGE === +# How many similar tools/concepts embeddings search returns as candidates +# 🔍 This is the FIRST filter - vector similarity matching +# Lower = faster, less comprehensive | Higher = slower, more comprehensive +AI_EMBEDDING_CANDIDATES=40 + +# Minimum similarity score threshold (0.0-1.0) +# Lower = more results but less relevant | Higher = fewer but more relevant +AI_SIMILARITY_THRESHOLD=0.3 + +# === AI SELECTION STAGE === +# Maximum tools the AI can select from embedding candidates +# 🤖 This is the SECOND filter - AI intelligent selection +# Should be ≤ AI_EMBEDDING_CANDIDATES +AI_MAX_SELECTED_ITEMS=25 + +# Maximum tools sent to AI for detailed analysis (micro-tasks) +# 📋 This is the FINAL context size sent to AI models +# Lower = less AI context, faster responses | Higher = more context, slower +AI_MAX_TOOLS_TO_ANALYZE=20 + +# Maximum concepts sent to AI for background knowledge selection +# 📚 Concepts are smaller than tools, so can be higher +AI_MAX_CONCEPTS_TO_ANALYZE=10 + +# === CONTEXT FLOW SUMMARY === +# 1. Vector Search: 111 total tools → AI_EMBEDDING_CANDIDATES (40) most similar +# 2. AI Selection: 40 candidates → AI_MAX_SELECTED_ITEMS (25) best matches +# 3. AI Analysis: 25 selected → AI_MAX_TOOLS_TO_ANALYZE (20) for micro-tasks +# 4. Final Output: Recommendations based on analyzed subset + +# ============================================================================ +# 4. AI PERFORMANCE & RATE LIMITING +# ============================================================================ + +# === USER RATE LIMITS (per minute) === +# Main queries per user per minute +AI_RATE_LIMIT_MAX_REQUESTS=4 + +# Total AI micro-task calls per user per minute (across all micro-tasks) +AI_MICRO_TASK_TOTAL_LIMIT=30 + +# === PIPELINE TIMING === +# Delay between micro-tasks within a single query (milliseconds) +# Higher = gentler on AI service | Lower = faster responses +AI_MICRO_TASK_DELAY_MS=500 + +# Delay between queued requests (milliseconds) +AI_RATE_LIMIT_DELAY_MS=2000 + +# === EMBEDDINGS BATCH PROCESSING === +# How many embeddings to generate per API call +AI_EMBEDDINGS_BATCH_SIZE=10 + +# Delay between embedding batches (milliseconds) +AI_EMBEDDINGS_BATCH_DELAY_MS=1000 + +# ============================================================================ +# 5. AI CONTEXT & TOKEN MANAGEMENT +# ============================================================================ + +# Maximum context tokens to maintain across micro-tasks +# Controls how much conversation history is preserved between AI calls +AI_MAX_CONTEXT_TOKENS=3000 + +# Maximum tokens per individual AI prompt +# Larger = more context per call | Smaller = faster responses +AI_MAX_PROMPT_TOKENS=1200 + +# Timeout for individual micro-tasks (milliseconds) +AI_MICRO_TASK_TIMEOUT_MS=25000 + +# Maximum size of the processing queue +AI_QUEUE_MAX_SIZE=50 + +# ============================================================================ +# 6. AUTHENTICATION & AUTHORIZATION (OPTIONAL) # ============================================================================ # Enable authentication for different features @@ -48,30 +128,47 @@ OIDC_CLIENT_ID=your-client-id OIDC_CLIENT_SECRET=your-client-secret # ============================================================================ -# 4. ADVANCED AI CONFIGURATION (FINE-TUNING - DEFAULT VALUES USUALLY WORK) +# 7. FILE UPLOADS - NEXTCLOUD INTEGRATION (OPTIONAL) # ============================================================================ -# Pipeline Performance Settings -AI_MAX_SELECTED_ITEMS=60 # Tools analyzed per micro-task -AI_EMBEDDING_CANDIDATES=60 # Vector search candidates -AI_MICRO_TASK_DELAY_MS=500 # Delay between AI micro-tasks +# Nextcloud server for file uploads (knowledgebase contributions) +# Leave empty to disable file upload functionality +NEXTCLOUD_ENDPOINT=https://your-nextcloud.com -# Rate Limiting (requests per minute) -AI_RATE_LIMIT_MAX_REQUESTS=6 # Main query rate limit -AI_MICRO_TASK_RATE_LIMIT=15 # Micro-task rate limit -AI_RATE_LIMIT_DELAY_MS=3000 # Delay between rate-limited calls +# Nextcloud credentials (app password recommended) +NEXTCLOUD_USERNAME=your-username +NEXTCLOUD_PASSWORD=your-app-password -# Embeddings Batch Processing -AI_EMBEDDINGS_BATCH_SIZE=20 # Embeddings processed per batch -AI_EMBEDDINGS_BATCH_DELAY_MS=1000 # Delay between embedding batches +# Upload directory on Nextcloud (will be created if doesn't exist) +NEXTCLOUD_UPLOAD_PATH=/kb-media -# Timeouts and Limits -AI_MICRO_TASK_TIMEOUT_MS=25000 # Max time per micro-task -AI_QUEUE_MAX_SIZE=50 # Max queued requests -AI_SIMILARITY_THRESHOLD=0.3 # Vector similarity threshold +# Public URL base for sharing uploaded files +# Usually your Nextcloud base URL + share path +NEXTCLOUD_PUBLIC_URL=https://your-nextcloud.com/s/ # ============================================================================ -# 5. FORENSIC AUDIT SYSTEM (OPTIONAL - FOR TRANSPARENCY AND DEBUGGING) +# 8. GIT CONTRIBUTIONS - ISSUE CREATION (OPTIONAL) +# ============================================================================ + +# Git provider: gitea, github, or gitlab +GIT_PROVIDER=gitea + +# Repository URL (used to extract owner/name) +# Example: https://git.example.com/owner/forensic-pathways.git +GIT_REPO_URL=https://git.example.com/owner/forensic-pathways.git + +# API endpoint for your git provider +# Gitea: https://git.example.com/api/v1 +# GitHub: https://api.github.com +# GitLab: https://gitlab.example.com/api/v4 +GIT_API_ENDPOINT=https://git.example.com/api/v1 + +# Personal access token or API token for creating issues +# Generate this in your git provider's settings +GIT_API_TOKEN=your-git-api-token + +# ============================================================================ +# 9. AUDIT & DEBUGGING (OPTIONAL) # ============================================================================ # Enable detailed audit trail of AI decision-making @@ -80,38 +177,49 @@ FORENSIC_AUDIT_ENABLED=false # Audit detail level: minimal, standard, verbose FORENSIC_AUDIT_DETAIL_LEVEL=standard -# Audit retention and limits -FORENSIC_AUDIT_RETENTION_HOURS=72 # Keep audit data for 3 days -FORENSIC_AUDIT_MAX_ENTRIES=50 # Max entries per request +# Audit retention time (hours) +FORENSIC_AUDIT_RETENTION_HOURS=24 + +# Maximum audit entries per request +FORENSIC_AUDIT_MAX_ENTRIES=50 + +# Enable detailed AI pipeline logging +AI_PIPELINE_DEBUG=false + +# Enable performance metrics collection +AI_PERFORMANCE_METRICS=false + +# Enable detailed micro-task debugging +AI_MICRO_TASK_DEBUG=false # ============================================================================ -# 6. QUALITY CONTROL AND BIAS DETECTION (OPTIONAL - ADVANCED FEATURES) +# 10. QUALITY CONTROL & BIAS DETECTION (ADVANCED) # ============================================================================ -# Confidence Scoring Weights (must sum to 1.0) +# Confidence scoring weights (must sum to 1.0) CONFIDENCE_EMBEDDINGS_WEIGHT=0.3 CONFIDENCE_CONSENSUS_WEIGHT=0.25 CONFIDENCE_DOMAIN_MATCH_WEIGHT=0.25 CONFIDENCE_FRESHNESS_WEIGHT=0.2 -# Confidence Thresholds (0-100) +# Confidence thresholds (0-100) CONFIDENCE_MINIMUM_THRESHOLD=40 CONFIDENCE_MEDIUM_THRESHOLD=60 CONFIDENCE_HIGH_THRESHOLD=80 -# Bias Detection Settings +# Bias detection settings BIAS_DETECTION_ENABLED=false -BIAS_POPULARITY_THRESHOLD=0.7 # Detect over-popular tools -BIAS_DIVERSITY_MINIMUM=0.6 # Require recommendation diversity -BIAS_CELEBRITY_TOOLS="Volatility 3,Wireshark,Autopsy,Maltego" +BIAS_POPULARITY_THRESHOLD=0.7 +BIAS_DIVERSITY_MINIMUM=0.6 +BIAS_CELEBRITY_TOOLS="" -# Quality Control Thresholds -QUALITY_MIN_RESPONSE_LENGTH=50 # Minimum AI response length -QUALITY_MIN_SELECTION_COUNT=1 # Minimum tools selected -QUALITY_MAX_PROCESSING_TIME_MS=30000 # Max processing time +# Quality control thresholds +QUALITY_MIN_RESPONSE_LENGTH=50 +QUALITY_MIN_SELECTION_COUNT=1 +QUALITY_MAX_PROCESSING_TIME_MS=30000 # ============================================================================ -# 7. USER INTERFACE PREFERENCES (OPTIONAL - UI DEFAULTS) +# 11. USER INTERFACE DEFAULTS (OPTIONAL) # ============================================================================ # Default UI behavior (users can override) @@ -121,34 +229,76 @@ UI_SHOW_BIAS_WARNINGS=true UI_AUDIT_TRAIL_COLLAPSIBLE=true # ============================================================================ -# 8. EXTERNAL INTEGRATIONS (OPTIONAL - ONLY IF USING THESE SERVICES) +# 12. CACHING & PERFORMANCE (OPTIONAL) # ============================================================================ -# Nextcloud Integration (for file uploads) -# NEXTCLOUD_ENDPOINT=https://your-nextcloud.com -# NEXTCLOUD_USERNAME=your-username -# NEXTCLOUD_PASSWORD=your-password -# NEXTCLOUD_UPLOAD_PATH=/kb-media -# NEXTCLOUD_PUBLIC_URL=https://your-nextcloud.com/s/ +# Cache AI responses (milliseconds) +AI_RESPONSE_CACHE_TTL_MS=3600000 + +# Queue cleanup interval (milliseconds) +AI_QUEUE_CLEANUP_INTERVAL_MS=300000 # ============================================================================ -# 9. PERFORMANCE AND MONITORING (OPTIONAL - FOR PRODUCTION OPTIMIZATION) +# PERFORMANCE TUNING PRESETS # ============================================================================ -# Caching and Queue Management -AI_RESPONSE_CACHE_TTL_MS=3600000 # Cache responses for 1 hour -AI_QUEUE_CLEANUP_INTERVAL_MS=300000 # Cleanup queue every 5 minutes +# 🚀 FOR FASTER RESPONSES (less comprehensive): +# AI_EMBEDDING_CANDIDATES=20 +# AI_MAX_SELECTED_ITEMS=15 +# AI_MAX_TOOLS_TO_ANALYZE=10 +# AI_MICRO_TASK_DELAY_MS=200 +# AI_MAX_CONTEXT_TOKENS=2000 -# Debug and Monitoring -AI_MICRO_TASK_DEBUG=false # Enable detailed micro-task logging -AI_PERFORMANCE_METRICS=false # Enable performance tracking +# 🎯 FOR BETTER QUALITY (more comprehensive): +# AI_EMBEDDING_CANDIDATES=60 +# AI_MAX_SELECTED_ITEMS=40 +# AI_MAX_TOOLS_TO_ANALYZE=30 +# AI_MICRO_TASK_DELAY_MS=800 +# AI_MAX_CONTEXT_TOKENS=4000 + +# 🔋 FOR LOW-POWER SYSTEMS (minimal resources): +# AI_EMBEDDING_CANDIDATES=15 +# AI_MAX_SELECTED_ITEMS=10 +# AI_MAX_TOOLS_TO_ANALYZE=8 +# AI_RATE_LIMIT_MAX_REQUESTS=2 +# AI_MICRO_TASK_DELAY_MS=1000 # ============================================================================ -# SETUP CHECKLIST: +# FEATURE COMBINATIONS GUIDE # ============================================================================ -# 1. Set PUBLIC_BASE_URL to your domain -# 2. Change AUTH_SECRET to a secure random string -# 3. Configure AI service endpoints and API keys -# 4. Set authentication options if needed -# 5. Test with default advanced settings before adjusting + +# 📝 BASIC SETUP (AI only): +# - Configure AI_ANALYZER_* and AI_EMBEDDINGS_* +# - Leave authentication, file uploads, and git disabled + +# 🔐 WITH AUTHENTICATION: +# - Set AUTHENTICATION_NECESSARY_* to true +# - Configure OIDC_* settings + +# 📁 WITH FILE UPLOADS: +# - Configure all NEXTCLOUD_* settings +# - Test connection before enabling in UI + +# 🔄 WITH CONTRIBUTIONS: +# - Configure all GIT_* settings +# - Test API token permissions for issue creation + +# 🔍 WITH FULL MONITORING: +# - Enable FORENSIC_AUDIT_ENABLED=true +# - Enable AI_PIPELINE_DEBUG=true +# - Configure audit retention and detail level + +# ============================================================================ +# SETUP CHECKLIST +# ============================================================================ +# ✅ 1. Set PUBLIC_BASE_URL to your domain +# ✅ 2. Change AUTH_SECRET to a secure random string +# ✅ 3. Configure AI endpoints (Ollama: leave API_KEY empty) +# ✅ 4. Start with default AI values, tune based on performance +# ✅ 5. Enable authentication if needed (configure OIDC) +# ✅ 6. Configure Nextcloud if file uploads needed +# ✅ 7. Configure Git provider if contributions needed +# ✅ 8. Test with a simple query to verify pipeline works +# ✅ 9. Enable audit trail for transparency if desired +# ✅ 10. Tune performance settings based on usage patterns # ============================================================================ \ No newline at end of file diff --git a/src/pages/api/ai/enhance-input.ts b/src/pages/api/ai/enhance-input.ts index 0d12a09..86086e2 100644 --- a/src/pages/api/ai/enhance-input.ts +++ b/src/pages/api/ai/enhance-input.ts @@ -1,4 +1,5 @@ -// src/pages/api/ai/enhance-input.ts - ENHANCED with forensics methodology +// src/pages/api/ai/enhance-input.ts - Enhanced AI service compatibility + import type { APIRoute } from 'astro'; import { withAPIAuth } from '../../../utils/auth.js'; import { apiError, apiServerError, createAuthErrorResponse } from '../../../utils/api.js'; @@ -20,7 +21,7 @@ const AI_ANALYZER_MODEL = getEnv('AI_ANALYZER_MODEL'); const rateLimitStore = new Map(); const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute -const RATE_LIMIT_MAX = 5; +const RATE_LIMIT_MAX = 5; function sanitizeInput(input: string): string { return input @@ -93,6 +94,45 @@ ${input} `.trim(); } +// Enhanced AI service call function +async function callAIService(prompt: string): Promise { + const endpoint = AI_ENDPOINT; + const apiKey = AI_ANALYZER_API_KEY; + const model = AI_ANALYZER_MODEL; + + // Simple headers - add auth only if API key exists + let headers: Record = { + 'Content-Type': 'application/json' + }; + + // Add authentication if API key is provided + if (apiKey) { + headers['Authorization'] = `Bearer ${apiKey}`; + console.log('[ENHANCE API] Using API key authentication'); + } else { + console.log('[ENHANCE API] No API key - making request without authentication'); + } + + // Simple request body + const requestBody = { + model, + messages: [{ role: 'user', content: prompt }], + max_tokens: 300, + temperature: 0.7, + top_p: 0.9, + frequency_penalty: 0.2, + presence_penalty: 0.1 + }; + + // FIXED: This function is already being called through enqueueApiCall in the main handler + // So we can use direct fetch here since the queuing happens at the caller level + return fetch(`${endpoint}/v1/chat/completions`, { + method: 'POST', + headers, + body: JSON.stringify(requestBody) + }); +} + export const POST: APIRoute = async ({ request }) => { try { const authResult = await withAPIAuth(request, 'ai'); @@ -121,31 +161,11 @@ export const POST: APIRoute = async ({ request }) => { const systemPrompt = createEnhancementPrompt(sanitizedInput); const taskId = `enhance_${userId}_${Date.now()}_${Math.random().toString(36).substr(2, 4)}`; - const aiResponse = await enqueueApiCall(() => - fetch(`${AI_ENDPOINT}/v1/chat/completions`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${AI_ANALYZER_API_KEY}` - }, - body: JSON.stringify({ - model: AI_ANALYZER_MODEL, - messages: [ - { - role: 'user', - content: systemPrompt - } - ], - max_tokens: 300, - temperature: 0.7, - top_p: 0.9, - frequency_penalty: 0.2, - presence_penalty: 0.1 - }) - }), taskId); + const aiResponse = await enqueueApiCall(() => callAIService(systemPrompt), taskId); if (!aiResponse.ok) { - console.error('AI enhancement error:', await aiResponse.text()); + const errorText = await aiResponse.text(); + console.error('[ENHANCE API] AI enhancement error:', errorText, 'Status:', aiResponse.status); return apiServerError.unavailable('Enhancement service unavailable'); } @@ -188,7 +208,7 @@ export const POST: APIRoute = async ({ request }) => { questions = []; } - console.log(`[AI Enhancement] User: ${userId}, Forensics Questions: ${questions.length}, Input length: ${sanitizedInput.length}`); + console.log(`[ENHANCE API] User: ${userId}, Forensics Questions: ${questions.length}, Input length: ${sanitizedInput.length}`); return new Response(JSON.stringify({ success: true, diff --git a/src/utils/aiPipeline.ts b/src/utils/aiPipeline.ts index a44512c..003523d 100644 --- a/src/utils/aiPipeline.ts +++ b/src/utils/aiPipeline.ts @@ -66,6 +66,11 @@ interface AnalysisContext { auditTrail: AuditEntry[]; } +interface SimilarityResult extends EmbeddingData { + similarity: number; +} + + class ImprovedMicroTaskAIPipeline { private config: AIConfig; private maxSelectedItems: number; @@ -267,39 +272,62 @@ class ImprovedMicroTaskAIPipeline { userQuery, this.embeddingCandidates, this.similarityThreshold - ); + ) as SimilarityResult[]; // Type assertion for similarity property - const toolNames = new Set(); - const conceptNames = new Set(); + console.log(`[IMPROVED PIPELINE] Embeddings found ${similarItems.length} similar items`); - similarItems.forEach(item => { - if (item.type === 'tool') toolNames.add(item.name); - if (item.type === 'concept') conceptNames.add(item.name); - }); + // FIXED: Create lookup maps for O(1) access while preserving original data + const toolsMap = new Map(toolsData.tools.map((tool: any) => [tool.name, tool])); + const conceptsMap = new Map(toolsData.concepts.map((concept: any) => [concept.name, concept])); - console.log(`[IMPROVED PIPELINE] Embeddings found: ${toolNames.size} tools, ${conceptNames.size} concepts`); + // FIXED: Process in similarity order, preserving the ranking + const similarTools = similarItems + .filter((item): item is SimilarityResult => item.type === 'tool') + .map(item => toolsMap.get(item.name)) + .filter((tool): tool is any => tool !== undefined); // Proper type guard - if (toolNames.size >= 15) { - candidateTools = toolsData.tools.filter((tool: any) => toolNames.has(tool.name)); - candidateConcepts = toolsData.concepts.filter((concept: any) => conceptNames.has(concept.name)); + const similarConcepts = similarItems + .filter((item): item is SimilarityResult => item.type === 'concept') + .map(item => conceptsMap.get(item.name)) + .filter((concept): concept is any => concept !== undefined); // Proper type guard + + console.log(`[IMPROVED PIPELINE] Similarity-ordered results: ${similarTools.length} tools, ${similarConcepts.length} concepts`); + + // Log the first few tools to verify ordering is preserved + if (similarTools.length > 0) { + console.log(`[IMPROVED PIPELINE] Top similar tools (in similarity order):`); + similarTools.slice(0, 5).forEach((tool, idx) => { + const originalSimilarItem = similarItems.find(item => item.name === tool.name); + console.log(` ${idx + 1}. ${tool.name} (similarity: ${originalSimilarItem?.similarity?.toFixed(4) || 'N/A'})`); + }); + } + + if (similarTools.length >= 15) { + candidateTools = similarTools; + candidateConcepts = similarConcepts; selectionMethod = 'embeddings_candidates'; - console.log(`[IMPROVED PIPELINE] Using embeddings candidates: ${candidateTools.length} tools`); + console.log(`[IMPROVED PIPELINE] Using embeddings candidates in similarity order: ${candidateTools.length} tools`); } else { - console.log(`[IMPROVED PIPELINE] Embeddings insufficient (${toolNames.size} < 15), using full dataset`); + console.log(`[IMPROVED PIPELINE] Embeddings insufficient (${similarTools.length} < 15), using full dataset`); candidateTools = toolsData.tools; candidateConcepts = toolsData.concepts; selectionMethod = 'full_dataset'; } - // NEW: Add Audit Entry for Embeddings Search + // NEW: Add Audit Entry for Embeddings Search with ordering verification if (this.auditConfig.enabled) { this.addAuditEntry(null, 'retrieval', 'embeddings-search', { query: userQuery, threshold: this.similarityThreshold, candidates: this.embeddingCandidates }, - { candidatesFound: similarItems.length, toolNames: Array.from(toolNames), conceptNames: Array.from(conceptNames) }, - similarItems.length >= 15 ? 85 : 60, // Confidence based on result quality + { + candidatesFound: similarItems.length, + toolsInOrder: similarTools.slice(0, 3).map((t: any) => t.name), + conceptsInOrder: similarConcepts.slice(0, 3).map((c: any) => c.name), + orderingPreserved: true + }, + similarTools.length >= 15 ? 85 : 60, embeddingsStart, - { selectionMethod, embeddingsEnabled: true } + { selectionMethod, embeddingsEnabled: true, orderingFixed: true } ); } } else { @@ -309,7 +337,7 @@ class ImprovedMicroTaskAIPipeline { selectionMethod = 'full_dataset'; } - console.log(`[IMPROVED PIPELINE] AI will analyze FULL DATA of ${candidateTools.length} candidate tools`); + console.log(`[IMPROVED PIPELINE] AI will analyze ${candidateTools.length} candidate tools (ordering preserved: ${selectionMethod === 'embeddings_candidates'})`); const finalSelection = await this.aiSelectionWithFullData(userQuery, candidateTools, candidateConcepts, mode, selectionMethod); return { @@ -735,33 +763,59 @@ ${JSON.stringify(conceptsWithFullData.slice(0, 10), null, 2)}`; } private async callAI(prompt: string, maxTokens: number = 1000): Promise { - const response = await fetch(`${this.config.endpoint}/v1/chat/completions`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.apiKey}` - }, - body: JSON.stringify({ - model: this.config.model, - messages: [{ role: 'user', content: prompt }], - max_tokens: maxTokens, - temperature: 0.3 - }) - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`AI API error: ${response.status} - ${errorText}`); - } - - const data = await response.json(); - const content = data.choices?.[0]?.message?.content; + const endpoint = this.config.endpoint; + const apiKey = this.config.apiKey; + const model = this.config.model; - if (!content) { - throw new Error('No response from AI model'); + // Simple headers - add auth only if API key exists + let headers: Record = { + 'Content-Type': 'application/json' + }; + + // Add authentication if API key is provided + if (apiKey) { + headers['Authorization'] = `Bearer ${apiKey}`; + console.log('[AI PIPELINE] Using API key authentication'); + } else { + console.log('[AI PIPELINE] No API key - making request without authentication'); } + + // Simple request body + const requestBody = { + model, + messages: [{ role: 'user', content: prompt }], + max_tokens: maxTokens, + temperature: 0.3 + }; + + try { + // FIXED: Use direct fetch since entire pipeline is already queued at query.ts level + const response = await fetch(`${endpoint}/v1/chat/completions`, { + method: 'POST', + headers, + body: JSON.stringify(requestBody) + }); - return content; + if (!response.ok) { + const errorText = await response.text(); + console.error(`[AI PIPELINE] AI API Error ${response.status}:`, errorText); + throw new Error(`AI API error: ${response.status} - ${errorText}`); + } + + const data = await response.json(); + const content = data.choices?.[0]?.message?.content; + + if (!content) { + console.error('[AI PIPELINE] No response content:', data); + throw new Error('No response from AI model'); + } + + return content; + + } catch (error) { + console.error('[AI PIPELINE] AI service call failed:', error.message); + throw error; + } } async processQuery(userQuery: string, mode: string): Promise { diff --git a/src/utils/embeddings.ts b/src/utils/embeddings.ts index 369b95d..7c73228 100644 --- a/src/utils/embeddings.ts +++ b/src/utils/embeddings.ts @@ -24,6 +24,10 @@ interface EmbeddingsDatabase { embeddings: EmbeddingData[]; } +interface SimilarityResult extends EmbeddingData { + similarity: number; +} + class EmbeddingsService { private embeddings: EmbeddingData[] = []; private isInitialized = false; @@ -211,8 +215,9 @@ class EmbeddingsService { return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); } - async findSimilar(query: string, maxResults: number = 30, threshold: number = 0.3): Promise { + async findSimilar(query: string, maxResults: number = 30, threshold: number = 0.3): Promise { if (!this.enabled || !this.isInitialized || this.embeddings.length === 0) { + console.log('[EMBEDDINGS] Service not available for similarity search'); return []; } @@ -221,18 +226,51 @@ class EmbeddingsService { const queryEmbeddings = await this.generateEmbeddingsBatch([query.toLowerCase()]); const queryEmbedding = queryEmbeddings[0]; - // Calculate similarities - const similarities = this.embeddings.map(item => ({ + console.log(`[EMBEDDINGS] Computing similarities for ${this.embeddings.length} items`); + + // Calculate similarities - properly typed + const similarities: SimilarityResult[] = this.embeddings.map(item => ({ ...item, similarity: this.cosineSimilarity(queryEmbedding, item.embedding) })); - // Filter by threshold and sort by similarity - return similarities + // Filter by threshold and sort by similarity (descending - highest first) + const results = similarities .filter(item => item.similarity >= threshold) - .sort((a, b) => b.similarity - a.similarity) + .sort((a, b) => b.similarity - a.similarity) // CRITICAL: Ensure descending order .slice(0, maxResults); + // ENHANCED: Verify ordering is correct + const orderingValid = results.every((item, index) => { + if (index === 0) return true; + return item.similarity <= results[index - 1].similarity; + }); + + if (!orderingValid) { + console.error('[EMBEDDINGS] CRITICAL: Similarity ordering is broken!'); + results.forEach((item, idx) => { + console.error(` ${idx}: ${item.name} = ${item.similarity.toFixed(4)}`); + }); + } + + // ENHANCED: Log top results for debugging + console.log(`[EMBEDDINGS] Found ${results.length} similar items (threshold: ${threshold})`); + if (results.length > 0) { + console.log('[EMBEDDINGS] Top 5 similarity matches:'); + results.slice(0, 5).forEach((item, idx) => { + console.log(` ${idx + 1}. ${item.name} (${item.type}) = ${item.similarity.toFixed(4)}`); + }); + + // Verify first result is indeed the highest + const topSimilarity = results[0].similarity; + const hasHigherSimilarity = results.some(item => item.similarity > topSimilarity); + if (hasHigherSimilarity) { + console.error('[EMBEDDINGS] CRITICAL: Top result is not actually the highest similarity!'); + } + } + + return results; + } catch (error) { console.error('[EMBEDDINGS] Failed to find similar items:', error); return []; @@ -257,7 +295,7 @@ class EmbeddingsService { // Global instance const embeddingsService = new EmbeddingsService(); -export { embeddingsService, type EmbeddingData }; +export { embeddingsService, type EmbeddingData, type SimilarityResult }; // Auto-initialize on import in server environment if (typeof window === 'undefined' && process.env.NODE_ENV !== 'test') {