Spaces:
Running
Running
import OpenAI from "openai"; | |
import { MessageType } from "@shared/schema"; | |
import { generateFallbackResponse, canUseOpenAI, canUseQwen } from "./fallbackChat"; | |
// the newest OpenAI model is "gpt-4o" which was released May 13, 2024. do not change this unless explicitly requested by the user | |
const OPENAI_MODEL = "gpt-4o"; | |
// Initialize OpenAI client | |
const openai = new OpenAI({ | |
apiKey: process.env.OPENAI_API_KEY | |
}); | |
const systemMessage: MessageType = { | |
role: "system", | |
content: `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. Always provide accurate, helpful information | |
3. Be respectful and considerate` | |
}; | |
// Flag to track current model in use | |
let currentModel: 'openai' | 'qwen' | 'unavailable' = 'openai'; | |
export async function generateChatResponse(messages: MessageType[], userSystemContext?: string): Promise<string> { | |
try { | |
// Check if we can use OpenAI API | |
const openAIAvailable = await canUseOpenAI(); | |
// If OpenAI API is not available, check if Qwen is available | |
if (!openAIAvailable) { | |
const qwenAvailable = await canUseQwen(); | |
if (qwenAvailable) { | |
if (currentModel !== 'qwen') { | |
console.log("Switching to Qwen model as fallback"); | |
currentModel = 'qwen'; | |
} | |
// Use Qwen fallback | |
return await generateFallbackResponse(messages, userSystemContext); | |
} else { | |
// Neither OpenAI nor Qwen is available | |
currentModel = 'unavailable'; | |
throw new Error("Both OpenAI and Qwen models are unavailable. Please check your API keys."); | |
} | |
} | |
// If we get here, we're using OpenAI | |
if (currentModel !== 'openai') { | |
console.log("Using OpenAI model"); | |
currentModel = 'openai'; | |
} | |
// Create the system message, incorporating user context if available | |
let enhancedSystemMessage = { ...systemMessage }; | |
if (userSystemContext) { | |
// Process the system context to extract key user information for explicit instructions | |
const nameMatch = userSystemContext.match(/name(?:\s+is)?(?:\s*:\s*|\s+)([\w\s.']+)/i); | |
const locationMatch = userSystemContext.match(/location(?:\s+is)?(?:\s*:\s*|\s+)([\w\s.,]+)/i); | |
const interestsMatch = userSystemContext.match(/interests(?:\s+are)?(?:\s*:\s*|\s+)([\w\s,.;]+)/i); | |
const professionMatch = userSystemContext.match(/profession(?:\s+is)?(?:\s*:\s*|\s+)([\w\s&,.-]+)/i); | |
const petsMatch = userSystemContext.match(/pets?(?:\s+are)?(?:\s*:\s*|\s+)([\w\s,.]+)/i); | |
// Build structured user profile information | |
let userInfo = ''; | |
if (nameMatch) userInfo += `- Name: ${nameMatch[1].trim()}\n`; | |
if (locationMatch) userInfo += `- Location: ${locationMatch[1].trim()}\n`; | |
if (interestsMatch) userInfo += `- Interests: ${interestsMatch[1].trim()}\n`; | |
if (professionMatch) userInfo += `- Profession: ${professionMatch[1].trim()}\n`; | |
if (petsMatch) userInfo += `- Pets: ${petsMatch[1].trim()}\n`; | |
// Append the user's custom context to the system message in a more structured way | |
enhancedSystemMessage.content = `${systemMessage.content} | |
User Details: | |
${userInfo || userSystemContext} | |
IMPORTANT: You must remember these user details and incorporate them naturally in your responses when relevant. | |
When the user asks about their name, location, interests, profession, or pets, always answer using the information above. | |
Never say you don't know their personal details if they're listed above. Answer as if you already know this information. | |
Original system context provided by user: | |
${userSystemContext}`; | |
console.log("Including enhanced user system context in OpenAI chat"); | |
} | |
// Always include system message at the beginning | |
const conversationWithSystem = [enhancedSystemMessage, ...messages]; | |
// Make API call to OpenAI | |
const response = await openai.chat.completions.create({ | |
model: OPENAI_MODEL, | |
messages: conversationWithSystem, | |
temperature: 0.7, | |
max_tokens: 1000, | |
}); | |
// Extract and return the generated text | |
return response.choices[0].message.content || "I'm sorry, I couldn't generate a response."; | |
} catch (error: any) { | |
console.error("AI Model error:", error); | |
// If we have an OpenAI error, try using Qwen as fallback | |
if (currentModel === 'openai') { | |
console.log("OpenAI API error, attempting to use Qwen fallback"); | |
try { | |
const qwenAvailable = await canUseQwen(); | |
if (qwenAvailable) { | |
currentModel = 'qwen'; | |
return await generateFallbackResponse(messages, userSystemContext); | |
} else { | |
currentModel = 'unavailable'; | |
} | |
} catch (fallbackError) { | |
console.error("Qwen fallback also failed:", fallbackError); | |
currentModel = 'unavailable'; | |
} | |
} | |
// If we've gotten this far, all fallbacks have failed, provide a helpful error | |
if (error.response) { | |
// API returned an error response | |
const status = error.response.status; | |
if (status === 429) { | |
if (error.code === 'insufficient_quota') { | |
throw new Error("OpenAI API quota exceeded. Your account may need a valid payment method or has reached its limit."); | |
} else { | |
throw new Error("Rate limit exceeded. Please try again later."); | |
} | |
} else if (status === 401) { | |
throw new Error("API key is invalid or expired."); | |
} else { | |
throw new Error(`OpenAI API error: ${error.response?.data?.error?.message || 'Unknown error'}`); | |
} | |
} else if (error.request) { | |
// Request was made but no response received | |
throw new Error("No response received from AI service. Please check your internet connection."); | |
} else { | |
// Something else happened | |
throw new Error(`Error: ${error.message}`); | |
} | |
} | |
} |