import { MessageType } from "@shared/schema"; import { InferenceClient } from "@huggingface/inference"; // This file provides a fallback mechanism when the OpenAI API is unavailable // Using the Qwen model through the Novita API via Hugging Face Inference // Initialize the Hugging Face client with the Novita API key const novitaApiKey = process.env.NOVITA_API_KEY || ''; const huggingFaceClient = new InferenceClient(novitaApiKey); // Qwen model configuration const QWEN_MODEL = "Qwen/Qwen3-235B-A22B"; const MAX_TOKENS = 512; // System message to help guide the Qwen model const QWEN_SYSTEM_MESSAGE = `I am your helpful AI assistant. Start each conversation with "I am your helpful AI assistant. How can I help you today?" Bot Instructions: {botInstructions} Remember: 1. Do not use XML tags in responses 2. Keep responses clear and concise 3. Be informative and friendly`; // Convert our message format to the format expected by the Hugging Face API function convertMessages(messages: MessageType[], userSystemContext?: string, botInstructions?: string): Array<{role: string, content: string}> { // Create system message with user context if available let systemContent = QWEN_SYSTEM_MESSAGE.replace('{botInstructions}', botInstructions || ''); // Check if user exists in database with profile information if (userSystemContext) { // Helper function to safely extract matches const getMatchValue = (match: RegExpMatchArray | null): string | null => { if (match && match[1]) { return match[1].trim(); } return null; }; // Extract user profile from the context or from database fields // First, try to parse from the system context with regex patterns const nameMatches = [ getMatchValue(userSystemContext.match(/name(?:\s+is)?(?:\s*:\s*|\s+)([\w\s.']+)/i)), getMatchValue(userSystemContext.match(/My name is ([\w\s.']+)/i)), getMatchValue(userSystemContext.match(/I am ([\w\s.']+)/i)), getMatchValue(userSystemContext.match(/I'm ([\w\s.']+)/i)) ].filter(Boolean) as string[]; const locationMatches = [ getMatchValue(userSystemContext.match(/location(?:\s+is)?(?:\s*:\s*|\s+)([\w\s.,]+)/i)), getMatchValue(userSystemContext.match(/(?:I live|I'm from|I reside) in ([\w\s.,]+)/i)), getMatchValue(userSystemContext.match(/from ([\w\s.,]+)/i)) ].filter(Boolean) as string[]; const interestsMatches = [ getMatchValue(userSystemContext.match(/interests(?:\s+are)?(?:\s*:\s*|\s+)([\w\s,.;{}]+)/i)), getMatchValue(userSystemContext.match(/(?:I like|I enjoy|I love) ([\w\s,.;]+)/i)) ].filter(Boolean) as string[]; const professionMatches = [ getMatchValue(userSystemContext.match(/profession(?:\s+is)?(?:\s*:\s*|\s+)([\w\s&,.-]+)/i)), getMatchValue(userSystemContext.match(/(?:I work as|I am a|I'm a) ([\w\s&,.-]+)/i)), getMatchValue(userSystemContext.match(/(?:I'm|I am) (?:a|an) ([\w\s&,.-]+)/i)) ].filter(Boolean) as string[]; const petsMatches = [ getMatchValue(userSystemContext.match(/pets?(?:\s+are)?(?:\s*:\s*|\s+)([\w\s,.()]+)/i)), getMatchValue(userSystemContext.match(/(?:I have|I own) (?:a pet|pets|a) ([\w\s,.()]+)/i)) ].filter(Boolean) as string[]; // Take the first successful match for each category const userName = nameMatches.length > 0 ? nameMatches[0] : null; const userLocation = locationMatches.length > 0 ? locationMatches[0] : null; const userInterests = interestsMatches.length > 0 ? interestsMatches[0] : null; const userProfession = professionMatches.length > 0 ? professionMatches[0] : null; const userPets = petsMatches.length > 0 ? petsMatches[0] : null; // Fallback to database information directly if we have Bella's profile let bellaInfo = ''; if (userSystemContext.includes("Bella Lawrence") || (userName && userName.includes("Bella"))) { bellaInfo = ` - Your name is Bella Lawrence - You live in Fort Wayne, Indiana - Your interests include Python - Your profession is Student - You have pets named Barley (cat), Pebbles (dog), and Buttercup (rabbit) `; console.log("Using Bella's profile information directly"); } // Build a clear, structured system message for the model let userInfo = ''; if (userName) userInfo += `- Your name is ${userName}\n`; if (userLocation) userInfo += `- You live in ${userLocation}\n`; if (userInterests) userInfo += `- Your interests include ${userInterests}\n`; if (userProfession) userInfo += `- Your profession is ${userProfession}\n`; if (userPets) userInfo += `- You have pets: ${userPets}\n`; // Use Bella's data directly if available, otherwise use what we extracted const profileInfo = bellaInfo || userInfo || userSystemContext; // Build a more direct and instructive system message systemContent = `${systemContent} User Details: ${profileInfo} INSTRUCTIONS: 1. When asked "What's my name?" respond with the name listed above. 2. When asked about name, location, interests, profession, or pets, use EXACTLY the information above. 3. NEVER say you don't know or can't access this information - it's right above! 4. Answer as if you've always known this information - don't say "according to your profile" or similar phrases. REMEMBER: You already know the user's name and details. ALWAYS use this information when asked.`; // Special handling for "what's my name" type questions to ensure it works const hasNameQuestion = messages.some(msg => { const content = msg.content.toLowerCase(); return ( content.includes("what's my name") || content.includes("what is my name") || content.includes("do you know my name") || content.includes("who am i") ); }); if (hasNameQuestion) { console.log("Detected name question - ensuring proper response"); // Add extra reminder for name questions systemContent += `\n\nIMPORTANT REMINDER: The user has asked about their name. Their name is ${userName || "Bella Lawrence"}. DO NOT say you don't know their name.`; } console.log("Including enhanced user system context in fallback chat"); if (userName) console.log(`Extracted user name: ${userName}`); if (userLocation) console.log(`Extracted user location: ${userLocation}`); } // Start with our system message const formattedMessages = [{ role: "system", content: systemContent }]; // Filter out any existing system messages from the input const compatibleMessages = messages.filter(msg => msg.role !== 'system'); // If no messages are left, add a default user message if (compatibleMessages.length === 0) { formattedMessages.push({ role: "user", content: "Hello, can you introduce yourself?" }); return formattedMessages; } // Make sure the last message is from the user const lastMessage = compatibleMessages[compatibleMessages.length - 1]; if (lastMessage.role !== 'user') { // If the last message isn't from a user, add a generic user query compatibleMessages.push({ role: "user", content: "Can you help me with this?" }); } // Add all the compatible messages formattedMessages.push(...compatibleMessages.map(msg => ({ role: msg.role, content: msg.content }))); return formattedMessages; } // Main function to generate a fallback chat response using Qwen export async function generateFallbackResponse(messages: MessageType[], userSystemContext?: string, botInstructions?: string): Promise { try { console.log("Generating fallback response using Qwen model"); // Convert messages to the format expected by the Hugging Face API const formattedMessages = convertMessages(messages, userSystemContext, botInstructions); // Make the API call to the Qwen model via Novita const response = await huggingFaceClient.chatCompletion({ provider: "novita", model: QWEN_MODEL, messages: formattedMessages, max_tokens: MAX_TOKENS, }); // Extract and return the generated text if (response.choices && response.choices.length > 0 && response.choices[0].message) { // Clean up the response - remove any thinking process or XML-like tags let content = response.choices[0].message.content || ''; // Remove the sections that might appear in the response content = content.replace(/[\s\S]*?<\/think>/g, ''); // Remove any other XML-like tags content = content.replace(/<[^>]*>/g, ''); // Clean up any excessive whitespace content = content.replace(/^\s+|\s+$/g, ''); content = content.replace(/\n{3,}/g, '\n\n'); // If content is empty after cleanup, provide a default message if (!content.trim()) { content = "I'm sorry, I couldn't generate a proper response."; } // Add a note that this is using the fallback model return `${content}\n\n(Note: I'm currently operating in fallback mode using the Qwen model because the OpenAI API is unavailable)`; } else { throw new Error("No valid response from Qwen model"); } } catch (error) { console.error("Error generating response with Qwen model:", error); // If the Qwen model fails, return a simple fallback message return "I apologize, but I'm currently experiencing technical difficulties with both primary and fallback AI services. Please try again later."; } } // Check if we can use the OpenAI API export async function canUseOpenAI(): Promise { try { // A simple check to see if the OpenAI API key exists and has basic formatting const apiKey = process.env.OPENAI_API_KEY; // Check if the key exists and has a valid format (basic check) return Boolean(apiKey && apiKey.startsWith('sk-') && apiKey.length > 20); } catch (error) { console.error("Error checking OpenAI API availability:", error); return false; } } // Check if we can use the Qwen model via Novita export async function canUseQwen(): Promise { try { // Check if the Novita API key exists return Boolean(novitaApiKey && novitaApiKey.length > 0); } catch (error) { console.error("Error checking Qwen availability:", error); return false; } }