diff --git a/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx b/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx index 7a6687c3c4..d85fb1d655 100644 --- a/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx +++ b/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx @@ -43,6 +43,7 @@ export class RemixAIPlugin extends ViewPlugin { } else { console.log('Activating RemixAIPlugin on browser') } + this.initialize() } async initialize(model1?:IModel, model2?:IModel, remoteModel?:IRemoteModel){ @@ -66,17 +67,17 @@ export class RemixAIPlugin extends ViewPlugin { async code_generation(prompt: string): Promise { console.log('code_generation') if (this.isOnDesktop) { - return this.call(this.remixDesktopPluginName, 'code_generation', prompt) + return await this.call(this.remixDesktopPluginName, 'code_generation', prompt) } else { - return this.remoteInferencer.code_generation(prompt) + return await this.remoteInferencer.code_generation(prompt) } } async code_completion(prompt: string): Promise { if (this.isOnDesktop) { - return this.call(this.remixDesktopPluginName, 'code_completion', prompt) + return await this.call(this.remixDesktopPluginName, 'code_completion', prompt) } else { - return this.remoteInferencer.code_completion(prompt) + return await this.remoteInferencer.code_completion(prompt) } } @@ -85,9 +86,9 @@ export class RemixAIPlugin extends ViewPlugin { let result if (this.isOnDesktop) { - result = this.call(this.remixDesktopPluginName, 'solidity_answer', prompt) + result = await this.call(this.remixDesktopPluginName, 'solidity_answer', prompt) } else { - result = this.remoteInferencer.solidity_answer(prompt) + result = await this.remoteInferencer.solidity_answer(prompt) } this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result }) @@ -101,7 +102,7 @@ export class RemixAIPlugin extends ViewPlugin { result = await this.call(this.remixDesktopPluginName, 'code_explaining', prompt) } else { - result = this.remoteInferencer.code_explaining(prompt) + result = await this.remoteInferencer.code_explaining(prompt) } if (result) this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result }) } @@ -120,9 +121,9 @@ export class RemixAIPlugin extends ViewPlugin { async code_insertion(msg_pfx: string, msg_sfx: string): Promise { if (this.isOnDesktop) { - return this.call(this.remixDesktopPluginName, 'code_insertion', msg_pfx, msg_sfx) + return await this.call(this.remixDesktopPluginName, 'code_insertion', msg_pfx, msg_sfx) } else { - return this.remoteInferencer.code_insertion(msg_pfx, msg_sfx) + return await this.remoteInferencer.code_insertion(msg_pfx, msg_sfx) } } diff --git a/apps/remixdesktop/src/lib/InferenceServerManager.ts b/apps/remixdesktop/src/lib/InferenceServerManager.ts index 69a2cfed59..d03e482f8b 100644 --- a/apps/remixdesktop/src/lib/InferenceServerManager.ts +++ b/apps/remixdesktop/src/lib/InferenceServerManager.ts @@ -4,8 +4,9 @@ import fs from 'fs'; import axios from "axios"; import { EventEmitter } from 'events'; import { ICompletions, IModel, IParams, InsertionParams, - CompletionParams, GenerationParams, ModelType, - IStreamResponse } from "../../../../libs/remix-ai-core/src/index" + CompletionParams, GenerationParams, ModelType, AIRequestType, + IStreamResponse, ChatHistory, + buildSolgptPromt } from "../../../../libs/remix-ai-core/src/index" class ServerStatusTimer { private intervalId: NodeJS.Timeout | null = null; @@ -127,7 +128,7 @@ export class InferenceManager implements ICompletions { console.log('Inference server not running') InferenceManager.instance = null this.stateTimer.interval += this.stateTimer.interval - + if (this.stateTimer.interval >= 60000) { // attempt to restart the server console.log('Attempting to restart the server') @@ -257,17 +258,27 @@ export class InferenceManager implements ICompletions { } } - private async _makeInferenceRequest(endpoint, payload){ + private async _makeInferenceRequest(endpoint, payload, rType:AIRequestType){ try { this.event.emit('onInference') const options = { headers: { 'Content-Type': 'application/json', } } const response = await axios.post(`${this.inferenceURL}/${endpoint}`, payload, options) + + const userPrompt = payload[Object.keys(payload)[0]] this.event.emit('onInferenceDone') + console.log('response', response) + console.log('userprompt', userPrompt) + console.log('chat history:', ChatHistory.getHistory()) + if (response.data?.generatedText) { + if (rType === AIRequestType.GENERAL) { + ChatHistory.pushHistory(userPrompt, response.data.generatedText) + } return response.data.generatedText } else { return "" } } catch (error) { + ChatHistory.clearHistory() console.error('Error making request to Inference server:', error.message); } } @@ -286,22 +297,31 @@ export class InferenceManager implements ICompletions { } , responseType: 'stream' }); + const userPrompt = payload[Object.keys(payload)[0]] + let resultText = "" response.data.on('data', (chunk: Buffer) => { try { const parsedData = JSON.parse(chunk.toString()); if (parsedData.isGenerating) { this.event.emit('onStreamResult', parsedData.generatedText); + resultText = resultText + parsedData.generatedText } else { + resultText = resultText + parsedData.generatedText + + // no additional check for streamed results + ChatHistory.pushHistory(userPrompt, resultText) return parsedData.generatedText } } catch (error) { + ChatHistory.clearHistory() console.error('Error parsing JSON:', error); } }); - return "" // return empty string for now as payload already handled in event + return "" // return empty string for now as payload is/will be handled in event } catch (error) { + ChatHistory.clearHistory() console.error('Error making stream request to Inference server:', error.message); } finally { @@ -310,6 +330,7 @@ export class InferenceManager implements ICompletions { } private async _makeRequest(endpoint, payload){ + // make a simple request to the inference server try { const options = { headers: { 'Content-Type': 'application/json', } } const response = await axios.post(`${this.inferenceURL}/${endpoint}`, payload, options) @@ -329,7 +350,7 @@ export class InferenceManager implements ICompletions { // as of now no prompt required const payload = { context_code: context, ...params } - return this._makeInferenceRequest('code_completion', payload) + return this._makeInferenceRequest('code_completion', payload, AIRequestType.COMPLETION) } async code_insertion(msg_pfx: string, msg_sfx: string, params:IParams=InsertionParams): Promise { @@ -338,7 +359,7 @@ export class InferenceManager implements ICompletions { return } const payload = { code_pfx:msg_pfx, code_sfx:msg_sfx, ...params } - return this._makeInferenceRequest('code_insertion', payload) + return this._makeInferenceRequest('code_insertion', payload, AIRequestType.COMPLETION) } @@ -347,7 +368,7 @@ export class InferenceManager implements ICompletions { console.log('model not ready yet') return } - return this._makeInferenceRequest('code_generation', { prompt, ...params }) + return this._makeInferenceRequest('code_generation', { prompt, ...params }, AIRequestType.GENERAL) } async code_explaining(code:string, context:string, params:IParams=GenerationParams): Promise { @@ -358,7 +379,7 @@ export class InferenceManager implements ICompletions { if (GenerationParams.stream_result) { return this._streamInferenceRequest('code_explaining', { code, context, ...params }) } else { - return this._makeInferenceRequest('code_explaining', { code, context, ...params }) + return this._makeInferenceRequest('code_explaining', { code, context, ...params }, AIRequestType.GENERAL) } } @@ -370,19 +391,27 @@ export class InferenceManager implements ICompletions { if (GenerationParams.stream_result) { return this._streamInferenceRequest('error_explaining', { prompt, ...params }) } else { - return this._makeInferenceRequest('error_explaining', { prompt, ...params }) + return this._makeInferenceRequest('error_explaining', { prompt, ...params }, AIRequestType.GENERAL) } } - async solidity_answer(prompt: string, params:IParams=GenerationParams): Promise { + async solidity_answer(userPrompt: string, params:IParams=GenerationParams): Promise { if (!this.isReady) { console.log('model not ready yet') return } + let modelOP = undefined + for (const model of this.selectedModels) { + if (model?.modelOP) { + modelOP = model.modelOP + } + } + const prompt = buildSolgptPromt(userPrompt, this.selectedModels[0]?.modelOP) + if (GenerationParams.stream_result) { return this._streamInferenceRequest('solidity_answer', { prompt, ...params }) } else { - return this._makeInferenceRequest('solidity_answer', { prompt, ...params }) + return this._makeInferenceRequest('solidity_answer', { prompt, ...params }, AIRequestType.GENERAL) } } diff --git a/libs/remix-ai-core/src/index.ts b/libs/remix-ai-core/src/index.ts index ecc00d4f65..f908c2927d 100644 --- a/libs/remix-ai-core/src/index.ts +++ b/libs/remix-ai-core/src/index.ts @@ -6,13 +6,14 @@ import { IModel, IModelResponse, IModelRequest, InferenceModel, ICompletions, import { ModelType } from './types/constants' import { DefaultModels, InsertionParams, CompletionParams, GenerationParams } from './types/models' import { getCompletionPrompt, getInsertionPrompt } from './prompts/completionPrompts' -import { PromptBuilder } from './prompts/promptBuilder' +import { buildSolgptPromt, PromptBuilder } from './prompts/promptBuilder' import { RemoteInferencer } from './inferencers/remote/remoteInference' +import { ChatHistory } from './prompts/chat' export { IModel, IModelResponse, IModelRequest, InferenceModel, ModelType, DefaultModels, ICompletions, IParams, IRemoteModel, - getCompletionPrompt, getInsertionPrompt, IStreamResponse, + getCompletionPrompt, getInsertionPrompt, IStreamResponse, buildSolgptPromt, RemoteInferencer, InsertionParams, CompletionParams, GenerationParams, - ChatEntry, AIRequestType, RemoteBackendOPModel, PromptBuilder + ChatEntry, AIRequestType, RemoteBackendOPModel, ChatHistory } \ No newline at end of file diff --git a/libs/remix-ai-core/src/inferencers/remote/remoteInference.ts b/libs/remix-ai-core/src/inferencers/remote/remoteInference.ts index 4e3f911916..1c35c3373e 100644 --- a/libs/remix-ai-core/src/inferencers/remote/remoteInference.ts +++ b/libs/remix-ai-core/src/inferencers/remote/remoteInference.ts @@ -1,14 +1,14 @@ -import { ICompletions, IParams, ChatEntry, AIRequestType, RemoteBackendOPModel } from "../../types/types"; -import { PromptBuilder } from "../../prompts/promptBuilder"; +import { ICompletions, IParams, AIRequestType, RemoteBackendOPModel } from "../../types/types"; +import { buildSolgptPromt } from "../../prompts/promptBuilder"; import axios from "axios"; import EventEmitter from "events"; +import { ChatHistory } from "../../prompts/chat"; const defaultErrorMessage = `Unable to get a response from AI server` export class RemoteInferencer implements ICompletions { api_url: string completion_url: string - solgpt_chat_history:ChatEntry[] max_history = 7 model_op = RemoteBackendOPModel.DEEPSEEK event: EventEmitter @@ -16,20 +16,14 @@ export class RemoteInferencer implements ICompletions { constructor(apiUrl?:string, completionUrl?:string) { this.api_url = apiUrl!==undefined ? apiUrl: "https://solcoder.remixproject.org" this.completion_url = completionUrl!==undefined ? completionUrl : "https://completion.remixproject.org" - this.solgpt_chat_history = [] this.event = new EventEmitter() } - private pushChatHistory(prompt, result){ - const chat:ChatEntry = [prompt, result.data[0]] - this.solgpt_chat_history.push(chat) - if (this.solgpt_chat_history.length > this.max_history){this.solgpt_chat_history.shift()} - } - private async _makeRequest(data, rType:AIRequestType){ this.event.emit("onInference") const requesURL = rType === AIRequestType.COMPLETION ? this.completion_url : this.api_url - console.log("requesting on ", requesURL, rType, data.data[1]) + const userPrompt = data.data[0] + console.log('userPrompt reuesting...') try { const result = await axios(requesURL, { @@ -51,7 +45,7 @@ export class RemoteInferencer implements ICompletions { case AIRequestType.GENERAL: if (result.statusText === "OK") { const resultText = result.data.data[0] - this.pushChatHistory(prompt, resultText) + ChatHistory.pushHistory(userPrompt, resultText) return resultText } else { return defaultErrorMessage @@ -59,7 +53,8 @@ export class RemoteInferencer implements ICompletions { } } catch (e) { - this.solgpt_chat_history = [] + ChatHistory.clearHistory() + console.error('Error making request to Inference server:', e.message) return e } finally { @@ -71,32 +66,37 @@ export class RemoteInferencer implements ICompletions { try { this.event.emit('onInference') const requesURL = rType === AIRequestType.COMPLETION ? this.completion_url : this.api_url - const options = { headers: { 'Content-Type': 'application/json', "Accept": "text/event-stream" } } + const userPrompt = data.data[0] const response = await axios({ method: 'post', - url: rType === AIRequestType.COMPLETION ? this.completion_url : this.api_url, + url: requesURL, data: data, headers: { 'Content-Type': 'application/json', "Accept": "text/event-stream" }, responseType: 'stream' }); + let resultText = "" response.data.on('data', (chunk: Buffer) => { try { const parsedData = JSON.parse(chunk.toString()); if (parsedData.isGenerating) { this.event.emit('onStreamResult', parsedData.generatedText); + resultText = resultText + parsedData.generatedText } else { + // stream generation is complete + resultText = resultText + parsedData.generatedText + ChatHistory.pushHistory(userPrompt, resultText) return parsedData.generatedText } - } catch (error) { console.error('Error parsing JSON:', error); + ChatHistory.clearHistory() } - }); return "" // return empty string for now as handled in event } catch (error) { + ChatHistory.clearHistory() console.error('Error making stream request to Inference server:', error.message); } finally { @@ -125,7 +125,7 @@ export class RemoteInferencer implements ICompletions { } async solidity_answer(prompt): Promise { - const main_prompt = this._build_solgpt_promt(prompt) + const main_prompt = buildSolgptPromt(prompt, this.model_op) const payload = { "data":[main_prompt, "solidity_answer", false,2000,0.9,0.8,50]} return this._makeRequest(payload, AIRequestType.GENERAL) } @@ -139,19 +139,4 @@ export class RemoteInferencer implements ICompletions { const payload = { "data":[prompt, "error_explaining", false,2000,0.9,0.8,50]} return this._makeRequest(payload, AIRequestType.GENERAL) } - - private _build_solgpt_promt(user_promt:string){ - if (this.solgpt_chat_history.length === 0){ - return user_promt - } else { - let new_promt = "" - for (const [question, answer] of this.solgpt_chat_history) { - new_promt += PromptBuilder(question.split('sol-gpt')[1], answer, this.model_op) - } - // finaly - new_promt = "sol-gpt " + new_promt + PromptBuilder(user_promt.split('sol-gpt')[1], "", this.model_op) - return new_promt - } - } - } diff --git a/libs/remix-ai-core/src/prompts/chat.ts b/libs/remix-ai-core/src/prompts/chat.ts new file mode 100644 index 0000000000..e4fe561d97 --- /dev/null +++ b/libs/remix-ai-core/src/prompts/chat.ts @@ -0,0 +1,21 @@ +import { ChatEntry } from "../types/types" + +export abstract class ChatHistory{ + + private static chatEntries:ChatEntry[] = [] + static queuSize:number = 7 // change the queue size wrt the GPU size + + public static pushHistory(prompt, result){ + const chat:ChatEntry = [prompt, result] + this.chatEntries.push(chat) + if (this.chatEntries.length > this.queuSize){this.chatEntries.shift()} + } + + public static getHistory(){ + return this.chatEntries + } + + public static clearHistory(){ + this.chatEntries = [] + } +} \ No newline at end of file diff --git a/libs/remix-ai-core/src/prompts/promptBuilder.ts b/libs/remix-ai-core/src/prompts/promptBuilder.ts index bff202e65a..8bd37ad900 100644 --- a/libs/remix-ai-core/src/prompts/promptBuilder.ts +++ b/libs/remix-ai-core/src/prompts/promptBuilder.ts @@ -1,7 +1,28 @@ import { RemoteBackendOPModel } from "../types/types" +import { ChatHistory } from "./chat" export const PromptBuilder = (inst, answr, modelop) => { if (modelop === RemoteBackendOPModel.CODELLAMA) return "" if (modelop === RemoteBackendOPModel.DEEPSEEK) return "\n### INSTRUCTION:\n" + inst + "\n### RESPONSE:\n" + answr if (modelop === RemoteBackendOPModel.MISTRAL) return "" +} + +export const buildSolgptPromt = (userPrompt:string, modelOP:RemoteBackendOPModel) => { + if (modelOP === undefined) { + console.log('WARNING: modelOP is undefined. Provide a valide model OP for chat history') + return userPrompt + } + if (ChatHistory.getHistory().length === 0){ + return userPrompt + } else { + let newPrompt = "" + for (const [question, answer] of ChatHistory.getHistory()) { + if (question.startsWith('sol-gpt')) newPrompt += PromptBuilder(question.split('sol-gpt')[1], answer, modelOP) + else if (question.startsWith('gpt')) newPrompt += PromptBuilder(question.split('sol-gpt')[1], answer, modelOP) + else newPrompt += PromptBuilder(question, answer, modelOP) + } + // finaly + newPrompt = "sol-gpt " + newPrompt + PromptBuilder(userPrompt.split('sol-gpt')[1], "", modelOP) + return newPrompt + } } \ No newline at end of file diff --git a/libs/remix-ai-core/src/types/models.ts b/libs/remix-ai-core/src/types/models.ts index ab53a3cc45..8229568825 100644 --- a/libs/remix-ai-core/src/types/models.ts +++ b/libs/remix-ai-core/src/types/models.ts @@ -2,12 +2,14 @@ // create a function getModels returning a list of all supported models // create a function getModel returning a model by its name -import { IModel, IParams } from './types'; +import { IModel, IParams, RemoteBackendOPModel } from './types'; import { ModelType } from './constants'; + const DefaultModels = (): IModel[] => { const model1:IModel = { name: 'DeepSeek', + modelOP: RemoteBackendOPModel.DEEPSEEK, task: 'text-generation', modelName: 'deepseek-coder-1.3b-instruct.gguf', downloadUrl: 'https://huggingface.co/TheBloke/deepseek-coder-1.3b-instruct-GGUF/resolve/main/deepseek-coder-1.3b-instruct.Q4_K_M.gguf?download=true', @@ -16,6 +18,7 @@ const DefaultModels = (): IModel[] => { }; const model2: IModel = { name: 'DeepSeek', + modelOP: RemoteBackendOPModel.DEEPSEEK, task: 'text-generation', modelName: 'deepseek-coder-6.7b-instruct.gguf', downloadUrl: 'https://huggingface.co/TheBloke/deepseek-coder-6.7B-instruct-GGUF/resolve/main/deepseek-coder-6.7b-instruct.Q4_K_M.gguf?download=true', @@ -24,6 +27,7 @@ const DefaultModels = (): IModel[] => { }; const model3: IModel = { name: 'DeepSeekTransformer', + modelOP: RemoteBackendOPModel.DEEPSEEK, task: 'text-generation', modelName: 'Xenova/deepseek-coder-1.3b-base', downloadUrl: 'Xenova/deepseek-coder-1.3b-base', @@ -32,6 +36,7 @@ const DefaultModels = (): IModel[] => { }; const model4: IModel = { name: 'DeepSeek', + modelOP: RemoteBackendOPModel.DEEPSEEK, task: 'text-generation', modelName: 'deepseek-coder-1.3b-base.gguf', downloadUrl: 'https://huggingface.co/TheBloke/deepseek-coder-1.3b-base-GGUF/resolve/main/deepseek-coder-1.3b-base.Q4_K_M.gguf?download=true', @@ -41,6 +46,7 @@ const DefaultModels = (): IModel[] => { const model5: IModel = { name: 'DeepSeek', + modelOP: RemoteBackendOPModel.DEEPSEEK, task: 'text-generation', modelName: 'deepseek-coder-6.7B-base-GGUF', downloadUrl: 'https://huggingface.co/TheBloke/deepseek-coder-6.7B-base-GGUF/resolve/main/deepseek-coder-6.7b-base.Q4_K_M.gguf?download=true', @@ -50,6 +56,7 @@ const DefaultModels = (): IModel[] => { const model6: IModel = { name: 'DeepSeek', + modelOP: RemoteBackendOPModel.DEEPSEEK, task: 'text-generation', modelName: 'DeepSeek-Coder-V2-Lite-Base.Q2_K.gguf', downloadUrl: 'https://huggingface.co/QuantFactory/DeepSeek-Coder-V2-Lite-Base-GGUF/resolve/main/DeepSeek-Coder-V2-Lite-Base.Q2_K.gguf?download=true', @@ -87,7 +94,7 @@ const GenerationParams:IParams = { topK: 40, topP: 0.92, max_new_tokens: 2000, - stream_result: true, + stream_result: false, } export { DefaultModels, CompletionParams, InsertionParams, GenerationParams } \ No newline at end of file diff --git a/libs/remix-ai-core/src/types/types.ts b/libs/remix-ai-core/src/types/types.ts index 5992c4a641..fe7ac5469d 100644 --- a/libs/remix-ai-core/src/types/types.ts +++ b/libs/remix-ai-core/src/types/types.ts @@ -18,6 +18,8 @@ export interface IModel { modelType: ModelType; modelReqs: IModelRequirements; downloadPath?: string; + modelOP?: RemoteBackendOPModel; + } export interface IRemoteModel { completionUrl: string;