Spaces:
Running
Running
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<string> { | |
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 <think> sections that might appear in the response | |
content = content.replace(/<think>[\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<boolean> { | |
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<boolean> { | |
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; | |
} | |
} |