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<string, string> = {
'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<string | null> {
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<string | null> {
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<string | null> {
// 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<string | null> {
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<boolean> {
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.`;
}
}