diff --git a/backend/src/entities/ai/ai-conversation-history/ai-chat-messages/ai-chat-message.entity.ts b/backend/src/entities/ai/ai-conversation-history/ai-chat-messages/ai-chat-message.entity.ts index f7a3ace74..13a2b315a 100644 --- a/backend/src/entities/ai/ai-conversation-history/ai-chat-messages/ai-chat-message.entity.ts +++ b/backend/src/entities/ai/ai-conversation-history/ai-chat-messages/ai-chat-message.entity.ts @@ -22,6 +22,9 @@ export class AiChatMessageEntity { @Column({ nullable: true, default: null, type: 'enum', enum: MessageRole }) role: MessageRole; + @Column({ nullable: true, default: null, type: 'varchar', length: 255 }) + response_id: string; + @CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) created_at: Date; diff --git a/backend/src/entities/ai/ai-conversation-history/ai-chat-messages/repository/ai-chat-message-repository.extension.ts b/backend/src/entities/ai/ai-conversation-history/ai-chat-messages/repository/ai-chat-message-repository.extension.ts index 77208d3c0..535eec1cb 100644 --- a/backend/src/entities/ai/ai-conversation-history/ai-chat-messages/repository/ai-chat-message-repository.extension.ts +++ b/backend/src/entities/ai/ai-conversation-history/ai-chat-messages/repository/ai-chat-message-repository.extension.ts @@ -10,6 +10,14 @@ export const aiChatMessageRepositoryExtension: IAiChatMessageRepository = { .getMany(); }, + async findLastAiMessageForChat(chatId: string): Promise { + return await this.createQueryBuilder('ai_chat_message') + .where('ai_chat_message.ai_chat_id = :chatId', { chatId }) + .andWhere('ai_chat_message.role = :role', { role: MessageRole.ai }) + .orderBy('ai_chat_message.created_at', 'DESC') + .getOne(); + }, + async deleteMessagesForChat(chatId: string): Promise { await this.createQueryBuilder() .delete() @@ -18,11 +26,17 @@ export const aiChatMessageRepositoryExtension: IAiChatMessageRepository = { .execute(); }, - async saveMessage(chatId: string, message: string, role: MessageRole): Promise { + async saveMessage( + chatId: string, + message: string, + role: MessageRole, + responseId?: string, + ): Promise { const newMessage = this.create({ ai_chat_id: chatId, message, role, + response_id: responseId, }); return await this.save(newMessage); }, diff --git a/backend/src/entities/ai/ai-conversation-history/ai-chat-messages/repository/ai-chat-message-repository.interface.ts b/backend/src/entities/ai/ai-conversation-history/ai-chat-messages/repository/ai-chat-message-repository.interface.ts index 27911caa3..c8df9f538 100644 --- a/backend/src/entities/ai/ai-conversation-history/ai-chat-messages/repository/ai-chat-message-repository.interface.ts +++ b/backend/src/entities/ai/ai-conversation-history/ai-chat-messages/repository/ai-chat-message-repository.interface.ts @@ -3,6 +3,7 @@ import { MessageRole } from '../message-role.enum.js'; export interface IAiChatMessageRepository { findMessagesForChat(chatId: string): Promise; + findLastAiMessageForChat(chatId: string): Promise; deleteMessagesForChat(chatId: string): Promise; - saveMessage(chatId: string, message: string, role: MessageRole): Promise; + saveMessage(chatId: string, message: string, role: MessageRole, responseId?: string): Promise; } diff --git a/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts b/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts index 93e830326..161f577c1 100644 --- a/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts +++ b/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts @@ -26,6 +26,7 @@ import { isValidMongoDbCommand, wrapQueryWithLimit, AIProviderType, + AIProviderConfig, } from '../../../ai-core/index.js'; import { UserAiChatEntity } from '../ai-conversation-history/user-ai-chat/user-ai-chat.entity.js'; import { MessageRole } from '../ai-conversation-history/ai-chat-messages/message-role.enum.js'; @@ -94,10 +95,20 @@ export class RequestInfoFromTableWithAIUseCaseV7 }); } - const messages = new MessageBuilder().system(systemPrompt).human(user_message).build(); + const { messages, previousResponseId } = await this.buildMessagesWithHistory( + systemPrompt, + user_message, + foundUserAiChat.id, + isNewChat, + ); try { - const { accumulatedResponse } = await this.processWithToolLoop( + const config: AIProviderConfig = {}; + if (this.aiProvider === AIProviderType.OPENAI && previousResponseId) { + config.previousResponseId = previousResponseId; + } + + const { accumulatedResponse, lastResponseId } = await this.processWithToolLoop( messages, tools, response, @@ -105,6 +116,7 @@ export class RequestInfoFromTableWithAIUseCaseV7 tableName, userEmail, foundConnection, + config, ); if (accumulatedResponse) { @@ -112,6 +124,7 @@ export class RequestInfoFromTableWithAIUseCaseV7 foundUserAiChat.id, accumulatedResponse, MessageRole.ai, + lastResponseId, ); } @@ -133,15 +146,22 @@ export class RequestInfoFromTableWithAIUseCaseV7 inputTableName: string, userEmail: string, foundConnection: ConnectionEntity, + config: AIProviderConfig = {}, ): Promise<{ lastResponseId: string | null; accumulatedResponse: string }> { let currentMessages = [...messages]; let lastResponseId: string | null = null; let depth = 0; let totalAccumulatedResponse = ''; + let currentConfig = { ...config }; while (depth < this.maxDepth) { try { - const stream = await this.aiCoreService.streamChatWithToolsAndProvider(this.aiProvider, currentMessages, tools); + const stream = await this.aiCoreService.streamChatWithToolsAndProvider( + this.aiProvider, + currentMessages, + tools, + currentConfig, + ); let pendingToolCalls: AIToolCall[] = []; let accumulatedContent = ''; @@ -174,6 +194,10 @@ export class RequestInfoFromTableWithAIUseCaseV7 foundConnection, ); + if (this.aiProvider === AIProviderType.OPENAI && lastResponseId) { + currentConfig = { ...currentConfig, previousResponseId: lastResponseId }; + } + const continuationBuilder = MessageBuilder.fromMessages(currentMessages); continuationBuilder.ai(accumulatedContent, pendingToolCalls); for (const result of toolResults) { @@ -347,6 +371,40 @@ export class RequestInfoFromTableWithAIUseCaseV7 return { foundConnection, dataAccessObject, databaseType, isMongoDb, userEmail }; } + private async buildMessagesWithHistory( + systemPrompt: string, + userMessage: string, + chatId: string, + isNewChat: boolean, + ): Promise<{ messages: BaseMessage[]; previousResponseId: string | null }> { + if (isNewChat) { + const messages = new MessageBuilder().system(systemPrompt).human(userMessage).build(); + return { messages, previousResponseId: null }; + } + + if (this.aiProvider === AIProviderType.OPENAI) { + const lastAiMessage = await this._dbContext.aiChatMessageRepository.findLastAiMessageForChat(chatId); + const previousResponseId = lastAiMessage?.response_id || null; + const messages = new MessageBuilder().system(systemPrompt).human(userMessage).build(); + return { messages, previousResponseId }; + } + + const previousMessages = await this._dbContext.aiChatMessageRepository.findMessagesForChat(chatId); + const builder = new MessageBuilder().system(systemPrompt); + + for (const msg of previousMessages) { + if (msg.role === MessageRole.user) { + builder.human(msg.message); + } else if (msg.role === MessageRole.ai) { + builder.ai(msg.message); + } + } + + builder.human(userMessage); + + return { messages: builder.build(), previousResponseId: null }; + } + private async generateAndUpdateChatName(chatId: string, userMessage: string): Promise { try { const CHAT_NAME_GENERATION_PROMPT = `Generate a very short, concise title (max 5-6 words) for a chat conversation based on the user's first question. diff --git a/backend/src/migrations/1769790101930-AddResponseIdToAiChatMessage.ts b/backend/src/migrations/1769790101930-AddResponseIdToAiChatMessage.ts new file mode 100644 index 000000000..90490696e --- /dev/null +++ b/backend/src/migrations/1769790101930-AddResponseIdToAiChatMessage.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddResponseIdToAiChatMessage1769790101930 implements MigrationInterface { + name = 'AddResponseIdToAiChatMessage1769790101930'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "ai_chat_message" ADD "response_id" character varying(255)`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "ai_chat_message" DROP COLUMN "response_id"`); + } +}