Skip to content

Commit e343558

Browse files
update chat engines
1 parent a39c9e6 commit e343558

1 file changed

Lines changed: 68 additions & 27 deletions

File tree

backend/routes/chat.js

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ const llmScorer = require('../services/llmScorer');
1414
const { isEnabled: isLlmEnabled } = llmScorer;
1515

1616
const MEMORY_MESSAGES = [];
17-
const MIN_CONF_SCORE = 40;
17+
const MIN_CONF_SCORE = 1;
1818
const MIN_CONF_COUNT = 5;
19-
const MAX_QUESTIONS = 25;
19+
const MAX_QUESTIONS = 50;
20+
const MIN_REFINEMENT_QUESTIONS = 5;
2021

2122
const DEFAULT_SUGGESTED_QUESTIONS = {
2223
high_school: [
@@ -33,6 +34,12 @@ const DEFAULT_SUGGESTED_QUESTIONS = {
3334
]
3435
};
3536

37+
const DEFAULT_SUGGESTED_ANSWERS = {
38+
high_school: ['Có', 'Có thể', 'Không'],
39+
university: ['Có', 'Có thể', 'Không'],
40+
professional: ['Có', 'Có thể', 'Không']
41+
};
42+
3643
function safeParse(value, fallback) {
3744
try {
3845
return JSON.parse(value);
@@ -48,7 +55,7 @@ function isUserAskingQuestion(message) {
4855
return /(la gi|là gì|nhu the nao|như thế nào|tai sao|ti sao|bao nhieu|bao lâu|co nen|có nên|lam sao|làm sao|nghe nao|ngh nào|nganh nao|ngành nào|tu van|tư vn)/i.test(text);
4956
}
5057

51-
function pickSuggestedQuestions(suggestions, userType) {
58+
function pickSuggestedQuestions(suggestions, userType, mode = 'question') {
5259
const normalized = Array.isArray(suggestions)
5360
? suggestions
5461
.map((item) => String(item || '').trim())
@@ -59,19 +66,11 @@ function pickSuggestedQuestions(suggestions, userType) {
5966
if (unique.length > 0) return unique;
6067

6168
const safeUserType = String(userType || 'high_school');
62-
const fallbacks = DEFAULT_SUGGESTED_QUESTIONS[safeUserType] || DEFAULT_SUGGESTED_QUESTIONS.high_school;
69+
const fallbackMap = mode === 'answer' ? DEFAULT_SUGGESTED_ANSWERS : DEFAULT_SUGGESTED_QUESTIONS;
70+
const fallbacks = fallbackMap[safeUserType] || fallbackMap.high_school;
6371
return Array.from(new Set(fallbacks)).slice(0, 2);
6472
}
6573

66-
function appendSuggestedQuestion(botReply, suggestedQuestion) {
67-
const base = String(botReply || '').trim();
68-
if (!base) return '';
69-
const suggestion = String(suggestedQuestion || '').trim();
70-
if (!suggestion) return base;
71-
if (/suggested question\s*:/i.test(base)) return base;
72-
return `${base}\n\nSuggested question: ${suggestion}`;
73-
}
74-
7574
function normalizeRecommendationsWithProbability(recommendations) {
7675
if (!Array.isArray(recommendations) || recommendations.length === 0) return [];
7776

@@ -99,6 +98,22 @@ function normalizeRecommendationsWithProbability(recommendations) {
9998
}));
10099
}
101100

101+
function getQuestionCount(state) {
102+
const all = [...(state?.answers || []), ...(state?.refinementAnswers || [])];
103+
return all.filter((a) => String(a?.questionId || '').startsWith('ai_question_')).length;
104+
}
105+
106+
function getRefinementQuestionCount(state) {
107+
const all = [...(state?.answers || []), ...(state?.refinementAnswers || [])];
108+
return all.filter((a) => String(a?.questionId || '').startsWith('ai_refine_')).length;
109+
}
110+
111+
function isAffirmativeRefinementRequest(message) {
112+
const text = String(message || '').trim().toLowerCase();
113+
if (!text) return false;
114+
return /(chua hai long|chưa hài lòng|hoi tiep|hi thêm|hoi them|muon them|mun thêm|them cau hoi|thêm câu hi|tiep tuc|tiếp tc)/i.test(text);
115+
}
116+
102117
function buildProfileFromState(profile, state, fallbackEducationLevel) {
103118
const safeProfile = { ...(state?.profile || {}), ...(profile || {}) };
104119
if (!state) {
@@ -200,9 +215,18 @@ router.post('/message', optionalAuth, async (req, res) => {
200215
recordAnswer(convId, questionId, message, state.lastQuestionText);
201216
}
202217

218+
state.refinementMode = Boolean(state.refinementMode) || Boolean(request_more);
219+
state.refinementQuestionsAsked = Number(state.refinementQuestionsAsked || 0);
220+
221+
if (request_more || isAffirmativeRefinementRequest(message)) {
222+
state.refinementMode = true;
223+
}
224+
203225
// Combine all answers for AI analysis
204226
const allAnswers = [...(state.answers || []), ...(state.refinementAnswers || [])];
205227
const totalAnswers = allAnswers.length;
228+
const aiQuestionCount = getQuestionCount(state);
229+
const refinementQuestionCount = getRefinementQuestionCount(state);
206230

207231
const historyForAgent = allAnswers.map((a) => ({
208232
q: a?.question || '',
@@ -218,7 +242,9 @@ router.post('/message', optionalAuth, async (req, res) => {
218242
let completed = false;
219243

220244
// Use AI to generate recommendations when we have enough information
221-
if (totalAnswers >= minAnswersForRecommendation && totalAnswers <= maxQuestions && !request_more) {
245+
const canConcludeByRefinement = !state.refinementMode || refinementQuestionCount >= MIN_REFINEMENT_QUESTIONS;
246+
247+
if (totalAnswers >= minAnswersForRecommendation && totalAnswers <= maxQuestions && !request_more && canConcludeByRefinement) {
222248
try {
223249
const aiRec = await llmScorer.generateCareerRecommendations({
224250
userType: effectiveUserType || 'high_school',
@@ -237,7 +263,7 @@ router.post('/message', optionalAuth, async (req, res) => {
237263
}
238264

239265
// Force completion if we've asked too many questions
240-
if (!completed && totalAnswers >= maxQuestions) {
266+
if (!completed && totalAnswers >= maxQuestions && canConcludeByRefinement) {
241267
try {
242268
const aiRec = await llmScorer.generateCareerRecommendations({
243269
userType: effectiveUserType || 'high_school',
@@ -254,33 +280,35 @@ router.post('/message', optionalAuth, async (req, res) => {
254280
}
255281
}
256282

257-
if (request_more) {
283+
if (state.refinementMode) {
258284
completed = false;
259285
recommendations = null;
260286
}
261287

262288
let botReply = '';
263289
let nextNode = null;
264290
let suggestedQuestions = [];
291+
let suggestionMode = 'question';
265292
const userAskedQuestion = isUserAskingQuestion(message);
266293

267294
if (completed && Array.isArray(recommendations) && recommendations.length) {
268295
const top = recommendations.slice(0, 10);
269296
const lines = top.map((r, idx) => `${idx + 1}. ${r.career_name} (${r.match_score}/100)`);
270297
botReply = `Mình đã phân tích xong và tìm ra những nghề nghiệp phù hợp nhất với bạn.\n\nTop gợi ý:\n${lines.join('\n')}\n\nChi tiết phân tích đã được cập nhật trong biểu đồ bên dưới.`;
271298
nextNode = null;
272-
suggestedQuestions = pickSuggestedQuestions([], effectiveUserType || 'high_school');
299+
suggestionMode = 'question';
300+
suggestedQuestions = pickSuggestedQuestions([], effectiveUserType || 'high_school', suggestionMode);
273301
} else {
274302
// Use AI to generate the next question
275303
if (!llmScorer.isEnabled()) {
276304
botReply = 'Hiện backend chưa được cấu hình LLM (thiếu GROQ_API_KEY), nên hệ thống không thể tư vấn. Bạn hãy cấu hình GROQ_API_KEY trên Render và redeploy backend để tiếp tục.';
277305
nextNode = 'ai_chat';
278-
const fallbackSuggestions = pickSuggestedQuestions([], effectiveUserType || 'high_school');
306+
const fallbackSuggestions = pickSuggestedQuestions([], effectiveUserType || 'high_school', 'question');
279307
return res.status(503).json({
280308
success: false,
281309
error: botReply,
282310
data: {
283-
bot_reply: appendSuggestedQuestion(botReply, fallbackSuggestions[0] || ''),
311+
bot_reply: botReply,
284312
options: fallbackSuggestions,
285313
suggested_questions: fallbackSuggestions,
286314
suggested_question: fallbackSuggestions[0] || '',
@@ -299,8 +327,11 @@ router.post('/message', optionalAuth, async (req, res) => {
299327

300328
if (aiReply && aiReply.bot_reply) {
301329
botReply = aiReply.bot_reply;
302-
nextNode = 'ai_chat';
303-
suggestedQuestions = pickSuggestedQuestions(aiReply.suggested_questions, effectiveUserType || 'high_school');
330+
nextNode = 'ai_answer';
331+
state.lastQuestionId = 'ai_answer';
332+
state.lastQuestionText = aiReply.bot_reply;
333+
suggestionMode = 'question';
334+
suggestedQuestions = pickSuggestedQuestions(aiReply.suggested_questions, effectiveUserType || 'high_school', suggestionMode);
304335
}
305336
}
306337

@@ -313,22 +344,28 @@ router.post('/message', optionalAuth, async (req, res) => {
313344
});
314345

315346
if (aiQuestion && aiQuestion.question) {
316-
state.lastQuestionId = 'ai_chat';
347+
const questionPrefix = state.refinementMode ? 'ai_refine_' : 'ai_question_';
348+
const nextQuestionIndex = (state.refinementMode ? refinementQuestionCount : aiQuestionCount) + 1;
349+
state.lastQuestionId = `${questionPrefix}${nextQuestionIndex}`;
317350
state.lastQuestionText = aiQuestion.question;
318351
botReply = aiQuestion.question;
319352
nextNode = 'ai_chat';
320-
suggestedQuestions = pickSuggestedQuestions(aiQuestion.options, effectiveUserType || 'high_school');
353+
suggestionMode = 'answer';
354+
suggestedQuestions = pickSuggestedQuestions(aiQuestion.options, effectiveUserType || 'high_school', suggestionMode);
355+
if (state.refinementMode) {
356+
state.refinementQuestionsAsked = Number(state.refinementQuestionsAsked || 0) + 1;
357+
}
321358
} else {
322359
botReply = 'Cảm ơn bạn đã chia sẻ. Mình đang phân tích thông tin để đưa ra gợi ý phù hợp nhất. Vui lòng đợi một chút...';
323360
nextNode = 'ai_chat';
324-
suggestedQuestions = pickSuggestedQuestions([], effectiveUserType || 'high_school');
361+
suggestionMode = 'question';
362+
suggestedQuestions = pickSuggestedQuestions([], effectiveUserType || 'high_school', suggestionMode);
325363
}
326364
}
327365
}
328366

329-
suggestedQuestions = pickSuggestedQuestions(suggestedQuestions, effectiveUserType || 'high_school');
367+
suggestedQuestions = pickSuggestedQuestions(suggestedQuestions, effectiveUserType || 'high_school', suggestionMode);
330368
const suggestedQuestion = suggestedQuestions[0] || '';
331-
botReply = appendSuggestedQuestion(botReply, suggestedQuestion);
332369

333370
if (userId) {
334371
await saveMessage(convId, userId, 'bot', botReply, nextNode);
@@ -348,7 +385,11 @@ router.post('/message', optionalAuth, async (req, res) => {
348385
next_node: nextNode,
349386
conversation_id: convId,
350387
recommendations: recommendations || undefined,
351-
completed
388+
completed,
389+
ai_question_count: aiQuestionCount,
390+
refinement_question_count: refinementQuestionCount,
391+
refinement_mode: Boolean(state.refinementMode),
392+
min_refinement_questions: MIN_REFINEMENT_QUESTIONS
352393
}
353394
});
354395

0 commit comments

Comments
 (0)