From fa79803836221677e8be04a54295b9d62fae3939 Mon Sep 17 00:00:00 2001 From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com> Date: Thu, 29 Aug 2024 23:06:58 +0200 Subject: [PATCH] Add OpenAI Compatible provider --- CMakeLists.txt | 1 + QodeAssistConstants.hpp | 1 + QodeAssistSettings.cpp | 18 ++-- QodeAssistSettings.hpp | 4 +- providers/OpenAICompatProvider.cpp | 129 +++++++++++++++++++++++++++++ providers/OpenAICompatProvider.h | 40 +++++++++ qodeassist.cpp | 2 + 7 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 providers/OpenAICompatProvider.cpp create mode 100644 providers/OpenAICompatProvider.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bef282..a269b24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,7 @@ add_qtc_plugin(QodeAssist providers/LLMProvider.hpp providers/OllamaProvider.hpp providers/OllamaProvider.cpp providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp + providers/OpenAICompatProvider.h providers/OpenAICompatProvider.cpp LLMProvidersManager.hpp LLMProvidersManager.cpp QodeAssistSettings.hpp QodeAssistSettings.cpp QodeAssist.qrc diff --git a/QodeAssistConstants.hpp b/QodeAssistConstants.hpp index 3478190..b6e4be9 100644 --- a/QodeAssistConstants.hpp +++ b/QodeAssistConstants.hpp @@ -54,6 +54,7 @@ const char MAX_FILE_THRESHOLD[] = "QodeAssist.maxFileThreshold"; const char OLLAMA_LIVETIME[] = "QodeAssist.ollamaLivetime"; const char SPECIFIC_INSTRUCTIONS[] = "QodeAssist.specificInstractions"; const char MULTILINE_COMPLETION[] = "QodeAssist.multilineCompletion"; +const char API_KEY[] = "QodeAssist.apiKey"; const char QODE_ASSIST_GENERAL_OPTIONS_ID[] = "QodeAssist.GeneralOptions"; const char QODE_ASSIST_GENERAL_OPTIONS_CATEGORY[] = "QodeAssist.Category"; diff --git a/QodeAssistSettings.cpp b/QodeAssistSettings.cpp index 77b3b59..43a4f33 100644 --- a/QodeAssistSettings.cpp +++ b/QodeAssistSettings.cpp @@ -123,15 +123,15 @@ QodeAssistSettings::QodeAssistSettings() topP.setSettingsKey(Constants::TOP_P); topP.setLabelText(Tr::tr("top_p")); topP.setDefaultValue(0.9); - topP.setRange(0.0, 10.0); + topP.setRange(0.0, 1.0); useTopK.setSettingsKey(Constants::USE_TOP_K); useTopK.setDefaultValue(false); topK.setSettingsKey(Constants::TOP_K); topK.setLabelText(Tr::tr("top_k")); - topK.setDefaultValue(0.1); - topK.setRange(0, 10.0); + topK.setDefaultValue(50); + topK.setRange(1, 1000); usePresencePenalty.setSettingsKey(Constants::USE_PRESENCE_PENALTY); usePresencePenalty.setDefaultValue(false); @@ -170,6 +170,11 @@ QodeAssistSettings::QodeAssistSettings() multiLineCompletion.setDefaultValue(true); multiLineCompletion.setLabelText(Tr::tr("Enable Multiline Completion")); + apiKey.setSettingsKey(Constants::API_KEY); + apiKey.setLabelText(Tr::tr("API Key:")); + apiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay); + apiKey.setPlaceHolderText(Tr::tr("Enter your API key here")); + const auto &manager = LLMProvidersManager::instance(); if (!manager.getProviderNames().isEmpty()) { const auto providerNames = manager.getProviderNames(); @@ -215,14 +220,15 @@ QodeAssistSettings::QodeAssistSettings() Form{Column{Row{selectModels, modelName}}}}, Group{title(Tr::tr("FIM Prompt Settings")), Form{Column{fimPrompts, + readFullFile, maxFileThreshold, + readStringsBeforeCursor, + readStringsAfterCursor, ollamaLivetime, + apiKey, specificInstractions, temperature, maxTokens, - readFullFile, - readStringsBeforeCursor, - readStringsAfterCursor, startSuggestionTimer, Row{useTopP, topP, Stretch{1}}, Row{useTopK, topK, Stretch{1}}, diff --git a/QodeAssistSettings.hpp b/QodeAssistSettings.hpp index 32cba11..da6690e 100644 --- a/QodeAssistSettings.hpp +++ b/QodeAssistSettings.hpp @@ -81,7 +81,7 @@ public: Utils::DoubleAspect topP{this}; Utils::BoolAspect useTopK{this}; - Utils::DoubleAspect topK{this}; + Utils::IntegerAspect topK{this}; Utils::BoolAspect usePresencePenalty{this}; Utils::DoubleAspect presencePenalty{this}; @@ -98,6 +98,8 @@ public: Utils::StringAspect specificInstractions{this}; Utils::BoolAspect multiLineCompletion{this}; + Utils::StringAspect apiKey{this}; + ButtonAspect resetToDefaults{this}; private: diff --git a/providers/OpenAICompatProvider.cpp b/providers/OpenAICompatProvider.cpp new file mode 100644 index 0000000..3b7c712 --- /dev/null +++ b/providers/OpenAICompatProvider.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +#include "OpenAICompatProvider.h" + +#include +#include +#include +#include +#include + +#include "PromptTemplateManager.hpp" +#include "QodeAssistSettings.hpp" + +namespace QodeAssist::Providers { + +OpenAICompatProvider::OpenAICompatProvider() {} + +QString OpenAICompatProvider::name() const +{ + return "OpenAI Compatible"; +} + +QString OpenAICompatProvider::url() const +{ + return "http://localhost"; +} + +int OpenAICompatProvider::defaultPort() const +{ + return 1234; +} + +QString OpenAICompatProvider::completionEndpoint() const +{ + return "/v1/chat/completions"; +} + +void OpenAICompatProvider::prepareRequest(QJsonObject &request) +{ + const auto ¤tTemplate = PromptTemplateManager::instance().getCurrentTemplate(); + + if (request.contains("prompt")) { + QJsonArray messages{ + {QJsonObject{{"role", "user"}, {"content", request.take("prompt").toString()}}}}; + request["messages"] = std::move(messages); + } + + request["max_tokens"] = settings().maxTokens(); + request["temperature"] = settings().temperature(); + request["stop"] = QJsonArray::fromStringList(currentTemplate->stopWords()); + if (settings().useTopP()) + request["top_p"] = settings().topP(); + if (settings().useTopK()) + request["top_k"] = settings().topK(); + if (settings().useFrequencyPenalty()) + request["frequency_penalty"] = settings().frequencyPenalty(); + if (settings().usePresencePenalty()) + request["presence_penalty"] = settings().presencePenalty(); + + const QString &apiKey = settings().apiKey.value(); + if (!apiKey.isEmpty()) { + request["api_key"] = apiKey; + } +} + +bool OpenAICompatProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse) +{ + bool isComplete = false; + while (reply->canReadLine()) { + QByteArray line = reply->readLine().trimmed(); + if (line.isEmpty()) { + continue; + } + if (line == "data: [DONE]") { + isComplete = true; + break; + } + if (line.startsWith("data: ")) { + line = line.mid(6); // Remove "data: " prefix + } + QJsonDocument jsonResponse = QJsonDocument::fromJson(line); + if (jsonResponse.isNull()) { + qWarning() << "Invalid JSON response from LM Studio:" << line; + continue; + } + QJsonObject responseObj = jsonResponse.object(); + if (responseObj.contains("choices")) { + QJsonArray choices = responseObj["choices"].toArray(); + if (!choices.isEmpty()) { + QJsonObject choice = choices.first().toObject(); + QJsonObject delta = choice["delta"].toObject(); + if (delta.contains("content")) { + QString completion = delta["content"].toString(); + + accumulatedResponse += completion; + } + if (choice["finish_reason"].toString() == "stop") { + isComplete = true; + break; + } + } + } + } + return isComplete; +} + +QList OpenAICompatProvider::getInstalledModels(const Utils::Environment &env) +{ + return QStringList(); +} + +} // namespace QodeAssist::Providers diff --git a/providers/OpenAICompatProvider.h b/providers/OpenAICompatProvider.h new file mode 100644 index 0000000..edb94a7 --- /dev/null +++ b/providers/OpenAICompatProvider.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +#pragma once + +#include "LLMProvider.hpp" + +namespace QodeAssist::Providers { + +class OpenAICompatProvider : public LLMProvider +{ +public: + OpenAICompatProvider(); + + QString name() const override; + QString url() const override; + int defaultPort() const override; + QString completionEndpoint() const override; + void prepareRequest(QJsonObject &request) override; + bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override; + QList getInstalledModels(const Utils::Environment &env) override; +}; + +} // namespace QodeAssist::Providers diff --git a/qodeassist.cpp b/qodeassist.cpp index bb37aba..90fd069 100644 --- a/qodeassist.cpp +++ b/qodeassist.cpp @@ -43,6 +43,7 @@ #include "QodeAssistClient.hpp" #include "providers/LMStudioProvider.hpp" #include "providers/OllamaProvider.hpp" +#include "providers/OpenAICompatProvider.h" #include "templates/CodeLLamaTemplate.hpp" #include "templates/CodeQwenChat.hpp" #include "templates/StarCoder2Template.hpp" @@ -73,6 +74,7 @@ public: auto &providerManager = LLMProvidersManager::instance(); providerManager.registerProvider(); providerManager.registerProvider(); + providerManager.registerProvider(); auto &templateManager = PromptTemplateManager::instance(); templateManager.registerTemplate();