mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-06-13 17:59:15 -04:00
Compare commits
8 Commits
v0.5.0
...
dev-rag-ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
142afa725f | ||
|
|
f36db033e6 | ||
|
|
5dfcf74128 | ||
|
|
02101665ca | ||
|
|
77a03d42ed | ||
|
|
09c38c8b0e | ||
|
|
7b73d7af7b | ||
|
|
5a426b4d9f |
@@ -8,7 +8,6 @@ set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
find_package(QtCreator REQUIRED COMPONENTS Core)
|
||||
find_package(Qt6 COMPONENTS Core Gui Quick Widgets Network REQUIRED)
|
||||
@@ -44,30 +43,26 @@ add_qtc_plugin(QodeAssist
|
||||
LLMClientInterface.hpp LLMClientInterface.cpp
|
||||
templates/Templates.hpp
|
||||
templates/CodeLlamaFim.hpp
|
||||
templates/Ollama.hpp
|
||||
templates/Claude.hpp
|
||||
templates/OpenAI.hpp
|
||||
templates/MistralAI.hpp
|
||||
templates/StarCoder2Fim.hpp
|
||||
# templates/DeepSeekCoderFim.hpp
|
||||
# templates/CustomFimTemplate.hpp
|
||||
templates/DeepSeekCoderFim.hpp
|
||||
templates/CustomFimTemplate.hpp
|
||||
templates/Qwen.hpp
|
||||
templates/OpenAICompatible.hpp
|
||||
templates/Ollama.hpp
|
||||
templates/BasicChat.hpp
|
||||
templates/Llama3.hpp
|
||||
templates/ChatML.hpp
|
||||
templates/Alpaca.hpp
|
||||
templates/Llama2.hpp
|
||||
templates/Claude.hpp
|
||||
templates/OpenAI.hpp
|
||||
templates/CodeLlamaQMLFim.hpp
|
||||
templates/GoogleAI.hpp
|
||||
providers/Providers.hpp
|
||||
providers/OllamaProvider.hpp providers/OllamaProvider.cpp
|
||||
providers/ClaudeProvider.hpp providers/ClaudeProvider.cpp
|
||||
providers/OpenAIProvider.hpp providers/OpenAIProvider.cpp
|
||||
providers/MistralAIProvider.hpp providers/MistralAIProvider.cpp
|
||||
providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp
|
||||
providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
|
||||
providers/OpenRouterAIProvider.hpp providers/OpenRouterAIProvider.cpp
|
||||
providers/GoogleAIProvider.hpp providers/GoogleAIProvider.cpp
|
||||
providers/ClaudeProvider.hpp providers/ClaudeProvider.cpp
|
||||
providers/OpenAIProvider.hpp providers/OpenAIProvider.cpp
|
||||
QodeAssist.qrc
|
||||
LSPCompletion.hpp
|
||||
LLMSuggestion.hpp LLMSuggestion.cpp
|
||||
|
||||
@@ -24,21 +24,24 @@
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectexplorer.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
#include <projectexplorer/projecttree.h>
|
||||
#include <utils/theme/theme.h>
|
||||
#include <utils/utilsicons.h>
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
|
||||
#include "ChatAssistantSettings.hpp"
|
||||
#include "ChatSerializer.hpp"
|
||||
#include "GeneralSettings.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "ProjectSettings.hpp"
|
||||
#include "context/TokenUtils.hpp"
|
||||
#include "context/ContextManager.hpp"
|
||||
#include "context/FileChunker.hpp"
|
||||
#include "context/RAGManager.hpp"
|
||||
#include "context/TokenUtils.hpp"
|
||||
|
||||
namespace QodeAssist::Chat {
|
||||
|
||||
@@ -447,6 +450,69 @@ void ChatRootView::openChatHistoryFolder()
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
|
||||
// ChatRootView.cpp
|
||||
|
||||
void ChatRootView::testRAG(const QString &message)
|
||||
{
|
||||
auto project = ProjectExplorer::ProjectTree::currentProject();
|
||||
if (!project) {
|
||||
qDebug() << "No active project found";
|
||||
return;
|
||||
}
|
||||
|
||||
const QString TEST_QUERY = message;
|
||||
|
||||
qDebug() << "Starting RAG test with query:";
|
||||
qDebug() << TEST_QUERY;
|
||||
qDebug() << "\nFirst, processing project files...";
|
||||
|
||||
auto files = Context::ContextManager::instance().getProjectSourceFiles(project);
|
||||
// Было: auto future = Context::RAGManager::instance().processFiles(project, files);
|
||||
// Стало:
|
||||
auto future = Context::RAGManager::instance().processProjectFiles(project, files);
|
||||
|
||||
connect(
|
||||
&Context::RAGManager::instance(),
|
||||
&Context::RAGManager::vectorizationProgress,
|
||||
this,
|
||||
[](int processed, int total) {
|
||||
qDebug() << QString("Vectorization progress: %1 of %2 files").arg(processed).arg(total);
|
||||
});
|
||||
|
||||
connect(
|
||||
&Context::RAGManager::instance(),
|
||||
&Context::RAGManager::vectorizationFinished,
|
||||
this,
|
||||
[this, project, TEST_QUERY]() {
|
||||
qDebug() << "\nVectorization completed. Starting similarity search...\n";
|
||||
// Было: Context::RAGManager::instance().searchSimilarDocuments(TEST_QUERY, project, 5);
|
||||
// Стало:
|
||||
auto future = Context::RAGManager::instance().findRelevantChunks(TEST_QUERY, project, 5);
|
||||
future.then([](const QList<Context::RAGManager::ChunkSearchResult> &results) {
|
||||
qDebug() << "Found" << results.size() << "relevant chunks:";
|
||||
for (const auto &result : results) {
|
||||
qDebug() << "File:" << result.filePath;
|
||||
qDebug() << "Lines:" << result.startLine << "-" << result.endLine;
|
||||
qDebug() << "Score:" << result.combinedScore;
|
||||
qDebug() << "Content:" << result.content;
|
||||
qDebug() << "---";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void ChatRootView::testChunking()
|
||||
{
|
||||
auto project = ProjectExplorer::ProjectTree::currentProject();
|
||||
if (!project) {
|
||||
qDebug() << "No active project found";
|
||||
return;
|
||||
}
|
||||
|
||||
Context::FileChunker::ChunkingConfig config;
|
||||
Context::ContextManager::instance().testProjectChunks(project, config);
|
||||
}
|
||||
|
||||
void ChatRootView::updateInputTokensCount()
|
||||
{
|
||||
int inputTokens = m_messageTokensCount;
|
||||
|
||||
@@ -65,6 +65,8 @@ public:
|
||||
Q_INVOKABLE void calculateMessageTokensCount(const QString &message);
|
||||
Q_INVOKABLE void setIsSyncOpenFiles(bool state);
|
||||
Q_INVOKABLE void openChatHistoryFolder();
|
||||
Q_INVOKABLE void testRAG(const QString &message);
|
||||
Q_INVOKABLE void testChunking();
|
||||
|
||||
Q_INVOKABLE void updateInputTokensCount();
|
||||
int inputTokensCount() const;
|
||||
|
||||
@@ -93,47 +93,51 @@ void ClientInterface::sendMessage(
|
||||
}
|
||||
|
||||
LLMCore::ContextData context;
|
||||
context.prefix = message;
|
||||
context.suffix = "";
|
||||
|
||||
if (chatAssistantSettings.useSystemPrompt()) {
|
||||
QString systemPrompt = chatAssistantSettings.systemPrompt();
|
||||
if (!linkedFiles.isEmpty()) {
|
||||
systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles);
|
||||
}
|
||||
context.systemPrompt = systemPrompt;
|
||||
QString systemPrompt;
|
||||
if (chatAssistantSettings.useSystemPrompt())
|
||||
systemPrompt = chatAssistantSettings.systemPrompt();
|
||||
|
||||
if (!linkedFiles.isEmpty()) {
|
||||
systemPrompt = getSystemPromptWithLinkedFiles(systemPrompt, linkedFiles);
|
||||
}
|
||||
|
||||
QVector<LLMCore::Message> messages;
|
||||
for (const auto &msg : m_chatModel->getChatHistory()) {
|
||||
messages.append({msg.role == ChatModel::ChatRole::User ? "user" : "assistant", msg.content});
|
||||
}
|
||||
context.history = messages;
|
||||
QJsonObject providerRequest;
|
||||
providerRequest["model"] = Settings::generalSettings().caModel();
|
||||
providerRequest["stream"] = chatAssistantSettings.stream();
|
||||
providerRequest["messages"] = m_chatModel->prepareMessagesForRequest(systemPrompt);
|
||||
|
||||
if (promptTemplate)
|
||||
promptTemplate->prepareRequest(providerRequest, context);
|
||||
else
|
||||
qWarning("No prompt template found");
|
||||
|
||||
if (provider)
|
||||
provider->prepareRequest(providerRequest, LLMCore::RequestType::Chat);
|
||||
else
|
||||
qWarning("No provider found");
|
||||
|
||||
LLMCore::LLMConfig config;
|
||||
config.requestType = LLMCore::RequestType::Chat;
|
||||
config.provider = provider;
|
||||
config.promptTemplate = promptTemplate;
|
||||
if (provider->providerID() == LLMCore::ProviderID::GoogleAI) {
|
||||
QString stream = chatAssistantSettings.stream() ? QString{"streamGenerateContent?alt=sse"}
|
||||
: QString{"generateContent?"};
|
||||
config.url = QUrl(QString("%1/models/%2:%3")
|
||||
.arg(
|
||||
Settings::generalSettings().caUrl(),
|
||||
Settings::generalSettings().caModel(),
|
||||
stream));
|
||||
} else {
|
||||
config.url
|
||||
= QString("%1%2").arg(Settings::generalSettings().caUrl(), provider->chatEndpoint());
|
||||
config.providerRequest
|
||||
= {{"model", Settings::generalSettings().caModel()},
|
||||
{"stream", chatAssistantSettings.stream()}};
|
||||
}
|
||||
|
||||
config.url = QString("%1%2").arg(Settings::generalSettings().caUrl(), provider->chatEndpoint());
|
||||
config.providerRequest = providerRequest;
|
||||
config.multiLineCompletion = false;
|
||||
config.apiKey = provider->apiKey();
|
||||
|
||||
config.provider
|
||||
->prepareRequest(config.providerRequest, promptTemplate, context, LLMCore::RequestType::Chat);
|
||||
QJsonObject request;
|
||||
request["id"] = QUuid::createUuid().toString();
|
||||
|
||||
auto errors = config.provider->validateRequest(config.providerRequest, promptTemplate->type());
|
||||
if (!errors.isEmpty()) {
|
||||
LOG_MESSAGE("Validate errors for chat request:");
|
||||
LOG_MESSAGES(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject request{{"id", QUuid::createUuid().toString()}};
|
||||
m_requestHandler->sendLLMRequest(config, request);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,18 +27,14 @@ Rectangle {
|
||||
|
||||
property alias msgModel: msgCreator.model
|
||||
property alias messageAttachments: attachmentsModel.model
|
||||
property bool isUserMessage: false
|
||||
|
||||
height: msgColumn.implicitHeight + 10
|
||||
radius: 8
|
||||
color: isUserMessage ? palette.alternateBase
|
||||
: palette.base
|
||||
|
||||
ColumnLayout {
|
||||
id: msgColumn
|
||||
|
||||
x: 5
|
||||
width: parent.width - x
|
||||
width: parent.width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 5
|
||||
|
||||
@@ -117,17 +113,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: userMessageMarker
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 3
|
||||
height: root.height - root.radius
|
||||
color: "#92BD6C"
|
||||
radius: root.radius
|
||||
visible: root.isUserMessage
|
||||
}
|
||||
|
||||
component TextComponent : TextBlock {
|
||||
required property var itemData
|
||||
height: implicitHeight + 10
|
||||
|
||||
@@ -96,7 +96,8 @@ ChatRootView {
|
||||
width: ListView.view.width - scroll.width
|
||||
msgModel: root.chatModel.processMessageContent(model.content)
|
||||
messageAttachments: model.attachments
|
||||
isUserMessage: model.roleType === ChatModel.User
|
||||
color: model.roleType === ChatModel.User ? palette.alternateBase
|
||||
: palette.base
|
||||
}
|
||||
|
||||
header: Item {
|
||||
@@ -197,6 +198,8 @@ ChatRootView {
|
||||
}
|
||||
attachFiles.onClicked: root.showAttachFilesDialog()
|
||||
linkFiles.onClicked: root.showLinkFilesDialog()
|
||||
testRag.onClicked: root.testRAG(messageInput.text)
|
||||
testChunks.onClicked: root.testChunking()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ Rectangle {
|
||||
property alias syncOpenFiles: syncOpenFilesId
|
||||
property alias attachFiles: attachFilesId
|
||||
property alias linkFiles: linkFilesId
|
||||
property alias testRag: testRagId
|
||||
property alias testChunks: testChunksId
|
||||
|
||||
color: palette.window.hslLightness > 0.5 ?
|
||||
Qt.darker(palette.window, 1.1) :
|
||||
@@ -91,6 +93,18 @@ Rectangle {
|
||||
ToolTip.text: qsTr("Automatically synchronize currently opened files with the model context")
|
||||
}
|
||||
|
||||
QoAButton {
|
||||
id: testRagId
|
||||
|
||||
text: qsTr("Test RAG")
|
||||
}
|
||||
|
||||
QoAButton {
|
||||
id: testChunksId
|
||||
|
||||
text: qsTr("Test Chunks")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
@@ -35,28 +35,6 @@ ConfigurationManager &ConfigurationManager::instance()
|
||||
void ConfigurationManager::init()
|
||||
{
|
||||
setupConnections();
|
||||
updateAllTemplateDescriptions();
|
||||
}
|
||||
|
||||
void ConfigurationManager::updateTemplateDescription(const Utils::StringAspect &templateAspect)
|
||||
{
|
||||
LLMCore::PromptTemplate *templ = m_templateManger.getFimTemplateByName(templateAspect.value());
|
||||
|
||||
if (!templ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (&templateAspect == &m_generalSettings.ccTemplate) {
|
||||
m_generalSettings.ccTemplateDescription.setValue(templ->description());
|
||||
} else if (&templateAspect == &m_generalSettings.caTemplate) {
|
||||
m_generalSettings.caTemplateDescription.setValue(templ->description());
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigurationManager::updateAllTemplateDescriptions()
|
||||
{
|
||||
updateTemplateDescription(m_generalSettings.ccTemplate);
|
||||
updateTemplateDescription(m_generalSettings.caTemplate);
|
||||
}
|
||||
|
||||
ConfigurationManager::ConfigurationManager(QObject *parent)
|
||||
@@ -86,14 +64,6 @@ void ConfigurationManager::setupConnections()
|
||||
connect(&m_generalSettings.ccPreset1SelectModel, &Button::clicked, this, &Config::selectModel);
|
||||
connect(
|
||||
&m_generalSettings.ccPreset1SelectTemplate, &Button::clicked, this, &Config::selectTemplate);
|
||||
|
||||
connect(&m_generalSettings.ccTemplate, &Utils::StringAspect::changed, this, [this]() {
|
||||
updateTemplateDescription(m_generalSettings.ccTemplate);
|
||||
});
|
||||
|
||||
connect(&m_generalSettings.caTemplate, &Utils::StringAspect::changed, this, [this]() {
|
||||
updateTemplateDescription(m_generalSettings.caTemplate);
|
||||
});
|
||||
}
|
||||
|
||||
void ConfigurationManager::selectProvider()
|
||||
@@ -167,14 +137,9 @@ void ConfigurationManager::selectTemplate()
|
||||
|
||||
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectTemplate);
|
||||
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectTemplate);
|
||||
const QString providerName = isCodeCompletion ? m_generalSettings.ccProvider.volatileValue()
|
||||
: isPreset1 ? m_generalSettings.ccPreset1Provider.volatileValue()
|
||||
: m_generalSettings.caProvider.volatileValue();
|
||||
auto providerID = m_providersManager.getProviderByName(providerName)->providerID();
|
||||
|
||||
const auto templateList = isCodeCompletion || isPreset1
|
||||
? m_templateManger.getFimTemplatesForProvider(providerID)
|
||||
: m_templateManger.getChatTemplatesForProvider(providerID);
|
||||
const auto templateList = isCodeCompletion || isPreset1 ? m_templateManger.fimTemplatesNames()
|
||||
: m_templateManger.chatTemplatesNames();
|
||||
|
||||
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccTemplate
|
||||
: isPreset1 ? m_generalSettings.ccPreset1Template
|
||||
|
||||
@@ -36,9 +36,6 @@ public:
|
||||
|
||||
void init();
|
||||
|
||||
void updateTemplateDescription(const Utils::StringAspect &templateAspect);
|
||||
void updateAllTemplateDescriptions();
|
||||
|
||||
public slots:
|
||||
void selectProvider();
|
||||
void selectModel();
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
#include <texteditor/textdocument.h>
|
||||
|
||||
#include "CodeHandler.hpp"
|
||||
#include "context/ContextManager.hpp"
|
||||
#include "context/DocumentContextReader.hpp"
|
||||
#include "llmcore/MessageBuilder.hpp"
|
||||
#include "llmcore/PromptTemplateManager.hpp"
|
||||
#include "llmcore/ProvidersManager.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
@@ -146,13 +146,24 @@ void LLMClientInterface::handleExit(const QJsonObject &request)
|
||||
emit finished();
|
||||
}
|
||||
|
||||
bool QodeAssist::LLMClientInterface::isSpecifyCompletion(const QJsonObject &request)
|
||||
{
|
||||
auto &generalSettings = Settings::generalSettings();
|
||||
|
||||
Context::ProgrammingLanguage documentLanguage = getDocumentLanguage(request);
|
||||
Context::ProgrammingLanguage preset1Language = Context::ProgrammingLanguageUtils::fromString(
|
||||
generalSettings.preset1Language.displayForIndex(generalSettings.preset1Language()));
|
||||
|
||||
return generalSettings.specifyPreset1() && documentLanguage == preset1Language;
|
||||
}
|
||||
|
||||
void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
||||
{
|
||||
auto updatedContext = prepareContext(request);
|
||||
const auto updatedContext = prepareContext(request);
|
||||
auto &completeSettings = Settings::codeCompletionSettings();
|
||||
auto &generalSettings = Settings::generalSettings();
|
||||
|
||||
bool isPreset1Active = Context::ContextManager::instance().isSpecifyCompletion(request);
|
||||
bool isPreset1Active = isSpecifyCompletion(request);
|
||||
|
||||
const auto providerName = !isPreset1Active ? generalSettings.ccProvider()
|
||||
: generalSettings.ccPreset1Provider();
|
||||
@@ -183,19 +194,14 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
||||
config.requestType = LLMCore::RequestType::CodeCompletion;
|
||||
config.provider = provider;
|
||||
config.promptTemplate = promptTemplate;
|
||||
// TODO refactor networking
|
||||
if (provider->providerID() == LLMCore::ProviderID::GoogleAI) {
|
||||
QString stream = completeSettings.stream() ? QString{"streamGenerateContent?alt=sse"}
|
||||
: QString{"generateContent?"};
|
||||
config.url = QUrl(QString("%1/models/%2:%3").arg(url, modelName, stream));
|
||||
} else {
|
||||
config.url = QUrl(QString("%1%2").arg(
|
||||
url,
|
||||
promptTemplate->type() == LLMCore::TemplateType::FIM ? provider->completionEndpoint()
|
||||
: provider->chatEndpoint()));
|
||||
config.providerRequest = {{"model", modelName}, {"stream", completeSettings.stream()}};
|
||||
}
|
||||
config.url = QUrl(QString("%1%2").arg(
|
||||
url,
|
||||
promptTemplate->type() == LLMCore::TemplateType::Fim ? provider->completionEndpoint()
|
||||
: provider->chatEndpoint()));
|
||||
config.apiKey = provider->apiKey();
|
||||
|
||||
config.providerRequest = {{"model", modelName}, {"stream", completeSettings.stream()}};
|
||||
|
||||
config.multiLineCompletion = completeSettings.multiLineCompletion();
|
||||
|
||||
const auto stopWords = QJsonArray::fromStringList(config.promptTemplate->stopWords());
|
||||
@@ -204,35 +210,28 @@ void LLMClientInterface::handleCompletion(const QJsonObject &request)
|
||||
|
||||
QString systemPrompt;
|
||||
if (completeSettings.useSystemPrompt())
|
||||
systemPrompt.append(completeSettings.useUserMessageTemplateForCC()
|
||||
&& promptTemplate->type() == LLMCore::TemplateType::Chat
|
||||
? completeSettings.systemPromptForNonFimModels()
|
||||
: completeSettings.systemPrompt());
|
||||
if (updatedContext.fileContext.has_value())
|
||||
systemPrompt.append(updatedContext.fileContext.value());
|
||||
systemPrompt.append(completeSettings.systemPrompt());
|
||||
if (!updatedContext.fileContext.isEmpty())
|
||||
systemPrompt.append(updatedContext.fileContext);
|
||||
|
||||
updatedContext.systemPrompt = systemPrompt;
|
||||
|
||||
if (promptTemplate->type() == LLMCore::TemplateType::Chat) {
|
||||
QString userMessage;
|
||||
if (completeSettings.useUserMessageTemplateForCC()) {
|
||||
userMessage = completeSettings.processMessageToFIM(
|
||||
updatedContext.prefix.value_or(""), updatedContext.suffix.value_or(""));
|
||||
} else {
|
||||
userMessage = updatedContext.prefix.value_or("") + updatedContext.suffix.value_or("");
|
||||
}
|
||||
|
||||
// TODO refactor add message
|
||||
QVector<LLMCore::Message> messages;
|
||||
messages.append({"user", userMessage});
|
||||
updatedContext.history = messages;
|
||||
QString userMessage;
|
||||
if (completeSettings.useUserMessageTemplateForCC() && promptTemplate->type() == LLMCore::TemplateType::Chat) {
|
||||
userMessage = completeSettings.userMessageTemplateForCC().arg(updatedContext.prefix, updatedContext.suffix);
|
||||
} else {
|
||||
userMessage = updatedContext.prefix;
|
||||
}
|
||||
|
||||
config.provider->prepareRequest(
|
||||
auto message = LLMCore::MessageBuilder()
|
||||
.addSystemMessage(systemPrompt)
|
||||
.addUserMessage(userMessage)
|
||||
.addSuffix(updatedContext.suffix)
|
||||
.addTokenizer(promptTemplate);
|
||||
|
||||
message.saveTo(
|
||||
config.providerRequest,
|
||||
promptTemplate,
|
||||
updatedContext,
|
||||
LLMCore::RequestType::CodeCompletion);
|
||||
providerName == "Ollama" ? LLMCore::ProvidersApi::Ollama : LLMCore::ProvidersApi::OpenAI);
|
||||
|
||||
config.provider->prepareRequest(config.providerRequest, LLMCore::RequestType::CodeCompletion);
|
||||
|
||||
auto errors = config.provider->validateRequest(config.providerRequest, promptTemplate->type());
|
||||
if (!errors.isEmpty()) {
|
||||
@@ -267,11 +266,29 @@ LLMCore::ContextData LLMClientInterface::prepareContext(const QJsonObject &reque
|
||||
return reader.prepareContext(lineNumber, cursorPosition);
|
||||
}
|
||||
|
||||
Context::ProgrammingLanguage LLMClientInterface::getDocumentLanguage(const QJsonObject &request) const
|
||||
{
|
||||
QJsonObject params = request["params"].toObject();
|
||||
QJsonObject doc = params["doc"].toObject();
|
||||
QString uri = doc["uri"].toString();
|
||||
|
||||
Utils::FilePath filePath = Utils::FilePath::fromString(QUrl(uri).toLocalFile());
|
||||
TextEditor::TextDocument *textDocument = TextEditor::TextDocument::textDocumentForFilePath(
|
||||
filePath);
|
||||
|
||||
if (!textDocument) {
|
||||
LOG_MESSAGE("Error: Document is not available for" + filePath.toString());
|
||||
return Context::ProgrammingLanguage::Unknown;
|
||||
}
|
||||
|
||||
return Context::ProgrammingLanguageUtils::fromMimeType(textDocument->mimeType());
|
||||
}
|
||||
|
||||
void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
||||
const QJsonObject &request,
|
||||
bool isComplete)
|
||||
{
|
||||
bool isPreset1Active = Context::ContextManager::instance().isSpecifyCompletion(request);
|
||||
bool isPreset1Active = isSpecifyCompletion(request);
|
||||
|
||||
auto templateName = !isPreset1Active ? Settings::generalSettings().ccTemplate()
|
||||
: Settings::generalSettings().ccPreset1Template();
|
||||
@@ -289,18 +306,18 @@ void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
||||
QJsonArray completions;
|
||||
QJsonObject completionItem;
|
||||
|
||||
LOG_MESSAGE(QString("Completions before filter: \n%1").arg(completion));
|
||||
|
||||
QString processedCompletion
|
||||
= promptTemplate->type() == LLMCore::TemplateType::Chat
|
||||
&& Settings::codeCompletionSettings().smartProcessInstuctText()
|
||||
? CodeHandler::processText(completion)
|
||||
: completion;
|
||||
? CodeHandler::processText(completion)
|
||||
: completion;
|
||||
|
||||
completionItem[LanguageServerProtocol::textKey] = processedCompletion;
|
||||
QJsonObject range;
|
||||
range["start"] = position;
|
||||
range["end"] = position;
|
||||
QJsonObject end = position;
|
||||
end["character"] = position["character"].toInt() + processedCompletion.length();
|
||||
range["end"] = end;
|
||||
completionItem[LanguageServerProtocol::rangeKey] = range;
|
||||
completionItem[LanguageServerProtocol::positionKey] = position;
|
||||
completions.append(completionItem);
|
||||
@@ -313,7 +330,7 @@ void LLMClientInterface::sendCompletionToClient(const QString &completion,
|
||||
.arg(QString::fromUtf8(QJsonDocument(completions).toJson(QJsonDocument::Indented))));
|
||||
|
||||
LOG_MESSAGE(QString("Full response: \n%1")
|
||||
.arg(QString::fromUtf8(QJsonDocument(response).toJson(QJsonDocument::Indented))));
|
||||
.arg(QString::fromUtf8(QJsonDocument(response).toJson(QJsonDocument::Indented))));
|
||||
|
||||
QString requestId = request["id"].toString();
|
||||
endTimeMeasurement(requestId);
|
||||
|
||||
@@ -61,6 +61,8 @@ private:
|
||||
|
||||
LLMCore::ContextData prepareContext(
|
||||
const QJsonObject &request, const QStringView &accumulatedCompletion = QString{});
|
||||
Context::ProgrammingLanguage getDocumentLanguage(const QJsonObject &request) const;
|
||||
bool isSpecifyCompletion(const QJsonObject &request);
|
||||
|
||||
LLMCore::RequestHandler m_requestHandler;
|
||||
QElapsedTimer m_completionTimer;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"Id" : "qodeassist",
|
||||
"Name" : "QodeAssist",
|
||||
"Version" : "0.5.0",
|
||||
"Version" : "0.4.13",
|
||||
"Vendor" : "Petr Mironychev",
|
||||
"VendorId" : "petrmironychev",
|
||||
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
|
||||
|
||||
@@ -5,11 +5,20 @@ add_library(Context STATIC
|
||||
ContentFile.hpp
|
||||
TokenUtils.hpp TokenUtils.cpp
|
||||
ProgrammingLanguage.hpp ProgrammingLanguage.cpp
|
||||
RAGManager.hpp RAGManager.cpp
|
||||
RAGStorage.hpp RAGStorage.cpp
|
||||
RAGData.hpp
|
||||
RAGVectorizer.hpp RAGVectorizer.cpp
|
||||
RAGSimilaritySearch.hpp RAGSimilaritySearch.cpp
|
||||
RAGPreprocessor.hpp RAGPreprocessor.cpp
|
||||
EnhancedRAGSimilaritySearch.hpp EnhancedRAGSimilaritySearch.cpp
|
||||
FileChunker.hpp FileChunker.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(Context
|
||||
PUBLIC
|
||||
Qt::Core
|
||||
Qt::Sql
|
||||
QtCreator::Core
|
||||
QtCreator::TextEditor
|
||||
QtCreator::Utils
|
||||
|
||||
@@ -21,13 +21,12 @@
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonObject>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "GeneralSettings.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <utils/filepath.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectnodes.h>
|
||||
|
||||
#include "FileChunker.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
@@ -70,33 +69,108 @@ ContentFile ContextManager::createContentFile(const QString &filePath) const
|
||||
return contentFile;
|
||||
}
|
||||
|
||||
ProgrammingLanguage ContextManager::getDocumentLanguage(const QJsonObject &request) const
|
||||
bool ContextManager::isInBuildDirectory(const QString &filePath) const
|
||||
{
|
||||
QJsonObject params = request["params"].toObject();
|
||||
QJsonObject doc = params["doc"].toObject();
|
||||
QString uri = doc["uri"].toString();
|
||||
static const QStringList buildDirPatterns
|
||||
= {QString(QDir::separator()) + QLatin1String("build") + QDir::separator(),
|
||||
QString(QDir::separator()) + QLatin1String("Build") + QDir::separator(),
|
||||
QString(QDir::separator()) + QLatin1String("BUILD") + QDir::separator(),
|
||||
QString(QDir::separator()) + QLatin1String("debug") + QDir::separator(),
|
||||
QString(QDir::separator()) + QLatin1String("Debug") + QDir::separator(),
|
||||
QString(QDir::separator()) + QLatin1String("DEBUG") + QDir::separator(),
|
||||
QString(QDir::separator()) + QLatin1String("release") + QDir::separator(),
|
||||
QString(QDir::separator()) + QLatin1String("Release") + QDir::separator(),
|
||||
QString(QDir::separator()) + QLatin1String("RELEASE") + QDir::separator(),
|
||||
QString(QDir::separator()) + QLatin1String("builds") + QDir::separator()};
|
||||
|
||||
Utils::FilePath filePath = Utils::FilePath::fromString(QUrl(uri).toLocalFile());
|
||||
TextEditor::TextDocument *textDocument = TextEditor::TextDocument::textDocumentForFilePath(
|
||||
filePath);
|
||||
// Нормализуем путь
|
||||
QString normalizedPath = QDir::fromNativeSeparators(filePath);
|
||||
|
||||
if (!textDocument) {
|
||||
LOG_MESSAGE("Error: Document is not available for" + filePath.toString());
|
||||
return Context::ProgrammingLanguage::Unknown;
|
||||
// Проверяем, содержит ли путь паттерны build-директории
|
||||
for (const QString &pattern : buildDirPatterns) {
|
||||
// Сравниваем с нормализованным паттерном
|
||||
QString normalizedPattern = QDir::fromNativeSeparators(pattern);
|
||||
if (normalizedPath.contains(normalizedPattern)) {
|
||||
qDebug() << "Skipping build file:" << filePath;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return Context::ProgrammingLanguageUtils::fromMimeType(textDocument->mimeType());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ContextManager::isSpecifyCompletion(const QJsonObject &request)
|
||||
QStringList ContextManager::getProjectSourceFiles(ProjectExplorer::Project *project) const
|
||||
{
|
||||
auto &generalSettings = Settings::generalSettings();
|
||||
QStringList sourceFiles;
|
||||
if (!project)
|
||||
return sourceFiles;
|
||||
|
||||
Context::ProgrammingLanguage documentLanguage = getDocumentLanguage(request);
|
||||
Context::ProgrammingLanguage preset1Language = Context::ProgrammingLanguageUtils::fromString(
|
||||
generalSettings.preset1Language.displayForIndex(generalSettings.preset1Language()));
|
||||
auto projectNode = project->rootProjectNode();
|
||||
if (!projectNode)
|
||||
return sourceFiles;
|
||||
|
||||
return generalSettings.specifyPreset1() && documentLanguage == preset1Language;
|
||||
projectNode->forEachNode(
|
||||
[&sourceFiles, this](ProjectExplorer::FileNode *fileNode) {
|
||||
if (fileNode) {
|
||||
QString filePath = fileNode->filePath().toString();
|
||||
if (shouldProcessFile(filePath) && !isInBuildDirectory(filePath)) {
|
||||
sourceFiles.append(filePath);
|
||||
}
|
||||
}
|
||||
},
|
||||
nullptr);
|
||||
|
||||
return sourceFiles;
|
||||
}
|
||||
|
||||
bool ContextManager::shouldProcessFile(const QString &filePath) const
|
||||
{
|
||||
static const QStringList supportedExtensions
|
||||
= {"cpp", "hpp", "c", "h", "cc", "hh", "cxx", "hxx", "qml", "js", "py"};
|
||||
|
||||
QFileInfo fileInfo(filePath);
|
||||
return supportedExtensions.contains(fileInfo.suffix().toLower());
|
||||
}
|
||||
|
||||
void ContextManager::testProjectChunks(
|
||||
ProjectExplorer::Project *project, const FileChunker::ChunkingConfig &config)
|
||||
{
|
||||
if (!project) {
|
||||
qDebug() << "No project provided";
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "\nStarting test chunking for project:" << project->displayName();
|
||||
|
||||
// Get source files
|
||||
QStringList sourceFiles = getProjectSourceFiles(project);
|
||||
qDebug() << "Found" << sourceFiles.size() << "source files";
|
||||
|
||||
// Create chunker
|
||||
auto chunker = new FileChunker(config, this);
|
||||
|
||||
// Connect progress and error signals
|
||||
connect(chunker, &FileChunker::progressUpdated, this, [](int processed, int total) {
|
||||
qDebug() << "Progress:" << processed << "/" << total << "files";
|
||||
});
|
||||
|
||||
connect(chunker, &FileChunker::error, this, [](const QString &error) {
|
||||
qDebug() << "Error:" << error;
|
||||
});
|
||||
|
||||
// Start chunking and handle results
|
||||
auto future = chunker->chunkFiles(sourceFiles);
|
||||
|
||||
// Используем QFutureWatcher для обработки результатов
|
||||
auto watcher = new QFutureWatcher<QList<FileChunk>>(this);
|
||||
|
||||
connect(watcher, &QFutureWatcher<QList<FileChunk>>::finished, this, [watcher, chunker]() {
|
||||
// Очистка
|
||||
watcher->deleteLater();
|
||||
chunker->deleteLater();
|
||||
});
|
||||
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
|
||||
@@ -19,11 +19,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ContentFile.hpp"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "ContentFile.hpp"
|
||||
#include "ProgrammingLanguage.hpp"
|
||||
#include "FileChunker.hpp"
|
||||
|
||||
namespace ProjectExplorer {
|
||||
class Project;
|
||||
}
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
@@ -33,17 +38,23 @@ class ContextManager : public QObject
|
||||
|
||||
public:
|
||||
static ContextManager &instance();
|
||||
|
||||
QString readFile(const QString &filePath) const;
|
||||
QList<ContentFile> getContentFiles(const QStringList &filePaths) const;
|
||||
ProgrammingLanguage getDocumentLanguage(const QJsonObject &request) const;
|
||||
bool isSpecifyCompletion(const QJsonObject &request);
|
||||
QStringList getProjectSourceFiles(ProjectExplorer::Project *project) const;
|
||||
|
||||
void testProjectChunks(
|
||||
ProjectExplorer::Project *project, const FileChunker::ChunkingConfig &config);
|
||||
|
||||
private:
|
||||
explicit ContextManager(QObject *parent = nullptr);
|
||||
~ContextManager() = default;
|
||||
ContextManager(const ContextManager &) = delete;
|
||||
ContextManager &operator=(const ContextManager &) = delete;
|
||||
|
||||
ContentFile createContentFile(const QString &filePath) const;
|
||||
bool shouldProcessFile(const QString &filePath) const;
|
||||
bool isInBuildDirectory(const QString &filePath) const;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
|
||||
@@ -210,13 +210,14 @@ LLMCore::ContextData DocumentContextReader::prepareContext(int lineNumber, int c
|
||||
QString contextAfter = getContextAfter(lineNumber, cursorPosition);
|
||||
|
||||
QString fileContext;
|
||||
fileContext.append("\n ").append(getLanguageAndFileInfo());
|
||||
if (Settings::codeCompletionSettings().useFilePathInContext())
|
||||
fileContext.append("\n ").append(getLanguageAndFileInfo());
|
||||
|
||||
if (Settings::codeCompletionSettings().useProjectChangesCache())
|
||||
fileContext.append("\n ").append(
|
||||
ChangesManager::instance().getRecentChangesContext(m_textDocument));
|
||||
|
||||
return {.prefix = contextBefore, .suffix = contextAfter, .fileContext = fileContext};
|
||||
return {contextBefore, contextAfter, fileContext};
|
||||
}
|
||||
|
||||
QString DocumentContextReader::getContextBefore(int lineNumber, int cursorPosition) const
|
||||
|
||||
265
context/EnhancedRAGSimilaritySearch.cpp
Normal file
265
context/EnhancedRAGSimilaritySearch.cpp
Normal file
@@ -0,0 +1,265 @@
|
||||
#include "EnhancedRAGSimilaritySearch.hpp"
|
||||
|
||||
#include <QSet>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
// Static regex getters
|
||||
const QRegularExpression &EnhancedRAGSimilaritySearch::getNamespaceRegex()
|
||||
{
|
||||
static const QRegularExpression regex(R"(namespace\s+(?:\w+\s*::\s*)*\w+\s*\{)");
|
||||
return regex;
|
||||
}
|
||||
|
||||
const QRegularExpression &EnhancedRAGSimilaritySearch::getClassRegex()
|
||||
{
|
||||
static const QRegularExpression regex(
|
||||
R"((?:template\s*<[^>]*>\s*)?(?:class|struct)\s+(\w+)\s*(?:final\s*)?(?::\s*(?:public|protected|private)\s+\w+(?:\s*,\s*(?:public|protected|private)\s+\w+)*\s*)?{)");
|
||||
return regex;
|
||||
}
|
||||
|
||||
const QRegularExpression &EnhancedRAGSimilaritySearch::getFunctionRegex()
|
||||
{
|
||||
static const QRegularExpression regex(
|
||||
R"((?:virtual\s+)?(?:static\s+)?(?:inline\s+)?(?:explicit\s+)?(?:constexpr\s+)?(?:[\w:]+\s+)?(?:\w+\s*::\s*)*\w+\s*\([^)]*\)\s*(?:const\s*)?(?:noexcept\s*)?(?:override\s*)?(?:final\s*)?(?:=\s*0\s*)?(?:=\s*default\s*)?(?:=\s*delete\s*)?(?:\s*->.*?)?\s*{)");
|
||||
return regex;
|
||||
}
|
||||
|
||||
const QRegularExpression &EnhancedRAGSimilaritySearch::getTemplateRegex()
|
||||
{
|
||||
static const QRegularExpression regex(R"(template\s*<[^>]*>\s*(?:class|struct|typename)\s+\w+)");
|
||||
return regex;
|
||||
}
|
||||
|
||||
// Cache getters
|
||||
QCache<QString, EnhancedRAGSimilaritySearch::SimilarityScore> &
|
||||
EnhancedRAGSimilaritySearch::getScoreCache()
|
||||
{
|
||||
static QCache<QString, SimilarityScore> cache(1000); // Cache size of 1000 entries
|
||||
return cache;
|
||||
}
|
||||
|
||||
QCache<QString, QStringList> &EnhancedRAGSimilaritySearch::getStructureCache()
|
||||
{
|
||||
static QCache<QString, QStringList> cache(500); // Cache size of 500 entries
|
||||
return cache;
|
||||
}
|
||||
|
||||
// Main public interface
|
||||
EnhancedRAGSimilaritySearch::SimilarityScore EnhancedRAGSimilaritySearch::calculateSimilarity(
|
||||
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2)
|
||||
{
|
||||
// Generate cache key based on content hashes
|
||||
QString cacheKey = QString("%1_%2").arg(qHash(code1)).arg(qHash(code2));
|
||||
|
||||
// Check cache first
|
||||
auto &scoreCache = getScoreCache();
|
||||
if (auto *cached = scoreCache.object(cacheKey)) {
|
||||
return *cached;
|
||||
}
|
||||
|
||||
// Calculate new similarity score
|
||||
SimilarityScore score = calculateSimilarityInternal(v1, v2, code1, code2);
|
||||
|
||||
// Cache the result
|
||||
scoreCache.insert(cacheKey, new SimilarityScore(score));
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
// Internal implementation
|
||||
EnhancedRAGSimilaritySearch::SimilarityScore EnhancedRAGSimilaritySearch::calculateSimilarityInternal(
|
||||
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2)
|
||||
{
|
||||
if (v1.empty() || v2.empty()) {
|
||||
LOG_MESSAGE("Warning: Empty vectors in similarity calculation");
|
||||
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
if (v1.size() != v2.size()) {
|
||||
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
// Calculate semantic similarity using vector embeddings
|
||||
float semantic_similarity = 0.0f;
|
||||
|
||||
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||
if (v1.size() >= 4) { // Use SSE for vectors of 4 or more elements
|
||||
semantic_similarity = calculateCosineSimilaritySSE(v1, v2);
|
||||
} else {
|
||||
semantic_similarity = calculateCosineSimilarity(v1, v2);
|
||||
}
|
||||
#else
|
||||
semantic_similarity = calculateCosineSimilarity(v1, v2);
|
||||
#endif
|
||||
|
||||
// If semantic similarity is very low, skip structural analysis
|
||||
if (semantic_similarity < 0.0001f) {
|
||||
return SimilarityScore(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
// Calculate structural similarity
|
||||
float structural_similarity = calculateStructuralSimilarity(code1, code2);
|
||||
|
||||
// Calculate combined score with dynamic weights
|
||||
float semantic_weight = 0.7f;
|
||||
const int large_file_threshold = 10000;
|
||||
|
||||
if (code1.size() > large_file_threshold || code2.size() > large_file_threshold) {
|
||||
semantic_weight = 0.8f; // Increase semantic weight for large files
|
||||
}
|
||||
|
||||
float combined_score = semantic_weight * semantic_similarity
|
||||
+ (1.0f - semantic_weight) * structural_similarity;
|
||||
|
||||
return SimilarityScore(semantic_similarity, structural_similarity, combined_score);
|
||||
}
|
||||
|
||||
float EnhancedRAGSimilaritySearch::calculateCosineSimilarity(const RAGVector &v1, const RAGVector &v2)
|
||||
{
|
||||
float dotProduct = 0.0f;
|
||||
float norm1 = 0.0f;
|
||||
float norm2 = 0.0f;
|
||||
|
||||
for (size_t i = 0; i < v1.size(); ++i) {
|
||||
dotProduct += v1[i] * v2[i];
|
||||
norm1 += v1[i] * v1[i];
|
||||
norm2 += v2[i] * v2[i];
|
||||
}
|
||||
|
||||
norm1 = std::sqrt(norm1);
|
||||
norm2 = std::sqrt(norm2);
|
||||
|
||||
if (norm1 == 0.0f || norm2 == 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return dotProduct / (norm1 * norm2);
|
||||
}
|
||||
|
||||
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||
float EnhancedRAGSimilaritySearch::calculateCosineSimilaritySSE(
|
||||
const RAGVector &v1, const RAGVector &v2)
|
||||
{
|
||||
const float *p1 = v1.data();
|
||||
const float *p2 = v2.data();
|
||||
const size_t size = v1.size();
|
||||
const size_t alignedSize = size & ~3ULL; // Round down to multiple of 4
|
||||
|
||||
__m128 sum = _mm_setzero_ps();
|
||||
__m128 norm1 = _mm_setzero_ps();
|
||||
__m128 norm2 = _mm_setzero_ps();
|
||||
|
||||
// Process 4 elements at a time using SSE
|
||||
for (size_t i = 0; i < alignedSize; i += 4) {
|
||||
__m128 v1_vec = _mm_loadu_ps(p1 + i); // Use unaligned load for safety
|
||||
__m128 v2_vec = _mm_loadu_ps(p2 + i);
|
||||
|
||||
sum = _mm_add_ps(sum, _mm_mul_ps(v1_vec, v2_vec));
|
||||
norm1 = _mm_add_ps(norm1, _mm_mul_ps(v1_vec, v1_vec));
|
||||
norm2 = _mm_add_ps(norm2, _mm_mul_ps(v2_vec, v2_vec));
|
||||
}
|
||||
|
||||
float dotProduct = horizontalSum(sum);
|
||||
float n1 = std::sqrt(horizontalSum(norm1));
|
||||
float n2 = std::sqrt(horizontalSum(norm2));
|
||||
|
||||
// Process remaining elements
|
||||
for (size_t i = alignedSize; i < size; ++i) {
|
||||
dotProduct += v1[i] * v2[i];
|
||||
n1 += v1[i] * v1[i];
|
||||
n2 += v2[i] * v2[i];
|
||||
}
|
||||
|
||||
if (n1 == 0.0f || n2 == 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return dotProduct / (std::sqrt(n1) * std::sqrt(n2));
|
||||
}
|
||||
|
||||
float EnhancedRAGSimilaritySearch::horizontalSum(__m128 x)
|
||||
{
|
||||
__m128 shuf = _mm_shuffle_ps(x, x, _MM_SHUFFLE(2, 3, 0, 1));
|
||||
__m128 sums = _mm_add_ps(x, shuf);
|
||||
shuf = _mm_movehl_ps(shuf, sums);
|
||||
sums = _mm_add_ss(sums, shuf);
|
||||
return _mm_cvtss_f32(sums);
|
||||
}
|
||||
#endif
|
||||
|
||||
float EnhancedRAGSimilaritySearch::calculateStructuralSimilarity(
|
||||
const QString &code1, const QString &code2)
|
||||
{
|
||||
QStringList structures1 = extractStructures(code1);
|
||||
QStringList structures2 = extractStructures(code2);
|
||||
|
||||
return calculateJaccardSimilarity(structures1, structures2);
|
||||
}
|
||||
|
||||
QStringList EnhancedRAGSimilaritySearch::extractStructures(const QString &code)
|
||||
{
|
||||
// Check cache first
|
||||
auto &structureCache = getStructureCache();
|
||||
QString cacheKey = QString::number(qHash(code));
|
||||
|
||||
if (auto *cached = structureCache.object(cacheKey)) {
|
||||
return *cached;
|
||||
}
|
||||
|
||||
QStringList structures;
|
||||
structures.reserve(100); // Reserve space for typical file
|
||||
|
||||
// Extract namespaces
|
||||
auto namespaceMatches = getNamespaceRegex().globalMatch(code);
|
||||
while (namespaceMatches.hasNext()) {
|
||||
structures.append(namespaceMatches.next().captured().trimmed());
|
||||
}
|
||||
|
||||
// Extract classes
|
||||
auto classMatches = getClassRegex().globalMatch(code);
|
||||
while (classMatches.hasNext()) {
|
||||
structures.append(classMatches.next().captured().trimmed());
|
||||
}
|
||||
|
||||
// Extract functions
|
||||
auto functionMatches = getFunctionRegex().globalMatch(code);
|
||||
while (functionMatches.hasNext()) {
|
||||
structures.append(functionMatches.next().captured().trimmed());
|
||||
}
|
||||
|
||||
// Extract templates
|
||||
auto templateMatches = getTemplateRegex().globalMatch(code);
|
||||
while (templateMatches.hasNext()) {
|
||||
structures.append(templateMatches.next().captured().trimmed());
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
structureCache.insert(cacheKey, new QStringList(structures));
|
||||
|
||||
return structures;
|
||||
}
|
||||
|
||||
float EnhancedRAGSimilaritySearch::calculateJaccardSimilarity(
|
||||
const QStringList &set1, const QStringList &set2)
|
||||
{
|
||||
if (set1.isEmpty() && set2.isEmpty()) {
|
||||
return 1.0f; // Пустые множества считаем идентичными
|
||||
}
|
||||
if (set1.isEmpty() || set2.isEmpty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
QSet<QString> set1Unique = QSet<QString>(set1.begin(), set1.end());
|
||||
QSet<QString> set2Unique = QSet<QString>(set2.begin(), set2.end());
|
||||
|
||||
QSet<QString> intersection = set1Unique;
|
||||
intersection.intersect(set2Unique);
|
||||
|
||||
QSet<QString> union_set = set1Unique;
|
||||
union_set.unite(set2Unique);
|
||||
|
||||
return static_cast<float>(intersection.size()) / union_set.size();
|
||||
}
|
||||
} // namespace QodeAssist::Context
|
||||
74
context/EnhancedRAGSimilaritySearch.hpp
Normal file
74
context/EnhancedRAGSimilaritySearch.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include <QCache>
|
||||
#include <QHash>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||
#include <emmintrin.h>
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
#include "RAGData.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class EnhancedRAGSimilaritySearch
|
||||
{
|
||||
public:
|
||||
struct SimilarityScore
|
||||
{
|
||||
float semantic_similarity{0.0f};
|
||||
float structural_similarity{0.0f};
|
||||
float combined_score{0.0f};
|
||||
|
||||
SimilarityScore() = default;
|
||||
SimilarityScore(float sem, float str, float comb)
|
||||
: semantic_similarity(sem)
|
||||
, structural_similarity(str)
|
||||
, combined_score(comb)
|
||||
{}
|
||||
};
|
||||
|
||||
static SimilarityScore calculateSimilarity(
|
||||
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2);
|
||||
|
||||
private:
|
||||
static SimilarityScore calculateSimilarityInternal(
|
||||
const RAGVector &v1, const RAGVector &v2, const QString &code1, const QString &code2);
|
||||
|
||||
static float calculateCosineSimilarity(const RAGVector &v1, const RAGVector &v2);
|
||||
|
||||
#if defined(__SSE__) || defined(_M_X64) || defined(_M_AMD64)
|
||||
static float calculateCosineSimilaritySSE(const RAGVector &v1, const RAGVector &v2);
|
||||
static float horizontalSum(__m128 x);
|
||||
#endif
|
||||
|
||||
static float calculateStructuralSimilarity(const QString &code1, const QString &code2);
|
||||
static QStringList extractStructures(const QString &code);
|
||||
static float calculateJaccardSimilarity(const QStringList &set1, const QStringList &set2);
|
||||
|
||||
static const QRegularExpression &getNamespaceRegex();
|
||||
static const QRegularExpression &getClassRegex();
|
||||
static const QRegularExpression &getFunctionRegex();
|
||||
static const QRegularExpression &getTemplateRegex();
|
||||
|
||||
// Cache for similarity scores
|
||||
static QCache<QString, SimilarityScore> &getScoreCache();
|
||||
|
||||
// Cache for extracted structures
|
||||
static QCache<QString, QStringList> &getStructureCache();
|
||||
|
||||
EnhancedRAGSimilaritySearch() = delete; // Prevent instantiation
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
198
context/FileChunker.cpp
Normal file
198
context/FileChunker.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
// FileChunker.cpp
|
||||
#include "FileChunker.hpp"
|
||||
|
||||
#include <coreplugin/idocument.h>
|
||||
#include <texteditor/syntaxhighlighter.h>
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <texteditor/texteditorconstants.h>
|
||||
|
||||
#include <QFutureWatcher>
|
||||
#include <QTimer>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
FileChunker::FileChunker(QObject *parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
FileChunker::FileChunker(const ChunkingConfig &config, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_config(config)
|
||||
{}
|
||||
|
||||
QFuture<QList<FileChunk>> FileChunker::chunkFiles(const QStringList &filePaths)
|
||||
{
|
||||
qDebug() << "\nStarting chunking process for" << filePaths.size() << "files";
|
||||
qDebug() << "Configuration:"
|
||||
<< "\n Max lines per chunk:" << m_config.maxLinesPerChunk
|
||||
<< "\n Overlap lines:" << m_config.overlapLines
|
||||
<< "\n Skip empty lines:" << m_config.skipEmptyLines
|
||||
<< "\n Preserve functions:" << m_config.preserveFunctions
|
||||
<< "\n Preserve classes:" << m_config.preserveClasses
|
||||
<< "\n Batch size:" << m_config.batchSize;
|
||||
|
||||
auto promise = std::make_shared<QPromise<QList<FileChunk>>>();
|
||||
promise->start();
|
||||
|
||||
if (filePaths.isEmpty()) {
|
||||
qDebug() << "No files to process";
|
||||
promise->addResult({});
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
processNextBatch(promise, filePaths, 0);
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
void FileChunker::processNextBatch(
|
||||
std::shared_ptr<QPromise<QList<FileChunk>>> promise, const QStringList &files, int startIndex)
|
||||
{
|
||||
if (startIndex >= files.size()) {
|
||||
emit chunkingComplete();
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
int endIndex = qMin(startIndex + m_config.batchSize, files.size());
|
||||
QList<FileChunk> batchChunks;
|
||||
|
||||
for (int i = startIndex; i < endIndex; ++i) {
|
||||
try {
|
||||
auto chunks = processFile(files[i]);
|
||||
batchChunks.append(chunks);
|
||||
} catch (const std::exception &e) {
|
||||
emit error(QString("Error processing file %1: %2").arg(files[i], e.what()));
|
||||
}
|
||||
emit progressUpdated(i + 1, files.size());
|
||||
}
|
||||
|
||||
promise->addResult(batchChunks);
|
||||
|
||||
// Планируем обработку следующего батча
|
||||
QTimer::singleShot(0, this, [this, promise, files, endIndex]() {
|
||||
processNextBatch(promise, files, endIndex);
|
||||
});
|
||||
}
|
||||
|
||||
QList<FileChunk> FileChunker::processFile(const QString &filePath)
|
||||
{
|
||||
qDebug() << "\nProcessing file:" << filePath;
|
||||
|
||||
auto document = new TextEditor::TextDocument;
|
||||
auto filePathObj = Utils::FilePath::fromString(filePath);
|
||||
auto result = document->open(&m_error, filePathObj, filePathObj);
|
||||
if (result != Core::IDocument::OpenResult::Success) {
|
||||
qDebug() << "Failed to open document:" << filePath << "-" << m_error;
|
||||
emit error(QString("Failed to open document: %1 - %2").arg(filePath, m_error));
|
||||
delete document;
|
||||
return {};
|
||||
}
|
||||
|
||||
qDebug() << "Document opened successfully. Line count:" << document->document()->blockCount();
|
||||
|
||||
auto chunks = createChunksForDocument(document);
|
||||
qDebug() << "Created" << chunks.size() << "chunks for file";
|
||||
|
||||
delete document;
|
||||
return chunks;
|
||||
}
|
||||
|
||||
QList<FileChunk> FileChunker::createChunksForDocument(TextEditor::TextDocument *document)
|
||||
{
|
||||
QList<FileChunk> chunks;
|
||||
QString filePath = document->filePath().toString();
|
||||
qDebug() << "\nCreating chunks for document:" << filePath << "\nConfiguration:"
|
||||
<< "\n Max lines per chunk:" << m_config.maxLinesPerChunk
|
||||
<< "\n Min lines per chunk:" << m_config.minLinesPerChunk
|
||||
<< "\n Overlap lines:" << m_config.overlapLines;
|
||||
// Если файл меньше минимального размера чанка, создаем один чанк
|
||||
if (document->document()->blockCount() <= m_config.minLinesPerChunk) {
|
||||
FileChunk chunk;
|
||||
chunk.filePath = filePath;
|
||||
chunk.startLine = 0;
|
||||
chunk.endLine = document->document()->blockCount() - 1;
|
||||
chunk.createdAt = QDateTime::currentDateTime();
|
||||
chunk.updatedAt = chunk.createdAt;
|
||||
|
||||
QString content;
|
||||
QTextBlock block = document->document()->firstBlock();
|
||||
while (block.isValid()) {
|
||||
content += block.text() + "\n";
|
||||
block = block.next();
|
||||
}
|
||||
chunk.content = content;
|
||||
|
||||
qDebug() << "File is smaller than minimum chunk size. Creating single chunk:"
|
||||
<< "\n Lines:" << chunk.lineCount() << "\n Content size:" << chunk.content.size()
|
||||
<< "bytes";
|
||||
|
||||
chunks.append(chunk);
|
||||
return chunks;
|
||||
}
|
||||
|
||||
// Для больших файлов создаем чанки фиксированного размера с перекрытием
|
||||
int currentStartLine = 0;
|
||||
int lineCount = 0;
|
||||
QString content;
|
||||
QTextBlock block = document->document()->firstBlock();
|
||||
|
||||
while (block.isValid()) {
|
||||
content += block.text() + "\n";
|
||||
lineCount++;
|
||||
|
||||
// Если достигли размера чанка или это последний блок
|
||||
if (lineCount >= m_config.maxLinesPerChunk || !block.next().isValid()) {
|
||||
FileChunk chunk;
|
||||
chunk.filePath = filePath;
|
||||
chunk.startLine = currentStartLine;
|
||||
chunk.endLine = currentStartLine + lineCount - 1;
|
||||
chunk.content = content;
|
||||
chunk.createdAt = QDateTime::currentDateTime();
|
||||
chunk.updatedAt = chunk.createdAt;
|
||||
|
||||
qDebug() << "Creating chunk:"
|
||||
<< "\n Start line:" << chunk.startLine << "\n End line:" << chunk.endLine
|
||||
<< "\n Lines:" << chunk.lineCount()
|
||||
<< "\n Content size:" << chunk.content.size() << "bytes";
|
||||
|
||||
chunks.append(chunk);
|
||||
|
||||
// Начинаем новый чанк с учетом перекрытия
|
||||
if (block.next().isValid()) {
|
||||
// Отступаем назад на размер перекрытия
|
||||
int overlapLines = qMin(m_config.overlapLines, lineCount);
|
||||
currentStartLine = chunk.endLine - overlapLines + 1;
|
||||
|
||||
// Сбрасываем контент, но добавляем перекрывающиеся строки
|
||||
content.clear();
|
||||
QTextBlock overlapBlock = document->document()->findBlockByLineNumber(
|
||||
currentStartLine);
|
||||
while (overlapBlock.isValid() && overlapBlock.blockNumber() <= chunk.endLine) {
|
||||
content += overlapBlock.text() + "\n";
|
||||
overlapBlock = overlapBlock.next();
|
||||
}
|
||||
lineCount = overlapLines;
|
||||
}
|
||||
}
|
||||
|
||||
block = block.next();
|
||||
}
|
||||
|
||||
qDebug() << "Finished creating chunks for file:" << filePath
|
||||
<< "\nTotal chunks:" << chunks.size();
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
void FileChunker::setConfig(const ChunkingConfig &config)
|
||||
{
|
||||
m_config = config;
|
||||
}
|
||||
|
||||
FileChunker::ChunkingConfig FileChunker::config() const
|
||||
{
|
||||
return m_config;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
68
context/FileChunker.hpp
Normal file
68
context/FileChunker.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
// FileChunker.hpp
|
||||
#pragma once
|
||||
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <QDateTime>
|
||||
#include <QFuture>
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
struct FileChunk
|
||||
{
|
||||
QString filePath; // Path to the source file
|
||||
int startLine; // Starting line of the chunk
|
||||
int endLine; // Ending line of the chunk
|
||||
QDateTime createdAt; // When the chunk was created
|
||||
QDateTime updatedAt; // When the chunk was last updated
|
||||
QString content; // Content of the chunk
|
||||
|
||||
// Helper methods
|
||||
int lineCount() const { return endLine - startLine + 1; }
|
||||
bool isValid() const { return !filePath.isEmpty() && startLine >= 0 && endLine >= startLine; }
|
||||
};
|
||||
|
||||
class FileChunker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct ChunkingConfig
|
||||
{
|
||||
int maxLinesPerChunk = 80;
|
||||
int minLinesPerChunk = 40;
|
||||
int overlapLines = 20;
|
||||
bool skipEmptyLines = true;
|
||||
bool preserveFunctions = true;
|
||||
bool preserveClasses = true;
|
||||
int batchSize = 10;
|
||||
};
|
||||
|
||||
explicit FileChunker(QObject *parent = nullptr);
|
||||
explicit FileChunker(const ChunkingConfig &config, QObject *parent = nullptr);
|
||||
|
||||
// Main chunking method
|
||||
QFuture<QList<FileChunk>> chunkFiles(const QStringList &filePaths);
|
||||
|
||||
// Configuration
|
||||
void setConfig(const ChunkingConfig &config);
|
||||
ChunkingConfig config() const;
|
||||
|
||||
signals:
|
||||
void progressUpdated(int processedFiles, int totalFiles);
|
||||
void chunkingComplete();
|
||||
void error(const QString &errorMessage);
|
||||
|
||||
private:
|
||||
QList<FileChunk> processFile(const QString &filePath);
|
||||
QList<FileChunk> createChunksForDocument(TextEditor::TextDocument *document);
|
||||
void processNextBatch(
|
||||
std::shared_ptr<QPromise<QList<FileChunk>>> promise,
|
||||
const QStringList &files,
|
||||
int startIndex);
|
||||
|
||||
ChunkingConfig m_config;
|
||||
QString m_error;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
@@ -24,8 +24,8 @@
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
enum class ProgrammingLanguage {
|
||||
QML, // QML/JavaScript
|
||||
Cpp, // C/C++
|
||||
QML,
|
||||
Cpp,
|
||||
Python,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
7
context/RAGData.hpp
Normal file
7
context/RAGData.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
using RAGVector = std::vector<float>;
|
||||
}
|
||||
443
context/RAGManager.cpp
Normal file
443
context/RAGManager.cpp
Normal file
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "RAGManager.hpp"
|
||||
#include "EnhancedRAGSimilaritySearch.hpp"
|
||||
#include "RAGPreprocessor.hpp"
|
||||
#include "RAGSimilaritySearch.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <QFile>
|
||||
#include <QtConcurrent>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
RAGManager &RAGManager::instance()
|
||||
{
|
||||
static RAGManager manager;
|
||||
return manager;
|
||||
}
|
||||
|
||||
RAGManager::RAGManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_vectorizer(std::make_unique<RAGVectorizer>())
|
||||
{}
|
||||
|
||||
RAGManager::~RAGManager() {}
|
||||
|
||||
QString RAGManager::getStoragePath(ProjectExplorer::Project *project) const
|
||||
{
|
||||
return QString("%1/qodeassist/%2/rag/vectors.db")
|
||||
.arg(Core::ICore::userResourcePath().toString(), project->displayName());
|
||||
}
|
||||
|
||||
std::optional<QString> RAGManager::loadFileContent(const QString &filePath)
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
qDebug() << "ERROR: Failed to open file for reading:" << filePath
|
||||
<< "Error:" << file.errorString();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QFileInfo fileInfo(filePath);
|
||||
qDebug() << "Loading content from file:" << fileInfo.fileName() << "Size:" << fileInfo.size()
|
||||
<< "bytes";
|
||||
|
||||
QString content = QString::fromUtf8(file.readAll());
|
||||
if (content.isEmpty()) {
|
||||
qDebug() << "WARNING: Empty content read from file:" << filePath;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
void RAGManager::ensureStorageForProject(ProjectExplorer::Project *project) const
|
||||
{
|
||||
qDebug() << "Ensuring storage for project:" << project->displayName();
|
||||
|
||||
if (m_currentProject == project && m_currentStorage) {
|
||||
qDebug() << "Using existing storage";
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Creating new storage";
|
||||
m_currentStorage.reset();
|
||||
m_currentProject = project;
|
||||
|
||||
if (project) {
|
||||
QString storagePath = getStoragePath(project);
|
||||
qDebug() << "Storage path:" << storagePath;
|
||||
|
||||
StorageOptions options;
|
||||
m_currentStorage = std::make_unique<RAGStorage>(storagePath, options);
|
||||
|
||||
qDebug() << "Initializing storage...";
|
||||
if (!m_currentStorage->init()) {
|
||||
qDebug() << "Failed to initialize storage";
|
||||
m_currentStorage.reset();
|
||||
return;
|
||||
}
|
||||
qDebug() << "Storage initialized successfully";
|
||||
}
|
||||
}
|
||||
|
||||
QFuture<void> RAGManager::processProjectFiles(
|
||||
ProjectExplorer::Project *project,
|
||||
const QStringList &filePaths,
|
||||
const FileChunker::ChunkingConfig &config)
|
||||
{
|
||||
qDebug() << "\nStarting batch processing of" << filePaths.size()
|
||||
<< "files for project:" << project->displayName();
|
||||
|
||||
auto promise = std::make_shared<QPromise<void>>();
|
||||
promise->start();
|
||||
|
||||
qDebug() << "Initializing storage...";
|
||||
ensureStorageForProject(project);
|
||||
|
||||
if (!m_currentStorage) {
|
||||
qDebug() << "Failed to initialize storage for project:" << project->displayName();
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
qDebug() << "Storage initialized successfully";
|
||||
|
||||
qDebug() << "Checking files for processing...";
|
||||
QSet<QString> uniqueFiles;
|
||||
for (const QString &filePath : filePaths) {
|
||||
qDebug() << "Checking file:" << filePath;
|
||||
if (isFileStorageOutdated(project, filePath)) {
|
||||
qDebug() << "File needs processing:" << filePath;
|
||||
uniqueFiles.insert(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList filesToProcess = uniqueFiles.values();
|
||||
|
||||
if (filesToProcess.isEmpty()) {
|
||||
qDebug() << "No files need processing";
|
||||
emit vectorizationFinished();
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
qDebug() << "Starting to process" << filesToProcess.size() << "files";
|
||||
const int batchSize = 10;
|
||||
processNextFileBatch(promise, project, filesToProcess, config, 0, batchSize);
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
void RAGManager::processNextFileBatch(
|
||||
std::shared_ptr<QPromise<void>> promise,
|
||||
ProjectExplorer::Project *project,
|
||||
const QStringList &files,
|
||||
const FileChunker::ChunkingConfig &config,
|
||||
int startIndex,
|
||||
int batchSize)
|
||||
{
|
||||
if (startIndex >= files.size()) {
|
||||
qDebug() << "All batches processed successfully";
|
||||
emit vectorizationFinished();
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
int endIndex = qMin(startIndex + batchSize, files.size());
|
||||
auto currentBatch = files.mid(startIndex, endIndex - startIndex);
|
||||
|
||||
qDebug() << "\nProcessing batch" << (startIndex / batchSize + 1) << "(" << currentBatch.size()
|
||||
<< "files)"
|
||||
<< "\nProgress:" << startIndex << "to" << endIndex << "of" << files.size();
|
||||
|
||||
for (const QString &filePath : currentBatch) {
|
||||
qDebug() << "Starting processing file:" << filePath;
|
||||
auto future = processFileWithChunks(project, filePath, config);
|
||||
auto watcher = new QFutureWatcher<bool>;
|
||||
watcher->setFuture(future);
|
||||
|
||||
connect(
|
||||
watcher,
|
||||
&QFutureWatcher<bool>::finished,
|
||||
this,
|
||||
[this,
|
||||
watcher,
|
||||
promise,
|
||||
project,
|
||||
files,
|
||||
startIndex,
|
||||
endIndex,
|
||||
batchSize,
|
||||
config,
|
||||
filePath]() {
|
||||
bool success = watcher->result();
|
||||
qDebug() << "File processed:" << filePath << "success:" << success;
|
||||
|
||||
bool isLastFileInBatch = (filePath == files[endIndex - 1]);
|
||||
if (isLastFileInBatch) {
|
||||
qDebug() << "Batch completed, moving to next batch";
|
||||
emit vectorizationProgress(endIndex, files.size());
|
||||
processNextFileBatch(promise, project, files, config, endIndex, batchSize);
|
||||
}
|
||||
|
||||
watcher->deleteLater();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QFuture<bool> RAGManager::processFileWithChunks(
|
||||
ProjectExplorer::Project *project,
|
||||
const QString &filePath,
|
||||
const FileChunker::ChunkingConfig &config)
|
||||
{
|
||||
auto promise = std::make_shared<QPromise<bool>>();
|
||||
promise->start();
|
||||
|
||||
ensureStorageForProject(project);
|
||||
if (!m_currentStorage) {
|
||||
qDebug() << "Storage not initialized for file:" << filePath;
|
||||
promise->addResult(false);
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
auto fileContent = loadFileContent(filePath);
|
||||
if (!fileContent) {
|
||||
qDebug() << "Failed to load content for file:" << filePath;
|
||||
promise->addResult(false);
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
qDebug() << "Creating chunks for file:" << filePath;
|
||||
auto chunksFuture = m_chunker.chunkFiles({filePath});
|
||||
auto chunks = chunksFuture.result();
|
||||
|
||||
if (chunks.isEmpty()) {
|
||||
qDebug() << "No chunks created for file:" << filePath;
|
||||
promise->addResult(false);
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
qDebug() << "Created" << chunks.size() << "chunks for file:" << filePath;
|
||||
|
||||
// Преобразуем FileChunk в FileChunkData
|
||||
QList<FileChunkData> chunkData;
|
||||
for (const auto &chunk : chunks) {
|
||||
FileChunkData data;
|
||||
data.filePath = chunk.filePath;
|
||||
data.startLine = chunk.startLine;
|
||||
data.endLine = chunk.endLine;
|
||||
data.content = chunk.content;
|
||||
chunkData.append(data);
|
||||
}
|
||||
|
||||
qDebug() << "Deleting old chunks for file:" << filePath;
|
||||
m_currentStorage->deleteChunksForFile(filePath);
|
||||
|
||||
auto vectorizeFuture = vectorizeAndStoreChunks(filePath, chunkData);
|
||||
auto watcher = new QFutureWatcher<void>;
|
||||
watcher->setFuture(vectorizeFuture);
|
||||
|
||||
connect(watcher, &QFutureWatcher<void>::finished, this, [promise, watcher, filePath]() {
|
||||
qDebug() << "Completed processing file:" << filePath;
|
||||
promise->addResult(true);
|
||||
promise->finish();
|
||||
watcher->deleteLater();
|
||||
});
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QFuture<void> RAGManager::vectorizeAndStoreChunks(
|
||||
const QString &filePath, const QList<FileChunkData> &chunks)
|
||||
{
|
||||
qDebug() << "Vectorizing and storing" << chunks.size() << "chunks for file:" << filePath;
|
||||
|
||||
auto promise = std::make_shared<QPromise<void>>();
|
||||
promise->start();
|
||||
|
||||
// Обрабатываем чанки последовательно
|
||||
processNextChunk(promise, chunks, 0);
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
void RAGManager::processNextChunk(
|
||||
std::shared_ptr<QPromise<void>> promise, const QList<FileChunkData> &chunks, int currentIndex)
|
||||
{
|
||||
if (currentIndex >= chunks.size()) {
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &chunk = chunks[currentIndex];
|
||||
QString processedContent = RAGPreprocessor::preprocessCode(chunk.content);
|
||||
qDebug() << "Processing chunk" << currentIndex + 1 << "of" << chunks.size();
|
||||
|
||||
auto vectorFuture = m_vectorizer->vectorizeText(processedContent);
|
||||
auto watcher = new QFutureWatcher<RAGVector>;
|
||||
watcher->setFuture(vectorFuture);
|
||||
|
||||
connect(
|
||||
watcher,
|
||||
&QFutureWatcher<RAGVector>::finished,
|
||||
this,
|
||||
[this, watcher, promise, chunks, currentIndex, chunk]() {
|
||||
auto vector = watcher->result();
|
||||
|
||||
if (!vector.empty()) {
|
||||
qDebug() << "Storing vector and chunk for file:" << chunk.filePath;
|
||||
bool vectorStored = m_currentStorage->storeVector(chunk.filePath, vector);
|
||||
bool chunkStored = m_currentStorage->storeChunk(chunk);
|
||||
qDebug() << "Storage results - Vector:" << vectorStored << "Chunk:" << chunkStored;
|
||||
} else {
|
||||
qDebug() << "Failed to vectorize chunk content";
|
||||
}
|
||||
|
||||
processNextChunk(promise, chunks, currentIndex + 1);
|
||||
|
||||
watcher->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
QFuture<QList<RAGManager::ChunkSearchResult>> RAGManager::findRelevantChunks(
|
||||
const QString &query, ProjectExplorer::Project *project, int topK)
|
||||
{
|
||||
auto promise = std::make_shared<QPromise<QList<ChunkSearchResult>>>();
|
||||
promise->start();
|
||||
|
||||
ensureStorageForProject(project);
|
||||
if (!m_currentStorage) {
|
||||
qDebug() << "Storage not initialized for project:" << project->displayName();
|
||||
promise->addResult({});
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QString processedQuery = RAGPreprocessor::preprocessCode(query);
|
||||
|
||||
auto vectorFuture = m_vectorizer->vectorizeText(processedQuery);
|
||||
vectorFuture.then([this, promise, project, processedQuery, topK](const RAGVector &queryVector) {
|
||||
if (queryVector.empty()) {
|
||||
qDebug() << "Failed to vectorize query";
|
||||
promise->addResult({});
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
auto files = m_currentStorage->getFilesWithChunks();
|
||||
QList<FileChunkData> allChunks;
|
||||
|
||||
for (const auto &filePath : files) {
|
||||
auto fileChunks = m_currentStorage->getChunksForFile(filePath);
|
||||
allChunks.append(fileChunks);
|
||||
}
|
||||
|
||||
auto results = rankChunks(queryVector, processedQuery, allChunks);
|
||||
|
||||
if (results.size() > topK) {
|
||||
results = results.mid(0, topK);
|
||||
}
|
||||
|
||||
qDebug() << "Found" << results.size() << "relevant chunks";
|
||||
promise->addResult(results);
|
||||
promise->finish();
|
||||
|
||||
closeStorage();
|
||||
});
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QList<RAGManager::ChunkSearchResult> RAGManager::rankChunks(
|
||||
const RAGVector &queryVector, const QString &queryText, const QList<FileChunkData> &chunks)
|
||||
{
|
||||
QList<ChunkSearchResult> results;
|
||||
results.reserve(chunks.size());
|
||||
|
||||
for (const auto &chunk : chunks) {
|
||||
auto chunkVector = m_currentStorage->getVector(chunk.filePath);
|
||||
if (!chunkVector.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString processedChunk = RAGPreprocessor::preprocessCode(chunk.content);
|
||||
|
||||
auto similarity = EnhancedRAGSimilaritySearch::calculateSimilarity(
|
||||
queryVector, chunkVector.value(), queryText, processedChunk);
|
||||
|
||||
results.append(ChunkSearchResult{
|
||||
chunk.filePath,
|
||||
chunk.startLine,
|
||||
chunk.endLine,
|
||||
chunk.content,
|
||||
similarity.semantic_similarity,
|
||||
similarity.structural_similarity,
|
||||
similarity.combined_score});
|
||||
}
|
||||
|
||||
std::sort(results.begin(), results.end());
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
QStringList RAGManager::getStoredFiles(ProjectExplorer::Project *project) const
|
||||
{
|
||||
ensureStorageForProject(project);
|
||||
if (!m_currentStorage) {
|
||||
return {};
|
||||
}
|
||||
return m_currentStorage->getAllFiles();
|
||||
}
|
||||
|
||||
bool RAGManager::isFileStorageOutdated(
|
||||
ProjectExplorer::Project *project, const QString &filePath) const
|
||||
{
|
||||
ensureStorageForProject(project);
|
||||
if (!m_currentStorage) {
|
||||
return true;
|
||||
}
|
||||
return m_currentStorage->needsUpdate(filePath);
|
||||
}
|
||||
|
||||
std::optional<RAGVector> RAGManager::loadVectorFromStorage(
|
||||
ProjectExplorer::Project *project, const QString &filePath)
|
||||
{
|
||||
ensureStorageForProject(project);
|
||||
if (!m_currentStorage) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return m_currentStorage->getVector(filePath);
|
||||
}
|
||||
|
||||
void RAGManager::closeStorage()
|
||||
{
|
||||
qDebug() << "Closing storage...";
|
||||
if (m_currentStorage) {
|
||||
m_currentStorage.reset();
|
||||
m_currentProject = nullptr;
|
||||
qDebug() << "Storage closed";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
119
context/RAGManager.hpp
Normal file
119
context/RAGManager.hpp
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <QFuture>
|
||||
#include <QObject>
|
||||
|
||||
#include "FileChunker.hpp"
|
||||
#include "RAGData.hpp"
|
||||
#include "RAGStorage.hpp"
|
||||
#include "RAGVectorizer.hpp"
|
||||
|
||||
namespace ProjectExplorer {
|
||||
class Project;
|
||||
}
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class RAGManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct ChunkSearchResult
|
||||
{
|
||||
QString filePath;
|
||||
int startLine;
|
||||
int endLine;
|
||||
QString content;
|
||||
float semanticScore;
|
||||
float structuralScore;
|
||||
float combinedScore;
|
||||
|
||||
bool operator<(const ChunkSearchResult &other) const
|
||||
{
|
||||
return combinedScore > other.combinedScore;
|
||||
}
|
||||
};
|
||||
|
||||
static RAGManager &instance();
|
||||
|
||||
QFuture<void> processProjectFiles(
|
||||
ProjectExplorer::Project *project,
|
||||
const QStringList &filePaths,
|
||||
const FileChunker::ChunkingConfig &config = FileChunker::ChunkingConfig());
|
||||
|
||||
QFuture<QList<ChunkSearchResult>> findRelevantChunks(
|
||||
const QString &query, ProjectExplorer::Project *project, int topK = 5);
|
||||
|
||||
QStringList getStoredFiles(ProjectExplorer::Project *project) const;
|
||||
bool isFileStorageOutdated(ProjectExplorer::Project *project, const QString &filePath) const;
|
||||
|
||||
void processNextChunk(
|
||||
std::shared_ptr<QPromise<void>> promise,
|
||||
const QList<FileChunkData> &chunks,
|
||||
int currentIndex);
|
||||
void closeStorage();
|
||||
signals:
|
||||
void vectorizationProgress(int processed, int total);
|
||||
void vectorizationFinished();
|
||||
|
||||
private:
|
||||
explicit RAGManager(QObject *parent = nullptr);
|
||||
~RAGManager();
|
||||
RAGManager(const RAGManager &) = delete;
|
||||
RAGManager &operator=(const RAGManager &) = delete;
|
||||
|
||||
QString getStoragePath(ProjectExplorer::Project *project) const;
|
||||
void ensureStorageForProject(ProjectExplorer::Project *project) const;
|
||||
std::optional<QString> loadFileContent(const QString &filePath);
|
||||
std::optional<RAGVector> loadVectorFromStorage(
|
||||
ProjectExplorer::Project *project, const QString &filePath);
|
||||
|
||||
void processNextFileBatch(
|
||||
std::shared_ptr<QPromise<void>> promise,
|
||||
ProjectExplorer::Project *project,
|
||||
const QStringList &files,
|
||||
const FileChunker::ChunkingConfig &config,
|
||||
int startIndex,
|
||||
int batchSize);
|
||||
|
||||
QFuture<bool> processFileWithChunks(
|
||||
ProjectExplorer::Project *project,
|
||||
const QString &filePath,
|
||||
const FileChunker::ChunkingConfig &config);
|
||||
|
||||
QFuture<void> vectorizeAndStoreChunks(
|
||||
const QString &filePath, const QList<FileChunkData> &chunks);
|
||||
|
||||
QList<ChunkSearchResult> rankChunks(
|
||||
const RAGVector &queryVector, const QString &queryText, const QList<FileChunkData> &chunks);
|
||||
|
||||
private:
|
||||
mutable std::unique_ptr<RAGVectorizer> m_vectorizer;
|
||||
mutable std::unique_ptr<RAGStorage> m_currentStorage;
|
||||
mutable ProjectExplorer::Project *m_currentProject{nullptr};
|
||||
FileChunker m_chunker;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
2
context/RAGPreprocessor.cpp
Normal file
2
context/RAGPreprocessor.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#include "RAGPreprocessor.hpp"
|
||||
|
||||
64
context/RAGPreprocessor.hpp
Normal file
64
context/RAGPreprocessor.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
|
||||
#include "Logger.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class RAGPreprocessor
|
||||
{
|
||||
public:
|
||||
static const QRegularExpression &getLicenseRegex()
|
||||
{
|
||||
static const QRegularExpression regex(
|
||||
R"((/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)|//[^\n]*(?:\n|$))",
|
||||
QRegularExpression::MultilineOption);
|
||||
return regex;
|
||||
}
|
||||
|
||||
static const QRegularExpression &getClassRegex()
|
||||
{
|
||||
static const QRegularExpression regex(
|
||||
R"((?:template\s*<[^>]*>\s*)?(?:class|struct)\s+(\w+)\s*(?:final\s*)?(?::\s*(?:public|protected|private)\s+\w+(?:\s*,\s*(?:public|protected|private)\s+\w+)*\s*)?{)");
|
||||
return regex;
|
||||
}
|
||||
|
||||
static QString preprocessCode(const QString &code)
|
||||
{
|
||||
if (code.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
try {
|
||||
QStringList lines = code.split('\n', Qt::SkipEmptyParts);
|
||||
return processLines(lines);
|
||||
} catch (const std::exception &e) {
|
||||
LOG_MESSAGE(QString("Error preprocessing code: %1").arg(e.what()));
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static QString processLines(const QStringList &lines)
|
||||
{
|
||||
const int estimatedAvgLength = 80;
|
||||
QString result;
|
||||
result.reserve(lines.size() * estimatedAvgLength);
|
||||
|
||||
for (const QString &line : lines) {
|
||||
const QString trimmed = line.trimmed();
|
||||
if (!trimmed.isEmpty()) {
|
||||
result += trimmed;
|
||||
result += QLatin1Char('\n');
|
||||
}
|
||||
}
|
||||
|
||||
if (result.endsWith('\n')) {
|
||||
result.chop(1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
67
context/RAGSimilaritySearch.cpp
Normal file
67
context/RAGSimilaritySearch.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "RAGSimilaritySearch.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
float RAGSimilaritySearch::l2Distance(const RAGVector &v1, const RAGVector &v2)
|
||||
{
|
||||
if (v1.size() != v2.size()) {
|
||||
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||
return std::numeric_limits<float>::max();
|
||||
}
|
||||
|
||||
float sum = 0.0f;
|
||||
for (size_t i = 0; i < v1.size(); ++i) {
|
||||
float diff = v1[i] - v2[i];
|
||||
sum += diff * diff;
|
||||
}
|
||||
return std::sqrt(sum);
|
||||
}
|
||||
|
||||
float RAGSimilaritySearch::cosineSimilarity(const RAGVector &v1, const RAGVector &v2)
|
||||
{
|
||||
if (v1.size() != v2.size()) {
|
||||
LOG_MESSAGE(QString("Vector size mismatch: %1 vs %2").arg(v1.size()).arg(v2.size()));
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float dotProduct = 0.0f;
|
||||
float norm1 = 0.0f;
|
||||
float norm2 = 0.0f;
|
||||
|
||||
for (size_t i = 0; i < v1.size(); ++i) {
|
||||
dotProduct += v1[i] * v2[i];
|
||||
norm1 += v1[i] * v1[i];
|
||||
norm2 += v2[i] * v2[i];
|
||||
}
|
||||
|
||||
norm1 = std::sqrt(norm1);
|
||||
norm2 = std::sqrt(norm2);
|
||||
|
||||
if (norm1 == 0.0f || norm2 == 0.0f)
|
||||
return 0.0f;
|
||||
return dotProduct / (norm1 * norm2);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
@@ -17,17 +17,21 @@
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
#pragma once
|
||||
|
||||
enum class ProviderID {
|
||||
Any,
|
||||
Ollama,
|
||||
LMStudio,
|
||||
Claude,
|
||||
OpenAI,
|
||||
OpenAICompatible,
|
||||
MistralAI,
|
||||
OpenRouter,
|
||||
GoogleAI
|
||||
#include "RAGData.hpp"
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class RAGSimilaritySearch
|
||||
{
|
||||
public:
|
||||
static float l2Distance(const RAGVector &v1, const RAGVector &v2);
|
||||
|
||||
static float cosineSimilarity(const RAGVector &v1, const RAGVector &v2);
|
||||
|
||||
private:
|
||||
RAGSimilaritySearch() = delete;
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
1047
context/RAGStorage.cpp
Normal file
1047
context/RAGStorage.cpp
Normal file
File diff suppressed because it is too large
Load Diff
174
context/RAGStorage.hpp
Normal file
174
context/RAGStorage.hpp
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// RAGStorage.hpp
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <QDateTime>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QSqlDatabase>
|
||||
#include <QString>
|
||||
#include <qsqlquery.h>
|
||||
|
||||
#include <RAGData.hpp>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
struct FileChunkData
|
||||
{
|
||||
QString filePath;
|
||||
int startLine;
|
||||
int endLine;
|
||||
QString content;
|
||||
QDateTime createdAt;
|
||||
QDateTime updatedAt;
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return !filePath.isEmpty() && startLine >= 0 && endLine >= startLine && !content.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct StorageOptions
|
||||
{
|
||||
int maxChunkSize = 1024 * 1024;
|
||||
int maxVectorSize = 1024;
|
||||
bool useCompression = false;
|
||||
bool enableLogging = false;
|
||||
};
|
||||
|
||||
struct StorageStatistics
|
||||
{
|
||||
int totalChunks;
|
||||
int totalVectors;
|
||||
int totalFiles;
|
||||
qint64 totalSize;
|
||||
QDateTime lastUpdate;
|
||||
};
|
||||
|
||||
class RAGStorage : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static constexpr int CURRENT_VERSION = 1;
|
||||
|
||||
enum class Status { Ok, DatabaseError, ValidationError, VersionError, ConnectionError };
|
||||
|
||||
struct ValidationResult
|
||||
{
|
||||
bool isValid;
|
||||
QString errorMessage;
|
||||
Status errorStatus;
|
||||
};
|
||||
|
||||
struct Error
|
||||
{
|
||||
QString message;
|
||||
QString sqlError;
|
||||
QString query;
|
||||
Status status;
|
||||
};
|
||||
|
||||
explicit RAGStorage(
|
||||
const QString &dbPath,
|
||||
const StorageOptions &options = StorageOptions(),
|
||||
QObject *parent = nullptr);
|
||||
~RAGStorage();
|
||||
|
||||
bool init();
|
||||
Status status() const;
|
||||
Error lastError() const;
|
||||
bool isReady() const;
|
||||
QString dbPath() const;
|
||||
|
||||
bool beginTransaction();
|
||||
bool commitTransaction();
|
||||
bool rollbackTransaction();
|
||||
|
||||
bool storeVector(const QString &filePath, const RAGVector &vector);
|
||||
bool updateVector(const QString &filePath, const RAGVector &vector);
|
||||
std::optional<RAGVector> getVector(const QString &filePath);
|
||||
bool needsUpdate(const QString &filePath);
|
||||
QStringList getAllFiles();
|
||||
|
||||
bool storeChunk(const FileChunkData &chunk);
|
||||
bool storeChunks(const QList<FileChunkData> &chunks);
|
||||
bool updateChunk(const FileChunkData &chunk);
|
||||
bool updateChunks(const QList<FileChunkData> &chunks);
|
||||
bool deleteChunksForFile(const QString &filePath);
|
||||
std::optional<FileChunkData> getChunk(const QString &filePath, int startLine, int endLine);
|
||||
QList<FileChunkData> getChunksForFile(const QString &filePath);
|
||||
bool chunkExists(const QString &filePath, int startLine, int endLine);
|
||||
|
||||
int getChunkCount(const QString &filePath);
|
||||
bool deleteOldChunks(const QString &filePath, const QDateTime &olderThan);
|
||||
bool deleteAllChunks();
|
||||
QStringList getFilesWithChunks();
|
||||
bool vacuum();
|
||||
bool backup(const QString &backupPath);
|
||||
bool restore(const QString &backupPath);
|
||||
StorageStatistics getStatistics() const;
|
||||
|
||||
int getStorageVersion() const;
|
||||
bool isVersionCompatible() const;
|
||||
|
||||
bool applyMigration(int version);
|
||||
signals:
|
||||
void errorOccurred(const Error &error);
|
||||
void operationCompleted(const QString &operation);
|
||||
|
||||
private:
|
||||
bool createTables();
|
||||
bool createIndices();
|
||||
bool createVersionTable();
|
||||
bool createChunksTable();
|
||||
bool createVectorsTable();
|
||||
bool openDatabase();
|
||||
bool initializeNewStorage();
|
||||
bool upgradeStorage(int fromVersion);
|
||||
bool validateSchema() const;
|
||||
|
||||
QDateTime getFileLastModified(const QString &filePath);
|
||||
RAGVector blobToVector(const QByteArray &blob);
|
||||
QByteArray vectorToBlob(const RAGVector &vector);
|
||||
|
||||
void setError(const QString &message, Status status = Status::DatabaseError);
|
||||
void clearError();
|
||||
bool prepareStatements();
|
||||
ValidationResult validateChunk(const FileChunkData &chunk) const;
|
||||
ValidationResult validateVector(const RAGVector &vector) const;
|
||||
|
||||
private:
|
||||
QSqlDatabase m_db;
|
||||
QString m_dbPath;
|
||||
StorageOptions m_options;
|
||||
mutable QMutex m_mutex;
|
||||
Error m_lastError;
|
||||
Status m_status;
|
||||
|
||||
QSqlQuery m_insertChunkQuery;
|
||||
QSqlQuery m_updateChunkQuery;
|
||||
QSqlQuery m_insertVectorQuery;
|
||||
QSqlQuery m_updateVectorQuery;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
116
context/RAGVectorizer.cpp
Normal file
116
context/RAGVectorizer.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "RAGVectorizer.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
RAGVectorizer::RAGVectorizer(const QString &providerUrl,
|
||||
const QString &modelName,
|
||||
QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_network(new QNetworkAccessManager(this))
|
||||
, m_embedProviderUrl(providerUrl)
|
||||
, m_model(modelName)
|
||||
{}
|
||||
|
||||
RAGVectorizer::~RAGVectorizer() {}
|
||||
|
||||
QJsonObject RAGVectorizer::prepareEmbeddingRequest(const QString &text) const
|
||||
{
|
||||
return QJsonObject{{"model", m_model}, {"prompt", text}};
|
||||
}
|
||||
|
||||
RAGVector RAGVectorizer::parseEmbeddingResponse(const QByteArray &response) const
|
||||
{
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response);
|
||||
if (doc.isNull()) {
|
||||
qDebug() << "Failed to parse JSON response";
|
||||
return RAGVector();
|
||||
}
|
||||
|
||||
QJsonObject obj = doc.object();
|
||||
if (!obj.contains("embedding")) {
|
||||
qDebug() << "Response does not contain 'embedding' field";
|
||||
// qDebug() << "Response content:" << response;
|
||||
return RAGVector();
|
||||
}
|
||||
|
||||
QJsonArray array = obj["embedding"].toArray();
|
||||
if (array.isEmpty()) {
|
||||
qDebug() << "Embedding array is empty";
|
||||
return RAGVector();
|
||||
}
|
||||
|
||||
RAGVector result;
|
||||
result.reserve(array.size());
|
||||
for (const auto &value : array) {
|
||||
result.push_back(value.toDouble());
|
||||
}
|
||||
|
||||
qDebug() << "Successfully parsed vector with size:" << result.size();
|
||||
return result;
|
||||
}
|
||||
|
||||
QFuture<RAGVector> RAGVectorizer::vectorizeText(const QString &text)
|
||||
{
|
||||
qDebug() << "Vectorizing text, length:" << text.length();
|
||||
qDebug() << "Using embedding provider:" << m_embedProviderUrl;
|
||||
|
||||
auto promise = std::make_shared<QPromise<RAGVector>>();
|
||||
promise->start();
|
||||
|
||||
QNetworkRequest request(QUrl(m_embedProviderUrl + "/api/embeddings"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QJsonObject requestData = prepareEmbeddingRequest(text);
|
||||
QByteArray jsonData = QJsonDocument(requestData).toJson();
|
||||
qDebug() << "Sending request to embeddings API:" << jsonData;
|
||||
|
||||
auto reply = m_network->post(request, jsonData);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [promise, reply, this]() {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray response = reply->readAll();
|
||||
// qDebug() << "Received response from embeddings API:" << response;
|
||||
|
||||
auto vector = parseEmbeddingResponse(response);
|
||||
qDebug() << "Parsed vector size:" << vector.size();
|
||||
promise->addResult(vector);
|
||||
} else {
|
||||
qDebug() << "Network error:" << reply->errorString();
|
||||
qDebug() << "HTTP status code:"
|
||||
<< reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << "Response:" << reply->readAll();
|
||||
|
||||
promise->addResult(RAGVector());
|
||||
}
|
||||
promise->finish();
|
||||
reply->deleteLater();
|
||||
});
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
51
context/RAGVectorizer.hpp
Normal file
51
context/RAGVectorizer.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFuture>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
|
||||
#include <RAGData.hpp>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
|
||||
class RAGVectorizer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RAGVectorizer(
|
||||
const QString &providerUrl = "http://localhost:11434",
|
||||
const QString &modelName = "all-minilm:33m-l12-v2-fp16",
|
||||
QObject *parent = nullptr);
|
||||
~RAGVectorizer();
|
||||
|
||||
QFuture<RAGVector> vectorizeText(const QString &text);
|
||||
|
||||
private:
|
||||
QJsonObject prepareEmbeddingRequest(const QString &text) const;
|
||||
RAGVector parseEmbeddingResponse(const QByteArray &response) const;
|
||||
|
||||
QNetworkAccessManager *m_network;
|
||||
QString m_embedProviderUrl;
|
||||
QString m_model;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Context
|
||||
@@ -10,7 +10,7 @@ add_library(LLMCore STATIC
|
||||
OllamaMessage.hpp OllamaMessage.cpp
|
||||
OpenAIMessage.hpp OpenAIMessage.cpp
|
||||
ValidationUtils.hpp ValidationUtils.cpp
|
||||
ProviderID.hpp
|
||||
MessageBuilder.hpp MessageBuilder.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(LLMCore
|
||||
|
||||
@@ -20,23 +20,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
struct Message
|
||||
{
|
||||
QString role;
|
||||
QString content;
|
||||
};
|
||||
|
||||
struct ContextData
|
||||
{
|
||||
std::optional<QString> systemPrompt;
|
||||
std::optional<QString> prefix;
|
||||
std::optional<QString> suffix;
|
||||
std::optional<QString> fileContext;
|
||||
std::optional<QVector<Message>> history;
|
||||
QString prefix;
|
||||
QString suffix;
|
||||
QString fileContext;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
||||
|
||||
93
llmcore/MessageBuilder.cpp
Normal file
93
llmcore/MessageBuilder.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "MessageBuilder.hpp"
|
||||
|
||||
QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addSystemMessage(
|
||||
const QString &content)
|
||||
{
|
||||
m_systemMessage = content;
|
||||
return *this;
|
||||
}
|
||||
|
||||
QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addUserMessage(
|
||||
const QString &content)
|
||||
{
|
||||
m_messages.append({MessageRole::User, content});
|
||||
return *this;
|
||||
}
|
||||
|
||||
QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addSuffix(
|
||||
const QString &content)
|
||||
{
|
||||
m_suffix = content;
|
||||
return *this;
|
||||
}
|
||||
|
||||
QodeAssist::LLMCore::MessageBuilder &QodeAssist::LLMCore::MessageBuilder::addTokenizer(
|
||||
PromptTemplate *promptTemplate)
|
||||
{
|
||||
m_promptTemplate = promptTemplate;
|
||||
return *this;
|
||||
}
|
||||
|
||||
QString QodeAssist::LLMCore::MessageBuilder::roleToString(MessageRole role) const
|
||||
{
|
||||
switch (role) {
|
||||
case MessageRole::System:
|
||||
return ROLE_SYSTEM;
|
||||
case MessageRole::User:
|
||||
return ROLE_USER;
|
||||
case MessageRole::Assistant:
|
||||
return ROLE_ASSISTANT;
|
||||
default:
|
||||
return ROLE_USER;
|
||||
}
|
||||
}
|
||||
|
||||
void QodeAssist::LLMCore::MessageBuilder::saveTo(QJsonObject &request, ProvidersApi api)
|
||||
{
|
||||
if (!m_promptTemplate) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContextData context{
|
||||
m_messages.isEmpty() ? QString() : m_messages.last().content, m_suffix, m_systemMessage};
|
||||
|
||||
if (api == ProvidersApi::Ollama) {
|
||||
if (m_promptTemplate->type() == TemplateType::Fim) {
|
||||
request["system"] = m_systemMessage;
|
||||
m_promptTemplate->prepareRequest(request, context);
|
||||
} else {
|
||||
QJsonArray messages;
|
||||
|
||||
messages.append(QJsonObject{{"role", "system"}, {"content", m_systemMessage}});
|
||||
messages.append(QJsonObject{{"role", "user"}, {"content", m_messages.last().content}});
|
||||
request["messages"] = messages;
|
||||
m_promptTemplate->prepareRequest(request, context);
|
||||
}
|
||||
} else if (api == ProvidersApi::OpenAI) {
|
||||
QJsonArray messages;
|
||||
|
||||
messages.append(QJsonObject{{"role", "system"}, {"content", m_systemMessage}});
|
||||
messages.append(QJsonObject{{"role", "user"}, {"content", m_messages.last().content}});
|
||||
request["messages"] = messages;
|
||||
m_promptTemplate->prepareRequest(request, context);
|
||||
}
|
||||
}
|
||||
68
llmcore/MessageBuilder.hpp
Normal file
68
llmcore/MessageBuilder.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
#include "PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
enum class MessageRole { System, User, Assistant };
|
||||
|
||||
enum class OllamaFormat { Messages, Completions };
|
||||
|
||||
enum class ProvidersApi { Ollama, OpenAI, Claude };
|
||||
|
||||
static const QString ROLE_SYSTEM = "system";
|
||||
static const QString ROLE_USER = "user";
|
||||
static const QString ROLE_ASSISTANT = "assistant";
|
||||
|
||||
struct Message
|
||||
{
|
||||
MessageRole role;
|
||||
QString content;
|
||||
};
|
||||
|
||||
class MessageBuilder
|
||||
{
|
||||
public:
|
||||
MessageBuilder &addSystemMessage(const QString &content);
|
||||
|
||||
MessageBuilder &addUserMessage(const QString &content);
|
||||
|
||||
MessageBuilder &addSuffix(const QString &content);
|
||||
|
||||
MessageBuilder &addTokenizer(PromptTemplate *promptTemplate);
|
||||
|
||||
QString roleToString(MessageRole role) const;
|
||||
|
||||
void saveTo(QJsonObject &request, ProvidersApi api);
|
||||
|
||||
private:
|
||||
QString m_systemMessage;
|
||||
QString m_suffix;
|
||||
QVector<Message> m_messages;
|
||||
PromptTemplate *m_promptTemplate;
|
||||
};
|
||||
} // namespace QodeAssist::LLMCore
|
||||
@@ -24,11 +24,10 @@
|
||||
#include <QString>
|
||||
|
||||
#include "ContextData.hpp"
|
||||
#include "ProviderID.hpp"
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
enum class TemplateType { Chat, FIM };
|
||||
enum class TemplateType { Chat, Fim };
|
||||
|
||||
class PromptTemplate
|
||||
{
|
||||
@@ -36,9 +35,9 @@ public:
|
||||
virtual ~PromptTemplate() = default;
|
||||
virtual TemplateType type() const = 0;
|
||||
virtual QString name() const = 0;
|
||||
virtual QString promptTemplate() const = 0;
|
||||
virtual QStringList stopWords() const = 0;
|
||||
virtual void prepareRequest(QJsonObject &request, const ContextData &context) const = 0;
|
||||
virtual QString description() const = 0;
|
||||
virtual bool isSupportProvider(ProviderID id) const = 0;
|
||||
};
|
||||
} // namespace QodeAssist::LLMCore
|
||||
|
||||
@@ -37,32 +37,6 @@ QStringList PromptTemplateManager::chatTemplatesNames() const
|
||||
return m_chatTemplates.keys();
|
||||
}
|
||||
|
||||
QStringList PromptTemplateManager::getFimTemplatesForProvider(ProviderID id)
|
||||
{
|
||||
QStringList templateList;
|
||||
|
||||
for (const auto tmpl : m_fimTemplates) {
|
||||
if (tmpl->isSupportProvider(id)) {
|
||||
templateList.append(tmpl->name());
|
||||
}
|
||||
}
|
||||
|
||||
return templateList;
|
||||
}
|
||||
|
||||
QStringList PromptTemplateManager::getChatTemplatesForProvider(ProviderID id)
|
||||
{
|
||||
QStringList templateList;
|
||||
|
||||
for (const auto tmpl : m_chatTemplates) {
|
||||
if (tmpl->isSupportProvider(id)) {
|
||||
templateList.append(tmpl->name());
|
||||
}
|
||||
}
|
||||
|
||||
return templateList;
|
||||
}
|
||||
|
||||
PromptTemplateManager::~PromptTemplateManager()
|
||||
{
|
||||
qDeleteAll(m_fimTemplates);
|
||||
@@ -70,15 +44,11 @@ PromptTemplateManager::~PromptTemplateManager()
|
||||
|
||||
PromptTemplate *PromptTemplateManager::getFimTemplateByName(const QString &templateName)
|
||||
{
|
||||
if (!m_fimTemplates.contains(templateName))
|
||||
return m_fimTemplates.first();
|
||||
return m_fimTemplates[templateName];
|
||||
}
|
||||
|
||||
PromptTemplate *PromptTemplateManager::getChatTemplateByName(const QString &templateName)
|
||||
{
|
||||
if (!m_chatTemplates.contains(templateName))
|
||||
return m_chatTemplates.first();
|
||||
return m_chatTemplates[templateName];
|
||||
}
|
||||
|
||||
|
||||
@@ -51,9 +51,6 @@ public:
|
||||
QStringList fimTemplatesNames() const;
|
||||
QStringList chatTemplatesNames() const;
|
||||
|
||||
QStringList getFimTemplatesForProvider(ProviderID id);
|
||||
QStringList getChatTemplatesForProvider(ProviderID id);
|
||||
|
||||
private:
|
||||
PromptTemplateManager() = default;
|
||||
PromptTemplateManager(const PromptTemplateManager &) = delete;
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <QNetworkRequest>
|
||||
#include <QString>
|
||||
|
||||
#include "ContextData.hpp"
|
||||
#include "PromptTemplate.hpp"
|
||||
#include "RequestType.hpp"
|
||||
|
||||
@@ -42,18 +41,13 @@ public:
|
||||
virtual QString completionEndpoint() const = 0;
|
||||
virtual QString chatEndpoint() const = 0;
|
||||
virtual bool supportsModelListing() const = 0;
|
||||
virtual void prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type)
|
||||
= 0;
|
||||
|
||||
virtual void prepareRequest(QJsonObject &request, RequestType type) = 0;
|
||||
virtual bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) = 0;
|
||||
virtual QList<QString> getInstalledModels(const QString &url) = 0;
|
||||
virtual QList<QString> validateRequest(const QJsonObject &request, TemplateType type) = 0;
|
||||
virtual QString apiKey() const = 0;
|
||||
virtual void prepareNetworkRequest(QNetworkRequest &networkRequest) const = 0;
|
||||
virtual ProviderID providerID() const = 0;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
||||
|
||||
@@ -39,8 +39,6 @@ ProvidersManager::~ProvidersManager()
|
||||
|
||||
Provider *ProvidersManager::getProviderByName(const QString &providerName)
|
||||
{
|
||||
if (!m_providers.contains(providerName))
|
||||
return m_providers.first();
|
||||
return m_providers[providerName];
|
||||
}
|
||||
|
||||
|
||||
@@ -58,10 +58,7 @@ void RequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &
|
||||
reply->deleteLater();
|
||||
m_activeRequests.remove(requestId);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
LOG_MESSAGE(QString("Error details: %1\nStatus code: %2\nResponse: %3")
|
||||
.arg(reply->errorString())
|
||||
.arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt())
|
||||
.arg(QString(reply->readAll())));
|
||||
LOG_MESSAGE(QString("Error in QodeAssist request: %1").arg(reply->errorString()));
|
||||
emit requestFinished(requestId, false, reply->errorString());
|
||||
} else {
|
||||
LOG_MESSAGE("Request finished successfully");
|
||||
|
||||
@@ -21,5 +21,5 @@
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
enum RequestType { CodeCompletion, Chat, Embedding };
|
||||
enum RequestType { CodeCompletion, Chat };
|
||||
}
|
||||
|
||||
@@ -30,10 +30,13 @@
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
ClaudeProvider::ClaudeProvider() {}
|
||||
|
||||
QString ClaudeProvider::name() const
|
||||
{
|
||||
return "Claude";
|
||||
@@ -59,17 +62,31 @@ bool ClaudeProvider::supportsModelListing() const
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClaudeProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type)
|
||||
void ClaudeProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
auto prepareMessages = [](QJsonObject &req) -> QJsonArray {
|
||||
QJsonArray messages;
|
||||
if (req.contains("messages")) {
|
||||
QJsonArray origMessages = req["messages"].toArray();
|
||||
for (const auto &msg : origMessages) {
|
||||
QJsonObject message = msg.toObject();
|
||||
if (message["role"].toString() == "system") {
|
||||
req["system"] = message["content"];
|
||||
} else {
|
||||
messages.append(message);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (req.contains("system")) {
|
||||
req["system"] = req["system"].toString();
|
||||
}
|
||||
if (req.contains("prompt")) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "user"}, {"content", req.take("prompt").toString()}});
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
};
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
@@ -81,6 +98,11 @@ void ClaudeProvider::prepareRequest(
|
||||
request["stream"] = true;
|
||||
};
|
||||
|
||||
QJsonArray messages = prepareMessages(request);
|
||||
if (!messages.isEmpty()) {
|
||||
request["messages"] = std::move(messages);
|
||||
}
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else {
|
||||
@@ -213,9 +235,4 @@ void ClaudeProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) cons
|
||||
}
|
||||
}
|
||||
|
||||
LLMCore::ProviderID ClaudeProvider::providerID() const
|
||||
{
|
||||
return LLMCore::ProviderID::Claude;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@@ -26,22 +26,19 @@ namespace QodeAssist::Providers {
|
||||
class ClaudeProvider : public LLMCore::Provider
|
||||
{
|
||||
public:
|
||||
ClaudeProvider();
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
QString completionEndpoint() const override;
|
||||
QString chatEndpoint() const override;
|
||||
bool supportsModelListing() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type) override;
|
||||
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||
LLMCore::ProviderID providerID() const override;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@@ -1,303 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "GoogleAIProvider.hpp"
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QtCore/qurlquery.h>
|
||||
|
||||
#include "llmcore/ValidationUtils.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
QString GoogleAIProvider::name() const
|
||||
{
|
||||
return "Google AI";
|
||||
}
|
||||
|
||||
QString GoogleAIProvider::url() const
|
||||
{
|
||||
return "https://generativelanguage.googleapis.com/v1beta";
|
||||
}
|
||||
|
||||
QString GoogleAIProvider::completionEndpoint() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
QString GoogleAIProvider::chatEndpoint() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
bool GoogleAIProvider::supportsModelListing() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void GoogleAIProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
QJsonObject generationConfig;
|
||||
generationConfig["maxOutputTokens"] = settings.maxTokens();
|
||||
generationConfig["temperature"] = settings.temperature();
|
||||
|
||||
if (settings.useTopP())
|
||||
generationConfig["topP"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
generationConfig["topK"] = settings.topK();
|
||||
|
||||
request["generationConfig"] = generationConfig;
|
||||
};
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
}
|
||||
|
||||
bool GoogleAIProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
||||
{
|
||||
if (reply->isFinished()) {
|
||||
if (reply->bytesAvailable() > 0) {
|
||||
QByteArray data = reply->readAll();
|
||||
|
||||
if (data.startsWith("data: ")) {
|
||||
return handleStreamResponse(data, accumulatedResponse);
|
||||
} else {
|
||||
return handleRegularResponse(data, accumulatedResponse);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
if (data.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.startsWith("data: ")) {
|
||||
return handleStreamResponse(data, accumulatedResponse);
|
||||
} else {
|
||||
return handleRegularResponse(data, accumulatedResponse);
|
||||
}
|
||||
}
|
||||
|
||||
QList<QString> GoogleAIProvider::getInstalledModels(const QString &url)
|
||||
{
|
||||
QList<QString> models;
|
||||
|
||||
QNetworkAccessManager manager;
|
||||
QNetworkRequest request(QString("%1/models?key=%2").arg(url, apiKey()));
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QNetworkReply *reply = manager.get(request);
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray responseData = reply->readAll();
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
||||
QJsonObject jsonObject = jsonResponse.object();
|
||||
|
||||
if (jsonObject.contains("models")) {
|
||||
QJsonArray modelArray = jsonObject["models"].toArray();
|
||||
models.clear();
|
||||
|
||||
for (const QJsonValue &value : modelArray) {
|
||||
QJsonObject modelObject = value.toObject();
|
||||
if (modelObject.contains("name")) {
|
||||
QString modelName = modelObject["name"].toString();
|
||||
if (modelName.contains("/")) {
|
||||
modelName = modelName.split("/").last();
|
||||
}
|
||||
models.append(modelName);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG_MESSAGE(QString("Error fetching Google AI models: %1").arg(reply->errorString()));
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
return models;
|
||||
}
|
||||
|
||||
QList<QString> GoogleAIProvider::validateRequest(
|
||||
const QJsonObject &request, LLMCore::TemplateType type)
|
||||
{
|
||||
QJsonObject templateReq;
|
||||
|
||||
templateReq = QJsonObject{
|
||||
{"contents", QJsonArray{}},
|
||||
{"system_instruction", QJsonArray{}},
|
||||
{"generationConfig",
|
||||
QJsonObject{{"temperature", {}}, {"maxOutputTokens", {}}, {"topP", {}}, {"topK", {}}}},
|
||||
{"safetySettings", QJsonArray{}}};
|
||||
|
||||
return LLMCore::ValidationUtils::validateRequestFields(request, templateReq);
|
||||
}
|
||||
|
||||
QString GoogleAIProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().googleAiApiKey();
|
||||
}
|
||||
|
||||
void GoogleAIProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) const
|
||||
{
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QUrl url = networkRequest.url();
|
||||
QUrlQuery query(url.query());
|
||||
query.addQueryItem("key", apiKey());
|
||||
url.setQuery(query);
|
||||
networkRequest.setUrl(url);
|
||||
}
|
||||
|
||||
LLMCore::ProviderID GoogleAIProvider::providerID() const
|
||||
{
|
||||
return LLMCore::ProviderID::GoogleAI;
|
||||
}
|
||||
|
||||
bool GoogleAIProvider::handleStreamResponse(const QByteArray &data, QString &accumulatedResponse)
|
||||
{
|
||||
QByteArrayList lines = data.split('\n');
|
||||
bool isDone = false;
|
||||
|
||||
for (const QByteArray &line : lines) {
|
||||
QByteArray trimmedLine = line.trimmed();
|
||||
if (trimmedLine.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmedLine == "data: [DONE]") {
|
||||
isDone = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmedLine.startsWith("data: ")) {
|
||||
QByteArray jsonData = trimmedLine.mid(6); // Remove "data: " prefix
|
||||
QJsonDocument doc = QJsonDocument::fromJson(jsonData);
|
||||
if (doc.isNull() || !doc.isObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject responseObj = doc.object();
|
||||
|
||||
if (responseObj.contains("error")) {
|
||||
QJsonObject error = responseObj["error"].toObject();
|
||||
LOG_MESSAGE("Error in Google AI stream response: " + error["message"].toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (responseObj.contains("candidates")) {
|
||||
QJsonArray candidates = responseObj["candidates"].toArray();
|
||||
if (!candidates.isEmpty()) {
|
||||
QJsonObject candidate = candidates.first().toObject();
|
||||
|
||||
if (candidate.contains("finishReason")
|
||||
&& !candidate["finishReason"].toString().isEmpty()) {
|
||||
isDone = true;
|
||||
}
|
||||
|
||||
if (candidate.contains("content")) {
|
||||
QJsonObject content = candidate["content"].toObject();
|
||||
if (content.contains("parts")) {
|
||||
QJsonArray parts = content["parts"].toArray();
|
||||
for (const auto &part : parts) {
|
||||
QJsonObject partObj = part.toObject();
|
||||
if (partObj.contains("text")) {
|
||||
accumulatedResponse += partObj["text"].toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isDone;
|
||||
}
|
||||
|
||||
bool GoogleAIProvider::handleRegularResponse(const QByteArray &data, QString &accumulatedResponse)
|
||||
{
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data);
|
||||
if (doc.isNull() || !doc.isObject()) {
|
||||
LOG_MESSAGE("Invalid JSON response from Google AI API");
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject response = doc.object();
|
||||
|
||||
if (response.contains("error")) {
|
||||
QJsonObject error = response["error"].toObject();
|
||||
LOG_MESSAGE("Error in Google AI response: " + error["message"].toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!response.contains("candidates") || response["candidates"].toArray().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject candidate = response["candidates"].toArray().first().toObject();
|
||||
if (!candidate.contains("content")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject content = candidate["content"].toObject();
|
||||
if (!content.contains("parts")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonArray parts = content["parts"].toArray();
|
||||
for (const auto &part : parts) {
|
||||
QJsonObject partObj = part.toObject();
|
||||
if (partObj.contains("text")) {
|
||||
accumulatedResponse += partObj["text"].toString();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "llmcore/Provider.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class GoogleAIProvider : public LLMCore::Provider
|
||||
{
|
||||
public:
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
QString completionEndpoint() const override;
|
||||
QString chatEndpoint() const override;
|
||||
bool supportsModelListing() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type) override;
|
||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||
LLMCore::ProviderID providerID() const override;
|
||||
|
||||
private:
|
||||
bool handleStreamResponse(const QByteArray &data, QString &accumulatedResponse);
|
||||
bool handleRegularResponse(const QByteArray &data, QString &accumulatedResponse);
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -33,6 +33,8 @@
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
LMStudioProvider::LMStudioProvider() {}
|
||||
|
||||
QString LMStudioProvider::name() const
|
||||
{
|
||||
return "LM Studio";
|
||||
@@ -58,6 +60,47 @@ bool LMStudioProvider::supportsModelListing() const
|
||||
return true;
|
||||
}
|
||||
|
||||
void LMStudioProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||
{
|
||||
auto prepareMessages = [](QJsonObject &req) -> QJsonArray {
|
||||
QJsonArray messages;
|
||||
if (req.contains("system")) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "system"}, {"content", req.take("system").toString()}});
|
||||
}
|
||||
if (req.contains("prompt")) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "user"}, {"content", req.take("prompt").toString()}});
|
||||
}
|
||||
return messages;
|
||||
};
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
QJsonArray messages = prepareMessages(request);
|
||||
if (!messages.isEmpty()) {
|
||||
request["messages"] = std::move(messages);
|
||||
}
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
}
|
||||
|
||||
bool LMStudioProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
||||
{
|
||||
QByteArray data = reply->readAll();
|
||||
@@ -168,42 +211,4 @@ void LMStudioProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) co
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
}
|
||||
|
||||
LLMCore::ProviderID LMStudioProvider::providerID() const
|
||||
{
|
||||
return LLMCore::ProviderID::LMStudio;
|
||||
}
|
||||
|
||||
void QodeAssist::Providers::LMStudioProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@@ -26,22 +26,19 @@ namespace QodeAssist::Providers {
|
||||
class LMStudioProvider : public LLMCore::Provider
|
||||
{
|
||||
public:
|
||||
LMStudioProvider();
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
QString completionEndpoint() const override;
|
||||
QString chatEndpoint() const override;
|
||||
bool supportsModelListing() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type) override;
|
||||
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||
LLMCore::ProviderID providerID() const override;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
#include "MistralAIProvider.hpp"
|
||||
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QtCore/qeventloop.h>
|
||||
|
||||
#include "llmcore/OpenAIMessage.hpp"
|
||||
#include "llmcore/ValidationUtils.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
QString MistralAIProvider::name() const
|
||||
{
|
||||
return "Mistral AI";
|
||||
}
|
||||
|
||||
QString MistralAIProvider::url() const
|
||||
{
|
||||
return "https://api.mistral.ai";
|
||||
}
|
||||
|
||||
QString MistralAIProvider::completionEndpoint() const
|
||||
{
|
||||
return "/v1/fim/completions";
|
||||
}
|
||||
|
||||
QString MistralAIProvider::chatEndpoint() const
|
||||
{
|
||||
return "/v1/chat/completions";
|
||||
}
|
||||
|
||||
bool MistralAIProvider::supportsModelListing() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MistralAIProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
||||
{
|
||||
QByteArray data = reply->readAll();
|
||||
if (data.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isDone = false;
|
||||
QByteArrayList lines = data.split('\n');
|
||||
|
||||
for (const QByteArray &line : lines) {
|
||||
if (line.trimmed().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line == "data: [DONE]") {
|
||||
isDone = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray jsonData = line;
|
||||
if (line.startsWith("data: ")) {
|
||||
jsonData = line.mid(6);
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error);
|
||||
|
||||
if (doc.isNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto message = LLMCore::OpenAIMessage::fromJson(doc.object());
|
||||
if (message.hasError()) {
|
||||
LOG_MESSAGE("Error in OpenAI response: " + message.error);
|
||||
continue;
|
||||
}
|
||||
|
||||
QString content = message.getContent();
|
||||
if (!content.isEmpty()) {
|
||||
accumulatedResponse += content;
|
||||
}
|
||||
|
||||
if (message.isDone()) {
|
||||
isDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
return isDone;
|
||||
}
|
||||
|
||||
QList<QString> MistralAIProvider::getInstalledModels(const QString &url)
|
||||
{
|
||||
QList<QString> models;
|
||||
QNetworkAccessManager manager;
|
||||
QNetworkRequest request(QString("%1/v1/models").arg(url));
|
||||
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
if (!apiKey().isEmpty()) {
|
||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8());
|
||||
}
|
||||
|
||||
QNetworkReply *reply = manager.get(request);
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray responseData = reply->readAll();
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(responseData);
|
||||
QJsonObject jsonObject = jsonResponse.object();
|
||||
|
||||
if (jsonObject.contains("data") && jsonObject["object"].toString() == "list") {
|
||||
QJsonArray modelArray = jsonObject["data"].toArray();
|
||||
for (const QJsonValue &value : modelArray) {
|
||||
QJsonObject modelObject = value.toObject();
|
||||
if (modelObject.contains("id")) {
|
||||
QString modelId = modelObject["id"].toString();
|
||||
models.append(modelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG_MESSAGE(QString("Error fetching Mistral AI models: %1").arg(reply->errorString()));
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
return models;
|
||||
}
|
||||
|
||||
QList<QString> MistralAIProvider::validateRequest(
|
||||
const QJsonObject &request, LLMCore::TemplateType type)
|
||||
{
|
||||
const auto fimReq = QJsonObject{
|
||||
{"model", {}},
|
||||
{"max_tokens", {}},
|
||||
{"stream", {}},
|
||||
{"temperature", {}},
|
||||
{"prompt", {}},
|
||||
{"suffix", {}}};
|
||||
|
||||
const auto templateReq = QJsonObject{
|
||||
{"model", {}},
|
||||
{"messages", QJsonArray{{QJsonObject{{"role", {}}, {"content", {}}}}}},
|
||||
{"temperature", {}},
|
||||
{"max_tokens", {}},
|
||||
{"top_p", {}},
|
||||
{"frequency_penalty", {}},
|
||||
{"presence_penalty", {}},
|
||||
{"stop", QJsonArray{}},
|
||||
{"stream", {}}};
|
||||
|
||||
return LLMCore::ValidationUtils::validateRequestFields(
|
||||
request, type == LLMCore::TemplateType::FIM ? fimReq : templateReq);
|
||||
}
|
||||
|
||||
QString MistralAIProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().mistralAiApiKey();
|
||||
}
|
||||
|
||||
void MistralAIProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) const
|
||||
{
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
if (!apiKey().isEmpty()) {
|
||||
networkRequest.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey()).toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
LLMCore::ProviderID MistralAIProvider::providerID() const
|
||||
{
|
||||
return LLMCore::ProviderID::MistralAI;
|
||||
}
|
||||
|
||||
void MistralAIProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
if (type == LLMCore::RequestType::Chat) {
|
||||
auto &settings = Settings::chatAssistantSettings();
|
||||
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
|
||||
if (settings.useTopP())
|
||||
request["top_p"] = settings.topP();
|
||||
|
||||
// request["random_seed"] = "";
|
||||
|
||||
if (settings.useFrequencyPenalty())
|
||||
request["frequency_penalty"] = settings.frequencyPenalty();
|
||||
if (settings.usePresencePenalty())
|
||||
request["presence_penalty"] = settings.presencePenalty();
|
||||
|
||||
} else {
|
||||
auto &settings = Settings::codeCompletionSettings();
|
||||
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
|
||||
if (settings.useTopP())
|
||||
request["top_p"] = settings.topP();
|
||||
|
||||
// request["random_seed"] = "";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "llmcore/Provider.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class MistralAIProvider : public LLMCore::Provider
|
||||
{
|
||||
public:
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
QString completionEndpoint() const override;
|
||||
QString chatEndpoint() const override;
|
||||
bool supportsModelListing() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type) override;
|
||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||
LLMCore::ProviderID providerID() const override;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -33,6 +33,8 @@
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OllamaProvider::OllamaProvider() {}
|
||||
|
||||
QString OllamaProvider::name() const
|
||||
{
|
||||
return "Ollama";
|
||||
@@ -58,18 +60,8 @@ bool OllamaProvider::supportsModelListing() const
|
||||
return true;
|
||||
}
|
||||
|
||||
void OllamaProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type)
|
||||
void OllamaProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applySettings = [&request](const auto &settings) {
|
||||
QJsonObject options;
|
||||
options["num_predict"] = settings.maxTokens();
|
||||
@@ -200,7 +192,7 @@ QList<QString> OllamaProvider::validateRequest(const QJsonObject &request, LLMCo
|
||||
{"presence_penalty", {}}}}};
|
||||
|
||||
return LLMCore::ValidationUtils::validateRequestFields(
|
||||
request, type == LLMCore::TemplateType::FIM ? fimReq : messageReq);
|
||||
request, type == LLMCore::TemplateType::Fim ? fimReq : messageReq);
|
||||
}
|
||||
|
||||
QString OllamaProvider::apiKey() const
|
||||
@@ -211,11 +203,6 @@ QString OllamaProvider::apiKey() const
|
||||
void OllamaProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) const
|
||||
{
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
}
|
||||
|
||||
LLMCore::ProviderID OllamaProvider::providerID() const
|
||||
{
|
||||
return LLMCore::ProviderID::Ollama;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@@ -26,22 +26,19 @@ namespace QodeAssist::Providers {
|
||||
class OllamaProvider : public LLMCore::Provider
|
||||
{
|
||||
public:
|
||||
OllamaProvider();
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
QString completionEndpoint() const override;
|
||||
QString chatEndpoint() const override;
|
||||
bool supportsModelListing() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type) override;
|
||||
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||
LLMCore::ProviderID providerID() const override;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OpenAICompatProvider::OpenAICompatProvider() {}
|
||||
|
||||
QString OpenAICompatProvider::name() const
|
||||
{
|
||||
return "OpenAI Compatible";
|
||||
@@ -59,17 +61,20 @@ bool OpenAICompatProvider::supportsModelListing() const
|
||||
return false;
|
||||
}
|
||||
|
||||
void OpenAICompatProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type)
|
||||
void OpenAICompatProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
auto prepareMessages = [](QJsonObject &req) -> QJsonArray {
|
||||
QJsonArray messages;
|
||||
if (req.contains("system")) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "system"}, {"content", req.take("system").toString()}});
|
||||
}
|
||||
if (req.contains("prompt")) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "user"}, {"content", req.take("prompt").toString()}});
|
||||
}
|
||||
return messages;
|
||||
};
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
@@ -85,6 +90,11 @@ void OpenAICompatProvider::prepareRequest(
|
||||
request["presence_penalty"] = settings.presencePenalty();
|
||||
};
|
||||
|
||||
QJsonArray messages = prepareMessages(request);
|
||||
if (!messages.isEmpty()) {
|
||||
request["messages"] = std::move(messages);
|
||||
}
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else {
|
||||
@@ -180,9 +190,4 @@ void OpenAICompatProvider::prepareNetworkRequest(QNetworkRequest &networkRequest
|
||||
}
|
||||
}
|
||||
|
||||
LLMCore::ProviderID OpenAICompatProvider::providerID() const
|
||||
{
|
||||
return LLMCore::ProviderID::OpenAICompatible;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@@ -26,22 +26,19 @@ namespace QodeAssist::Providers {
|
||||
class OpenAICompatProvider : public LLMCore::Provider
|
||||
{
|
||||
public:
|
||||
OpenAICompatProvider();
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
QString completionEndpoint() const override;
|
||||
QString chatEndpoint() const override;
|
||||
bool supportsModelListing() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type) override;
|
||||
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||
LLMCore::ProviderID providerID() const override;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OpenAIProvider::OpenAIProvider() {}
|
||||
|
||||
QString OpenAIProvider::name() const
|
||||
{
|
||||
return "OpenAI";
|
||||
@@ -60,17 +62,20 @@ bool OpenAIProvider::supportsModelListing() const
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenAIProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type)
|
||||
void OpenAIProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
auto prepareMessages = [](QJsonObject &req) -> QJsonArray {
|
||||
QJsonArray messages;
|
||||
if (req.contains("system")) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "system"}, {"content", req.take("system").toString()}});
|
||||
}
|
||||
if (req.contains("prompt")) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "user"}, {"content", req.take("prompt").toString()}});
|
||||
}
|
||||
return messages;
|
||||
};
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
@@ -86,6 +91,11 @@ void OpenAIProvider::prepareRequest(
|
||||
request["presence_penalty"] = settings.presencePenalty();
|
||||
};
|
||||
|
||||
QJsonArray messages = prepareMessages(request);
|
||||
if (!messages.isEmpty()) {
|
||||
request["messages"] = std::move(messages);
|
||||
}
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else {
|
||||
@@ -216,9 +226,4 @@ void OpenAIProvider::prepareNetworkRequest(QNetworkRequest &networkRequest) cons
|
||||
}
|
||||
}
|
||||
|
||||
LLMCore::ProviderID OpenAIProvider::providerID() const
|
||||
{
|
||||
return LLMCore::ProviderID::OpenAI;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@@ -26,22 +26,19 @@ namespace QodeAssist::Providers {
|
||||
class OpenAIProvider : public LLMCore::Provider
|
||||
{
|
||||
public:
|
||||
OpenAIProvider();
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
QString completionEndpoint() const override;
|
||||
QString chatEndpoint() const override;
|
||||
bool supportsModelListing() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
LLMCore::PromptTemplate *prompt,
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::RequestType type) override;
|
||||
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||
QList<QString> getInstalledModels(const QString &url) override;
|
||||
QList<QString> validateRequest(const QJsonObject &request, LLMCore::TemplateType type) override;
|
||||
QString apiKey() const override;
|
||||
void prepareNetworkRequest(QNetworkRequest &networkRequest) const override;
|
||||
LLMCore::ProviderID providerID() const override;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include "OpenRouterAIProvider.hpp"
|
||||
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
@@ -31,6 +33,8 @@
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OpenRouterProvider::OpenRouterProvider() {}
|
||||
|
||||
QString OpenRouterProvider::name() const
|
||||
{
|
||||
return "OpenRouter";
|
||||
@@ -41,6 +45,47 @@ QString OpenRouterProvider::url() const
|
||||
return "https://openrouter.ai/api";
|
||||
}
|
||||
|
||||
void OpenRouterProvider::prepareRequest(QJsonObject &request, LLMCore::RequestType type)
|
||||
{
|
||||
auto prepareMessages = [](QJsonObject &req) -> QJsonArray {
|
||||
QJsonArray messages;
|
||||
if (req.contains("system")) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "system"}, {"content", req.take("system").toString()}});
|
||||
}
|
||||
if (req.contains("prompt")) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "user"}, {"content", req.take("prompt").toString()}});
|
||||
}
|
||||
return messages;
|
||||
};
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
QJsonArray messages = prepareMessages(request);
|
||||
if (!messages.isEmpty()) {
|
||||
request["messages"] = std::move(messages);
|
||||
}
|
||||
|
||||
if (type == LLMCore::RequestType::CodeCompletion) {
|
||||
applyModelParams(Settings::codeCompletionSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenRouterProvider::handleResponse(QNetworkReply *reply, QString &accumulatedResponse)
|
||||
{
|
||||
QByteArray data = reply->readAll();
|
||||
@@ -97,9 +142,4 @@ QString OpenRouterProvider::apiKey() const
|
||||
return Settings::providerSettings().openRouterApiKey();
|
||||
}
|
||||
|
||||
LLMCore::ProviderID OpenRouterProvider::providerID() const
|
||||
{
|
||||
return LLMCore::ProviderID::OpenRouter;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@@ -27,11 +27,13 @@ namespace QodeAssist::Providers {
|
||||
class OpenRouterProvider : public OpenAICompatProvider
|
||||
{
|
||||
public:
|
||||
OpenRouterProvider();
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(QJsonObject &request, LLMCore::RequestType type) override;
|
||||
bool handleResponse(QNetworkReply *reply, QString &accumulatedResponse) override;
|
||||
QString apiKey() const override;
|
||||
LLMCore::ProviderID providerID() const override;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@@ -21,9 +21,7 @@
|
||||
|
||||
#include "llmcore/ProvidersManager.hpp"
|
||||
#include "providers/ClaudeProvider.hpp"
|
||||
#include "providers/GoogleAIProvider.hpp"
|
||||
#include "providers/LMStudioProvider.hpp"
|
||||
#include "providers/MistralAIProvider.hpp"
|
||||
#include "providers/OllamaProvider.hpp"
|
||||
#include "providers/OpenAICompatProvider.hpp"
|
||||
#include "providers/OpenAIProvider.hpp"
|
||||
@@ -35,13 +33,11 @@ inline void registerProviders()
|
||||
{
|
||||
auto &providerManager = LLMCore::ProvidersManager::instance();
|
||||
providerManager.registerProvider<OllamaProvider>();
|
||||
providerManager.registerProvider<LMStudioProvider>();
|
||||
providerManager.registerProvider<OpenAICompatProvider>();
|
||||
providerManager.registerProvider<OpenRouterProvider>();
|
||||
providerManager.registerProvider<ClaudeProvider>();
|
||||
providerManager.registerProvider<OpenAIProvider>();
|
||||
providerManager.registerProvider<OpenAICompatProvider>();
|
||||
providerManager.registerProvider<LMStudioProvider>();
|
||||
providerManager.registerProvider<OpenRouterProvider>();
|
||||
providerManager.registerProvider<MistralAIProvider>();
|
||||
providerManager.registerProvider<GoogleAIProvider>();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
|
||||
@@ -108,10 +108,8 @@ public:
|
||||
UpdateDialog::checkForUpdatesAndShow(Core::ICore::mainWindow());
|
||||
});
|
||||
|
||||
if (Settings::generalSettings().enableChat()) {
|
||||
m_chatOutputPane = new Chat::ChatOutputPane(this);
|
||||
m_navigationPanel = new Chat::NavigationPanel();
|
||||
}
|
||||
m_chatOutputPane = new Chat::ChatOutputPane(this);
|
||||
m_navigationPanel = new Chat::NavigationPanel();
|
||||
|
||||
Settings::setupProjectPanel();
|
||||
ConfigurationManager::instance().init();
|
||||
|
||||
@@ -90,7 +90,7 @@ CodeCompletionSettings::CodeCompletionSettings()
|
||||
maxTokens.setSettingsKey(Constants::CC_MAX_TOKENS);
|
||||
maxTokens.setLabelText(Tr::tr("Max Tokens:"));
|
||||
maxTokens.setRange(-1, 900000);
|
||||
maxTokens.setDefaultValue(100);
|
||||
maxTokens.setDefaultValue(50);
|
||||
|
||||
// Advanced Parameters
|
||||
useTopP.setSettingsKey(Constants::CC_USE_TOP_P);
|
||||
@@ -151,47 +151,31 @@ CodeCompletionSettings::CodeCompletionSettings()
|
||||
|
||||
systemPrompt.setSettingsKey(Constants::CC_SYSTEM_PROMPT);
|
||||
systemPrompt.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
systemPromptForNonFimModels.setLabelText(Tr::tr("System prompt for FIM models:"));
|
||||
systemPrompt.setDefaultValue(
|
||||
"You are an expert C++, Qt, and QML code completion assistant. Your task is to provide "
|
||||
"precise and contextually appropriate code completions.\n\n");
|
||||
"You are an expert in C++, Qt, and QML programming. Your task is to provide code "
|
||||
"suggestions that seamlessly integrate with existing code. Do not repeat code from position "
|
||||
"before or after <cursor>. You will receive a code context with specified insertion points. "
|
||||
"Your goal is to complete only one code block."
|
||||
"Here is the code context with insertion points:<code_context>Before: {{variable}}After: "
|
||||
"{{variable}}</code_context> Instructions: 1. Carefully analyze the provided code context. "
|
||||
"2. Consider the existing code and the specified insertion points.3. Generate a code "
|
||||
"suggestion that completes one logic expression between the 'Before' and 'After' points. "
|
||||
"4. Ensure your suggestion does not repeat any existing code. 5. Format your suggestion as "
|
||||
"a code block using triple backticks. 6. Do not include any comments or descriptions with "
|
||||
"your code suggestion.");
|
||||
|
||||
useUserMessageTemplateForCC.setSettingsKey(Constants::CC_USE_USER_TEMPLATE);
|
||||
useUserMessageTemplateForCC.setDefaultValue(true);
|
||||
useUserMessageTemplateForCC.setLabelText(
|
||||
Tr::tr("Use special system prompt and user message for non FIM models"));
|
||||
|
||||
systemPromptForNonFimModels.setSettingsKey(Constants::CC_SYSTEM_PROMPT_FOR_NON_FIM);
|
||||
systemPromptForNonFimModels.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
systemPromptForNonFimModels.setLabelText(Tr::tr("System prompt for non FIM models:"));
|
||||
systemPromptForNonFimModels.setDefaultValue(
|
||||
"You are an expert C++, Qt, and QML code completion assistant. Your task is to provide "
|
||||
"precise and contextually appropriate code completions.\n\n"
|
||||
"Core Requirements:\n"
|
||||
"1. Continue code exactly from the cursor position, ensuring it properly connects with any "
|
||||
"existing code after the cursor\n"
|
||||
"2. Never repeat existing code before or after the cursor\n"
|
||||
"Specific Guidelines:\n"
|
||||
"- For function calls: Complete parameters with appropriate types and names\n"
|
||||
"- For class members: Respect access modifiers and class conventions\n"
|
||||
"- Respect existing indentation and formatting\n"
|
||||
"- Consider scope and visibility of referenced symbols\n"
|
||||
"- Ensure seamless integration with code both before and after the cursor\n\n"
|
||||
"Context Format:\n"
|
||||
"<code_context>\n"
|
||||
"{{code before cursor}}<cursor>{{code after cursor}}\n"
|
||||
"</code_context>\n\n"
|
||||
"Response Format:\n"
|
||||
"- No explanations or comments\n"
|
||||
"- Only include new characters needed to create valid code\n"
|
||||
"- Should be codeblock with language\n");
|
||||
useUserMessageTemplateForCC.setLabelText(Tr::tr("Use User Template for code completion message for non-FIM models"));
|
||||
|
||||
userMessageTemplateForCC.setSettingsKey(Constants::CC_USER_TEMPLATE);
|
||||
userMessageTemplateForCC.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
userMessageTemplateForCC.setLabelText(Tr::tr("User message for non FIM models:"));
|
||||
userMessageTemplateForCC.setDefaultValue(
|
||||
"Here is the code context with insertion points:\n"
|
||||
"<code_context>\n${prefix}<cursor>${suffix}\n</code_context>\n\n");
|
||||
userMessageTemplateForCC.setDefaultValue("Here is the code context with insertion points: <code_context>"
|
||||
"\nBefore: %1After: %2\n </code_context>");
|
||||
|
||||
useFilePathInContext.setSettingsKey(Constants::CC_USE_FILE_PATH_IN_CONTEXT);
|
||||
useFilePathInContext.setDefaultValue(true);
|
||||
useFilePathInContext.setLabelText(Tr::tr("Use File Path in Context"));
|
||||
|
||||
useProjectChangesCache.setSettingsKey(Constants::CC_USE_PROJECT_CHANGES_CACHE);
|
||||
useProjectChangesCache.setDefaultValue(true);
|
||||
@@ -252,14 +236,10 @@ CodeCompletionSettings::CodeCompletionSettings()
|
||||
|
||||
auto contextItem = Column{Row{contextGrid, Stretch{1}},
|
||||
Row{useSystemPrompt, Stretch{1}},
|
||||
Group{title(Tr::tr("Prompts for FIM models")),
|
||||
Column{systemPrompt}},
|
||||
Group{title(Tr::tr("Prompts for Non FIM models")),
|
||||
Column{
|
||||
Row{useUserMessageTemplateForCC, Stretch{1}},
|
||||
systemPromptForNonFimModels,
|
||||
userMessageTemplateForCC,
|
||||
}},
|
||||
systemPrompt,
|
||||
Row{useUserMessageTemplateForCC, Stretch{1}},
|
||||
userMessageTemplateForCC,
|
||||
Row{useFilePathInContext, Stretch{1}},
|
||||
Row{useProjectChangesCache, maxChangesCacheSize, Stretch{1}}};
|
||||
|
||||
return Column{
|
||||
@@ -347,24 +327,14 @@ void CodeCompletionSettings::resetSettingsToDefaults()
|
||||
resetAspect(readStringsAfterCursor);
|
||||
resetAspect(useSystemPrompt);
|
||||
resetAspect(systemPrompt);
|
||||
resetAspect(useFilePathInContext);
|
||||
resetAspect(useProjectChangesCache);
|
||||
resetAspect(maxChangesCacheSize);
|
||||
resetAspect(ollamaLivetime);
|
||||
resetAspect(contextWindow);
|
||||
resetAspect(useUserMessageTemplateForCC);
|
||||
resetAspect(userMessageTemplateForCC);
|
||||
resetAspect(systemPromptForNonFimModels);
|
||||
}
|
||||
}
|
||||
|
||||
QString CodeCompletionSettings::processMessageToFIM(const QString &prefix, const QString &suffix)
|
||||
{
|
||||
QString result = userMessageTemplateForCC();
|
||||
result.replace("${prefix}", prefix);
|
||||
result.replace("${suffix}", suffix);
|
||||
return result;
|
||||
}
|
||||
|
||||
class CodeCompletionSettingsPage : public Core::IOptionsPage
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -67,8 +67,8 @@ public:
|
||||
Utils::BoolAspect useSystemPrompt{this};
|
||||
Utils::StringAspect systemPrompt{this};
|
||||
Utils::BoolAspect useUserMessageTemplateForCC{this};
|
||||
Utils::StringAspect systemPromptForNonFimModels{this};
|
||||
Utils::StringAspect userMessageTemplateForCC{this};
|
||||
Utils::BoolAspect useFilePathInContext{this};
|
||||
Utils::BoolAspect useProjectChangesCache{this};
|
||||
Utils::IntegerAspect maxChangesCacheSize{this};
|
||||
|
||||
@@ -79,8 +79,6 @@ public:
|
||||
// API Configuration Settings
|
||||
Utils::StringAspect apiKey{this};
|
||||
|
||||
QString processMessageToFIM(const QString &prefix, const QString &suffix);
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
void resetSettingsToDefaults();
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/detailswidget.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/utilsicons.h>
|
||||
#include <QInputDialog>
|
||||
@@ -66,10 +65,6 @@ GeneralSettings::GeneralSettings()
|
||||
enableCheckUpdate.setLabelText(TrConstants::ENABLE_CHECK_UPDATE_ON_START);
|
||||
enableCheckUpdate.setDefaultValue(true);
|
||||
|
||||
enableChat.setSettingsKey(Constants::ENABLE_CHAT);
|
||||
enableChat.setLabelText(TrConstants::ENABLE_CHAT);
|
||||
enableChat.setDefaultValue(true);
|
||||
|
||||
resetToDefaults.m_buttonText = TrConstants::RESET_TO_DEFAULTS;
|
||||
checkUpdate.m_buttonText = TrConstants::CHECK_UPDATE;
|
||||
|
||||
@@ -94,11 +89,6 @@ GeneralSettings::GeneralSettings()
|
||||
ccStatus.setDefaultValue("");
|
||||
ccTest.m_buttonText = TrConstants::TEST;
|
||||
|
||||
ccTemplateDescription.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
ccTemplateDescription.setReadOnly(true);
|
||||
ccTemplateDescription.setDefaultValue("");
|
||||
ccTemplateDescription.setLabelText(TrConstants::CURRENT_TEMPLATE_DESCRIPTION);
|
||||
|
||||
// preset1
|
||||
specifyPreset1.setSettingsKey(Constants::CC_SPECIFY_PRESET1);
|
||||
specifyPreset1.setLabelText(TrConstants::ADD_NEW_PRESET);
|
||||
@@ -154,11 +144,6 @@ GeneralSettings::GeneralSettings()
|
||||
caStatus.setDefaultValue("");
|
||||
caTest.m_buttonText = TrConstants::TEST;
|
||||
|
||||
caTemplateDescription.setDisplayStyle(Utils::StringAspect::TextEditDisplay);
|
||||
caTemplateDescription.setReadOnly(true);
|
||||
caTemplateDescription.setDefaultValue("");
|
||||
caTemplateDescription.setLabelText(TrConstants::CURRENT_TEMPLATE_DESCRIPTION);
|
||||
|
||||
readSettings();
|
||||
|
||||
Logger::instance().setLoggingEnabled(enableLogging());
|
||||
@@ -190,19 +175,13 @@ GeneralSettings::GeneralSettings()
|
||||
|
||||
auto ccGroup = Group{
|
||||
title(TrConstants::CODE_COMPLETION),
|
||||
Column{
|
||||
ccGrid,
|
||||
ccTemplateDescription,
|
||||
Row{specifyPreset1, preset1Language, Stretch{1}},
|
||||
ccPreset1Grid}};
|
||||
auto caGroup
|
||||
= Group{title(TrConstants::CHAT_ASSISTANT), Column{caGrid, caTemplateDescription}};
|
||||
Column{ccGrid, Row{specifyPreset1, preset1Language, Stretch{1}}, ccPreset1Grid}};
|
||||
auto caGroup = Group{title(TrConstants::CHAT_ASSISTANT), caGrid};
|
||||
|
||||
auto rootLayout = Column{
|
||||
Row{enableQodeAssist, Stretch{1}, Row{checkUpdate, resetToDefaults}},
|
||||
Row{enableLogging, Stretch{1}},
|
||||
Row{enableCheckUpdate, Stretch{1}},
|
||||
Row{enableChat, Stretch{1}},
|
||||
Space{8},
|
||||
ccGroup,
|
||||
Space{8},
|
||||
@@ -213,8 +192,10 @@ GeneralSettings::GeneralSettings()
|
||||
});
|
||||
}
|
||||
|
||||
void GeneralSettings::showSelectionDialog(
|
||||
const QStringList &data, Utils::StringAspect &aspect, const QString &title, const QString &text)
|
||||
void GeneralSettings::showSelectionDialog(const QStringList &data,
|
||||
Utils::StringAspect &aspect,
|
||||
const QString &title,
|
||||
const QString &text)
|
||||
{
|
||||
if (data.isEmpty())
|
||||
return;
|
||||
@@ -258,7 +239,6 @@ void GeneralSettings::showModelsNotFoundDialog(Utils::StringAspect &aspect)
|
||||
auto selectProviderBtn = new QPushButton(TrConstants::SELECT_PROVIDER);
|
||||
auto selectUrlBtn = new QPushButton(TrConstants::SELECT_URL);
|
||||
auto enterManuallyBtn = new QPushButton(TrConstants::ENTER_MODEL_MANUALLY);
|
||||
auto configureApiKeyBtn = new QPushButton(TrConstants::CONFIGURE_API_KEY);
|
||||
|
||||
connect(selectProviderBtn, &QPushButton::clicked, &dialog, [this, providerButton, &dialog]() {
|
||||
dialog.close();
|
||||
@@ -275,15 +255,9 @@ void GeneralSettings::showModelsNotFoundDialog(Utils::StringAspect &aspect)
|
||||
showModelsNotSupportedDialog(aspect);
|
||||
});
|
||||
|
||||
connect(configureApiKeyBtn, &QPushButton::clicked, &dialog, [&dialog]() {
|
||||
dialog.close();
|
||||
Core::ICore::showOptionsDialog(Constants::QODE_ASSIST_PROVIDER_SETTINGS_PAGE_ID);
|
||||
});
|
||||
|
||||
dialog.buttonLayout()->addWidget(selectProviderBtn);
|
||||
dialog.buttonLayout()->addWidget(selectUrlBtn);
|
||||
dialog.buttonLayout()->addWidget(enterManuallyBtn);
|
||||
dialog.buttonLayout()->addWidget(configureApiKeyBtn);
|
||||
}
|
||||
|
||||
auto closeBtn = new QPushButton(TrConstants::CLOSE);
|
||||
@@ -392,7 +366,7 @@ void GeneralSettings::setupConnections()
|
||||
|
||||
connect(&specifyPreset1, &Utils::BoolAspect::volatileValueChanged, this, [this]() {
|
||||
updatePreset1Visiblity(specifyPreset1.volatileValue());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void GeneralSettings::resetPageToDefaults()
|
||||
|
||||
@@ -36,8 +36,6 @@ public:
|
||||
Utils::BoolAspect enableQodeAssist{this};
|
||||
Utils::BoolAspect enableLogging{this};
|
||||
Utils::BoolAspect enableCheckUpdate{this};
|
||||
Utils::BoolAspect enableChat{this};
|
||||
|
||||
ButtonAspect checkUpdate{this};
|
||||
ButtonAspect resetToDefaults{this};
|
||||
|
||||
@@ -57,8 +55,6 @@ public:
|
||||
Utils::StringAspect ccStatus{this};
|
||||
ButtonAspect ccTest{this};
|
||||
|
||||
Utils::StringAspect ccTemplateDescription{this};
|
||||
|
||||
// TODO create dynamic presets system
|
||||
// preset1 for code completion settings
|
||||
Utils::BoolAspect specifyPreset1{this};
|
||||
@@ -92,8 +88,6 @@ public:
|
||||
Utils::StringAspect caStatus{this};
|
||||
ButtonAspect caTest{this};
|
||||
|
||||
Utils::StringAspect caTemplateDescription{this};
|
||||
|
||||
void showSelectionDialog(const QStringList &data,
|
||||
Utils::StringAspect &aspect,
|
||||
const QString &title = {},
|
||||
|
||||
@@ -69,7 +69,6 @@ ProviderSettings::ProviderSettings()
|
||||
claudeApiKey.setDefaultValue("");
|
||||
claudeApiKey.setAutoApply(true);
|
||||
|
||||
// OpenAI Settings
|
||||
openAiApiKey.setSettingsKey(Constants::OPEN_AI_API_KEY);
|
||||
openAiApiKey.setLabelText(Tr::tr("OpenAI API Key:"));
|
||||
openAiApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
@@ -78,24 +77,6 @@ ProviderSettings::ProviderSettings()
|
||||
openAiApiKey.setDefaultValue("");
|
||||
openAiApiKey.setAutoApply(true);
|
||||
|
||||
// MistralAI Settings
|
||||
mistralAiApiKey.setSettingsKey(Constants::MISTRAL_AI_API_KEY);
|
||||
mistralAiApiKey.setLabelText(Tr::tr("Mistral AI API Key:"));
|
||||
mistralAiApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
mistralAiApiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
||||
mistralAiApiKey.setHistoryCompleter(Constants::MISTRAL_AI_API_KEY_HISTORY);
|
||||
mistralAiApiKey.setDefaultValue("");
|
||||
mistralAiApiKey.setAutoApply(true);
|
||||
|
||||
// GoogleAI Settings
|
||||
googleAiApiKey.setSettingsKey(Constants::GOOGLE_AI_API_KEY);
|
||||
googleAiApiKey.setLabelText(Tr::tr("Google AI API Key:"));
|
||||
googleAiApiKey.setDisplayStyle(Utils::StringAspect::LineEditDisplay);
|
||||
googleAiApiKey.setPlaceHolderText(Tr::tr("Enter your API key here"));
|
||||
googleAiApiKey.setHistoryCompleter(Constants::GOOGLE_AI_API_KEY_HISTORY);
|
||||
googleAiApiKey.setDefaultValue("");
|
||||
googleAiApiKey.setAutoApply(true);
|
||||
|
||||
resetToDefaults.m_buttonText = Tr::tr("Reset Page to Defaults");
|
||||
|
||||
readSettings();
|
||||
@@ -115,10 +96,6 @@ ProviderSettings::ProviderSettings()
|
||||
Group{title(Tr::tr("OpenAI Compatible Settings")), Column{openAiCompatApiKey}},
|
||||
Space{8},
|
||||
Group{title(Tr::tr("Claude Settings")), Column{claudeApiKey}},
|
||||
Space{8},
|
||||
Group{title(Tr::tr("Mistral AI Settings")), Column{mistralAiApiKey}},
|
||||
Space{8},
|
||||
Group{title(Tr::tr("Google AI Settings")), Column{googleAiApiKey}},
|
||||
Stretch{1}};
|
||||
});
|
||||
}
|
||||
@@ -135,12 +112,6 @@ void ProviderSettings::setupConnections()
|
||||
});
|
||||
connect(&claudeApiKey, &ButtonAspect::changed, this, [this]() { claudeApiKey.writeSettings(); });
|
||||
connect(&openAiApiKey, &ButtonAspect::changed, this, [this]() { openAiApiKey.writeSettings(); });
|
||||
connect(&mistralAiApiKey, &ButtonAspect::changed, this, [this]() {
|
||||
mistralAiApiKey.writeSettings();
|
||||
});
|
||||
connect(&googleAiApiKey, &ButtonAspect::changed, this, [this]() {
|
||||
googleAiApiKey.writeSettings();
|
||||
});
|
||||
}
|
||||
|
||||
void ProviderSettings::resetSettingsToDefaults()
|
||||
@@ -157,8 +128,6 @@ void ProviderSettings::resetSettingsToDefaults()
|
||||
resetAspect(openAiCompatApiKey);
|
||||
resetAspect(claudeApiKey);
|
||||
resetAspect(openAiApiKey);
|
||||
resetAspect(mistralAiApiKey);
|
||||
resetAspect(googleAiApiKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,6 @@ public:
|
||||
Utils::StringAspect openAiCompatApiKey{this};
|
||||
Utils::StringAspect claudeApiKey{this};
|
||||
Utils::StringAspect openAiApiKey{this};
|
||||
Utils::StringAspect mistralAiApiKey{this};
|
||||
Utils::StringAspect googleAiApiKey{this};
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
|
||||
@@ -60,7 +60,6 @@ const char ENABLE_QODE_ASSIST[] = "QodeAssist.enableQodeAssist";
|
||||
const char CC_AUTO_COMPLETION[] = "QodeAssist.ccAutoCompletion";
|
||||
const char ENABLE_LOGGING[] = "QodeAssist.enableLogging";
|
||||
const char ENABLE_CHECK_UPDATE[] = "QodeAssist.enableCheckUpdate";
|
||||
const char ENABLE_CHAT[] = "QodeAssist.enableChat";
|
||||
|
||||
const char PROVIDER_PATHS[] = "QodeAssist.providerPaths";
|
||||
const char СС_START_SUGGESTION_TIMER[] = "QodeAssist.startSuggestionTimer";
|
||||
@@ -101,18 +100,14 @@ const char CLAUDE_API_KEY[] = "QodeAssist.claudeApiKey";
|
||||
const char CLAUDE_API_KEY_HISTORY[] = "QodeAssist.claudeApiKeyHistory";
|
||||
const char OPEN_AI_API_KEY[] = "QodeAssist.openAiApiKey";
|
||||
const char OPEN_AI_API_KEY_HISTORY[] = "QodeAssist.openAiApiKeyHistory";
|
||||
const char MISTRAL_AI_API_KEY[] = "QodeAssist.mistralAiApiKey";
|
||||
const char MISTRAL_AI_API_KEY_HISTORY[] = "QodeAssist.mistralAiApiKeyHistory";
|
||||
const char GOOGLE_AI_API_KEY[] = "QodeAssist.googleAiApiKey";
|
||||
const char GOOGLE_AI_API_KEY_HISTORY[] = "QodeAssist.googleAiApiKeyHistory";
|
||||
|
||||
// context settings
|
||||
const char CC_READ_FULL_FILE[] = "QodeAssist.ccReadFullFile";
|
||||
const char CC_READ_STRINGS_BEFORE_CURSOR[] = "QodeAssist.ccReadStringsBeforeCursor";
|
||||
const char CC_READ_STRINGS_AFTER_CURSOR[] = "QodeAssist.ccReadStringsAfterCursor";
|
||||
const char CC_USE_SYSTEM_PROMPT[] = "QodeAssist.ccUseSystemPrompt";
|
||||
const char CC_USE_FILE_PATH_IN_CONTEXT[] = "QodeAssist.ccUseFilePathInContext";
|
||||
const char CC_SYSTEM_PROMPT[] = "QodeAssist.ccSystemPrompt";
|
||||
const char CC_SYSTEM_PROMPT_FOR_NON_FIM[] = "QodeAssist.ccSystemPromptForNonFim";
|
||||
const char CC_USE_USER_TEMPLATE[] = "QodeAssist.ccUseUserTemplate";
|
||||
const char CC_USER_TEMPLATE[] = "QodeAssist.ccUserTemplate";
|
||||
const char CC_USE_PROJECT_CHANGES_CACHE[] = "QodeAssist.ccUseProjectChangesCache";
|
||||
|
||||
@@ -39,9 +39,6 @@ inline const char *TEST = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Test");
|
||||
inline const char *ENABLE_LOG = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Enable Logging");
|
||||
inline const char *ENABLE_CHECK_UPDATE_ON_START
|
||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Check for updates when Qt Creator starts");
|
||||
inline const char *ENABLE_CHAT = QT_TRANSLATE_NOOP(
|
||||
"QtC::QodeAssist",
|
||||
"Enable Chat(If you have performance issues try disabling this, need restart QtC)");
|
||||
|
||||
inline const char *CODE_COMPLETION = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Code Completion");
|
||||
inline const char *CHAT_ASSISTANT = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Chat Assistant");
|
||||
@@ -49,8 +46,6 @@ inline const char *RESET_SETTINGS = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Reset
|
||||
inline const char *CONFIRMATION
|
||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist",
|
||||
"Are you sure you want to reset all settings to default values?");
|
||||
inline const char *CURRENT_TEMPLATE_DESCRIPTION
|
||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Current template description:");
|
||||
|
||||
inline const char CONNECTION_ERROR[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Connection Error");
|
||||
inline const char NO_MODELS_FOUND[]
|
||||
@@ -60,8 +55,7 @@ inline const char CHECK_CONNECTION[] = QT_TRANSLATE_NOOP(
|
||||
"Please verify the following:\n"
|
||||
"- Server is running and accessible\n"
|
||||
"- URL is correct\n"
|
||||
"- Provider is properly configured\n"
|
||||
"- API key is correctly set (if required)\n\n"
|
||||
"- Provider is properly configured\n\n"
|
||||
"You can try selecting a different provider or changing the URL:");
|
||||
inline const char SELECT_PROVIDER[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Select Provider");
|
||||
inline const char SELECT_URL[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Select URL");
|
||||
@@ -79,7 +73,6 @@ inline const char OK[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "OK");
|
||||
inline const char CANCEL[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Cancel");
|
||||
inline const char ENTER_MODEL_MANUALLY[]
|
||||
= QT_TRANSLATE_NOOP("QtC::QodeAssist", "Enter Model Manually");
|
||||
inline const char CONFIGURE_API_KEY[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "Configure API Key");
|
||||
inline const char URL_SELECTION[] = QT_TRANSLATE_NOOP("QtC::QodeAssist", "URL Selection");
|
||||
inline const char URL_SELECTION_INFO[] = QT_TRANSLATE_NOOP(
|
||||
"QtC::QodeAssist",
|
||||
|
||||
@@ -29,60 +29,38 @@ class Alpaca : public LLMCore::PromptTemplate
|
||||
public:
|
||||
QString name() const override { return "Alpaca"; }
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||
QString promptTemplate() const override { return {}; }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "### Instruction:" << "### Response:";
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
QJsonArray messages = request["messages"].toArray();
|
||||
|
||||
QString fullContent;
|
||||
for (int i = 0; i < messages.size(); ++i) {
|
||||
QJsonObject message = messages[i].toObject();
|
||||
QString role = message["role"].toString();
|
||||
QString content = message["content"].toString();
|
||||
|
||||
if (context.systemPrompt) {
|
||||
fullContent += context.systemPrompt.value() + "\n\n";
|
||||
}
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
if (msg.role == "user") {
|
||||
fullContent += QString("### Instruction:\n%1\n\n").arg(msg.content);
|
||||
} else if (msg.role == "assistant") {
|
||||
fullContent += QString("### Response:\n%1\n\n").arg(msg.content);
|
||||
}
|
||||
QString formattedContent;
|
||||
if (role == "system") {
|
||||
formattedContent = content + "\n\n";
|
||||
} else if (role == "user") {
|
||||
formattedContent = "### Instruction:\n" + content + "\n\n";
|
||||
} else if (role == "assistant") {
|
||||
formattedContent = "### Response:\n" + content + "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
messages.append(QJsonObject{{"role", "user"}, {"content", fullContent}});
|
||||
message["content"] = formattedContent;
|
||||
messages[i] = message;
|
||||
}
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for models using Alpaca instruction format:\n\n"
|
||||
"{\n"
|
||||
" \"messages\": [\n"
|
||||
" {\n"
|
||||
" \"role\": \"user\",\n"
|
||||
" \"content\": \"<system prompt>\\n\\n"
|
||||
"### Instruction:\\n<user message>\\n\\n"
|
||||
"### Response:\\n<assistant response>\\n\\n\"\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Combines all messages into a single formatted prompt.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
||||
case QodeAssist::LLMCore::ProviderID::LMStudio:
|
||||
case QodeAssist::LLMCore::ProviderID::OpenRouter:
|
||||
case QodeAssist::LLMCore::ProviderID::OpenAICompatible:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return "The message will contain the following tokens: ### Instruction:\n### Response:\n";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
40
templates/BasicChat.hpp
Normal file
40
templates/BasicChat.hpp
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "llmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class BasicChat : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||
QString name() const override { return "Basic Chat"; }
|
||||
QString promptTemplate() const override { return {}; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{}
|
||||
QString description() const override { return "chat without tokens"; }
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@@ -30,60 +30,30 @@ class ChatML : public LLMCore::PromptTemplate
|
||||
public:
|
||||
QString name() const override { return "ChatML"; }
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||
QString promptTemplate() const override { return {}; }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "<|im_start|>" << "<|im_end|>";
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
QJsonArray messages = request["messages"].toArray();
|
||||
|
||||
if (context.systemPrompt) {
|
||||
messages.append(QJsonObject{
|
||||
{"role", "system"},
|
||||
{"content",
|
||||
QString("<|im_start|>system\n%2\n<|im_end|>").arg(context.systemPrompt.value())}});
|
||||
}
|
||||
for (int i = 0; i < messages.size(); ++i) {
|
||||
QJsonObject message = messages[i].toObject();
|
||||
QString role = message["role"].toString();
|
||||
QString content = message["content"].toString();
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
messages.append(QJsonObject{
|
||||
{"role", msg.role},
|
||||
{"content",
|
||||
QString("<|im_start|>%1\n%2\n<|im_end|>").arg(msg.role, msg.content)}});
|
||||
}
|
||||
message["content"] = QString("<|im_start|>%1\n%2\n<|im_end|>").arg(role, content);
|
||||
|
||||
messages[i] = message;
|
||||
}
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for models supporting ChatML format:\n\n"
|
||||
"{\n"
|
||||
" \"messages\": [\n"
|
||||
" {\n"
|
||||
" \"role\": \"system\",\n"
|
||||
" \"content\": \"<|im_start|>system\\n<system prompt>\\n<|im_end|>\"\n"
|
||||
" },\n"
|
||||
" {\n"
|
||||
" \"role\": \"user\",\n"
|
||||
" \"content\": \"<|im_start|>user\\n<user message>\\n<|im_end|>\"\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Compatible with multiple providers supporting the ChatML token format.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
||||
case QodeAssist::LLMCore::ProviderID::LMStudio:
|
||||
case QodeAssist::LLMCore::ProviderID::OpenRouter:
|
||||
case QodeAssist::LLMCore::ProviderID::OpenAICompatible:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return "The message will contain the following tokens: <|im_start|>%1\n%2\n<|im_end|>";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -30,46 +30,10 @@ class Claude : public LLMCore::PromptTemplate
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||
QString name() const override { return "Claude"; }
|
||||
QString promptTemplate() const override { return {}; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
|
||||
if (context.systemPrompt) {
|
||||
request["system"] = context.systemPrompt.value();
|
||||
}
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
if (msg.role != "system") {
|
||||
messages.append(QJsonObject{{"role", msg.role}, {"content", msg.content}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for Anthropic's Claude models:\n\n"
|
||||
"{\n"
|
||||
" \"system\": \"<system prompt>\",\n"
|
||||
" \"messages\": [\n"
|
||||
" {\"role\": \"user\", \"content\": \"<user message>\"},\n"
|
||||
" {\"role\": \"assistant\", \"content\": \"<assistant response>\"}\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Formats content according to Claude API specifications.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::Claude:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override {}
|
||||
QString description() const override { return "Claude"; }
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
|
||||
@@ -26,35 +26,21 @@ namespace QodeAssist::Templates {
|
||||
class CodeLlamaFim : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::FIM; }
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
|
||||
QString name() const override { return "CodeLlama FIM"; }
|
||||
QString promptTemplate() const override { return "<PRE> %1 <SUF>%2 <MID>"; }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "<EOT>" << "<PRE>" << "<SUF" << "<MID>";
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
request["prompt"] = QString("<PRE> %1 <SUF>%2 <MID>")
|
||||
.arg(context.prefix.value_or(""), context.suffix.value_or(""));
|
||||
request["system"] = context.systemPrompt.value_or("");
|
||||
QString formattedPrompt = promptTemplate().arg(context.prefix, context.suffix);
|
||||
request["prompt"] = formattedPrompt;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Specialized template for CodeLlama FIM:\n\n"
|
||||
"{\n"
|
||||
" \"prompt\": \"<PRE> <code prefix> <SUF><code suffix> <MID>\",\n"
|
||||
" \"system\": \"<system prompt>\"\n"
|
||||
"}\n\n"
|
||||
"Optimized for code completion with CodeLlama models.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return "The message will contain the following tokens: <PRE> %1 <SUF>%2 <MID>";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -26,8 +26,9 @@ namespace QodeAssist::Templates {
|
||||
class CodeLlamaQMLFim : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::FIM; }
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
|
||||
QString name() const override { return "CodeLlama QML FIM"; }
|
||||
QString promptTemplate() const override { return "<SUF>%1<PRE>%2<MID>"; }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "<SUF>" << "<PRE>" << "</PRE>" << "</SUF>" << "< EOT >" << "\\end"
|
||||
@@ -35,27 +36,12 @@ public:
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
request["prompt"] = QString("<SUF>%1<PRE>%2<MID>")
|
||||
.arg(context.suffix.value_or(""), context.prefix.value_or(""));
|
||||
request["system"] = context.systemPrompt.value_or("");
|
||||
QString formattedPrompt = promptTemplate().arg(context.suffix, context.prefix);
|
||||
request["prompt"] = formattedPrompt;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Specialized template for QML code completion with CodeLlama:\n\n"
|
||||
"{\n"
|
||||
" \"prompt\": \"<SUF><code suffix><PRE><code prefix><MID>\",\n"
|
||||
" \"system\": \"<system prompt>\"\n"
|
||||
"}\n\n"
|
||||
"Specifically optimized for QML/JavaScript code completion.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return "The message will contain the following tokens: <SUF>%1<PRE>%2<MID>";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "llmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class GoogleAI : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||
QString name() const override { return "Google AI"; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray contents;
|
||||
|
||||
if (context.systemPrompt && !context.systemPrompt->isEmpty()) {
|
||||
request["system_instruction"] = QJsonObject{
|
||||
{"parts", QJsonObject{{"text", context.systemPrompt.value()}}}};
|
||||
}
|
||||
|
||||
for (const auto &msg : context.history.value()) {
|
||||
QJsonObject content;
|
||||
QJsonArray parts;
|
||||
|
||||
parts.append(QJsonObject{{"text", msg.content}});
|
||||
|
||||
QString role = msg.role;
|
||||
if (role == "assistant") {
|
||||
role = "model";
|
||||
}
|
||||
|
||||
content["role"] = role;
|
||||
content["parts"] = parts;
|
||||
contents.append(content);
|
||||
}
|
||||
|
||||
request["contents"] = contents;
|
||||
}
|
||||
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for Google AI models (Gemini):\n\n"
|
||||
"{\n"
|
||||
" \"system_instruction\": {\"parts\": {\"text\": \"<system prompt>\"}},\n"
|
||||
" \"contents\": [\n"
|
||||
" {\n"
|
||||
" \"role\": \"user\",\n"
|
||||
" \"parts\": [{\"text\": \"<user message>\"}]\n"
|
||||
" },\n"
|
||||
" {\n"
|
||||
" \"role\": \"model\",\n"
|
||||
" \"parts\": [{\"text\": \"<assistant response>\"}]\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Supports proper role mapping, including model/user roles.";
|
||||
}
|
||||
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
return id == QodeAssist::LLMCore::ProviderID::GoogleAI;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@@ -29,58 +29,35 @@ class Llama2 : public LLMCore::PromptTemplate
|
||||
public:
|
||||
QString name() const override { return "Llama 2"; }
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||
QString promptTemplate() const override { return {}; }
|
||||
QStringList stopWords() const override { return QStringList() << "[INST]"; }
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
QJsonArray messages = request["messages"].toArray();
|
||||
|
||||
QString fullContent;
|
||||
for (int i = 0; i < messages.size(); ++i) {
|
||||
QJsonObject message = messages[i].toObject();
|
||||
QString role = message["role"].toString();
|
||||
QString content = message["content"].toString();
|
||||
|
||||
if (context.systemPrompt) {
|
||||
fullContent
|
||||
+= QString("[INST]<<SYS>>\n%1\n<</SYS>>[/INST]\n").arg(context.systemPrompt.value());
|
||||
}
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
if (msg.role == "user") {
|
||||
fullContent += QString("[INST]%1[/INST]\n").arg(msg.content);
|
||||
} else if (msg.role == "assistant") {
|
||||
fullContent += msg.content + "\n";
|
||||
}
|
||||
QString formattedContent;
|
||||
if (role == "system") {
|
||||
formattedContent = QString("[INST]<<SYS>>\n%1\n<</SYS>>[/INST]\n").arg(content);
|
||||
} else if (role == "user") {
|
||||
formattedContent = QString("[INST]%1[/INST]\n").arg(content);
|
||||
} else if (role == "assistant") {
|
||||
formattedContent = content + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
messages.append(QJsonObject{{"role", "user"}, {"content", fullContent}});
|
||||
message["content"] = formattedContent;
|
||||
messages[i] = message;
|
||||
}
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for Llama 2 models:\n\n"
|
||||
"{\n"
|
||||
" \"messages\": [\n"
|
||||
" {\n"
|
||||
" \"role\": \"user\",\n"
|
||||
" \"content\": \"[INST]<<SYS>>\\n<system prompt>\\n<</SYS>>[/INST]\\n"
|
||||
"<assistant response>\\n"
|
||||
"[INST]<user message>[/INST]\\n\"\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Compatible with Ollama, LM Studio, and other services for Llama 2.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
||||
case QodeAssist::LLMCore::ProviderID::LMStudio:
|
||||
case QodeAssist::LLMCore::ProviderID::OpenRouter:
|
||||
case QodeAssist::LLMCore::ProviderID::OpenAICompatible:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return "The message will contain the following tokens: [INST]%1[/INST]\n";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -30,64 +30,32 @@ class Llama3 : public LLMCore::PromptTemplate
|
||||
public:
|
||||
QString name() const override { return "Llama 3"; }
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||
QString promptTemplate() const override { return ""; }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "<|start_header_id|>" << "<|end_header_id|>" << "<|eot_id|>";
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
QJsonArray messages = request["messages"].toArray();
|
||||
|
||||
if (context.systemPrompt) {
|
||||
messages.append(QJsonObject{
|
||||
{"role", "system"},
|
||||
{"content",
|
||||
QString("<|start_header_id|>system<|end_header_id|>%2<|eot_id|>")
|
||||
.arg(context.systemPrompt.value())}});
|
||||
}
|
||||
for (int i = 0; i < messages.size(); ++i) {
|
||||
QJsonObject message = messages[i].toObject();
|
||||
QString role = message["role"].toString();
|
||||
QString content = message["content"].toString();
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
messages.append(QJsonObject{
|
||||
{"role", msg.role},
|
||||
{"content",
|
||||
QString("<|start_header_id|>%1<|end_header_id|>%2<|eot_id|>")
|
||||
.arg(msg.role, msg.content)}});
|
||||
}
|
||||
message["content"]
|
||||
= QString("<|start_header_id|>%1<|end_header_id|>%2<|eot_id|>").arg(role, content);
|
||||
|
||||
messages[i] = message;
|
||||
}
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for Llama 3 models:\n\n"
|
||||
"{\n"
|
||||
" \"messages\": [\n"
|
||||
" {\n"
|
||||
" \"role\": \"system\",\n"
|
||||
" \"content\": \"<|start_header_id|>system<|end_header_id|><system "
|
||||
"prompt><|eot_id|>\"\n"
|
||||
" },\n"
|
||||
" {\n"
|
||||
" \"role\": \"user\",\n"
|
||||
" \"content\": \"<|start_header_id|>user<|end_header_id|><user "
|
||||
"message><|eot_id|>\"\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Compatible with Ollama, LM Studio, and OpenAI-compatible services for Llama 3.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
||||
case QodeAssist::LLMCore::ProviderID::LMStudio:
|
||||
case QodeAssist::LLMCore::ProviderID::OpenRouter:
|
||||
case QodeAssist::LLMCore::ProviderID::OpenAICompatible:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return "The message will contain the following tokens: "
|
||||
"<|start_header_id|>%1<|end_header_id|>%2<|eot_id|>";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "llmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class MistralAIFim : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::FIM; }
|
||||
QString name() const override { return "Mistral AI FIM"; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
request["prompt"] = context.prefix.value_or("");
|
||||
request["suffix"] = context.suffix.value_or("");
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for MistralAI models with FIM support:\n\n"
|
||||
"{\n"
|
||||
" \"prompt\": \"<code prefix>\",\n"
|
||||
" \"suffix\": \"<code suffix>\"\n"
|
||||
"}\n\n"
|
||||
"Optimized for code completion with MistralAI models.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::MistralAI:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class MistralAIChat : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||
QString name() const override { return "Mistral AI Chat"; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
|
||||
if (context.systemPrompt) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "system"}, {"content", context.systemPrompt.value()}});
|
||||
}
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
messages.append(QJsonObject{{"role", msg.role}, {"content", msg.content}});
|
||||
}
|
||||
}
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for MistralAI chat-capable models:\n\n"
|
||||
"{\n"
|
||||
" \"messages\": [\n"
|
||||
" {\"role\": \"system\", \"content\": \"<system prompt>\"},\n"
|
||||
" {\"role\": \"user\", \"content\": \"<user message>\"},\n"
|
||||
" {\"role\": \"assistant\", \"content\": \"<assistant response>\"}\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Supports system messages and conversation history.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::MistralAI:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@@ -25,84 +25,33 @@
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class OllamaFim : public LLMCore::PromptTemplate
|
||||
class OllamaAutoFim : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::FIM; }
|
||||
QString name() const override { return "Ollama FIM"; }
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
|
||||
QString name() const override { return "Ollama Auto FIM"; }
|
||||
QString promptTemplate() const override { return {}; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
request["prompt"] = context.prefix.value_or("");
|
||||
request["suffix"] = context.suffix.value_or("");
|
||||
request["system"] = context.systemPrompt.value_or("");
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Default Ollama FIM (Fill-in-Middle) template with native format:\n\n"
|
||||
"{\n"
|
||||
" \"prompt\": \"<code prefix>\",\n"
|
||||
" \"suffix\": \"<code suffix>\",\n"
|
||||
" \"system\": \"<system prompt>\"\n"
|
||||
"}\n\n"
|
||||
"Recommended for Ollama models with FIM capability.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
request["prompt"] = context.prefix;
|
||||
request["suffix"] = context.suffix;
|
||||
}
|
||||
QString description() const override { return "template will take from ollama modelfile"; }
|
||||
};
|
||||
|
||||
class OllamaChat : public LLMCore::PromptTemplate
|
||||
class OllamaAutoChat : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||
QString name() const override { return "Ollama Chat"; }
|
||||
QString name() const override { return "Ollama Auto Chat"; }
|
||||
QString promptTemplate() const override { return {}; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
|
||||
if (context.systemPrompt) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "system"}, {"content", context.systemPrompt.value()}});
|
||||
}
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
messages.append(QJsonObject{{"role", msg.role}, {"content", msg.content}});
|
||||
}
|
||||
}
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for Ollama Chat with message array format:\n\n"
|
||||
"{\n"
|
||||
" \"messages\": [\n"
|
||||
" {\"role\": \"system\", \"content\": \"<system prompt>\"},\n"
|
||||
" {\"role\": \"user\", \"content\": \"<user message>\"},\n"
|
||||
" {\"role\": \"assistant\", \"content\": \"<assistant response>\"}\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Recommended for Ollama models with chat capability.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
QString description() const override { return "template will take from ollama modelfile"; }
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
|
||||
@@ -30,45 +30,10 @@ class OpenAI : public LLMCore::PromptTemplate
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||
QString name() const override { return "OpenAI"; }
|
||||
QString promptTemplate() const override { return {}; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
|
||||
if (context.systemPrompt) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "system"}, {"content", context.systemPrompt.value()}});
|
||||
}
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
messages.append(QJsonObject{{"role", msg.role}, {"content", msg.content}});
|
||||
}
|
||||
}
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for OpenAI models (GPT series):\n\n"
|
||||
"{\n"
|
||||
" \"messages\": [\n"
|
||||
" {\"role\": \"system\", \"content\": \"<system prompt>\"},\n"
|
||||
" {\"role\": \"user\", \"content\": \"<user message>\"},\n"
|
||||
" {\"role\": \"assistant\", \"content\": \"<assistant response>\"}\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Standard Chat API format for OpenAI.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::OpenAI:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override {}
|
||||
QString description() const override { return "OpenAI"; }
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "llmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class OpenAICompatible : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Chat; }
|
||||
QString name() const override { return "OpenAI Compatible"; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
|
||||
if (context.systemPrompt) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "system"}, {"content", context.systemPrompt.value()}});
|
||||
}
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
messages.append(QJsonObject{{"role", msg.role}, {"content", msg.content}});
|
||||
}
|
||||
}
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Generic template for OpenAI API-compatible services:\n\n"
|
||||
"{\n"
|
||||
" \"messages\": [\n"
|
||||
" {\"role\": \"system\", \"content\": \"<system prompt>\"},\n"
|
||||
" {\"role\": \"user\", \"content\": \"<user message>\"},\n"
|
||||
" {\"role\": \"assistant\", \"content\": \"<assistant response>\"}\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Works with any service implementing the OpenAI Chat API specification.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::OpenAICompatible:
|
||||
case QodeAssist::LLMCore::ProviderID::OpenRouter:
|
||||
case QodeAssist::LLMCore::ProviderID::LMStudio:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@@ -28,32 +28,21 @@ class QwenFim : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
QString name() const override { return "Qwen FIM"; }
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::FIM; }
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
|
||||
QString promptTemplate() const override
|
||||
{
|
||||
return "<|fim_prefix|>%1<|fim_suffix|>%2<|fim_middle|>";
|
||||
}
|
||||
QStringList stopWords() const override { return QStringList() << "<|endoftext|>" << "<|EOT|>"; }
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
request["prompt"] = QString("<|fim_prefix|>%1<|fim_suffix|>%2<|fim_middle|>")
|
||||
.arg(context.prefix.value_or(""), context.suffix.value_or(""));
|
||||
request["system"] = context.systemPrompt.value_or("");
|
||||
QString formattedPrompt = promptTemplate().arg(context.prefix, context.suffix);
|
||||
request["prompt"] = formattedPrompt;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for Qwen models with FIM support:\n\n"
|
||||
"{\n"
|
||||
" \"prompt\": \"<|fim_prefix|><code prefix><|fim_suffix|><code "
|
||||
"suffix><|fim_middle|>\",\n"
|
||||
" \"system\": \"<system prompt>\"\n"
|
||||
"}\n\n"
|
||||
"Ideal for code completion with Qwen models.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return "The message will contain the following tokens: "
|
||||
"<|fim_prefix|>%1<|fim_suffix|>%2<|fim_middle|>";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -26,8 +26,9 @@ namespace QodeAssist::Templates {
|
||||
class StarCoder2Fim : public LLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::FIM; }
|
||||
LLMCore::TemplateType type() const override { return LLMCore::TemplateType::Fim; }
|
||||
QString name() const override { return "StarCoder2 FIM"; }
|
||||
QString promptTemplate() const override { return "<fim_prefix>%1<fim_suffix>%2<fim_middle>"; }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "<|endoftext|>" << "<file_sep>" << "<fim_prefix>" << "<fim_suffix>"
|
||||
@@ -35,27 +36,13 @@ public:
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const LLMCore::ContextData &context) const override
|
||||
{
|
||||
request["prompt"] = QString("<fim_prefix>%1<fim_suffix>%2<fim_middle>")
|
||||
.arg(context.prefix.value_or(""), context.suffix.value_or(""));
|
||||
request["system"] = context.systemPrompt.value_or("");
|
||||
QString formattedPrompt = promptTemplate().arg(context.prefix, context.suffix);
|
||||
request["prompt"] = formattedPrompt;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for StarCoder2 with FIM format:\n\n"
|
||||
"{\n"
|
||||
" \"prompt\": \"<fim_prefix><code prefix><fim_suffix><code suffix><fim_middle>\",\n"
|
||||
" \"system\": \"<system prompt>\"\n"
|
||||
"}\n\n"
|
||||
"Includes stop words to prevent token duplication.";
|
||||
}
|
||||
bool isSupportProvider(LLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::LLMCore::ProviderID::Ollama:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return "The message will contain the following tokens: "
|
||||
"<fim_prefix>%1<fim_suffix>%2<fim_middle>";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -21,19 +21,17 @@
|
||||
|
||||
#include "llmcore/PromptTemplateManager.hpp"
|
||||
#include "templates/Alpaca.hpp"
|
||||
#include "templates/BasicChat.hpp"
|
||||
#include "templates/ChatML.hpp"
|
||||
#include "templates/Claude.hpp"
|
||||
#include "templates/CodeLlamaFim.hpp"
|
||||
#include "templates/CodeLlamaQMLFim.hpp"
|
||||
#include "templates/MistralAI.hpp"
|
||||
#include "templates/Ollama.hpp"
|
||||
#include "templates/OpenAI.hpp"
|
||||
#include "templates/OpenAICompatible.hpp"
|
||||
// #include "templates/CustomFimTemplate.hpp"
|
||||
// #include "templates/DeepSeekCoderFim.hpp"
|
||||
#include "templates/GoogleAI.hpp"
|
||||
#include "templates/CustomFimTemplate.hpp"
|
||||
#include "templates/DeepSeekCoderFim.hpp"
|
||||
#include "templates/Llama2.hpp"
|
||||
#include "templates/Llama3.hpp"
|
||||
#include "templates/Ollama.hpp"
|
||||
#include "templates/OpenAI.hpp"
|
||||
#include "templates/Qwen.hpp"
|
||||
#include "templates/StarCoder2Fim.hpp"
|
||||
|
||||
@@ -42,24 +40,21 @@ namespace QodeAssist::Templates {
|
||||
inline void registerTemplates()
|
||||
{
|
||||
auto &templateManager = LLMCore::PromptTemplateManager::instance();
|
||||
templateManager.registerTemplate<OllamaChat>();
|
||||
templateManager.registerTemplate<OllamaFim>();
|
||||
templateManager.registerTemplate<CodeLlamaFim>();
|
||||
templateManager.registerTemplate<StarCoder2Fim>();
|
||||
templateManager.registerTemplate<DeepSeekCoderFim>();
|
||||
templateManager.registerTemplate<CustomTemplate>();
|
||||
templateManager.registerTemplate<QwenFim>();
|
||||
templateManager.registerTemplate<OllamaAutoFim>();
|
||||
templateManager.registerTemplate<OllamaAutoChat>();
|
||||
templateManager.registerTemplate<BasicChat>();
|
||||
templateManager.registerTemplate<Llama3>();
|
||||
templateManager.registerTemplate<ChatML>();
|
||||
templateManager.registerTemplate<Alpaca>();
|
||||
templateManager.registerTemplate<Llama2>();
|
||||
templateManager.registerTemplate<Claude>();
|
||||
templateManager.registerTemplate<OpenAI>();
|
||||
templateManager.registerTemplate<MistralAIFim>();
|
||||
templateManager.registerTemplate<MistralAIChat>();
|
||||
templateManager.registerTemplate<CodeLlamaQMLFim>();
|
||||
templateManager.registerTemplate<ChatML>();
|
||||
templateManager.registerTemplate<Llama2>();
|
||||
templateManager.registerTemplate<Llama3>();
|
||||
templateManager.registerTemplate<StarCoder2Fim>();
|
||||
// templateManager.registerTemplate<DeepSeekCoderFim>();
|
||||
// templateManager.registerTemplate<CustomTemplate>();
|
||||
templateManager.registerTemplate<QwenFim>();
|
||||
templateManager.registerTemplate<OpenAICompatible>();
|
||||
templateManager.registerTemplate<Alpaca>();
|
||||
templateManager.registerTemplate<GoogleAI>();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
|
||||
Reference in New Issue
Block a user