ideas-generator/server/utils/openai.js
DJP c936e81a47 Complete GPT-5 integration with reasoning effort controls and fix conversation loading
- Implement GPT-5 model support with correct parameters (max_completion_tokens, temperature=1)
- Add configurable reasoning effort levels (low/medium/high) with database migration
- Create admin interface controls for reasoning effort management in agent creation/editing
- Fix conversation loading regression to prevent double-click requirement
- Update home page cards for uniform 280px height with proper text truncation
- Add comprehensive GPT-5 parameter handling in OpenAI service integration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-03 15:40:25 -04:00

175 lines
No EOL
5.6 KiB
JavaScript

const OpenAI = require('openai');
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
organization: process.env.OPENAI_ORG_ID,
});
class OpenAIService {
constructor() {
if (!process.env.OPENAI_API_KEY || process.env.OPENAI_API_KEY.includes('your-actual')) {
console.warn('⚠️ OpenAI API key not configured properly');
}
}
async createResponse(messages, assistantConfig = {}) {
try {
const {
model = 'gpt-4o',
temperature = 0.7,
maxTokens = 4000,
stream = false,
reasoningEffort = 'medium',
} = assistantConfig;
console.log(`Making OpenAI request with model: ${model}`);
const requestParams = {
model,
messages,
temperature,
stream,
};
// GPT-5 uses max_completion_tokens instead of max_tokens and requires temperature = 1
if (model === 'gpt-5') {
console.log(`GPT-5 detected, using max_completion_tokens, temperature = 1, and reasoning_effort = ${reasoningEffort}`);
requestParams.max_completion_tokens = maxTokens;
requestParams.temperature = 1; // GPT-5 only supports temperature = 1
requestParams.reasoning_effort = reasoningEffort; // GPT-5 reasoning effort from agent config
} else {
requestParams.max_tokens = maxTokens;
}
const response = await openai.chat.completions.create(requestParams);
return response;
} catch (error) {
console.error('OpenAI Responses API Error:', {
model: assistantConfig.model,
error: error.message,
status: error.status,
code: error.code,
type: error.type
});
throw error;
}
}
async createStreamingResponse(messages, assistantConfig = {}) {
try {
const {
model = 'gpt-4o',
temperature = 0.7,
maxTokens = 4000,
reasoningEffort = 'medium',
} = assistantConfig;
console.log(`Making streaming OpenAI request with model: ${model}`);
const requestParams = {
model,
messages,
temperature,
stream: true,
};
// GPT-5 uses max_completion_tokens instead of max_tokens and requires temperature = 1
if (model === 'gpt-5') {
console.log(`GPT-5 detected for streaming, using max_completion_tokens, temperature = 1, and reasoning_effort = ${reasoningEffort}`);
requestParams.max_completion_tokens = maxTokens;
requestParams.temperature = 1; // GPT-5 only supports temperature = 1
requestParams.reasoning_effort = reasoningEffort; // GPT-5 reasoning effort from agent config
} else {
requestParams.max_tokens = maxTokens;
}
const stream = await openai.chat.completions.create(requestParams);
return stream;
} catch (error) {
console.error('OpenAI Streaming Responses API Error:', {
model: assistantConfig.model,
error: error.message,
status: error.status,
code: error.code,
type: error.type
});
throw error;
}
}
formatMessagesForAPI(messages) {
return messages.map(msg => ({
role: msg.role,
content: msg.content,
}));
}
buildSystemMessage(systemPrompt) {
return {
role: 'system',
content: systemPrompt,
};
}
async generateConversationTitle(userMessage, assistantResponse, agentName) {
try {
if (!process.env.ENABLE_TITLE_GENERATION || process.env.ENABLE_TITLE_GENERATION !== 'true') {
console.log('Title generation disabled, using fallback');
return `Chat with ${agentName}`;
}
const titlePrompt = `Based on this conversation between a user and an AI assistant called "${agentName}", generate a short, descriptive title (3-6 words) that captures what the user is asking about or discussing. Be specific and concise.
User message: "${userMessage}"
Assistant response: "${assistantResponse}"
Generate only the title, nothing else. Examples:
- "Marketing Campaign Ideas"
- "JavaScript Function Help"
- "Travel Planning Advice"
- "Budget Analysis Discussion"`;
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini', // Use cheaper model for title generation
messages: [
{ role: 'user', content: titlePrompt }
],
temperature: 0.3, // Lower temperature for more consistent titles
max_tokens: 20, // Short titles only
});
const title = response.choices[0]?.message?.content?.trim();
if (!title) {
console.warn('No title generated, using fallback');
return `Chat with ${agentName}`;
}
// Clean up the title - remove quotes if present
const cleanTitle = title.replace(/^["']|["']$/g, '');
console.log(`Generated title: "${cleanTitle}"`);
return cleanTitle;
} catch (error) {
console.error('Error generating conversation title:', error);
return `Chat with ${agentName}`; // Fallback title
}
}
async testConnection() {
try {
const response = await this.createResponse([
{ role: 'user', content: 'Hello, this is a test message.' }
], { maxTokens: 50 });
console.log('✅ OpenAI Responses API connection test successful');
return { success: true, response };
} catch (error) {
console.error('❌ OpenAI Responses API connection test failed:', error.message);
return { success: false, error: error.message };
}
}
}
module.exports = new OpenAIService();