ваше сообщение коммита

This commit is contained in:
2025-08-08 16:30:47 +03:00
parent 0a72902c37
commit badb8b9557
15 changed files with 921 additions and 218 deletions

View File

@@ -303,15 +303,15 @@ router.post('/guest-message', upload.array('attachments'), async (req, res) => {
`INSERT INTO guest_messages
(guest_id_encrypted, content_encrypted, language_encrypted, is_ai,
attachment_filename_encrypted, attachment_mimetype_encrypted, attachment_size, attachment_data)
VALUES (encrypt_text($1, $8), ${messageContent ? 'encrypt_text($2, $8)' : 'NULL'}, encrypt_text($3, $8), false, ${attachmentFilename ? 'encrypt_text($4, $8)' : 'NULL'}, ${attachmentMimetype ? 'encrypt_text($5, $8)' : 'NULL'}, $6, $7) RETURNING id`,
VALUES (encrypt_text($1, $8), encrypt_text($2, $8), encrypt_text($3, $8), false, encrypt_text($4, $8), encrypt_text($5, $8), $6, $7) RETURNING id`,
[
guestId,
messageContent, // Текст сообщения или NULL
messageContent || '', // Текст сообщения или пустая строка
'ru', // Устанавливаем русский язык по умолчанию
attachmentFilename,
attachmentMimetype,
attachmentSize,
attachmentData, // BYTEA данные файла или NULL
attachmentFilename || '', // Имя файла или пустая строка
attachmentMimetype || '', // MIME тип или пустая строка
attachmentSize || null,
attachmentData || null, // BYTEA данные файла или NULL
encryptionKey
]
);
@@ -330,9 +330,12 @@ router.post('/guest-message', upload.array('attachments'), async (req, res) => {
logger.info('Session saved after guest message');
} catch (sessionError) {
logger.error('Error saving session after guest message:', sessionError);
// Не прерываем ответ пользователю из-за ошибки сессии
// Не прерываем ответ пользователя из-за ошибки сессии
}
// ВАЖНО: до авторизации ИИ-ответы гостям не отправляем. Только сохраняем гостевое сообщение и возвращаем системный текст.
let aiResponseContent = null;
// Получаем настройки ассистента для systemMessage
let telegramBotUrl = null;
let supportEmailAddr = null;
@@ -352,6 +355,7 @@ router.post('/guest-message', upload.array('attachments'), async (req, res) => {
success: true,
messageId: savedMessageId, // Возвращаем ID сохраненного сообщения
guestId: guestId, // Возвращаем использованный guestId
aiResponse: aiResponseContent, // Возвращаем AI ответ
systemMessage: 'Для продолжения диалога авторизуйтесь: подключите кошелек, перейдите в чат-бот Telegram или отправьте письмо на email.',
telegramBotUrl,
supportEmail: supportEmailAddr
@@ -525,7 +529,7 @@ router.post('/message', requireAuth, upload.array('attachments'), async (req, re
let ragResult = null;
if (ragTableId) {
const { ragAnswerWithConversation, generateLLMResponse } = require('../services/ragService');
const threshold = 200; // Увеличиваем threshold для более широкого поиска
const threshold = 10; // Жёстче порог совпадения, чтобы не подмешивать нерелевантный RAG
// Получаем историю беседы
const historyResult = await db.getQuery()(
@@ -533,28 +537,32 @@ router.post('/message', requireAuth, upload.array('attachments'), async (req, re
[conversationId, userMessage.id, encryptionKey]
);
const history = historyResult.rows.reverse().map(msg => ({
role: msg.sender_type === 'user' ? 'user' : 'assistant',
// Любые человеческие сообщения (user/admin) считаем role='user'. Только 'assistant' — ассистент
role: msg.sender_type === 'assistant' ? 'assistant' : 'user',
content: msg.content
}));
logger.info(`[RAG] Запуск поиска по RAG с беседой: tableId=${ragTableId}, вопрос="${messageContent}", threshold=${threshold}, historyLength=${history.length}`);
const ragResult = await ragAnswerWithConversation({
const ragSearchResult = await ragAnswerWithConversation({
tableId: ragTableId,
userQuestion: messageContent,
threshold,
history,
conversationId
conversationId,
// Не пересобираем индекс на каждом запросе. Кнопка /rebuild-index дергает rebuild.
forceReindex: false
});
logger.info(`[RAG] Результат поиска по RAG:`, ragResult);
logger.info(`[RAG] Score type: ${typeof ragResult.score}, value: ${ragResult.score}, threshold: ${threshold}, isFollowUp: ${ragResult.isFollowUp}`);
if (ragResult && ragResult.answer && typeof ragResult.score === 'number' && Math.abs(ragResult.score) <= threshold) {
logger.info(`[RAG] Найден confident-ответ (score=${ragResult.score}), отправляем ответ из базы.`);
logger.info(`[RAG] Результат поиска по RAG:`, ragSearchResult);
logger.info(`[RAG] Score type: ${typeof ragSearchResult.score}, value: ${ragSearchResult.score}, threshold: ${threshold}, isFollowUp: ${ragSearchResult.isFollowUp}`);
const isConfident = ragSearchResult && typeof ragSearchResult.score === 'number' && Math.abs(ragSearchResult.score) <= threshold;
if (isConfident && ragSearchResult.answer) {
logger.info(`[RAG] Найден confident-ответ (score=${ragSearchResult.score}), отправляем ответ из базы.`);
// Прямой ответ из RAG
logger.info(`[RAG] Сохраняем AI сообщение с контентом: "${ragResult.answer}"`);
logger.info(`[RAG] Сохраняем AI сообщение с контентом: "${ragSearchResult.answer}"`);
aiMessage = await encryptedDb.saveData('messages', {
conversation_id: conversationId,
user_id: userId,
content: ragResult.answer,
content: ragSearchResult.answer,
sender_type: 'assistant',
role: 'assistant',
channel: 'web'
@@ -562,17 +570,19 @@ router.post('/message', requireAuth, upload.array('attachments'), async (req, re
logger.info(`[RAG] AI сообщение сохранено:`, aiMessage);
// Пушим новое сообщение через WebSocket
broadcastChatMessage(aiMessage);
} else if (ragResult) {
logger.info(`[RAG] Нет confident-ответа (score=${ragResult.score}), переходим к генерации через LLM.`);
} else if (ragSearchResult) {
logger.info(`[RAG] Нет confident-ответа (score=${ragSearchResult.score}), переходим к генерации через LLM.`);
// Генерация через LLM с подстановкой значений из RAG и историей беседы
const llmResponse = await generateLLMResponse({
userQuestion: messageContent,
context: ragResult.context,
answer: ragResult.answer,
clarifyingAnswer: ragResult.clarifyingAnswer,
objectionAnswer: ragResult.objectionAnswer,
// ВАЖНО: если совпадение неуверенное — НЕ подмешиваем RAG-контент,
// иначе модель уходит в ответы про MetaMask и прочие нерелевантные темы
context: '',
answer: '',
clarifyingAnswer: ragSearchResult.clarifyingAnswer,
objectionAnswer: ragSearchResult.objectionAnswer,
systemPrompt: aiSettings ? aiSettings.system_prompt : '',
history: ragResult.conversationContext ? ragResult.conversationContext.conversationHistory : history,
history: ragSearchResult.conversationContext ? ragSearchResult.conversationContext.conversationHistory : history,
model: aiSettings ? aiSettings.model : undefined
});
if (llmResponse) {