diff --git a/backend/package.json b/backend/package.json index 28ffa9324..1beb74503 100644 --- a/backend/package.json +++ b/backend/package.json @@ -34,7 +34,6 @@ "@faker-js/faker": "^10.3.0", "@langchain/aws": "^1.3.3", "@langchain/core": "^1.1.33", - "@langchain/openai": "^1.3.0", "@nestjs/common": "11.1.17", "@nestjs/config": "4.0.3", "@nestjs/core": "11.1.17", @@ -82,7 +81,6 @@ "node-sql-parser": "^5.3.0", "nodemailer": "^8.0.4", "nunjucks": "^3.2.4", - "openai": "^6.32.0", "otplib": "^12.0.1", "p-queue": "9.1.0", "pg": "^8.20.0", diff --git a/backend/src/ai-core/ai-core.module.ts b/backend/src/ai-core/ai-core.module.ts index 58cc64e1f..154d738af 100644 --- a/backend/src/ai-core/ai-core.module.ts +++ b/backend/src/ai-core/ai-core.module.ts @@ -1,6 +1,5 @@ import { Global, Module } from '@nestjs/common'; import { LangchainBedrockProvider } from './providers/langchain-bedrock.provider.js'; -import { LangchainOpenAIProvider } from './providers/langchain-openai.provider.js'; import { AICoreService } from './services/ai-core.service.js'; export const AI_CORE_SERVICE = 'AI_CORE_SERVICE'; @@ -8,7 +7,6 @@ export const AI_CORE_SERVICE = 'AI_CORE_SERVICE'; @Global() @Module({ providers: [ - LangchainOpenAIProvider, LangchainBedrockProvider, AICoreService, { @@ -16,6 +14,6 @@ export const AI_CORE_SERVICE = 'AI_CORE_SERVICE'; useExisting: AICoreService, }, ], - exports: [AICoreService, AI_CORE_SERVICE, LangchainOpenAIProvider, LangchainBedrockProvider], + exports: [AICoreService, AI_CORE_SERVICE, LangchainBedrockProvider], }) export class AICoreModule {} diff --git a/backend/src/ai-core/interfaces/ai-provider.interface.ts b/backend/src/ai-core/interfaces/ai-provider.interface.ts index 669ba9ada..50b6ae853 100644 --- a/backend/src/ai-core/interfaces/ai-provider.interface.ts +++ b/backend/src/ai-core/interfaces/ai-provider.interface.ts @@ -6,7 +6,6 @@ export interface AIProviderConfig { temperature?: number; maxTokens?: number; streaming?: boolean; - previousResponseId?: string; // For conversation continuation (OpenAI Responses API) } export interface AIToolDefinition { diff --git a/backend/src/ai-core/interfaces/ai-service.interface.ts b/backend/src/ai-core/interfaces/ai-service.interface.ts index fd8dadd3c..284843216 100644 --- a/backend/src/ai-core/interfaces/ai-service.interface.ts +++ b/backend/src/ai-core/interfaces/ai-service.interface.ts @@ -9,7 +9,6 @@ import { } from './ai-provider.interface.js'; export enum AIProviderType { - OPENAI = 'openai', BEDROCK = 'bedrock', } diff --git a/backend/src/ai-core/providers/index.ts b/backend/src/ai-core/providers/index.ts index 2355df78c..079b54ddf 100644 --- a/backend/src/ai-core/providers/index.ts +++ b/backend/src/ai-core/providers/index.ts @@ -1,2 +1 @@ export * from './langchain-bedrock.provider.js'; -export * from './langchain-openai.provider.js'; diff --git a/backend/src/ai-core/providers/langchain-openai.provider.ts b/backend/src/ai-core/providers/langchain-openai.provider.ts deleted file mode 100644 index ddd7bf74f..000000000 --- a/backend/src/ai-core/providers/langchain-openai.provider.ts +++ /dev/null @@ -1,375 +0,0 @@ -import { AIMessage, AIMessageChunk, BaseMessage, HumanMessage, ToolMessage } from '@langchain/core/messages'; -import { tool } from '@langchain/core/tools'; -import { IterableReadableStream } from '@langchain/core/utils/stream'; -import { ChatOpenAI } from '@langchain/openai'; -import { Injectable } from '@nestjs/common'; -import OpenAI from 'openai'; -import { z } from 'zod'; -import { getOptionalEnvVariable, getRequiredEnvVariable } from '../../helpers/app/get-requeired-env-variable.js'; -import { - AICompletionResult, - AIProviderConfig, - AIStreamChunk, - AIToolCall, - AIToolDefinition, - AIToolResult, - IAIProvider, -} from '../interfaces/ai-provider.interface.js'; - -@Injectable() -export class LangchainOpenAIProvider implements IAIProvider { - private readonly defaultModelId = 'gpt-5'; - private readonly responsesApiModel = 'gpt-5'; - private openaiClient: OpenAI; - - constructor() { - const apiKey = getOptionalEnvVariable('OPENAI_API_KEY'); - this.openaiClient = new OpenAI({ apiKey }); - } - - private createModel(config?: AIProviderConfig): ChatOpenAI { - const apiKey = getRequiredEnvVariable('OPENAI_API_KEY'); - return new ChatOpenAI({ - openAIApiKey: apiKey, - modelName: config?.modelId || this.defaultModelId, - temperature: config?.temperature ?? 0.7, - maxTokens: config?.maxTokens, - streaming: config?.streaming ?? false, - }); - } - - private convertToolsToLangchain(tools: AIToolDefinition[]) { - return tools.map((toolDef) => { - const schemaProperties: Record = {}; - const properties = (toolDef.parameters as { properties?: Record }) - .properties; - const required = (toolDef.parameters as { required?: string[] }).required || []; - - if (properties) { - for (const [key, value] of Object.entries(properties)) { - let zodType: z.ZodTypeAny; - switch (value.type) { - case 'string': - zodType = z.string().describe(value.description || ''); - break; - case 'number': - zodType = z.number().describe(value.description || ''); - break; - case 'boolean': - zodType = z.boolean().describe(value.description || ''); - break; - case 'array': - zodType = z.array(z.any()).describe(value.description || ''); - break; - case 'object': - zodType = z.record(z.string(), z.any()).describe(value.description || ''); - break; - default: - zodType = z.any().describe(value.description || ''); - } - schemaProperties[key] = required.includes(key) ? zodType : zodType.optional(); - } - } - - return tool(async (_input) => '', { - name: toolDef.name, - description: toolDef.description, - schema: z.object(schemaProperties), - }); - }); - } - - private extractToolCalls(message: AIMessage): AIToolCall[] { - if (!message.tool_calls || message.tool_calls.length === 0) { - return []; - } - - return message.tool_calls.map((toolCall) => ({ - id: toolCall.id || '', - name: toolCall.name, - arguments: toolCall.args as Record, - })); - } - - public async generateCompletion(prompt: string, config?: AIProviderConfig): Promise { - const model = this.createModel(config); - const response = await model.invoke([new HumanMessage(prompt)]); - return typeof response.content === 'string' ? response.content : JSON.stringify(response.content); - } - - public async generateChatCompletion(messages: BaseMessage[], config?: AIProviderConfig): Promise { - const model = this.createModel(config); - const response = await model.invoke(messages); - - return { - content: typeof response.content === 'string' ? response.content : JSON.stringify(response.content), - toolCalls: this.extractToolCalls(response), - responseId: response.id, - }; - } - - public async generateStreamingCompletion( - messages: BaseMessage[], - config?: AIProviderConfig, - ): Promise> { - const model = this.createModel({ ...config, streaming: true }); - const stream = await model.stream(messages); - - return this.transformToAIStreamChunks(stream); - } - - public async generateWithTools( - messages: BaseMessage[], - tools: AIToolDefinition[], - config?: AIProviderConfig, - ): Promise { - const model = this.createModel(config); - const langchainTools = this.convertToolsToLangchain(tools); - const modelWithTools = model.bindTools(langchainTools); - const response = await modelWithTools.invoke(messages); - - return { - content: typeof response.content === 'string' ? response.content : JSON.stringify(response.content), - toolCalls: this.extractToolCalls(response), - responseId: response.id, - }; - } - - public async generateStreamingWithTools( - messages: BaseMessage[], - tools: AIToolDefinition[], - config?: AIProviderConfig, - ): Promise> { - return this.streamWithResponsesApi(messages, tools, config || {}); - } - - private async streamWithResponsesApi( - messages: BaseMessage[], - tools: AIToolDefinition[], - config: AIProviderConfig, - ): Promise> { - const systemMessage = messages.find((m) => m._getType() === 'system'); - const systemPrompt = systemMessage ? (systemMessage.content as string) : ''; - - const openaiTools = this.convertToolsToOpenAIFormat(tools); - - const input = this.buildResponsesApiInput(messages); - - const stream = await this.openaiClient.responses.create({ - model: config.modelId || this.responsesApiModel, - input: input, - instructions: systemPrompt, - tools: openaiTools, - tool_choice: 'auto', - stream: true, - previous_response_id: config.previousResponseId, - }); - - return this.transformResponsesApiStream(stream); - } - - private buildResponsesApiInput(messages: BaseMessage[]): string | OpenAI.Responses.ResponseInputItem[] { - const toolMessages = messages.filter((m) => m._getType() === 'tool'); - - if (toolMessages.length > 0) { - const inputItems: OpenAI.Responses.ResponseInputItem[] = []; - - for (const msg of toolMessages) { - const toolMsg = msg as ToolMessage; - inputItems.push({ - type: 'function_call_output', - call_id: toolMsg.tool_call_id, - output: typeof toolMsg.content === 'string' ? toolMsg.content : JSON.stringify(toolMsg.content), - }); - } - - return inputItems; - } - - const humanMessage = messages.find((m) => m._getType() === 'human'); - const userMessage = humanMessage ? (humanMessage.content as string) : ''; - return userMessage; - } - - private convertToolsToOpenAIFormat(tools: AIToolDefinition[]): OpenAI.Responses.FunctionTool[] { - return tools.map((toolDef) => ({ - type: 'function' as const, - name: toolDef.name, - description: toolDef.description, - parameters: toolDef.parameters as OpenAI.FunctionParameters, - strict: false, - })); - } - - private async transformResponsesApiStream( - stream: AsyncIterable, - ): Promise> { - async function* generateChunks(): AsyncGenerator { - let currentResponseId: string | null = null; - - for await (const event of stream) { - if (event.type === 'response.created') { - currentResponseId = event.response.id; - } - - if (event.type === 'response.output_text.delta') { - yield { - type: 'text', - content: event.delta, - responseId: currentResponseId || undefined, - }; - } - if (event.type === 'response.output_item.done' && event.item?.type === 'function_call') { - const item = event.item; - let parsedArgs: Record = {}; - try { - parsedArgs = JSON.parse(item.arguments || '{}'); - } catch { - // Failed to parse arguments - } - - const toolCall: AIToolCall = { - id: item.call_id || item.id || '', - name: item.name || '', - arguments: parsedArgs, - }; - - yield { - type: 'tool_call', - toolCall, - responseId: currentResponseId || undefined, - }; - } - } - - yield { type: 'done', responseId: currentResponseId || undefined }; - } - - const generator = generateChunks(); - return IterableReadableStream.fromAsyncGenerator(generator); - } - - public async continueWithToolResults( - messages: BaseMessage[], - toolResults: AIToolResult[], - tools: AIToolDefinition[], - config?: AIProviderConfig, - ): Promise { - const toolMessages = toolResults.map( - (result) => - new ToolMessage({ - tool_call_id: result.toolCallId, - content: result.result, - }), - ); - - const allMessages = [...messages, ...toolMessages]; - return this.generateWithTools(allMessages, tools, config); - } - - public async continueStreamingWithToolResults( - messages: BaseMessage[], - toolResults: AIToolResult[], - tools: AIToolDefinition[], - config?: AIProviderConfig, - ): Promise> { - const toolMessages = toolResults.map( - (result) => - new ToolMessage({ - tool_call_id: result.toolCallId, - content: result.result, - }), - ); - - const allMessages = [...messages, ...toolMessages]; - return this.generateStreamingWithTools(allMessages, tools, config); - } - - public getProviderName(): string { - return 'OpenAI'; - } - - public getDefaultModelId(): string { - return this.defaultModelId; - } - - private async transformToAIStreamChunks( - stream: AsyncIterable, - ): Promise> { - async function* generateChunks(): AsyncGenerator { - const currentToolCalls: Map = new Map(); - - try { - for await (const chunk of stream) { - if (chunk.tool_call_chunks && chunk.tool_call_chunks.length > 0) { - for (const toolCallChunk of chunk.tool_call_chunks) { - const index = toolCallChunk.index ?? 0; - - if (!currentToolCalls.has(index)) { - currentToolCalls.set(index, { - id: toolCallChunk.id || '', - name: toolCallChunk.name || '', - argsString: '', - }); - } - - const currentCall = currentToolCalls.get(index)!; - - if (toolCallChunk.id) { - currentCall.id = toolCallChunk.id; - } - if (toolCallChunk.name) { - currentCall.name = toolCallChunk.name; - } - if (toolCallChunk.args) { - currentCall.argsString += toolCallChunk.args; - } - } - } - - if (chunk.content) { - const content = typeof chunk.content === 'string' ? chunk.content : JSON.stringify(chunk.content); - if (content) { - yield { - type: 'text', - content, - responseId: chunk.id, - }; - } - } - } - } catch (streamError) { - throw streamError; - } - - for (const [index, toolCallData] of currentToolCalls.entries()) { - if (toolCallData.name) { - let parsedArgs: Record = {}; - if (toolCallData.argsString) { - try { - parsedArgs = JSON.parse(toolCallData.argsString); - } catch { - // Failed to parse args - } - } - - const toolCall: AIToolCall = { - id: toolCallData.id, - name: toolCallData.name, - arguments: parsedArgs, - }; - - yield { - type: 'tool_call', - toolCall, - }; - } - } - - yield { type: 'done' }; - } - - const generator = generateChunks(); - return IterableReadableStream.fromAsyncGenerator(generator); - } -} diff --git a/backend/src/ai-core/services/ai-core.service.ts b/backend/src/ai-core/services/ai-core.service.ts index 7f901db37..6d4ac3a4f 100644 --- a/backend/src/ai-core/services/ai-core.service.ts +++ b/backend/src/ai-core/services/ai-core.service.ts @@ -11,22 +11,16 @@ import { } from '../interfaces/ai-provider.interface.js'; import { AIProviderType, IAIService } from '../interfaces/ai-service.interface.js'; import { LangchainBedrockProvider } from '../providers/langchain-bedrock.provider.js'; -import { LangchainOpenAIProvider } from '../providers/langchain-openai.provider.js'; @Injectable() export class AICoreService implements IAIService { - private defaultProvider: AIProviderType = AIProviderType.OPENAI; + private defaultProvider: AIProviderType = AIProviderType.BEDROCK; - constructor( - private readonly openAIProvider: LangchainOpenAIProvider, - private readonly bedrockProvider: LangchainBedrockProvider, - ) {} + constructor(private readonly bedrockProvider: LangchainBedrockProvider) {} private getProvider(type?: AIProviderType): IAIProvider { const providerType = type || this.defaultProvider; switch (providerType) { - case AIProviderType.OPENAI: - return this.openAIProvider; case AIProviderType.BEDROCK: return this.bedrockProvider; default: @@ -135,11 +129,6 @@ export class AICoreService implements IAIService { public getAvailableProviders(): Array<{ type: AIProviderType; name: string; defaultModel: string }> { return [ - { - type: AIProviderType.OPENAI, - name: this.openAIProvider.getProviderName(), - defaultModel: this.openAIProvider.getDefaultModelId(), - }, { type: AIProviderType.BEDROCK, name: this.bedrockProvider.getProviderName(), diff --git a/backend/src/common/application/global-database-context.interface.ts b/backend/src/common/application/global-database-context.interface.ts index 74aa75ed9..9ee2ef771 100644 --- a/backend/src/common/application/global-database-context.interface.ts +++ b/backend/src/common/application/global-database-context.interface.ts @@ -4,8 +4,6 @@ import { AiChatMessageEntity } from '../../entities/ai/ai-conversation-history/a import { IAiChatMessageRepository } from '../../entities/ai/ai-conversation-history/ai-chat-messages/repository/ai-chat-message-repository.interface.js'; import { IUserAiChatRepository } from '../../entities/ai/ai-conversation-history/user-ai-chat/repository/user-ai-chat-repository.interface.js'; import { UserAiChatEntity } from '../../entities/ai/ai-conversation-history/user-ai-chat/user-ai-chat.entity.js'; -import { AiResponsesToUserEntity } from '../../entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user.entity.js'; -import { IAiResponsesToUserRepository } from '../../entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user-repository.interface.js'; import { UserApiKeyEntity } from '../../entities/api-key/api-key.entity.js'; import { IUserApiKeyRepository } from '../../entities/api-key/repository/user-api-key-repository.interface.js'; import { CompanyFaviconEntity } from '../../entities/company-favicon/company-favicon.entity.js'; @@ -97,7 +95,6 @@ export interface IGlobalDatabaseContext extends IDatabaseContext { companyFaviconRepository: Repository; companyTabTitleRepository: Repository; tableFiltersRepository: Repository & ITableFiltersCustomRepository; - aiResponsesToUserRepository: Repository & IAiResponsesToUserRepository; tableCategoriesRepository: Repository & ITableCategoriesCustomRepository; userSecretRepository: Repository & IUserSecretRepository; secretAccessLogRepository: Repository & ISecretAccessLogRepository; diff --git a/backend/src/common/application/global-database-context.ts b/backend/src/common/application/global-database-context.ts index 65ba690bb..43d7e5e31 100644 --- a/backend/src/common/application/global-database-context.ts +++ b/backend/src/common/application/global-database-context.ts @@ -9,9 +9,6 @@ import { IAiChatMessageRepository } from '../../entities/ai/ai-conversation-hist import { userAiChatRepositoryExtension } from '../../entities/ai/ai-conversation-history/user-ai-chat/repository/user-ai-chat-repository.extension.js'; import { IUserAiChatRepository } from '../../entities/ai/ai-conversation-history/user-ai-chat/repository/user-ai-chat-repository.interface.js'; import { UserAiChatEntity } from '../../entities/ai/ai-conversation-history/user-ai-chat/user-ai-chat.entity.js'; -import { aiResponsesToUserRepositoryExtension } from '../../entities/ai/ai-data-entities/ai-reponses-to-user/ai-reponses-to-user-repository.extension.js'; -import { AiResponsesToUserEntity } from '../../entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user.entity.js'; -import { IAiResponsesToUserRepository } from '../../entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user-repository.interface.js'; import { UserApiKeyEntity } from '../../entities/api-key/api-key.entity.js'; import { userApiRepositoryExtension } from '../../entities/api-key/repository/user-api-key-repository.extension.js'; import { IUserApiKeyRepository } from '../../entities/api-key/repository/user-api-key-repository.interface.js'; @@ -149,7 +146,6 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext { private _companyFaviconRepository: Repository; private _companyTabTitleRepository: Repository; private _tableFiltersRepository: Repository & ITableFiltersCustomRepository; - private _aiResponsesToUserRepository: Repository & IAiResponsesToUserRepository; private _tableCategoriesRepository: Repository & ITableCategoriesCustomRepository; private _userSecretRepository: Repository & IUserSecretRepository; private _secretAccessLogRepository: Repository & ISecretAccessLogRepository; @@ -237,9 +233,6 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext { this._tableFiltersRepository = this.appDataSource .getRepository(TableFiltersEntity) .extend(tableFiltersCustomRepositoryExtension); - this._aiResponsesToUserRepository = this.appDataSource - .getRepository(AiResponsesToUserEntity) - .extend(aiResponsesToUserRepositoryExtension); this._tableCategoriesRepository = this.appDataSource .getRepository(TableCategoriesEntity) .extend(tableCategoriesCustomRepositoryExtension); @@ -390,10 +383,6 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext { return this._tableFiltersRepository; } - public get aiResponsesToUserRepository(): Repository & IAiResponsesToUserRepository { - return this._aiResponsesToUserRepository; - } - public get tableCategoriesRepository(): Repository & ITableCategoriesCustomRepository { return this._tableCategoriesRepository; } diff --git a/backend/src/common/data-injection.tokens.ts b/backend/src/common/data-injection.tokens.ts index 26d637a1a..79804b05a 100644 --- a/backend/src/common/data-injection.tokens.ts +++ b/backend/src/common/data-injection.tokens.ts @@ -163,8 +163,6 @@ export enum UseCaseType { GET_API_KEY = 'GET_API_KEY', DELETE_API_KEY = 'DELETE_API_KEY', - REQUEST_INFO_FROM_TABLE_WITH_AI_V2 = 'REQUEST_INFO_FROM_TABLE_WITH_AI_V2', - REQUEST_INFO_FROM_TABLE_WITH_AI_V3 = 'REQUEST_INFO_FROM_TABLE_WITH_AI_V3', REQUEST_INFO_FROM_TABLE_WITH_AI_V4 = 'REQUEST_INFO_FROM_TABLE_WITH_AI_V4', REQUEST_AI_SETTINGS_AND_WIDGETS_CREATION = 'REQUEST_AI_SETTINGS_AND_WIDGETS_CREATION', FIND_USER_AI_CHATS = 'FIND_USER_AI_CHATS', 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 44b6fb8a2..df7a10d45 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,9 +22,6 @@ 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 0e52bbd3c..e47b8e56d 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,14 +10,6 @@ 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() @@ -26,17 +18,11 @@ export const aiChatMessageRepositoryExtension: IAiChatMessageRepository = { .execute(); }, - async saveMessage( - chatId: string, - message: string, - role: MessageRole, - responseId?: string, - ): Promise { + async saveMessage(chatId: string, message: string, role: MessageRole): 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 c8df9f538..27911caa3 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,7 +3,6 @@ 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, responseId?: string): Promise; + saveMessage(chatId: string, message: string, role: MessageRole): Promise; } diff --git a/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-reponses-to-user-repository.extension.ts b/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-reponses-to-user-repository.extension.ts deleted file mode 100644 index 2d1bc8468..000000000 --- a/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-reponses-to-user-repository.extension.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { AiResponsesToUserEntity } from './ai-responses-to-user.entity.js'; -import { IAiResponsesToUserRepository } from './ai-responses-to-user-repository.interface.js'; - -export const aiResponsesToUserRepositoryExtension: IAiResponsesToUserRepository = { - async findResponseByIdAndUserId(responseId: string, userId: string): Promise { - return await this.createQueryBuilder('ai_responses_to_user') - .leftJoin('ai_responses_to_user.user', 'user') - .where('user.id = :userId', { userId }) - .andWhere('ai_responses_to_user.id = :responseId', { responseId }) - .getOne(); - }, -}; diff --git a/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user-repository.interface.ts b/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user-repository.interface.ts deleted file mode 100644 index 5e4a5f1e8..000000000 --- a/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user-repository.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { AiResponsesToUserEntity } from './ai-responses-to-user.entity.js'; - -export interface IAiResponsesToUserRepository { - findResponseByIdAndUserId(responseId: string, userId: string): Promise; -} diff --git a/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user.entity.ts b/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user.entity.ts deleted file mode 100644 index 24d4c7e8a..000000000 --- a/backend/src/entities/ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user.entity.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { BeforeUpdate, Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, Relation } from 'typeorm'; -import { UserEntity } from '../../../user/user.entity.js'; - -@Entity('ai_responses_to_user') -export class AiResponsesToUserEntity { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ type: 'varchar', length: 128, nullable: true }) - ai_response_id: string; - - @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) - createdAt: Date; - - @Column({ type: 'timestamp', default: null, nullable: true }) - updatedAt: Date; - - @Column() - user_id: string; - - @ManyToOne( - (_) => UserEntity, - (user) => user.ai_responses, - { onDelete: 'CASCADE' }, - ) - @JoinColumn({ name: 'user_id' }) - user: Relation; - - @BeforeUpdate() - updateTimestamp() { - this.updatedAt = new Date(); - } -} diff --git a/backend/src/entities/ai/ai.module.ts b/backend/src/entities/ai/ai.module.ts index 9ca85b5df..f46b6fde1 100644 --- a/backend/src/entities/ai/ai.module.ts +++ b/backend/src/entities/ai/ai.module.ts @@ -13,8 +13,6 @@ import { FindUserAiChatsUseCase } from './ai-conversation-history/use-cases/find import { UserAiChatEntity } from './ai-conversation-history/user-ai-chat/user-ai-chat.entity.js'; import { UserAiChatController } from './ai-conversation-history/user-ai-chat.controller.js'; import { RequestAISettingsAndWidgetsCreationUseCase } from './use-cases/request-ai-settings-and-widgets-creation.use.case.js'; -import { RequestInfoFromTableWithAIUseCaseV5 } from './use-cases/request-info-from-table-with-ai-v5.use.case.js'; -import { RequestInfoFromTableWithAIUseCaseV6 } from './use-cases/request-info-from-table-with-ai-v6.use.case.js'; import { RequestInfoFromTableWithAIUseCaseV7 } from './use-cases/request-info-from-table-with-ai-v7.use.case.js'; import { UserAIRequestsControllerV2 } from './user-ai-requests-v2.controller.js'; @@ -26,14 +24,6 @@ import { UserAIRequestsControllerV2 } from './user-ai-requests-v2.controller.js' provide: BaseType.GLOBAL_DB_CONTEXT, useClass: GlobalDatabaseContext, }, - { - provide: UseCaseType.REQUEST_INFO_FROM_TABLE_WITH_AI_V2, - useClass: RequestInfoFromTableWithAIUseCaseV5, - }, - { - provide: UseCaseType.REQUEST_INFO_FROM_TABLE_WITH_AI_V3, - useClass: RequestInfoFromTableWithAIUseCaseV6, - }, { provide: UseCaseType.REQUEST_INFO_FROM_TABLE_WITH_AI_V4, useClass: RequestInfoFromTableWithAIUseCaseV7, @@ -64,8 +54,6 @@ export class AIModule implements NestModule { consumer .apply(AuthMiddleware) .forRoutes( - { path: '/ai/v2/request/:connectionId', method: RequestMethod.POST }, - { path: '/ai/v3/request/:connectionId', method: RequestMethod.POST }, { path: '/ai/v4/request/:connectionId', method: RequestMethod.POST }, { path: '/ai/v2/setup/:connectionId', method: RequestMethod.GET }, { path: '/ai/chats', method: RequestMethod.GET }, diff --git a/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v5.use.case.ts b/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v5.use.case.ts deleted file mode 100644 index 2e724f901..000000000 --- a/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v5.use.case.ts +++ /dev/null @@ -1,344 +0,0 @@ -import { BaseMessage } from '@langchain/core/messages'; -import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; -import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; -import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.js'; -import { IDataAccessObject } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object.interface.js'; -import { IDataAccessObjectAgent } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object-agent.interface.js'; -import Sentry from '@sentry/minimal'; -import { Response } from 'express'; -import { - AICoreService, - AIToolCall, - AIToolDefinition, - createDatabaseQuerySystemPrompt, - createDatabaseTools, - isValidMongoDbCommand, - isValidSQLQuery, - MessageBuilder, - wrapQueryWithLimit, -} from '../../../ai-core/index.js'; -import AbstractUseCase from '../../../common/abstract-use.case.js'; -import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; -import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; -import { slackPostMessage } from '../../../helpers/index.js'; -import { isConnectionTypeAgent } from '../../../helpers/is-connection-entity-agent.js'; -import { ConnectionEntity } from '../../connection/connection.entity.js'; -import { AiResponsesToUserEntity } from '../ai-data-entities/ai-reponses-to-user/ai-responses-to-user.entity.js'; -import { IRequestInfoFromTableV2 } from '../ai-use-cases.interface.js'; -import { RequestInfoFromTableDSV2 } from '../application/data-structures/request-info-from-table.ds.js'; - -@Injectable({ scope: Scope.REQUEST }) -export class RequestInfoFromTableWithAIUseCaseV5 - extends AbstractUseCase - implements IRequestInfoFromTableV2 -{ - private readonly maxDepth: number = 10; - - constructor( - @Inject(BaseType.GLOBAL_DB_CONTEXT) - protected _dbContext: IGlobalDatabaseContext, - private readonly aiCoreService: AICoreService, - ) { - super(); - } - - public async implementation(inputData: RequestInfoFromTableDSV2): Promise { - const { connectionId, tableName, user_message, master_password, user_id, response, ai_thread_id } = inputData; - - this.setupResponseHeaders(response); - - const { foundConnection, dataAccessObject, isMongoDb, userEmail } = await this.setupConnection( - connectionId, - master_password, - user_id, - ); - - const tools = createDatabaseTools(isMongoDb); - - const systemPrompt = createDatabaseQuerySystemPrompt( - tableName, - foundConnection.type as ConnectionTypesEnum, - foundConnection.schema, - ); - - let threadIdForHeader: string | null = null; - let foundUserAiResponse: AiResponsesToUserEntity | null = null; - let previousResponseId: string | null = null; - - if (ai_thread_id) { - foundUserAiResponse = await this._dbContext.aiResponsesToUserRepository.findResponseByIdAndUserId( - ai_thread_id, - user_id, - ); - if (foundUserAiResponse) { - threadIdForHeader = foundUserAiResponse.id; - previousResponseId = foundUserAiResponse.ai_response_id; - } - } - - if (!foundUserAiResponse) { - const newAiResponse = new AiResponsesToUserEntity(); - newAiResponse.ai_response_id = null; - newAiResponse.user_id = user_id; - foundUserAiResponse = await this._dbContext.aiResponsesToUserRepository.save(newAiResponse); - threadIdForHeader = foundUserAiResponse.id; - } - - if (threadIdForHeader) { - response.setHeader('X-AI-Thread-ID', threadIdForHeader); - } - - const messages = new MessageBuilder().system(systemPrompt).human(user_message).build(); - - try { - const lastResponseId = await this.processWithToolLoop( - messages, - tools, - response, - dataAccessObject, - tableName, - userEmail, - foundConnection, - previousResponseId, - ); - - if (foundUserAiResponse && lastResponseId) { - foundUserAiResponse.ai_response_id = lastResponseId; - await this._dbContext.aiResponsesToUserRepository.save(foundUserAiResponse); - } - response.end(); - } catch (error) { - await slackPostMessage(error?.message); - Sentry.captureException(error); - if (!response.headersSent) { - response.status(500).send({ error: 'An error occurred while processing your request.' }); - } - } - } - - private async processWithToolLoop( - messages: BaseMessage[], - tools: AIToolDefinition[], - response: Response, - dataAccessObject: IDataAccessObject | IDataAccessObjectAgent, - inputTableName: string, - userEmail: string, - foundConnection: ConnectionEntity, - previousResponseId: string | null = null, - ): Promise { - let currentMessages = [...messages]; - let lastResponseId: string | null = previousResponseId; - let depth = 0; - - while (depth < this.maxDepth) { - try { - const config = lastResponseId ? { previousResponseId: lastResponseId } : undefined; - - const stream = await this.aiCoreService.streamChatWithTools(currentMessages, tools, config); - - let accumulatedContent = ''; - let pendingToolCalls: AIToolCall[] = []; - - for await (const chunk of stream) { - if (chunk.type === 'text' && chunk.content) { - response.write(chunk.content); - accumulatedContent += chunk.content; - } - - if (chunk.type === 'tool_call' && chunk.toolCall) { - pendingToolCalls.push(chunk.toolCall); - } - - if (chunk.responseId) { - lastResponseId = chunk.responseId; - } - - if (chunk.type === 'done') { - // Stream complete - } - } - - if (pendingToolCalls.length === 0) { - break; - } - - const toolResults = await this.executeToolCalls( - pendingToolCalls, - dataAccessObject, - inputTableName, - userEmail, - foundConnection, - ); - - const toolMessageBuilder = new MessageBuilder(); - for (const result of toolResults) { - toolMessageBuilder.toolResult(result.toolCallId, result.result); - } - currentMessages = toolMessageBuilder.build(); - - depth++; - } catch (loopError) { - throw loopError; - } - } - - if (depth >= this.maxDepth) { - response.write( - '\n\nYour question is too complex to process at this time. Please try simplifying it or breaking it down into smaller parts.', - ); - } - - return lastResponseId; - } - - private async executeToolCalls( - toolCalls: AIToolCall[], - dataAccessObject: IDataAccessObject | IDataAccessObjectAgent, - inputTableName: string, - userEmail: string, - foundConnection: ConnectionEntity, - ): Promise> { - const results: Array<{ toolCallId: string; result: string }> = []; - - for (const toolCall of toolCalls) { - let result: string; - - try { - switch (toolCall.name) { - case 'getTableStructure': { - const tableName = (toolCall.arguments.tableName as string) || inputTableName; - const structureInfo = await this.getTableStructureInfo( - dataAccessObject, - tableName, - userEmail, - foundConnection, - ); - result = JSON.stringify(structureInfo); - break; - } - - case 'executeRawSql': { - const query = toolCall.arguments.query as string; - if (!query) { - throw new Error('Missing required function argument "query"'); - } - if (!isValidSQLQuery(query)) { - throw new Error( - 'Invalid SQL query. Please ensure it is a read-only SELECT statement without any forbidden keywords.', - ); - } - const wrappedQuery = wrapQueryWithLimit(query, foundConnection.type as ConnectionTypesEnum); - const queryResult = await dataAccessObject.executeRawQuery(wrappedQuery, inputTableName, userEmail); - result = JSON.stringify(queryResult); - break; - } - - case 'executeAggregationPipeline': { - const pipeline = toolCall.arguments.pipeline as string; - if (!pipeline) { - throw new Error('Missing required function argument "pipeline"'); - } - if (!isValidMongoDbCommand(pipeline)) { - throw new Error( - 'Invalid MongoDB command. Please ensure it is a read-only aggregation pipeline without any forbidden keywords.', - ); - } - const pipelineResult = await dataAccessObject.executeRawQuery(pipeline, inputTableName, userEmail); - result = JSON.stringify(pipelineResult); - break; - } - - default: - result = JSON.stringify({ error: `Unknown tool: ${toolCall.name}` }); - } - } catch (error) { - result = JSON.stringify({ error: error.message }); - } - - results.push({ toolCallId: toolCall.id, result }); - } - - return results; - } - - private async getTableStructureInfo( - dao: IDataAccessObject | IDataAccessObjectAgent, - tableName: string, - userEmail: string, - foundConnection: ConnectionEntity, - ) { - const [tableStructure, tableForeignKeys, referencedTableNamesAndColumns] = await Promise.all([ - dao.getTableStructure(tableName, userEmail), - dao.getTableForeignKeys(tableName, userEmail), - dao.getReferencedTableNamesAndColumns(tableName, userEmail), - ]); - - const referencedTablesStructures = []; - const structurePromises = referencedTableNamesAndColumns.flatMap((referencedTable) => - referencedTable.referenced_by.map((table) => - dao.getTableStructure(table.table_name, userEmail).then((structure) => ({ - tableName: table.table_name, - structure, - })), - ), - ); - referencedTablesStructures.push(...(await Promise.all(structurePromises))); - - const foreignTablesStructures = []; - const foreignTablesStructurePromises = tableForeignKeys.flatMap((foreignKey) => - dao.getTableStructure(foreignKey.referenced_table_name, userEmail).then((structure) => ({ - tableName: foreignKey.referenced_table_name, - structure, - })), - ); - foreignTablesStructures.push(...(await Promise.all(foreignTablesStructurePromises))); - - return { - tableStructure, - tableName, - schema: foundConnection.schema || null, - tableForeignKeys, - referencedTableNamesAndColumns, - referencedTablesStructures, - foreignTablesStructures, - }; - } - - private setupResponseHeaders(response: Response): void { - response.setHeader('Content-Type', 'text/event-stream'); - response.setHeader('Cache-Control', 'no-cache'); - response.setHeader('Connection', 'keep-alive'); - response.setHeader('Access-Control-Expose-Headers', 'X-AI-Thread-ID'); - } - - private async setupConnection(connectionId: string, master_password: string, user_id: string) { - const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( - connectionId, - master_password, - ); - - if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); - } - - let userEmail: string; - if (isConnectionTypeAgent(foundConnection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(user_id); - } - - const connectionProperties = - await this._dbContext.connectionPropertiesRepository.findConnectionProperties(connectionId); - - if (connectionProperties && !connectionProperties.allow_ai_requests) { - throw new BadRequestException(Messages.AI_REQUESTS_NOT_ALLOWED); - } - - const dataAccessObject = getDataAccessObject(foundConnection); - const databaseType = foundConnection.type; - const isMongoDb = - databaseType === ConnectionTypesEnum.mongodb || databaseType === ConnectionTypesEnum.agent_mongodb; - - return { foundConnection, dataAccessObject, databaseType, isMongoDb, userEmail }; - } -} diff --git a/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v6.use.case.ts b/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v6.use.case.ts deleted file mode 100644 index 1bb01b63c..000000000 --- a/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v6.use.case.ts +++ /dev/null @@ -1,337 +0,0 @@ -import { BaseMessage } from '@langchain/core/messages'; -import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; -import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; -import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.js'; -import { IDataAccessObject } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object.interface.js'; -import { IDataAccessObjectAgent } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object-agent.interface.js'; -import Sentry from '@sentry/minimal'; -import { Response } from 'express'; -import { - AICoreService, - AIProviderType, - AIToolCall, - AIToolDefinition, - createDatabaseQuerySystemPrompt, - createDatabaseTools, - isValidMongoDbCommand, - isValidSQLQuery, - MessageBuilder, - wrapQueryWithLimit, -} from '../../../ai-core/index.js'; -import AbstractUseCase from '../../../common/abstract-use.case.js'; -import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; -import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; -import { slackPostMessage } from '../../../helpers/index.js'; -import { isConnectionTypeAgent } from '../../../helpers/is-connection-entity-agent.js'; -import { ConnectionEntity } from '../../connection/connection.entity.js'; -import { AiResponsesToUserEntity } from '../ai-data-entities/ai-reponses-to-user/ai-responses-to-user.entity.js'; -import { IRequestInfoFromTableV2 } from '../ai-use-cases.interface.js'; -import { RequestInfoFromTableDSV2 } from '../application/data-structures/request-info-from-table.ds.js'; - -@Injectable({ scope: Scope.REQUEST }) -export class RequestInfoFromTableWithAIUseCaseV6 - extends AbstractUseCase - implements IRequestInfoFromTableV2 -{ - private readonly maxDepth: number = 10; - private readonly aiProvider: AIProviderType = AIProviderType.BEDROCK; - - constructor( - @Inject(BaseType.GLOBAL_DB_CONTEXT) - protected _dbContext: IGlobalDatabaseContext, - private readonly aiCoreService: AICoreService, - ) { - super(); - } - - public async implementation(inputData: RequestInfoFromTableDSV2): Promise { - const { connectionId, tableName, user_message, master_password, user_id, response, ai_thread_id } = inputData; - - this.setupResponseHeaders(response); - - const { foundConnection, dataAccessObject, isMongoDb, userEmail } = await this.setupConnection( - connectionId, - master_password, - user_id, - ); - - const tools = createDatabaseTools(isMongoDb); - - const systemPrompt = createDatabaseQuerySystemPrompt( - tableName, - foundConnection.type as ConnectionTypesEnum, - foundConnection.schema, - ); - - let threadIdForHeader: string | null = null; - let foundUserAiResponse: AiResponsesToUserEntity | null = null; - - if (ai_thread_id) { - foundUserAiResponse = await this._dbContext.aiResponsesToUserRepository.findResponseByIdAndUserId( - ai_thread_id, - user_id, - ); - if (foundUserAiResponse) { - threadIdForHeader = foundUserAiResponse.id; - } - } - - if (!foundUserAiResponse) { - const newAiResponse = new AiResponsesToUserEntity(); - newAiResponse.ai_response_id = null; - newAiResponse.user_id = user_id; - foundUserAiResponse = await this._dbContext.aiResponsesToUserRepository.save(newAiResponse); - threadIdForHeader = foundUserAiResponse.id; - } - - if (threadIdForHeader) { - response.setHeader('X-AI-Thread-ID', threadIdForHeader); - } - - const messages = new MessageBuilder().system(systemPrompt).human(user_message).build(); - - try { - const lastResponseId = await this.processWithToolLoop( - messages, - tools, - response, - dataAccessObject, - tableName, - userEmail, - foundConnection, - ); - - if (foundUserAiResponse && lastResponseId) { - foundUserAiResponse.ai_response_id = lastResponseId; - await this._dbContext.aiResponsesToUserRepository.save(foundUserAiResponse); - } - response.end(); - } catch (error) { - await slackPostMessage(error?.message); - Sentry.captureException(error); - if (!response.headersSent) { - response.status(500).send({ error: 'An error occurred while processing your request.' }); - } - } - } - - private async processWithToolLoop( - messages: BaseMessage[], - tools: AIToolDefinition[], - response: Response, - dataAccessObject: IDataAccessObject | IDataAccessObjectAgent, - inputTableName: string, - userEmail: string, - foundConnection: ConnectionEntity, - ): Promise { - let currentMessages = [...messages]; - let lastResponseId: string | null = null; - let depth = 0; - - while (depth < this.maxDepth) { - try { - const stream = await this.aiCoreService.streamChatWithToolsAndProvider(this.aiProvider, currentMessages, tools); - - let pendingToolCalls: AIToolCall[] = []; - let accumulatedContent = ''; - - for await (const chunk of stream) { - if (chunk.type === 'text' && chunk.content) { - response.write(chunk.content); - accumulatedContent += chunk.content; - } - - if (chunk.type === 'tool_call' && chunk.toolCall) { - pendingToolCalls.push(chunk.toolCall); - } - - if (chunk.responseId) { - lastResponseId = chunk.responseId; - } - } - - if (pendingToolCalls.length === 0) { - break; - } - - const toolResults = await this.executeToolCalls( - pendingToolCalls, - dataAccessObject, - inputTableName, - userEmail, - foundConnection, - ); - - const continuationBuilder = MessageBuilder.fromMessages(currentMessages); - continuationBuilder.ai(accumulatedContent, pendingToolCalls); - for (const result of toolResults) { - continuationBuilder.toolResult(result.toolCallId, result.result); - } - currentMessages = continuationBuilder.build(); - - depth++; - } catch (loopError) { - throw loopError; - } - } - - if (depth >= this.maxDepth) { - response.write( - '\n\nYour question is too complex to process at this time. Please try simplifying it or breaking it down into smaller parts.', - ); - } - - return lastResponseId; - } - - private async executeToolCalls( - toolCalls: AIToolCall[], - dataAccessObject: IDataAccessObject | IDataAccessObjectAgent, - inputTableName: string, - userEmail: string, - foundConnection: ConnectionEntity, - ): Promise> { - const results: Array<{ toolCallId: string; result: string }> = []; - - for (const toolCall of toolCalls) { - let result: string; - - try { - switch (toolCall.name) { - case 'getTableStructure': { - const tableName = (toolCall.arguments.tableName as string) || inputTableName; - const structureInfo = await this.getTableStructureInfo( - dataAccessObject, - tableName, - userEmail, - foundConnection, - ); - result = JSON.stringify(structureInfo); - break; - } - - case 'executeRawSql': { - const query = toolCall.arguments.query as string; - if (!query) { - throw new Error('Missing required function argument "query"'); - } - if (!isValidSQLQuery(query)) { - throw new Error( - 'Invalid SQL query. Please ensure it is a read-only SELECT statement without any forbidden keywords.', - ); - } - const wrappedQuery = wrapQueryWithLimit(query, foundConnection.type as ConnectionTypesEnum); - const queryResult = await dataAccessObject.executeRawQuery(wrappedQuery, inputTableName, userEmail); - result = JSON.stringify(queryResult); - break; - } - - case 'executeAggregationPipeline': { - const pipeline = toolCall.arguments.pipeline as string; - if (!pipeline) { - throw new Error('Missing required function argument "pipeline"'); - } - if (!isValidMongoDbCommand(pipeline)) { - throw new Error( - 'Invalid MongoDB command. Please ensure it is a read-only aggregation pipeline without any forbidden keywords.', - ); - } - const pipelineResult = await dataAccessObject.executeRawQuery(pipeline, inputTableName, userEmail); - result = JSON.stringify(pipelineResult); - break; - } - - default: - result = JSON.stringify({ error: `Unknown tool: ${toolCall.name}` }); - } - } catch (error) { - result = JSON.stringify({ error: error.message }); - } - - results.push({ toolCallId: toolCall.id, result }); - } - - return results; - } - - private async getTableStructureInfo( - dao: IDataAccessObject | IDataAccessObjectAgent, - tableName: string, - userEmail: string, - foundConnection: ConnectionEntity, - ) { - const [tableStructure, tableForeignKeys, referencedTableNamesAndColumns] = await Promise.all([ - dao.getTableStructure(tableName, userEmail), - dao.getTableForeignKeys(tableName, userEmail), - dao.getReferencedTableNamesAndColumns(tableName, userEmail), - ]); - - const referencedTablesStructures = []; - const structurePromises = referencedTableNamesAndColumns.flatMap((referencedTable) => - referencedTable.referenced_by.map((table) => - dao.getTableStructure(table.table_name, userEmail).then((structure) => ({ - tableName: table.table_name, - structure, - })), - ), - ); - referencedTablesStructures.push(...(await Promise.all(structurePromises))); - - const foreignTablesStructures = []; - const foreignTablesStructurePromises = tableForeignKeys.flatMap((foreignKey) => - dao.getTableStructure(foreignKey.referenced_table_name, userEmail).then((structure) => ({ - tableName: foreignKey.referenced_table_name, - structure, - })), - ); - foreignTablesStructures.push(...(await Promise.all(foreignTablesStructurePromises))); - - return { - tableStructure, - tableName, - schema: foundConnection.schema || null, - tableForeignKeys, - referencedTableNamesAndColumns, - referencedTablesStructures, - foreignTablesStructures, - }; - } - - private setupResponseHeaders(response: Response): void { - response.setHeader('Content-Type', 'text/event-stream'); - response.setHeader('Cache-Control', 'no-cache'); - response.setHeader('Connection', 'keep-alive'); - response.setHeader('Access-Control-Expose-Headers', 'X-AI-Thread-ID'); - } - - private async setupConnection(connectionId: string, master_password: string, user_id: string) { - const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( - connectionId, - master_password, - ); - - if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); - } - - let userEmail: string; - if (isConnectionTypeAgent(foundConnection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(user_id); - } - - const connectionProperties = - await this._dbContext.connectionPropertiesRepository.findConnectionProperties(connectionId); - - if (connectionProperties && !connectionProperties.allow_ai_requests) { - throw new BadRequestException(Messages.AI_REQUESTS_NOT_ALLOWED); - } - - const dataAccessObject = getDataAccessObject(foundConnection); - const databaseType = foundConnection.type; - const isMongoDb = - databaseType === ConnectionTypesEnum.mongodb || databaseType === ConnectionTypesEnum.agent_mongodb; - - return { foundConnection, dataAccessObject, databaseType, isMongoDb, userEmail }; - } -} 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 e9fbd93cf..3fd3935ff 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 @@ -8,7 +8,6 @@ import Sentry from '@sentry/minimal'; import { Response } from 'express'; import { AICoreService, - AIProviderConfig, AIProviderType, AIToolCall, AIToolDefinition, @@ -98,20 +97,10 @@ export class RequestInfoFromTableWithAIUseCaseV7 }); } - const { messages, previousResponseId } = await this.buildMessagesWithHistory( - systemPrompt, - user_message, - foundUserAiChat.id, - isNewChat, - ); + const messages = await this.buildMessagesWithHistory(systemPrompt, user_message, foundUserAiChat.id, isNewChat); try { - const config: AIProviderConfig = {}; - if (this.aiProvider === AIProviderType.OPENAI && previousResponseId) { - config.previousResponseId = previousResponseId; - } - - const { accumulatedResponse, lastResponseId } = await this.processWithToolLoop( + const accumulatedResponse = await this.processWithToolLoop( messages, tools, response, @@ -119,7 +108,6 @@ export class RequestInfoFromTableWithAIUseCaseV7 tableName, userEmail, foundConnection, - config, ); if (accumulatedResponse) { @@ -127,7 +115,6 @@ export class RequestInfoFromTableWithAIUseCaseV7 foundUserAiChat.id, accumulatedResponse, MessageRole.ai, - lastResponseId, ); } @@ -149,24 +136,16 @@ export class RequestInfoFromTableWithAIUseCaseV7 inputTableName: string, userEmail: string, foundConnection: ConnectionEntity, - config: AIProviderConfig = {}, - ): Promise<{ lastResponseId: string | null; accumulatedResponse: string }> { + ): Promise { 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, - currentConfig, - ); + const stream = await this.aiCoreService.streamChatWithToolsAndProvider(this.aiProvider, currentMessages, tools); - let pendingToolCalls: AIToolCall[] = []; + const pendingToolCalls: AIToolCall[] = []; let accumulatedContent = ''; for await (const chunk of stream) { @@ -178,10 +157,6 @@ export class RequestInfoFromTableWithAIUseCaseV7 if (chunk.type === 'tool_call' && chunk.toolCall) { pendingToolCalls.push(chunk.toolCall); } - - if (chunk.responseId) { - lastResponseId = chunk.responseId; - } } this.logger.log( @@ -213,10 +188,6 @@ export class RequestInfoFromTableWithAIUseCaseV7 this.logger.log(`Tool result for ${toolResult.toolCallId}: resultLength=${toolResult.result.length}`); } - if (this.aiProvider === AIProviderType.OPENAI && lastResponseId) { - currentConfig = { ...currentConfig, previousResponseId: lastResponseId }; - } - const continuationBuilder = MessageBuilder.fromMessages(currentMessages); continuationBuilder.ai(accumulatedContent, pendingToolCalls); for (const result of toolResults) { @@ -239,7 +210,7 @@ export class RequestInfoFromTableWithAIUseCaseV7 totalAccumulatedResponse += maxDepthMessage; } - return { lastResponseId, accumulatedResponse: totalAccumulatedResponse }; + return totalAccumulatedResponse; } private writeChunk( @@ -409,17 +380,9 @@ export class RequestInfoFromTableWithAIUseCaseV7 userMessage: string, chatId: string, isNewChat: boolean, - ): Promise<{ messages: BaseMessage[]; previousResponseId: string | null }> { + ): Promise { 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 }; + return new MessageBuilder().system(systemPrompt).human(userMessage).build(); } const previousMessages = await this._dbContext.aiChatMessageRepository.findMessagesForChat(chatId); @@ -435,7 +398,7 @@ export class RequestInfoFromTableWithAIUseCaseV7 builder.human(userMessage); - return { messages: builder.build(), previousResponseId: null }; + return builder.build(); } private async generateAndUpdateChatName(chatId: string, userMessage: string): Promise { diff --git a/backend/src/entities/ai/user-ai-requests-v2.controller.ts b/backend/src/entities/ai/user-ai-requests-v2.controller.ts index cb0ac9118..28028f3ea 100644 --- a/backend/src/entities/ai/user-ai-requests-v2.controller.ts +++ b/backend/src/entities/ai/user-ai-requests-v2.controller.ts @@ -34,100 +34,12 @@ import { RequestInfoFromTableBodyDTO } from './application/dto/request-info-from @Injectable() export class UserAIRequestsControllerV2 { constructor( - @Inject(UseCaseType.REQUEST_INFO_FROM_TABLE_WITH_AI_V2) - private readonly requestInfoFromTableWithAIUseCase: IRequestInfoFromTableV2, - @Inject(UseCaseType.REQUEST_INFO_FROM_TABLE_WITH_AI_V3) - private readonly requestInfoFromTableWithAIUseCaseV3: IRequestInfoFromTableV2, @Inject(UseCaseType.REQUEST_INFO_FROM_TABLE_WITH_AI_V4) private readonly requestInfoFromTableWithAIUseCaseV4: IRequestInfoFromTableV2, @Inject(UseCaseType.REQUEST_AI_SETTINGS_AND_WIDGETS_CREATION) private readonly requestAISettingsAndWidgetsCreationUseCase: IAISettingsAndWidgetsCreation, ) {} - @ApiOperation({ - summary: 'Request info from table in connection with AI (Version 2)', - }) - @ApiResponse({ - status: 201, - description: 'Returned info.', - }) - @UseGuards(TableReadGuard) - @ApiBody({ type: RequestInfoFromTableBodyDTO }) - @ApiQuery({ name: 'tableName', required: true, type: String }) - @ApiQuery({ name: 'threadId', required: false, type: String }) - @Timeout(process.env.NODE_ENV !== 'test' ? TimeoutDefaults.AI : TimeoutDefaults.AI_TEST) - @Post('/ai/v2/request/:connectionId') - public async requestInfoFromTableWithAI( - @SlugUuid('connectionId') connectionId: string, - @Query('threadId') threadId: string, - @QueryTableName() tableName: string, - @MasterPassword() masterPassword: string, - @UserId() userId: string, - @Body() requestData: RequestInfoFromTableBodyDTO, - @Res({ passthrough: true }) response: Response, - ): Promise { - if (threadId) { - if (!ValidationHelper.isValidUUID(threadId)) { - response.status(400).send({ - error: 'Invalid threadId format. It should be a valid UUID.', - }); - return; - } - } - const inputData: RequestInfoFromTableDSV2 = { - connectionId, - tableName, - user_message: requestData.user_message, - master_password: masterPassword, - user_id: userId, - response, - ai_thread_id: threadId || null, - }; - return await this.requestInfoFromTableWithAIUseCase.execute(inputData, InTransactionEnum.OFF); - } - - @ApiOperation({ - summary: 'Request info from table in connection with AI using Bedrock (Version 3)', - }) - @ApiResponse({ - status: 201, - description: 'Returned info.', - }) - @UseGuards(TableReadGuard) - @ApiBody({ type: RequestInfoFromTableBodyDTO }) - @ApiQuery({ name: 'tableName', required: true, type: String }) - @ApiQuery({ name: 'threadId', required: false, type: String }) - @Timeout(process.env.NODE_ENV !== 'test' ? TimeoutDefaults.AI : TimeoutDefaults.AI_TEST) - @Post('/ai/v3/request/:connectionId') - public async requestInfoFromTableWithAIBedrock( - @SlugUuid('connectionId') connectionId: string, - @Query('threadId') threadId: string, - @QueryTableName() tableName: string, - @MasterPassword() masterPassword: string, - @UserId() userId: string, - @Body() requestData: RequestInfoFromTableBodyDTO, - @Res({ passthrough: true }) response: Response, - ): Promise { - if (threadId) { - if (!ValidationHelper.isValidUUID(threadId)) { - response.status(400).send({ - error: 'Invalid threadId format. It should be a valid UUID.', - }); - return; - } - } - const inputData: RequestInfoFromTableDSV2 = { - connectionId, - tableName, - user_message: requestData.user_message, - master_password: masterPassword, - user_id: userId, - response, - ai_thread_id: threadId || null, - }; - return await this.requestInfoFromTableWithAIUseCaseV3.execute(inputData, InTransactionEnum.OFF); - } - @ApiOperation({ summary: 'Request info from table in connection with AI with conversation history (Version 4)', }) diff --git a/backend/src/entities/table-schema/ai/run-schema-change-ai-loop.ts b/backend/src/entities/table-schema/ai/run-schema-change-ai-loop.ts index bbd62afa4..6719f9243 100644 --- a/backend/src/entities/table-schema/ai/run-schema-change-ai-loop.ts +++ b/backend/src/entities/table-schema/ai/run-schema-change-ai-loop.ts @@ -48,7 +48,7 @@ export async function runSchemaChangeAiLoop(opts: RunSchemaChangeAiLoopOptions): const maxDepth = opts.maxDepth ?? 6; let currentMessages = [...opts.messages]; - let currentConfig: AIProviderConfig = {}; + const currentConfig: AIProviderConfig = {}; let lastResponseId: string | null = null; for (let depth = 0; depth < maxDepth; depth++) { @@ -95,10 +95,6 @@ export async function runSchemaChangeAiLoop(opts: RunSchemaChangeAiLoopOptions): const toolResults = await executeInspectionToolCalls(pendingToolCalls, dao, userEmail, logger); - if (provider === AIProviderType.OPENAI && lastResponseId) { - currentConfig = { ...currentConfig, previousResponseId: lastResponseId }; - } - const continuation = MessageBuilder.fromMessages(currentMessages); continuation.ai(accumulatedContent, pendingToolCalls); for (const result of toolResults) { diff --git a/backend/src/entities/user/user.entity.ts b/backend/src/entities/user/user.entity.ts index 5f8095090..3ebb8bc09 100644 --- a/backend/src/entities/user/user.entity.ts +++ b/backend/src/entities/user/user.entity.ts @@ -14,7 +14,6 @@ import { } from 'typeorm'; import { Encryptor } from '../../helpers/encryption/encryptor.js'; import { UserAiChatEntity } from '../ai/ai-conversation-history/user-ai-chat/user-ai-chat.entity.js'; -import { AiResponsesToUserEntity } from '../ai/ai-data-entities/ai-reponses-to-user/ai-responses-to-user.entity.js'; import { UserApiKeyEntity } from '../api-key/api-key.entity.js'; import { CompanyInfoEntity } from '../company-info/company-info.entity.js'; import { ConnectionEntity } from '../connection/connection.entity.js'; @@ -151,12 +150,6 @@ export class UserEntity { ) api_keys: Relation[]; - @OneToMany( - (_) => AiResponsesToUserEntity, - (response) => response.user, - ) - ai_responses: Relation[]; - @OneToMany( (_) => SignInAuditEntity, (signInAudit) => signInAudit.user, diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-ai-chat-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-ai-chat-e2e.test.ts index ffa273075..5afb37bd4 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-ai-chat-e2e.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-ai-chat-e2e.test.ts @@ -52,7 +52,7 @@ const mockAICoreService = { streamChat: async () => createMockAIStream('Mocked stream'), chatWithTools: async () => ({ content: 'Mocked tools', responseId: faker.string.uuid() }), streamChatWithTools: async () => createMockAIStream('Mocked tools stream'), - getDefaultProvider: () => 'openai', + getDefaultProvider: () => 'bedrock', setDefaultProvider: () => {}, getAvailableProviders: () => [], }; diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-cassandra-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-cassandra-e2e.test.ts index 4cda393ba..215307b62 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-cassandra-e2e.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-cassandra-e2e.test.ts @@ -80,7 +80,7 @@ const mockAICoreService = { chatWithProvider: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueAfterToolCall: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueStreamingAfterToolCall: async () => createProposalStream(nextProposal!), - getDefaultProvider: () => 'openai', + getDefaultProvider: () => 'bedrock', setDefaultProvider: () => {}, getAvailableProviders: () => [], }; diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-clickhouse-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-clickhouse-e2e.test.ts index 0a4223cac..b74f1065c 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-clickhouse-e2e.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-clickhouse-e2e.test.ts @@ -80,7 +80,7 @@ const mockAICoreService = { chatWithProvider: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueAfterToolCall: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueStreamingAfterToolCall: async () => createProposalStream(nextProposal!), - getDefaultProvider: () => 'openai', + getDefaultProvider: () => 'bedrock', setDefaultProvider: () => {}, getAvailableProviders: () => [], }; diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-dynamodb-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-dynamodb-e2e.test.ts index c4b3662ab..d968fb7fb 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-dynamodb-e2e.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-dynamodb-e2e.test.ts @@ -88,7 +88,7 @@ const mockAICoreService = { chatWithProvider: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueAfterToolCall: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueStreamingAfterToolCall: async () => createDynamoProposalStream(nextProposal!), - getDefaultProvider: () => 'openai', + getDefaultProvider: () => 'bedrock', setDefaultProvider: () => {}, getAvailableProviders: () => [], }; diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-elasticsearch-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-elasticsearch-e2e.test.ts index 8f4b9f7ed..435b38bad 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-elasticsearch-e2e.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-elasticsearch-e2e.test.ts @@ -80,7 +80,7 @@ const mockAICoreService = { chatWithProvider: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueAfterToolCall: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueStreamingAfterToolCall: async () => createElasticProposalStream(nextProposal!), - getDefaultProvider: () => 'openai', + getDefaultProvider: () => 'bedrock', setDefaultProvider: () => {}, getAvailableProviders: () => [], }; diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-ibmdb2-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-ibmdb2-e2e.test.ts index 903ac08e1..3b85c1aa0 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-ibmdb2-e2e.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-ibmdb2-e2e.test.ts @@ -80,7 +80,7 @@ const mockAICoreService = { chatWithProvider: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueAfterToolCall: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueStreamingAfterToolCall: async () => createProposalStream(nextProposal!), - getDefaultProvider: () => 'openai', + getDefaultProvider: () => 'bedrock', setDefaultProvider: () => {}, getAvailableProviders: () => [], }; diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-mongodb-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-mongodb-e2e.test.ts index fb1d98da0..64ec987f5 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-mongodb-e2e.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-mongodb-e2e.test.ts @@ -80,7 +80,7 @@ const mockAICoreService = { chatWithProvider: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueAfterToolCall: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueStreamingAfterToolCall: async () => createMongoProposalStream(nextProposal!), - getDefaultProvider: () => 'openai', + getDefaultProvider: () => 'bedrock', setDefaultProvider: () => {}, getAvailableProviders: () => [], }; diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-mssql-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-mssql-e2e.test.ts index a5e39d5d6..938ad7c59 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-mssql-e2e.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-mssql-e2e.test.ts @@ -81,7 +81,7 @@ const mockAICoreService = { chatWithProvider: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueAfterToolCall: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueStreamingAfterToolCall: async () => createProposalStream(nextProposal!), - getDefaultProvider: () => 'openai', + getDefaultProvider: () => 'bedrock', setDefaultProvider: () => {}, getAvailableProviders: () => [], }; diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-mysql-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-mysql-e2e.test.ts index 8851ce8ce..c386e7577 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-mysql-e2e.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-mysql-e2e.test.ts @@ -85,7 +85,7 @@ const mockAICoreService = { chatWithProvider: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueAfterToolCall: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueStreamingAfterToolCall: async () => createProposalStream(resolveProposals()), - getDefaultProvider: () => 'openai', + getDefaultProvider: () => 'bedrock', setDefaultProvider: () => {}, getAvailableProviders: () => [], }; diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-oracle-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-oracle-e2e.test.ts index 149fa73a5..c51e476c7 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-oracle-e2e.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-oracle-e2e.test.ts @@ -81,7 +81,7 @@ const mockAICoreService = { chatWithProvider: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueAfterToolCall: async () => ({ content: 'mocked', responseId: faker.string.uuid() }), continueStreamingAfterToolCall: async () => createProposalStream(nextProposal!), - getDefaultProvider: () => 'openai', + getDefaultProvider: () => 'bedrock', setDefaultProvider: () => {}, getAvailableProviders: () => [], }; diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-postgres-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-postgres-e2e.test.ts index 044ae6206..c8cce4372 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-postgres-e2e.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-postgres-e2e.test.ts @@ -85,7 +85,7 @@ const mockAICoreService = { chatWithProvider: async () => ({ content: 'Mocked', responseId: faker.string.uuid() }), continueAfterToolCall: async () => ({ content: 'Mocked', responseId: faker.string.uuid() }), continueStreamingAfterToolCall: async () => createProposalStream(resolveProposals()), - getDefaultProvider: () => 'openai', + getDefaultProvider: () => 'bedrock', setDefaultProvider: () => {}, getAvailableProviders: () => [], }; diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-redis-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-redis-e2e.test.ts index 5123dd045..8ec63df00 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-redis-e2e.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-table-schema-redis-e2e.test.ts @@ -57,7 +57,7 @@ const mockAICoreService = { yield { type: 'text', content: 'mocked', responseId: faker.string.uuid() }; }, }), - getDefaultProvider: () => 'openai', + getDefaultProvider: () => 'bedrock', setDefaultProvider: () => {}, getAvailableProviders: () => [], }; diff --git a/backend/test/ava-tests/saas-tests/ai-chat-e2e.test.ts b/backend/test/ava-tests/saas-tests/ai-chat-e2e.test.ts index 5a3215c89..dee66722b 100644 --- a/backend/test/ava-tests/saas-tests/ai-chat-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/ai-chat-e2e.test.ts @@ -48,7 +48,7 @@ const mockAICoreService = { streamChat: async () => createMockAIStream('Mocked stream'), chatWithTools: async () => ({ content: 'Mocked tools', responseId: faker.string.uuid() }), streamChatWithTools: async () => createMockAIStream('Mocked tools stream'), - getDefaultProvider: () => 'openai', + getDefaultProvider: () => 'bedrock', setDefaultProvider: () => {}, getAvailableProviders: () => [], }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b2981b2e..ed7251fb2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,9 +67,6 @@ importers: '@langchain/core': specifier: ^1.1.33 version: 1.1.40(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0) - '@langchain/openai': - specifier: ^1.3.0 - version: 1.4.4(@langchain/core@1.1.40(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0))(ws@8.20.0) '@nestjs/common': specifier: 11.1.17 version: 11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -211,9 +208,6 @@ importers: nunjucks: specifier: ^3.2.4 version: 3.2.4(chokidar@4.0.3) - openai: - specifier: ^6.32.0 - version: 6.34.0(ws@8.20.0)(zod@4.3.6) otplib: specifier: ^12.0.1 version: 12.0.1 @@ -1413,12 +1407,6 @@ packages: zod-to-json-schema: optional: true - '@langchain/openai@1.4.4': - resolution: {integrity: sha512-mRr/X5rvlwPj6cSXPxbL+CtOqYANO1/+CQ3Z+5t48kWnrlgPYOazmA+UAWvqQOuwJ6LaYn3SFrt43rR4lte/Ow==} - engines: {node: '>=20'} - peerDependencies: - '@langchain/core': ^1.1.39 - '@lukeed/csprng@1.1.0': resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} engines: {node: '>=8'} @@ -7449,15 +7437,6 @@ snapshots: - svelte - vue - '@langchain/openai@1.4.4(@langchain/core@1.1.40(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0))(ws@8.20.0)': - dependencies: - '@langchain/core': 1.1.40(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0) - js-tiktoken: 1.0.21 - openai: 6.34.0(ws@8.20.0)(zod@4.3.6) - zod: 4.3.6 - transitivePeerDependencies: - - ws - '@lukeed/csprng@1.1.0': {} '@mapbox/node-pre-gyp@2.0.3(encoding@0.1.13)': @@ -10917,6 +10896,7 @@ snapshots: optionalDependencies: ws: 8.20.0 zod: 4.3.6 + optional: true ora@5.4.1: dependencies: