Files
reason-flow/server/services/chatRouter.js
T
2025-11-06 11:08:59 +01:00

156 lines
6.3 KiB
JavaScript

const groqService = require('./groqService');
const model1Service = require('./model1Service');
const logger = require('../utils/logger');
class ChatRouter {
constructor() {
this.systemPrompt = `You are ReasonAI, a specialized engineering assistant. Your role is to:
1. **Identify Engineering Questions**: Determine if a user's message requires an engineering plan or is a simple question/conversation.
2. **Respond Appropriately**:
- For engineering questions: Indicate that a plan should be generated
- For simple questions/greetings: Provide helpful responses within your engineering scope
- For conversation context: Use conversation history to provide relevant responses
- For out-of-scope questions: Politely redirect to engineering topics
3. **Scope Boundaries**:
- ✅ Engineering: Civil, mechanical, electrical, chemical, environmental, software engineering
- ✅ Technical: Calculations, design, analysis, standards, codes, materials
- ✅ Simple: Greetings, basic engineering concepts, "what is 2+2"
- ✅ Conversation: Questions about previous messages, context, follow-ups
- ❌ Out-of-scope: Sports, entertainment, politics, general knowledge outside engineering
4. **Context Awareness**: Use conversation history to provide relevant responses. If user asks about previous messages or context, respond helpfully.
5. Some questiosn might be engineering questions but not every questions requires a plan, if it can be answered straightforwardly, then it doesnt need a plan
6. **Response Format (STRICT)**: Respond ONLY with a single, minified JSON object. No prose, no code fences, no backticks, no explanations. Use DOUBLE QUOTES for all keys and string values. If unsure, default to simple_response. The JSON schema is:
{
"isEngineeringQuestion": boolean,
"responseType": "plan_needed" | "simple_response" | "out_of_scope",
"response": "Your response to the user",
"reasoning": "Why you classified it this way"
}`;
}
async routeMessage(content, context = {}) {
try {
const messages = [
{
role: 'system',
content: this.systemPrompt
}
];
// Add conversation history for context
if (context.conversationHistory && context.conversationHistory.length > 0) {
context.conversationHistory.forEach(msg => {
messages.push({
role: msg.role,
content: msg.content
});
});
}
// Add current message
messages.push({
role: 'user',
content: `Current user message: "${content}"\n\nContext: ${JSON.stringify(context)}\n\nConversation History: ${context.conversationHistory ? JSON.stringify(context.conversationHistory) : 'None'}`
});
const response = await groqService.generateResponse(messages, {
temperature: 0.3,
max_tokens: 4000
});
// Parse the JSON response
let routingDecision;
try {
// Prefer fenced json block if present
const fence = response.content.match(/```json\s*([\s\S]*?)```/i);
const raw = fence ? fence[1] : (response.content.match(/\{[\s\S]*\}/) || [null])[0];
if (!raw) throw new Error('No JSON found in response');
// Trim whitespace and attempt strict parse
const candidate = raw.trim();
routingDecision = JSON.parse(candidate);
} catch (parseError) {
// Downgrade to warn to avoid noisy error logs for non-strict JSON
logger.warn('Routing JSON parse failed; falling back to heuristic classification');
// Smart fallback based on content analysis
const lowerContent = content.toLowerCase();
const engineeringKeywords = ['design', 'calculate', 'analyze', 'beam', 'steel', 'concrete', 'structure', 'load', 'stress', 'engineering', 'technical', 'specification', 'code', 'standard'];
const isEngineering = engineeringKeywords.some(keyword => lowerContent.includes(keyword));
routingDecision = {
isEngineeringQuestion: isEngineering,
responseType: isEngineering ? 'plan_needed' : 'simple_response',
response: isEngineering ?
"I'll generate an engineering plan for your request." :
"I'm ReasonAI, your engineering assistant. I can help with engineering questions, calculations, and technical problems. How can I assist you today?",
reasoning: "Fallback routing due to parse error"
};
}
logger.info(`Message routed as: ${routingDecision.responseType}`, {
content: content.substring(0, 100),
reasoning: routingDecision.reasoning,
conversationHistoryLength: context.conversationHistory ? context.conversationHistory.length : 0
});
return routingDecision;
} catch (error) {
logger.error('Chat routing error:', error);
// Fallback response
return {
isEngineeringQuestion: false,
responseType: 'simple_response',
response: "I'm ReasonAI, your engineering assistant. I'm here to help with engineering questions and technical problems. How can I assist you today?",
reasoning: "Routing service error"
};
}
}
async generateSimpleResponse(content, context = {}) {
try {
const messages = [
{
role: 'system',
content: `You are ReasonAI, a friendly engineering assistant. Respond helpfully to the user's message while staying within your engineering scope. Be concise and professional. Consider the conversation history for context.`
}
];
// Add conversation history for context
if (context.conversationHistory && context.conversationHistory.length > 0) {
context.conversationHistory.forEach(msg => {
messages.push({
role: msg.role,
content: msg.content
});
});
}
// Add current message
messages.push({
role: 'user',
content: content
});
const response = await groqService.generateResponse(messages, {
temperature: 0.7,
max_tokens: 300
});
return response.content;
} catch (error) {
logger.error('Simple response generation error:', error);
return "I'm ReasonAI, your engineering assistant. I can help with engineering questions, calculations, and technical problems. How can I assist you today?";
}
}
}
module.exports = new ChatRouter();