import axios from 'axios'; import { ChatbotService } from './ChatbotService.js'; export interface ChatMessage { role: 'system' | 'user' | 'assistant'; content: string; } export interface ChatRequest { model: string; messages: ChatMessage[]; temperature: number; } export interface ChatChoice { message: ChatMessage; } export interface ChatResponse { choices: ChatChoice[]; } export class AIService { private apiKey: string; private model: string; private baseUrl: string; private relPath: string; private temperature: number; private chatbotService: ChatbotService; // Predefined models from your C# code private static readonly PREDEFINED_MODELS: Record = { 'dobby': 'sentientagi/dobby-mini-unhinged-plus-llama-3.1-8b', 'dolphin': 'cognitivecomputations/dolphin-mixtral-8x22b', 'dolphin_free': 'cognitivecomputations/dolphin3.0-mistral-24b:free', 'gemma': 'google/gemma-3-12b-it', 'gpt-4o-mini': 'openai/gpt-4o-mini', 'gpt-4.1-nano': 'openai/gpt-4.1-nano', 'qwen': 'qwen/qwen3-30b-a3b', 'unslop': 'thedrummer/unslopnemo-12b', 'euryale': 'sao10k/l3.3-euryale-70b', 'wizard': 'microsoft/wizardlm-2-8x22b', 'deepseek': 'deepseek/deepseek-chat-v3-0324' }; constructor() { this.apiKey = process.env.OPENROUTER_API_KEY || 'sk-or-REPLACE_ME'; this.model = process.env.OPENROUTER_MODEL || 'gemma'; this.baseUrl = process.env.OPENROUTER_BASE_URL || 'openrouter.ai'; this.relPath = process.env.OPENROUTER_REL_PATH || '/api'; this.temperature = parseFloat(process.env.OPENROUTER_TEMPERATURE || '0.7'); this.chatbotService = new ChatbotService(); // Map predefined model names to full model names if (AIService.PREDEFINED_MODELS[this.model]) { this.model = AIService.PREDEFINED_MODELS[this.model]; } console.log(`[DEBUG] AIService initialized:`); console.log(`[DEBUG] - API Key: ${this.apiKey.substring(0, 10)}...`); console.log(`[DEBUG] - Model: ${this.model}`); console.log(`[DEBUG] - Base URL: ${this.baseUrl}`); console.log(`[DEBUG] - Rel Path: ${this.relPath}`); console.log(`[DEBUG] - Temperature: ${this.temperature}`); console.log(`[DEBUG] - Chatbot Service: ${this.chatbotService ? 'Enabled' : 'Disabled'}`); } async generateResponse(prompt: string, systemMessage?: string): Promise { try { const messages: ChatMessage[] = []; if (systemMessage) { messages.push({ role: 'system', content: systemMessage }); } messages.push({ role: 'user', content: prompt }); const payload: ChatRequest = { model: this.model, messages: messages, temperature: this.temperature }; const url = `https://${this.baseUrl}${this.relPath}/v1/chat/completions`; console.log(`[DEBUG] Sending to OpenRouter - Model: ${this.model}, URL: ${url}`); console.log(`[DEBUG] Prompt length: ${prompt.length} characters`); const response = await axios.post(url, payload, { headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' } }); const data = response.data as ChatResponse; const aiResponse = data.choices?.[0]?.message?.content || null; console.log(`[DEBUG] OpenRouter Response: ${aiResponse}`); return aiResponse; } catch (error) { console.error('Error calling OpenRouter:', error); if (error.response) { console.error('Response status:', error.response.status); console.error('Response data:', error.response.data); } return null; } } async generateResponseWithHistory( userMessage: string, conversationHistory: any[], systemMessage?: string ): Promise { try { const messages: ChatMessage[] = []; if (systemMessage) { messages.push({ role: 'system', content: systemMessage }); } // Add conversation history conversationHistory.forEach(msg => { if (msg.sender === 'candidate' || msg.sender === 'user') { messages.push({ role: 'user', content: msg.message }); } else if (msg.sender === 'ai' || msg.sender === 'assistant') { messages.push({ role: 'assistant', content: msg.message }); } }); // Add current user message messages.push({ role: 'user', content: userMessage }); const payload: ChatRequest = { model: this.model, messages: messages, temperature: this.temperature }; const url = `https://${this.baseUrl}${this.relPath}/v1/chat/completions`; console.log(`[DEBUG] Sending to OpenRouter with history - Model: ${this.model}`); console.log(`[DEBUG] Messages count: ${messages.length}`); const response = await axios.post(url, payload, { headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' } }); const data = response.data as ChatResponse; const aiResponse = data.choices?.[0]?.message?.content || null; console.log(`[DEBUG] OpenRouter Response: ${aiResponse}`); return aiResponse; } catch (error) { console.error('Error calling OpenRouter with history:', error); if (error.response) { console.error('Response status:', error.response.status); console.error('Response data:', error.response.data); } return null; } } /** * Generate response using chatbot service with fallback to direct OpenRouter */ async generateResponseWithChatbot( userMessage: string, conversationHistory: any[], systemMessage?: string, job?: any, candidateName?: string, linkId?: string ): Promise { // Try chatbot service first try { const isHealthy = await this.chatbotService.isHealthy(); if (isHealthy) { console.log(`[DEBUG] Using chatbot service for response generation`); const response = await this.chatbotService.sendMessage({ message: userMessage, conversationHistory, systemMessage, job, candidateName, linkId }); if (response) { return response; } } } catch (error) { console.error('[ERROR] Chatbot service failed, falling back to direct OpenRouter:', error); } // Fallback to direct OpenRouter if (this.chatbotService.shouldUseFallback()) { console.log(`[DEBUG] Falling back to direct OpenRouter`); return await this.generateResponseWithHistory(userMessage, conversationHistory, systemMessage); } return null; } /** * Initialize interview using chatbot service */ async initializeInterviewWithChatbot( job: any, candidateName: string, linkId: string, conversationHistory: any[] = [] ): Promise { try { const isHealthy = await this.chatbotService.isHealthy(); if (isHealthy) { console.log(`[DEBUG] Using chatbot service for interview initialization`); return await this.chatbotService.initializeInterview(job, candidateName, linkId, conversationHistory); } } catch (error) { console.error('[ERROR] Chatbot service failed for interview initialization:', error); } // Fallback to direct OpenRouter if (this.chatbotService.shouldUseFallback()) { console.log(`[DEBUG] Falling back to direct OpenRouter for interview initialization`); const systemMessage = this.buildInterviewSystemMessage(job, candidateName, conversationHistory); return await this.generateResponse(`The candidate's name is ${candidateName}. Please start the interview.`, systemMessage); } return null; } /** * End interview using chatbot service */ async endInterviewWithChatbot(linkId: string): Promise { try { const isHealthy = await this.chatbotService.isHealthy(); if (isHealthy) { console.log(`[DEBUG] Using chatbot service for interview end`); return await this.chatbotService.endInterview(linkId); } } catch (error) { console.error('[ERROR] Chatbot service failed for interview end:', error); } return false; } /** * Build interview system message */ private buildInterviewSystemMessage(job: any, candidateName: string, conversationHistory: any[] = []): string { const skills = job.skills_required ? job.skills_required.join(', ') : 'various technical skills'; const experience = job.experience_level.replace('_', ' '); // Build context from conversation history (mandatory question answers) const conversationContext = conversationHistory .map(msg => `${msg.sender === 'candidate' ? 'Candidate' : 'Interviewer'}: ${msg.message}`) .join('\n'); return `You are an AI interview agent conducting an interview for the position: ${job.title} Job Description: ${job.description} Requirements: ${job.requirements} Required Skills: ${skills} Experience Level: ${experience} Location: ${job.location || 'Remote'} ${conversationContext ? `Previous conversation (mandatory questions answered): ${conversationContext} Based on the candidate's answers to the mandatory questions above, you should now conduct a deeper interview.` : ''} Your task is to: 1. Greet the candidate warmly and professionally 2. Introduce yourself as their evaluation agent 3. ${conversationContext ? 'Acknowledge their previous answers and build upon them' : 'Explain that you\'ll be conducting a comprehensive interview'} 4. Ask them to tell you about themselves and their interest in this role 5. Keep your response conversational and engaging 6. Don't ask multiple questions at once - start with one open-ended question Respond in a friendly, professional tone. Keep it concise but welcoming.`; } }