diff --git a/CMakeLists.txt b/CMakeLists.txt index af6193c..5616d1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,10 +32,12 @@ add_qtc_plugin(QodeAssist LLMClientInterface.hpp LLMClientInterface.cpp PromptTemplateManager.hpp PromptTemplateManager.cpp templates/PromptTemplate.hpp - templates/CodeLLamaTemplate.hpp + templates/CodeLlamaFimTemplate.hpp templates/StarCoder2Template.hpp templates/DeepSeekCoderV2.hpp templates/CustomTemplate.hpp + templates/DeepSeekCoderChatTemplate.hpp + templates/CodeLlamaInstruct.hpp providers/LLMProvider.hpp providers/OllamaProvider.hpp providers/OllamaProvider.cpp providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp diff --git a/LLMClientInterface.cpp b/LLMClientInterface.cpp index 01e17fa..6dd25ae 100644 --- a/LLMClientInterface.cpp +++ b/LLMClientInterface.cpp @@ -148,13 +148,16 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request) auto updatedContext = prepareContext(request); LLMConfig config; - config.provider = LLMProvidersManager::instance().getCurrentProvider(); - config.promptTemplate = PromptTemplateManager::instance().getCurrentTemplate(); + config.requestType = RequestType::Fim; + config.provider = LLMProvidersManager::instance().getCurrentFimProvider(); + config.promptTemplate = PromptTemplateManager::instance().getCurrentFimTemplate(); config.url = QUrl(QString("%1%2").arg(Settings::generalSettings().url(), Settings::generalSettings().endPoint())); config.providerRequest = {{"model", Settings::generalSettings().modelName.value()}, - {"stream", true}}; + {"stream", true}, + {"stop", + QJsonArray::fromStringList(config.promptTemplate->stopWords())}}; config.promptTemplate->prepareRequest(config.providerRequest, updatedContext); config.provider->prepareRequest(config.providerRequest); diff --git a/LLMProvidersManager.cpp b/LLMProvidersManager.cpp index 7fdccb8..ebbaad1 100644 --- a/LLMProvidersManager.cpp +++ b/LLMProvidersManager.cpp @@ -19,6 +19,8 @@ #include "LLMProvidersManager.hpp" +#include "QodeAssistUtils.hpp" + namespace QodeAssist { LLMProvidersManager &LLMProvidersManager::instance() @@ -27,25 +29,53 @@ LLMProvidersManager &LLMProvidersManager::instance() return instance; } -QStringList LLMProvidersManager::getProviderNames() const +Providers::LLMProvider *LLMProvidersManager::setCurrentFimProvider(const QString &name) { - return m_providers.keys(); + logMessage("Setting current FIM provider to: " + name); + if (!m_providers.contains(name)) { + logMessage("Can't find provider with name: " + name); + return nullptr; + } + + m_currentFimProvider = m_providers[name]; + return m_currentFimProvider; } -void LLMProvidersManager::setCurrentProvider(const QString &name) +Providers::LLMProvider *LLMProvidersManager::setCurrentChatProvider(const QString &name) { - if (m_providers.contains(name)) { - m_currentProviderName = name; + logMessage("Setting current chat provider to: " + name); + if (!m_providers.contains(name)) { + logMessage("Can't find chat provider with name: " + name); + return nullptr; } + + m_currentChatProvider = m_providers[name]; + return m_currentChatProvider; } -Providers::LLMProvider *LLMProvidersManager::getCurrentProvider() +Providers::LLMProvider *LLMProvidersManager::getCurrentFimProvider() { - if (m_currentProviderName.isEmpty()) { + if (m_currentFimProvider == nullptr) { + logMessage("Current fim provider is null"); return nullptr; } - return m_providers[m_currentProviderName]; + return m_currentFimProvider; +} + +Providers::LLMProvider *LLMProvidersManager::getCurrentChatProvider() +{ + if (m_currentChatProvider == nullptr) { + logMessage("Current chat provider is null"); + return nullptr; + } + + return m_currentChatProvider; +} + +QStringList LLMProvidersManager::providersNames() const +{ + return m_providers.keys(); } LLMProvidersManager::~LLMProvidersManager() diff --git a/LLMProvidersManager.hpp b/LLMProvidersManager.hpp index c9698b6..b6f3305 100644 --- a/LLMProvidersManager.hpp +++ b/LLMProvidersManager.hpp @@ -29,6 +29,7 @@ class LLMProvidersManager { public: static LLMProvidersManager &instance(); + ~LLMProvidersManager(); template void registerProvider() @@ -40,11 +41,13 @@ class LLMProvidersManager m_providers[name] = provider; } - QStringList getProviderNames() const; - void setCurrentProvider(const QString &name); - Providers::LLMProvider *getCurrentProvider(); + Providers::LLMProvider *setCurrentFimProvider(const QString &name); + Providers::LLMProvider *setCurrentChatProvider(const QString &name); - ~LLMProvidersManager(); + Providers::LLMProvider *getCurrentFimProvider(); + Providers::LLMProvider *getCurrentChatProvider(); + + QStringList providersNames() const; private: LLMProvidersManager() = default; @@ -52,7 +55,8 @@ class LLMProvidersManager LLMProvidersManager &operator=(const LLMProvidersManager &) = delete; QMap m_providers; - QString m_currentProviderName; + Providers::LLMProvider *m_currentFimProvider = nullptr; + Providers::LLMProvider *m_currentChatProvider = nullptr; }; } // namespace QodeAssist diff --git a/PromptTemplateManager.cpp b/PromptTemplateManager.cpp index 80de111..559f014 100644 --- a/PromptTemplateManager.cpp +++ b/PromptTemplateManager.cpp @@ -19,6 +19,8 @@ #include "PromptTemplateManager.hpp" +#include "QodeAssistUtils.hpp" + namespace QodeAssist { PromptTemplateManager &PromptTemplateManager::instance() @@ -27,27 +29,60 @@ PromptTemplateManager &PromptTemplateManager::instance() return instance; } -void PromptTemplateManager::setCurrentTemplate(const QString &name) +void PromptTemplateManager::setCurrentFimTemplate(const QString &name) +{ + logMessage("Setting current FIM provider to: " + name); + if (!m_fimTemplates.contains(name) || m_fimTemplates[name] == nullptr) { + logMessage("Error to set current FIM template" + name); + return; + } + + m_currentFimTemplate = m_fimTemplates[name]; +} + +Templates::PromptTemplate *PromptTemplateManager::getCurrentFimTemplate() +{ + if (m_currentFimTemplate == nullptr) { + logMessage("Current fim provider is null"); + return nullptr; + } + + return m_currentFimTemplate; +} + +void PromptTemplateManager::setCurrentChatTemplate(const QString &name) { - if (m_templates.contains(name)) { - m_currentTemplateName = name; + logMessage("Setting current chat provider to: " + name); + if (!m_chatTemplates.contains(name) || m_chatTemplates[name] == nullptr) { + logMessage("Error to set current chat template" + name); + return; } + + m_currentChatTemplate = m_chatTemplates[name]; +} + +Templates::PromptTemplate *PromptTemplateManager::getCurrentChatTemplate() +{ + if (m_currentChatTemplate == nullptr) + logMessage("Current chat provider is null"); + + return m_currentChatTemplate; } -Templates::PromptTemplate *PromptTemplateManager::getCurrentTemplate() +QStringList PromptTemplateManager::fimTemplatesNames() const { - auto it = m_templates.find(m_currentTemplateName); - return it != m_templates.end() ? it.value() : nullptr; + return m_fimTemplates.keys(); } -QStringList PromptTemplateManager::getTemplateNames() const +QStringList PromptTemplateManager::chatTemplatesNames() const { - return m_templates.keys(); + return m_chatTemplates.keys(); } PromptTemplateManager::~PromptTemplateManager() { - qDeleteAll(m_templates); + qDeleteAll(m_fimTemplates); + qDeleteAll(m_chatTemplates); } } // namespace QodeAssist diff --git a/PromptTemplateManager.hpp b/PromptTemplateManager.hpp index 2b698f3..47347d4 100644 --- a/PromptTemplateManager.hpp +++ b/PromptTemplateManager.hpp @@ -30,6 +30,7 @@ class PromptTemplateManager { public: static PromptTemplateManager &instance(); + ~PromptTemplateManager(); template void registerTemplate() @@ -38,22 +39,31 @@ class PromptTemplateManager "T must inherit from PromptTemplate"); T *template_ptr = new T(); QString name = template_ptr->name(); - m_templates[name] = template_ptr; + if (template_ptr->type() == Templates::TemplateType::Fim) { + m_fimTemplates[name] = template_ptr; + } else if (template_ptr->type() == Templates::TemplateType::Chat) { + m_chatTemplates[name] = template_ptr; + } } - void setCurrentTemplate(const QString &name); - Templates::PromptTemplate *getCurrentTemplate(); - QStringList getTemplateNames() const; + void setCurrentFimTemplate(const QString &name); + Templates::PromptTemplate *getCurrentFimTemplate(); - ~PromptTemplateManager(); + void setCurrentChatTemplate(const QString &name); + Templates::PromptTemplate *getCurrentChatTemplate(); + + QStringList fimTemplatesNames() const; + QStringList chatTemplatesNames() const; private: PromptTemplateManager() = default; PromptTemplateManager(const PromptTemplateManager &) = delete; PromptTemplateManager &operator=(const PromptTemplateManager &) = delete; - QMap m_templates; - QString m_currentTemplateName; + QMap m_fimTemplates; + QMap m_chatTemplates; + Templates::PromptTemplate *m_currentFimTemplate; + Templates::PromptTemplate *m_currentChatTemplate; }; } // namespace QodeAssist diff --git a/QodeAssistConstants.hpp b/QodeAssistConstants.hpp index 8f51de7..fc5b6d8 100644 --- a/QodeAssistConstants.hpp +++ b/QodeAssistConstants.hpp @@ -61,6 +61,12 @@ const char USE_FILE_PATH_IN_CONTEXT[] = "QodeAssist.useFilePathInContext"; const char CUSTOM_JSON_TEMPLATE[] = "QodeAssist.customJsonTemplate"; const char USE_PROJECT_CHANGES_CACHE[] = "QodeAssist.useProjectChangesCache"; const char MAX_CHANGES_CACHE_SIZE[] = "QodeAssist.maxChangesCacheSize"; +const char CHAT_LLM_PROVIDERS[] = "QodeAssist.chatLlmProviders"; +const char CHAT_URL[] = "QodeAssist.chatUrl"; +const char CHAT_END_POINT[] = "QodeAssist.chatEndPoint"; +const char CHAT_MODEL_NAME[] = "QodeAssist.chatModelName"; +const char CHAT_SELECT_MODELS[] = "QodeAssist.chatSelectModels"; +const char CHAT_PROMPTS[] = "QodeAssist.chatPrompts"; const char QODE_ASSIST_GENERAL_OPTIONS_ID[] = "QodeAssist.GeneralOptions"; const char QODE_ASSIST_GENERAL_SETTINGS_PAGE_ID[] = "QodeAssist.1GeneralSettingsPageId"; diff --git a/QodeAssistUtils.hpp b/QodeAssistUtils.hpp index 63ab992..677c1a6 100644 --- a/QodeAssistUtils.hpp +++ b/QodeAssistUtils.hpp @@ -47,6 +47,7 @@ inline void logMessage(const QString &message, bool silent = true) return; const QString prefixedMessage = QLatin1String("[Qode Assist] ") + message; + qDebug() << prefixedMessage; if (silent) { Core::MessageManager::writeSilently(prefixedMessage); } else { @@ -60,6 +61,8 @@ inline void logMessages(const QStringList &messages, bool silent = true) return; QStringList prefixedMessages; + qDebug() << prefixedMessages; + for (const QString &message : messages) { prefixedMessages << (QLatin1String("[Qode Assist] ") + message); } diff --git a/chat/ChatClientInterface.cpp b/chat/ChatClientInterface.cpp index af718e7..34d7942 100644 --- a/chat/ChatClientInterface.cpp +++ b/chat/ChatClientInterface.cpp @@ -18,99 +18,136 @@ */ #include "ChatClientInterface.hpp" +#include "LLMProvidersManager.hpp" +#include "PromptTemplateManager.hpp" #include "QodeAssistUtils.hpp" +#include "settings/ContextSettings.hpp" +#include "settings/GeneralSettings.hpp" +#include "settings/PresetPromptsSettings.hpp" #include #include +#include namespace QodeAssist::Chat { ChatClientInterface::ChatClientInterface(QObject *parent) : QObject(parent) - , m_manager(new QNetworkAccessManager(this)) + , m_requestHandler(new LLMRequestHandler(this)) { - logMessage("ChatClientInterface initialized"); + connect(m_requestHandler, + &LLMRequestHandler::completionReceived, + this, + [this](const QString &completion, const QJsonObject &, bool isComplete) { + handleLLMResponse(completion, isComplete); + }); + + connect(m_requestHandler, + &LLMRequestHandler::requestFinished, + this, + [this](const QString &, bool success, const QString &errorString) { + if (!success) { + emit errorOccurred(errorString); + } + }); + + // QJsonObject systemMessage; + // systemMessage["role"] = "system"; + // systemMessage["content"] = "You are a helpful C++ and QML programming assistant."; + // m_chatHistory.append(systemMessage); } ChatClientInterface::~ChatClientInterface() { - logMessage("ChatClientInterface destroyed"); } void ChatClientInterface::sendMessage(const QString &message) { logMessage("Sending message: " + message); + logMessage("chatProvider " + Settings::generalSettings().chatLlmProviders.stringValue()); + logMessage("chatTemplate " + Settings::generalSettings().chatPrompts.stringValue()); - QJsonObject request; - prepareRequest(request, message); + auto chatTemplate = PromptTemplateManager::instance().getCurrentChatTemplate(); + auto chatProvider = LLMProvidersManager::instance().getCurrentChatProvider(); - QUrl url(SERVER_URL + ENDPOINT); - QNetworkRequest networkRequest(url); - networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + ContextData context; + context.prefix = message; + context.suffix = ""; + if (Settings::contextSettings().useSpecificInstructions()) + context.instriuctions = Settings::contextSettings().specificInstractions(); - logMessage("Sending request to URL: " + url.toString()); + QJsonObject providerRequest; + providerRequest["model"] = Settings::generalSettings().chatModelName(); + providerRequest["stream"] = true; - QNetworkReply *reply = m_manager->post(networkRequest, QJsonDocument(request).toJson()); + providerRequest["messages"] = m_chatHistory; - m_accumulatedResponse.clear(); + chatTemplate->prepareRequest(providerRequest, context); + chatProvider->prepareRequest(providerRequest); - connect(reply, &QNetworkReply::readyRead, this, [this, reply]() { - QByteArray data = reply->readAll(); - logMessage("Received data: " + QString::fromUtf8(data)); - processStreamedResponse(data); - }); - - connect(reply, &QNetworkReply::finished, this, [this, reply]() { - if (reply->error() != QNetworkReply::NoError) { - QString errorString = reply->errorString(); - logMessage("Network error: " + errorString); - emit errorOccurred(errorString); - } else { - logMessage("Message completed. Final response: " + m_accumulatedResponse); - if (!m_accumulatedResponse.isEmpty()) { - emit messageReceived(m_accumulatedResponse.trimmed()); - } else { - emit errorOccurred("Received empty response from server"); - } - } - reply->deleteLater(); - }); + m_chatHistory = providerRequest["messages"].toArray(); + + LLMConfig config; + config.requestType = RequestType::Chat; + config.provider = chatProvider; + config.promptTemplate = chatTemplate; + config.url = QString("%1%2").arg(Settings::generalSettings().chatUrl(), + Settings::generalSettings().chatEndPoint()); + config.providerRequest = providerRequest; + + QJsonObject request; + request["id"] = QUuid::createUuid().toString(); + + m_accumulatedResponse.clear(); + m_pendingMessage = message; + m_requestHandler->sendLLMRequest(config, request); } -void ChatClientInterface::prepareRequest(QJsonObject &request, const QString &message) +void ChatClientInterface::handleLLMResponse(const QString &response, bool isComplete) { - request["model"] = MODEL_NAME; + m_accumulatedResponse += response; + logMessage("Accumulated response: " + m_accumulatedResponse); + + if (isComplete) { + logMessage("Message completed. Final response: " + m_accumulatedResponse); + emit messageReceived(m_accumulatedResponse.trimmed()); - QJsonArray messages = {QJsonObject{{"role", "user"}, {"content", message}}}; - request["messages"] = messages; + QJsonObject assistantMessage; + assistantMessage["role"] = "assistant"; + assistantMessage["content"] = m_accumulatedResponse.trimmed(); + m_chatHistory.append(assistantMessage); - request["temperature"] = 0.7; - request["stream"] = true; + m_pendingMessage.clear(); + m_accumulatedResponse.clear(); - logMessage("Prepared chat request: " + QString::fromUtf8(QJsonDocument(request).toJson())); + trimChatHistory(); + } } -void ChatClientInterface::processStreamedResponse(const QByteArray &data) +void ChatClientInterface::trimChatHistory() { - QJsonDocument doc = QJsonDocument::fromJson(data); - if (!doc.isObject()) { - logMessage("Received invalid JSON data"); - return; + int maxTokens = 4000; + int totalTokens = 0; + QJsonArray newHistory; + + if (!m_chatHistory.isEmpty() + && m_chatHistory.first().toObject()["role"].toString() == "system") { + newHistory.append(m_chatHistory.first()); } - QJsonObject responseObj = doc.object(); - if (responseObj.contains("message")) { - QJsonObject message = responseObj["message"].toObject(); - if (message.contains("content")) { - QString content = message["content"].toString(); - m_accumulatedResponse += content; - logMessage("Accumulated response: " + m_accumulatedResponse); + for (int i = m_chatHistory.size() - 1; i >= 0; --i) { + QJsonObject message = m_chatHistory[i].toObject(); + int messageTokens = message["content"].toString().length() / 4; + + if (totalTokens + messageTokens > maxTokens) { + break; } - } - if (responseObj.contains("done") && responseObj["done"].toBool()) { - logMessage("Stream ended. Final accumulated response: " + m_accumulatedResponse); + newHistory.prepend(message); + totalTokens += messageTokens; } + + m_chatHistory = newHistory; } } // namespace QodeAssist::Chat diff --git a/chat/ChatClientInterface.hpp b/chat/ChatClientInterface.hpp index 4af173f..f1d7b69 100644 --- a/chat/ChatClientInterface.hpp +++ b/chat/ChatClientInterface.hpp @@ -19,12 +19,10 @@ #pragma once -#include -#include -#include #include - +#include #include "QodeAssistData.hpp" +#include "core/LLMRequestHandler.hpp" namespace QodeAssist::Chat { @@ -42,21 +40,14 @@ class ChatClientInterface : public QObject void messageReceived(const QString &message); void errorOccurred(const QString &error); -protected: - void prepareRequest(QJsonObject &request, const QString &message); - void processStreamedResponse(const QByteArray &data); +private: + void handleLLMResponse(const QString &response, bool isComplete); + void trimChatHistory(); - QNetworkAccessManager *m_manager; + LLMRequestHandler *m_requestHandler; QString m_accumulatedResponse; - - // Захардкоженные значения - const QString MODEL_NAME = "codellama:7b-instruct"; - const QString SERVER_URL = "http://localhost:11434"; - const QString ENDPOINT = "/api/chat"; + QString m_pendingMessage; + QJsonArray m_chatHistory; }; } // namespace QodeAssist::Chat -// Захардкоженные значения -// const QString MODEL_NAME = "codellama:7b-instruct"; -// const QString SERVER_URL = "http://localhost:11434"; -// const QString ENDPOINT = "/api/chat"; diff --git a/chat/ChatOutputPane.h b/chat/ChatOutputPane.h index 7c340cb..12e8267 100644 --- a/chat/ChatOutputPane.h +++ b/chat/ChatOutputPane.h @@ -32,7 +32,6 @@ class ChatOutputPane : public Core::IOutputPane explicit ChatOutputPane(QObject *parent = nullptr); ~ChatOutputPane() override; - // Переопределенные методы из IOutputPane QWidget *outputWidget(QWidget *parent) override; QList toolBarWidgets() const override; void clearContents() override; diff --git a/chat/ChatWidget.cpp b/chat/ChatWidget.cpp index b24e0ce..dadc8f0 100644 --- a/chat/ChatWidget.cpp +++ b/chat/ChatWidget.cpp @@ -30,7 +30,7 @@ namespace QodeAssist::Chat { ChatWidget::ChatWidget(QWidget *parent) : QWidget(parent) - , m_showTimestamp(true) + , m_showTimestamp(false) , m_chatClient(new ChatClientInterface(this)) { setupUi(); diff --git a/core/LLMRequestConfig.hpp b/core/LLMRequestConfig.hpp index 33ccdb2..10b6ca2 100644 --- a/core/LLMRequestConfig.hpp +++ b/core/LLMRequestConfig.hpp @@ -7,12 +7,15 @@ namespace QodeAssist { +enum class RequestType { Fim, Chat }; + struct LLMConfig { QUrl url; Providers::LLMProvider *provider; Templates::PromptTemplate *promptTemplate; QJsonObject providerRequest; + RequestType requestType; }; } // namespace QodeAssist diff --git a/core/LLMRequestHandler.cpp b/core/LLMRequestHandler.cpp index 7fa3976..bb5b3ae 100644 --- a/core/LLMRequestHandler.cpp +++ b/core/LLMRequestHandler.cpp @@ -18,6 +18,7 @@ */ #include "LLMRequestHandler.hpp" +#include "LLMProvidersManager.hpp" #include "QodeAssistUtils.hpp" #include "settings/GeneralSettings.hpp" @@ -72,20 +73,28 @@ void LLMRequestHandler::handleLLMResponse(QNetworkReply *reply, const QJsonObject &request, const LLMConfig &config) { + qDebug() << "Handling LLM response" << request; + QString &accumulatedResponse = m_accumulatedResponses[reply]; bool isComplete = config.provider->handleResponse(reply, accumulatedResponse); - if (!Settings::generalSettings().multiLineCompletion() - && processSingleLineCompletion(reply, request, accumulatedResponse, config)) { - return; + if (config.requestType == RequestType::Fim) { + if (!Settings::generalSettings().multiLineCompletion() + && processSingleLineCompletion(reply, request, accumulatedResponse, config)) { + return; + } } if (isComplete || reply->isFinished()) { if (isComplete) { - auto cleanedCompletion = removeStopWords(accumulatedResponse, - config.promptTemplate->stopWords()); - emit completionReceived(cleanedCompletion, request, true); + if (config.requestType == RequestType::Fim) { + auto cleanedCompletion = removeStopWords(accumulatedResponse, + config.promptTemplate->stopWords()); + emit completionReceived(cleanedCompletion, request, true); + } else { + emit completionReceived(accumulatedResponse, request, true); + } } else { emit completionReceived(accumulatedResponse, request, false); } diff --git a/providers/LLMProvider.hpp b/providers/LLMProvider.hpp index 5e4799d..521231d 100644 --- a/providers/LLMProvider.hpp +++ b/providers/LLMProvider.hpp @@ -35,6 +35,7 @@ class LLMProvider virtual QString name() const = 0; virtual QString url() const = 0; virtual QString completionEndpoint() const = 0; + virtual QString chatEndpoint() const = 0; virtual void prepareRequest(QJsonObject &request) = 0; virtual void prepareChatRequest(QJsonObject &request) = 0; diff --git a/providers/LMStudioProvider.cpp b/providers/LMStudioProvider.cpp index 9f6cd3b..63b7672 100644 --- a/providers/LMStudioProvider.cpp +++ b/providers/LMStudioProvider.cpp @@ -48,12 +48,14 @@ QString LMStudioProvider::completionEndpoint() const return "/v1/chat/completions"; } +QString LMStudioProvider::chatEndpoint() const +{ + return "/v1/chat/completions"; +} + void LMStudioProvider::prepareRequest(QJsonObject &request) { auto &settings = Settings::presetPromptsSettings(); - const auto ¤tTemplate = PromptTemplateManager::instance().getCurrentTemplate(); - if (currentTemplate->name() == "Custom Template") - return; if (request.contains("prompt")) { QJsonArray messages{ {QJsonObject{{"role", "user"}, {"content", request.take("prompt").toString()}}}}; @@ -62,7 +64,6 @@ void LMStudioProvider::prepareRequest(QJsonObject &request) 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()) diff --git a/providers/LMStudioProvider.hpp b/providers/LMStudioProvider.hpp index d5a201a..5c6f700 100644 --- a/providers/LMStudioProvider.hpp +++ b/providers/LMStudioProvider.hpp @@ -31,6 +31,7 @@ class LMStudioProvider : public LLMProvider QString name() const override; QString url() const override; QString completionEndpoint() const override; + QString chatEndpoint() const override; void prepareRequest(QJsonObject &request) override; void prepareChatRequest(QJsonObject &request) override; bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override; diff --git a/providers/OllamaProvider.cpp b/providers/OllamaProvider.cpp index 4fc70a2..52b9d00 100644 --- a/providers/OllamaProvider.cpp +++ b/providers/OllamaProvider.cpp @@ -48,18 +48,19 @@ QString OllamaProvider::completionEndpoint() const return "/api/generate"; } +QString OllamaProvider::chatEndpoint() const +{ + return "/api/chat"; +} + void OllamaProvider::prepareRequest(QJsonObject &request) { auto &settings = Settings::presetPromptsSettings(); - auto currentTemplate = PromptTemplateManager::instance().getCurrentTemplate(); - if (currentTemplate->name() == "Custom Template") - return; QJsonObject options; options["num_predict"] = settings.maxTokens(); options["keep_alive"] = settings.ollamaLivetime(); options["temperature"] = settings.temperature(); - options["stop"] = QJsonArray::fromStringList(currentTemplate->stopWords()); if (settings.useTopP()) options["top_p"] = settings.topP(); if (settings.useTopK()) @@ -75,28 +76,60 @@ void OllamaProvider::prepareChatRequest(QJsonObject &request) {} bool OllamaProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse) { + QString endpoint = reply->url().path(); + logMessage("Handling response from endpoint: " + endpoint); + bool isComplete = false; while (reply->canReadLine()) { QByteArray line = reply->readLine().trimmed(); if (line.isEmpty()) { continue; } - QJsonDocument jsonResponse = QJsonDocument::fromJson(line); - if (jsonResponse.isNull()) { - qWarning() << "Invalid JSON response from Ollama:" << line; + + logMessage("Raw response from Ollama: " + QString::fromUtf8(line)); + + QJsonDocument doc = QJsonDocument::fromJson(line); + if (doc.isNull()) { + logMessage("Invalid JSON response from Ollama: " + QString::fromUtf8(line)); continue; } - QJsonObject responseObj = jsonResponse.object(); - if (responseObj.contains("response")) { - QString completion = responseObj["response"].toString(); - accumulatedResponse += completion; + QJsonObject responseObj = doc.object(); + + if (responseObj.contains("error")) { + QString errorMessage = responseObj["error"].toString(); + logMessage("Error in Ollama response: " + errorMessage); + return false; + } + + if (endpoint == completionEndpoint()) { + // Обработка ответа от /api/generate + if (responseObj.contains("response")) { + QString completion = responseObj["response"].toString(); + accumulatedResponse += completion; + logMessage("Accumulated response from /api/generate: " + accumulatedResponse); + } + } else if (endpoint == chatEndpoint()) { + // Обработка ответа от /api/chat + if (responseObj.contains("message")) { + QJsonObject message = responseObj["message"].toObject(); + if (message.contains("content")) { + QString content = message["content"].toString(); + accumulatedResponse += content; + logMessage("Accumulated response from /api/chat: " + accumulatedResponse); + } + } + } else { + logMessage("Unknown endpoint: " + endpoint); } - if (responseObj["done"].toBool()) { + + if (responseObj.contains("done") && responseObj["done"].toBool()) { isComplete = true; + logMessage("Stream ended. Final accumulated response: " + accumulatedResponse); break; } } + return isComplete; } diff --git a/providers/OllamaProvider.hpp b/providers/OllamaProvider.hpp index 8ecf58e..fbb6184 100644 --- a/providers/OllamaProvider.hpp +++ b/providers/OllamaProvider.hpp @@ -31,6 +31,7 @@ class OllamaProvider : public LLMProvider QString name() const override; QString url() const override; QString completionEndpoint() const override; + QString chatEndpoint() const override; void prepareRequest(QJsonObject &request) override; void prepareChatRequest(QJsonObject &request) override; bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override; diff --git a/providers/OpenAICompatProvider.cpp b/providers/OpenAICompatProvider.cpp index 5039b2f..25d1150 100644 --- a/providers/OpenAICompatProvider.cpp +++ b/providers/OpenAICompatProvider.cpp @@ -46,13 +46,14 @@ QString OpenAICompatProvider::completionEndpoint() const return "/v1/chat/completions"; } +QString OpenAICompatProvider::chatEndpoint() const +{ + return "/v1/chat/completions"; +} + void OpenAICompatProvider::prepareRequest(QJsonObject &request) { auto &settings = Settings::presetPromptsSettings(); - const auto ¤tTemplate = PromptTemplateManager::instance().getCurrentTemplate(); - if (currentTemplate->name() == "Custom Template") - return; - if (request.contains("prompt")) { QJsonArray messages{ {QJsonObject{{"role", "user"}, {"content", request.take("prompt").toString()}}}}; @@ -61,7 +62,6 @@ void OpenAICompatProvider::prepareRequest(QJsonObject &request) 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()) diff --git a/providers/OpenAICompatProvider.hpp b/providers/OpenAICompatProvider.hpp index f9694d7..a91c2ca 100644 --- a/providers/OpenAICompatProvider.hpp +++ b/providers/OpenAICompatProvider.hpp @@ -31,6 +31,7 @@ class OpenAICompatProvider : public LLMProvider QString name() const override; QString url() const override; QString completionEndpoint() const override; + QString chatEndpoint() const override; void prepareRequest(QJsonObject &request) override; void prepareChatRequest(QJsonObject &request) override; bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override; diff --git a/qodeassist.cpp b/qodeassist.cpp index feb93e0..9dc8128 100644 --- a/qodeassist.cpp +++ b/qodeassist.cpp @@ -46,8 +46,12 @@ #include "providers/LMStudioProvider.hpp" #include "providers/OllamaProvider.hpp" #include "providers/OpenAICompatProvider.hpp" -#include "templates/CodeLLamaTemplate.hpp" + +#include "settings/GeneralSettings.hpp" +#include "templates/CodeLlamaFimTemplate.hpp" +#include "templates/CodeLlamaInstruct.hpp" #include "templates/CustomTemplate.hpp" +#include "templates/DeepSeekCoderChatTemplate.hpp" #include "templates/DeepSeekCoderV2.hpp" #include "templates/StarCoder2Template.hpp" @@ -80,12 +84,12 @@ class QodeAssistPlugin final : public ExtensionSystem::IPlugin providerManager.registerProvider(); auto &templateManager = PromptTemplateManager::instance(); - templateManager.registerTemplate(); + templateManager.registerTemplate(); templateManager.registerTemplate(); templateManager.registerTemplate(); templateManager.registerTemplate(); - - m_chatOutputPane.reset(new Chat::ChatOutputPane(this)); + templateManager.registerTemplate(); + templateManager.registerTemplate(); Utils::Icon QCODEASSIST_ICON( {{":/resources/images/qoderassist-icon.png", Utils::Theme::IconsBaseColor}}); @@ -110,6 +114,8 @@ class QodeAssistPlugin final : public ExtensionSystem::IPlugin auto toggleButton = new QToolButton; toggleButton->setDefaultAction(requestAction.contextAction()); StatusBarManager::addStatusBarWidget(toggleButton, StatusBarManager::RightCorner); + + m_chatOutputPane = new Chat::ChatOutputPane(this); } void extensionsInitialized() final @@ -143,7 +149,7 @@ class QodeAssistPlugin final : public ExtensionSystem::IPlugin private: QPointer m_qodeAssistClient; - QScopedPointer m_chatOutputPane; + QPointer m_chatOutputPane; }; } // namespace QodeAssist::Internal diff --git a/settings/GeneralSettings.cpp b/settings/GeneralSettings.cpp index 25af1ec..58875c8 100644 --- a/settings/GeneralSettings.cpp +++ b/settings/GeneralSettings.cpp @@ -84,9 +84,8 @@ GeneralSettings::GeneralSettings() autoCompletionTypingInterval.setDefaultValue(2000); llmProviders.setSettingsKey(Constants::LLM_PROVIDERS); - llmProviders.setDisplayName(Tr::tr("FIM Provider:")); + llmProviders.setDisplayName(Tr::tr("AI Suggest Provider:")); llmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); - llmProviders.setDefaultValue(0); url.setSettingsKey(Constants::URL); url.setLabelText(Tr::tr("URL:")); @@ -97,37 +96,53 @@ GeneralSettings::GeneralSettings() endPoint.setDisplayStyle(Utils::StringAspect::LineEditDisplay); modelName.setSettingsKey(Constants::MODEL_NAME); - modelName.setLabelText(Tr::tr("LLM Name:")); + modelName.setLabelText(Tr::tr("Model name:")); modelName.setDisplayStyle(Utils::StringAspect::LineEditDisplay); selectModels.m_buttonText = Tr::tr("Select Fill-In-the-Middle Model"); fimPrompts.setDisplayName(Tr::tr("Fill-In-the-Middle Prompt")); fimPrompts.setSettingsKey(Constants::FIM_PROMPTS); - fimPrompts.setDefaultValue(0); fimPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults"); - const auto &manager = LLMProvidersManager::instance(); - if (!manager.getProviderNames().isEmpty()) { - const auto providerNames = manager.getProviderNames(); - for (const QString &name : providerNames) { - llmProviders.addOption(name); - } - } + chatLlmProviders.setSettingsKey(Constants::CHAT_LLM_PROVIDERS); + chatLlmProviders.setDisplayName(Tr::tr("AI Chat Provider:")); + chatLlmProviders.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); - const auto &promptManager = PromptTemplateManager::instance(); - if (!promptManager.getTemplateNames().isEmpty()) { - const auto promptNames = promptManager.getTemplateNames(); - for (const QString &name : promptNames) { - fimPrompts.addOption(name); - } - } + chatUrl.setSettingsKey(Constants::CHAT_URL); + chatUrl.setLabelText(Tr::tr("URL:")); + chatUrl.setDisplayStyle(Utils::StringAspect::LineEditDisplay); + + chatEndPoint.setSettingsKey(Constants::CHAT_END_POINT); + chatEndPoint.setLabelText(Tr::tr("Chat Endpoint:")); + chatEndPoint.setDisplayStyle(Utils::StringAspect::LineEditDisplay); + + chatModelName.setSettingsKey(Constants::CHAT_MODEL_NAME); + chatModelName.setLabelText(Tr::tr("Model name:")); + chatModelName.setDisplayStyle(Utils::StringAspect::LineEditDisplay); + + chatSelectModels.m_buttonText = Tr::tr("Select Chat Model"); + + chatPrompts.setDisplayName(Tr::tr("Chat Prompt")); + chatPrompts.setSettingsKey(Constants::CHAT_PROMPTS); + chatPrompts.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); + + loadProviders(); + loadPrompts(); readSettings(); - LLMProvidersManager::instance().setCurrentProvider(llmProviders.stringValue()); - PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.stringValue()); + auto fimProviderName = llmProviders.displayForIndex(llmProviders.value()); + setCurrentFimProvider(fimProviderName); + auto chatProviderName = chatLlmProviders.displayForIndex(chatLlmProviders.value()); + setCurrentChatProvider(chatProviderName); + + auto nameFimPromts = fimPrompts.displayForIndex(fimPrompts.value()); + PromptTemplateManager::instance().setCurrentFimTemplate(nameFimPromts); + auto nameChatPromts = chatPrompts.displayForIndex(chatPrompts.value()); + PromptTemplateManager::instance().setCurrentChatTemplate(nameChatPromts); + setLoggingEnabled(enableLogging()); setupConnections(); @@ -135,23 +150,29 @@ GeneralSettings::GeneralSettings() setLayouter([this]() { using namespace Layouting; - auto rootLayout = Column{Row{enableQodeAssist, Stretch{1}, resetToDefaults}, - enableAutoComplete, - multiLineCompletion, - Row{autoCompletionCharThreshold, - autoCompletionTypingInterval, - startSuggestionTimer, - Stretch{1}}, - Space{8}, - enableLogging, - Space{8}, - Row{llmProviders, Stretch{1}}, - Row{url, endPoint, urlIndicator}, - Space{8}, - Row{selectModels, modelName, modelIndicator}, - Space{8}, - fimPrompts, - Stretch{1}}; + auto rootLayout + = Column{Row{enableQodeAssist, Stretch{1}, resetToDefaults}, + enableAutoComplete, + multiLineCompletion, + Row{autoCompletionCharThreshold, + autoCompletionTypingInterval, + startSuggestionTimer, + Stretch{1}}, + Space{8}, + enableLogging, + Space{8}, + Group{title(Tr::tr("AI Suggestions")), + Column{Row{llmProviders, Stretch{1}}, + Row{url, endPoint, fimUrlIndicator}, + Row{selectModels, modelName, fimModelIndicator}, + Row{fimPrompts, Stretch{1}}}}, + Space{16}, + Group{title(Tr::tr("AI Chat")), + Column{Row{chatLlmProviders, Stretch{1}}, + Row{chatUrl, chatEndPoint, chatUrlIndicator}, + Row{chatSelectModels, chatModelName, chatModelIndicator}, + Row{chatPrompts, Stretch{1}}}}, + Stretch{1}}; return rootLayout; }); @@ -161,17 +182,32 @@ GeneralSettings::GeneralSettings() void GeneralSettings::setupConnections() { connect(&llmProviders, &Utils::SelectionAspect::volatileValueChanged, this, [this]() { - int index = llmProviders.volatileValue(); - logMessage(QString("currentProvider %1").arg(llmProviders.displayForIndex(index))); - LLMProvidersManager::instance().setCurrentProvider(llmProviders.displayForIndex(index)); - updateProviderSettings(); + auto providerName = llmProviders.displayForIndex(llmProviders.volatileValue()); + setCurrentFimProvider(providerName); + }); + connect(&chatLlmProviders, &Utils::SelectionAspect::volatileValueChanged, this, [this]() { + auto providerName = chatLlmProviders.displayForIndex(chatLlmProviders.volatileValue()); + setCurrentChatProvider(providerName); }); + connect(&fimPrompts, &Utils::SelectionAspect::volatileValueChanged, this, [this]() { int index = fimPrompts.volatileValue(); - logMessage(QString("currentPrompt %1").arg(fimPrompts.displayForIndex(index))); - PromptTemplateManager::instance().setCurrentTemplate(fimPrompts.displayForIndex(index)); + PromptTemplateManager::instance().setCurrentFimTemplate(fimPrompts.displayForIndex(index)); }); - connect(&selectModels, &ButtonAspect::clicked, this, [this]() { showModelSelectionDialog(); }); + connect(&chatPrompts, &Utils::SelectionAspect::volatileValueChanged, this, [this]() { + int index = chatPrompts.volatileValue(); + PromptTemplateManager::instance().setCurrentChatTemplate(chatPrompts.displayForIndex(index)); + }); + + connect(&selectModels, &ButtonAspect::clicked, this, [this]() { + auto *provider = LLMProvidersManager::instance().getCurrentFimProvider(); + showModelSelectionDialog(&modelName, provider); + }); + connect(&chatSelectModels, &ButtonAspect::clicked, this, [this]() { + auto *provider = LLMProvidersManager::instance().getCurrentChatProvider(); + showModelSelectionDialog(&chatModelName, provider); + }); + connect(&enableLogging, &Utils::BoolAspect::volatileValueChanged, this, [this]() { setLoggingEnabled(enableLogging.volatileValue()); }); @@ -185,22 +221,19 @@ void GeneralSettings::setupConnections() &Utils::StringAspect::volatileValueChanged, this, &GeneralSettings::updateStatusIndicators); + connect(&chatUrl, + &Utils::StringAspect::volatileValueChanged, + this, + &GeneralSettings::updateStatusIndicators); + connect(&chatModelName, + &Utils::StringAspect::volatileValueChanged, + this, + &GeneralSettings::updateStatusIndicators); } -void GeneralSettings::updateProviderSettings() -{ - const auto provider = LLMProvidersManager::instance().getCurrentProvider(); - - if (provider) { - url.setVolatileValue(provider->url()); - endPoint.setVolatileValue(provider->completionEndpoint()); - modelName.setVolatileValue(""); - } -} - -void GeneralSettings::showModelSelectionDialog() +void GeneralSettings::showModelSelectionDialog(Utils::StringAspect *modelNameObj, + Providers::LLMProvider *provider) { - auto *provider = LLMProvidersManager::instance().getCurrentProvider(); Utils::Environment env = Utils::Environment::systemEnvironment(); if (provider) { @@ -215,7 +248,7 @@ void GeneralSettings::showModelSelectionDialog() &ok); if (ok && !selectedModel.isEmpty()) { - modelName.setVolatileValue(selectedModel); + modelNameObj->setVolatileValue(selectedModel); writeSettings(); } } @@ -233,42 +266,58 @@ void GeneralSettings::resetPageToDefaults() if (reply == QMessageBox::Yes) { resetAspect(enableQodeAssist); resetAspect(enableAutoComplete); - resetAspect(llmProviders); - resetAspect(url); - resetAspect(endPoint); - resetAspect(modelName); - resetAspect(fimPrompts); resetAspect(enableLogging); resetAspect(startSuggestionTimer); resetAspect(autoCompletionTypingInterval); resetAspect(autoCompletionCharThreshold); } - fimPrompts.setStringValue("StarCoder2"); - llmProviders.setStringValue("Ollama"); + int fimIndex = llmProviders.indexForDisplay("Ollama"); + llmProviders.setVolatileValue(fimIndex); + int chatIndex = chatLlmProviders.indexForDisplay("Ollama"); + chatLlmProviders.setVolatileValue(chatIndex); + modelName.setVolatileValue(""); + chatModelName.setVolatileValue(""); + updateStatusIndicators(); } void GeneralSettings::updateStatusIndicators() { - bool urlValid = !url.volatileValue().isEmpty() && !endPoint.volatileValue().isEmpty(); - bool modelValid = !modelName.volatileValue().isEmpty(); - - bool pingSuccessful = false; - if (urlValid) { + bool fimUrlValid = !url.volatileValue().isEmpty() && !endPoint.volatileValue().isEmpty(); + bool fimModelValid = !modelName.volatileValue().isEmpty(); + bool chatUrlValid = !chatUrl.volatileValue().isEmpty() + && !chatEndPoint.volatileValue().isEmpty(); + bool chatModelValid = !chatModelName.volatileValue().isEmpty(); + + bool fimPingSuccessful = false; + if (fimUrlValid) { QUrl pingUrl(url.volatileValue()); - pingSuccessful = QodeAssist::pingUrl(pingUrl); + fimPingSuccessful = QodeAssist::pingUrl(pingUrl); + } + bool chatPingSuccessful = false; + if (chatUrlValid) { + QUrl pingUrl(chatUrl.volatileValue()); + chatPingSuccessful = QodeAssist::pingUrl(pingUrl); } - setIndicatorStatus(modelIndicator, - modelValid ? tr("Model is properly configured") - : tr("No model selected or model name is invalid"), - modelValid); - - setIndicatorStatus(urlIndicator, - pingSuccessful ? tr("Server is reachable") - : tr("Server is not reachable or URL is invalid"), - pingSuccessful); + setIndicatorStatus(fimModelIndicator, + fimModelValid ? tr("Model is properly configured") + : tr("No model selected or model name is invalid"), + fimModelValid); + setIndicatorStatus(fimUrlIndicator, + fimPingSuccessful ? tr("Server is reachable") + : tr("Server is not reachable or URL is invalid"), + fimPingSuccessful); + + setIndicatorStatus(chatModelIndicator, + chatModelValid ? tr("Model is properly configured") + : tr("No model selected or model name is invalid"), + chatModelValid); + setIndicatorStatus(chatUrlIndicator, + chatPingSuccessful ? tr("Server is reachable") + : tr("Server is not reachable or URL is invalid"), + chatPingSuccessful); } void GeneralSettings::setIndicatorStatus(Utils::StringAspect &indicator, @@ -280,6 +329,44 @@ void GeneralSettings::setIndicatorStatus(Utils::StringAspect &indicator, indicator.setToolTip(tooltip); } +void GeneralSettings::setCurrentFimProvider(const QString &name) +{ + const auto provider = LLMProvidersManager::instance().setCurrentFimProvider(name); + if (!provider) + return; + + url.setValue(provider->url()); + endPoint.setValue(provider->completionEndpoint()); +} + +void GeneralSettings::setCurrentChatProvider(const QString &name) +{ + const auto provider = LLMProvidersManager::instance().setCurrentChatProvider(name); + if (!provider) + return; + + chatUrl.setValue(provider->url()); + chatEndPoint.setValue(provider->chatEndpoint()); +} + +void GeneralSettings::loadProviders() +{ + for (const auto &name : LLMProvidersManager::instance().providersNames()) { + llmProviders.addOption(name); + chatLlmProviders.addOption(name); + } +} + +void GeneralSettings::loadPrompts() +{ + for (const auto &name : PromptTemplateManager::instance().fimTemplatesNames()) { + fimPrompts.addOption(name); + } + for (const auto &name : PromptTemplateManager::instance().chatTemplatesNames()) { + chatPrompts.addOption(name); + } +} + class GeneralSettingsPage : public Core::IOptionsPage { public: diff --git a/settings/GeneralSettings.hpp b/settings/GeneralSettings.hpp index 709954e..804d609 100644 --- a/settings/GeneralSettings.hpp +++ b/settings/GeneralSettings.hpp @@ -21,6 +21,7 @@ #include +#include "providers/LLMProvider.hpp" #include "settings/SettingsUtils.hpp" namespace QodeAssist::Settings { @@ -47,17 +48,33 @@ class GeneralSettings : public Utils::AspectContainer Utils::SelectionAspect fimPrompts{this}; ButtonAspect resetToDefaults{this}; - Utils::StringAspect modelIndicator{this}; - Utils::StringAspect urlIndicator{this}; + Utils::SelectionAspect chatLlmProviders{this}; + Utils::StringAspect chatUrl{this}; + Utils::StringAspect chatEndPoint{this}; + + Utils::StringAspect chatModelName{this}; + ButtonAspect chatSelectModels{this}; + Utils::SelectionAspect chatPrompts{this}; + + Utils::StringAspect fimModelIndicator{this}; + Utils::StringAspect fimUrlIndicator{this}; + Utils::StringAspect chatModelIndicator{this}; + Utils::StringAspect chatUrlIndicator{this}; private: void setupConnections(); - void updateProviderSettings(); - void showModelSelectionDialog(); + void showModelSelectionDialog(Utils::StringAspect *modelNameObj, + Providers::LLMProvider *provider); void resetPageToDefaults(); void updateStatusIndicators(); void setIndicatorStatus(Utils::StringAspect &indicator, const QString &tooltip, bool isValid); + + void setCurrentFimProvider(const QString &name); + void setCurrentChatProvider(const QString &name); + + void loadProviders(); + void loadPrompts(); }; GeneralSettings &generalSettings(); diff --git a/templates/CodeLLamaTemplate.hpp b/templates/CodeLlamaFimTemplate.hpp similarity index 88% rename from templates/CodeLLamaTemplate.hpp rename to templates/CodeLlamaFimTemplate.hpp index 6115d28..48fedfc 100644 --- a/templates/CodeLLamaTemplate.hpp +++ b/templates/CodeLlamaFimTemplate.hpp @@ -23,10 +23,11 @@ namespace QodeAssist::Templates { -class CodeLLamaTemplate : public PromptTemplate +class CodeLlamaFimTemplate : public PromptTemplate { public: - QString name() const override { return "CodeLlama"; } + TemplateType type() const override { return TemplateType::Fim; } + QString name() const override { return "CodeLlama FIM"; } QString promptTemplate() const override { return "%1
 %2 %3 "; }
     QStringList stopWords() const override
     {
diff --git a/templates/CodeLlamaInstruct.hpp b/templates/CodeLlamaInstruct.hpp
new file mode 100644
index 0000000..08484d3
--- /dev/null
+++ b/templates/CodeLlamaInstruct.hpp
@@ -0,0 +1,49 @@
+/* 
+ * 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 
+#include "PromptTemplate.hpp"
+
+namespace QodeAssist::Templates {
+
+class CodeLlamaInstructTemplate : public PromptTemplate
+{
+public:
+    TemplateType type() const override { return TemplateType::Chat; }
+    QString name() const override { return "CodeLlama Instruct"; }
+    QString promptTemplate() const override { return "[INST] %1 [/INST]"; }
+    QStringList stopWords() const override { return QStringList() << "[INST]" << "[/INST]"; }
+
+    void prepareRequest(QJsonObject &request, const ContextData &context) const override
+    {
+        QString formattedPrompt = promptTemplate().arg(context.prefix);
+        QJsonArray messages = request["messages"].toArray();
+
+        QJsonObject newMessage;
+        newMessage["role"] = "user";
+        newMessage["content"] = formattedPrompt;
+        messages.append(newMessage);
+
+        request["messages"] = messages;
+    }
+};
+
+} // namespace QodeAssist::Templates
diff --git a/templates/CustomTemplate.hpp b/templates/CustomTemplate.hpp
index 5ae2e24..5e1b913 100644
--- a/templates/CustomTemplate.hpp
+++ b/templates/CustomTemplate.hpp
@@ -32,7 +32,8 @@ namespace QodeAssist::Templates {
 class CustomTemplate : public PromptTemplate
 {
 public:
-    QString name() const override { return "Custom Template"; }
+    TemplateType type() const override { return TemplateType::Fim; }
+    QString name() const override { return "Custom FIM Template"; }
     QString promptTemplate() const override
     {
         return Settings::customPromptSettings().customJsonTemplate();
diff --git a/templates/DeepSeekCoderChatTemplate.hpp b/templates/DeepSeekCoderChatTemplate.hpp
new file mode 100644
index 0000000..5657574
--- /dev/null
+++ b/templates/DeepSeekCoderChatTemplate.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include 
+#include "PromptTemplate.hpp"
+
+namespace QodeAssist::Templates {
+
+class DeepSeekCoderChatTemplate : public PromptTemplate
+{
+public:
+    QString name() const override { return "DeepSeekCoderChat"; }
+    TemplateType type() const override { return TemplateType::Chat; }
+
+    QString promptTemplate() const override { return "### Instruction:\n%1\n### Response:\n"; }
+
+    QStringList stopWords() const override
+    {
+        return QStringList() << "### Instruction:" << "### Response:" << "\n\n### ";
+    }
+
+    void prepareRequest(QJsonObject &request, const ContextData &context) const override
+    {
+        QString formattedPrompt = promptTemplate().arg(context.prefix);
+        QJsonArray messages = request["messages"].toArray();
+
+        QJsonObject newMessage;
+        newMessage["role"] = "user";
+        newMessage["content"] = formattedPrompt;
+        messages.append(newMessage);
+
+        request["messages"] = messages;
+    }
+};
+
+} // namespace QodeAssist::Templates
diff --git a/templates/DeepSeekCoderV2.hpp b/templates/DeepSeekCoderV2.hpp
index 69886fb..34e47cf 100644
--- a/templates/DeepSeekCoderV2.hpp
+++ b/templates/DeepSeekCoderV2.hpp
@@ -26,6 +26,7 @@ namespace QodeAssist::Templates {
 class DeepSeekCoderV2Template : public PromptTemplate
 {
 public:
+    TemplateType type() const override { return TemplateType::Fim; }
     QString name() const override { return "DeepSeekCoderV2"; }
     QString promptTemplate() const override
     {
diff --git a/templates/PromptTemplate.hpp b/templates/PromptTemplate.hpp
index 5b134ee..0dee78e 100644
--- a/templates/PromptTemplate.hpp
+++ b/templates/PromptTemplate.hpp
@@ -27,10 +27,13 @@
 
 namespace QodeAssist::Templates {
 
+enum class TemplateType { Chat, Fim };
+
 class PromptTemplate
 {
 public:
     virtual ~PromptTemplate() = default;
+    virtual TemplateType type() const = 0;
     virtual QString name() const = 0;
     virtual QString promptTemplate() const = 0;
     virtual QStringList stopWords() const = 0;
diff --git a/templates/StarCoder2Template.hpp b/templates/StarCoder2Template.hpp
index 8f89679..ddc8e08 100644
--- a/templates/StarCoder2Template.hpp
+++ b/templates/StarCoder2Template.hpp
@@ -26,6 +26,7 @@ namespace QodeAssist::Templates {
 class StarCoder2Template : public PromptTemplate
 {
 public:
+    TemplateType type() const override { return TemplateType::Fim; }
     QString name() const override { return "StarCoder2"; }
     QString promptTemplate() const override { return "%1%2%3"; }
     QStringList stopWords() const override