mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2025-10-03 18:46:22 -04:00
Refactor llm providers to use internal http client (#227)
* refactor: Move http client into provider * refactor: Rework ollama provider for work with internal http client * refactor: Rework LM Studio provider to work with internal http client * refactor: Rework Mistral AI to work with internal http client * fix: Replace url and header to QNetworkRequest * refactor: Rework Google provider to use internal http client * refactor: OpenAI compatible providers switch to use internal http client * fix: Remove m_requestHandler from tests * refactor: Remove old handleData method * fix: Remove LLMClientInterfaceTest
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
add_library(LLMCore STATIC
|
||||
RequestType.hpp
|
||||
Provider.hpp
|
||||
Provider.hpp Provider.cpp
|
||||
ProvidersManager.hpp ProvidersManager.cpp
|
||||
ContextData.hpp
|
||||
IPromptProvider.hpp
|
||||
@ -10,8 +10,6 @@ add_library(LLMCore STATIC
|
||||
PromptTemplate.hpp
|
||||
PromptTemplateManager.hpp PromptTemplateManager.cpp
|
||||
RequestConfig.hpp
|
||||
RequestHandlerBase.hpp RequestHandlerBase.cpp
|
||||
RequestHandler.hpp RequestHandler.cpp
|
||||
OllamaMessage.hpp OllamaMessage.cpp
|
||||
OpenAIMessage.hpp OpenAIMessage.cpp
|
||||
ValidationUtils.hpp ValidationUtils.cpp
|
||||
|
@ -46,24 +46,11 @@ HttpClient::~HttpClient()
|
||||
|
||||
void HttpClient::onSendRequest(const HttpRequest &request)
|
||||
{
|
||||
QNetworkRequest networkRequest(request.url);
|
||||
networkRequest.setTransferTimeout(300000);
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
networkRequest.setRawHeader("Accept", "text/event-stream");
|
||||
networkRequest.setRawHeader("Cache-Control", "no-cache");
|
||||
networkRequest.setRawHeader("Connection", "keep-alive");
|
||||
|
||||
if (request.headers.has_value()) {
|
||||
for (const auto &[headername, value] : request.headers->asKeyValueRange()) {
|
||||
networkRequest.setRawHeader(headername.toUtf8(), value.toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
QJsonDocument doc(request.payload);
|
||||
LOG_MESSAGE(QString("HttpClient: Sending POST to %1").arg(request.url.toString()));
|
||||
LOG_MESSAGE(QString("HttpClient: data: %1").arg(doc.toJson(QJsonDocument::Indented)));
|
||||
|
||||
QNetworkReply *reply = m_manager->post(networkRequest, doc.toJson(QJsonDocument::Compact));
|
||||
QNetworkReply *reply
|
||||
= m_manager->post(request.networkRequest, doc.toJson(QJsonDocument::Compact));
|
||||
addActiveRequest(reply, request.requestId);
|
||||
|
||||
connect(reply, &QNetworkReply::readyRead, this, &HttpClient::onReadyRead);
|
||||
|
@ -32,10 +32,9 @@ namespace QodeAssist::LLMCore {
|
||||
|
||||
struct HttpRequest
|
||||
{
|
||||
QUrl url;
|
||||
QNetworkRequest networkRequest;
|
||||
QString requestId;
|
||||
QJsonObject payload;
|
||||
std::optional<QMap<QString, QString>> headers;
|
||||
};
|
||||
|
||||
class HttpClient : public QObject
|
||||
|
18
llmcore/Provider.cpp
Normal file
18
llmcore/Provider.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
#include "Provider.hpp"
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
Provider::Provider(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_httpClient(std::make_unique<HttpClient>())
|
||||
{
|
||||
connect(m_httpClient.get(), &HttpClient::dataReceived, this, &Provider::onDataReceived);
|
||||
connect(m_httpClient.get(), &HttpClient::requestFinished, this, &Provider::onRequestFinished);
|
||||
}
|
||||
|
||||
HttpClient *Provider::httpClient() const
|
||||
{
|
||||
return m_httpClient.get();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
@ -21,9 +21,11 @@
|
||||
|
||||
#include <utils/environment.h>
|
||||
#include <QNetworkRequest>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "ContextData.hpp"
|
||||
#include "HttpClient.hpp"
|
||||
#include "PromptTemplate.hpp"
|
||||
#include "RequestType.hpp"
|
||||
|
||||
@ -32,9 +34,12 @@ class QJsonObject;
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
class Provider
|
||||
class Provider : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Provider(QObject *parent = nullptr);
|
||||
|
||||
virtual ~Provider() = default;
|
||||
|
||||
virtual QString name() const = 0;
|
||||
@ -48,12 +53,28 @@ public:
|
||||
LLMCore::ContextData context,
|
||||
LLMCore::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;
|
||||
|
||||
virtual void sendRequest(const QString &requestId, const QUrl &url, const QJsonObject &payload)
|
||||
= 0;
|
||||
|
||||
HttpClient *httpClient() const;
|
||||
|
||||
public slots:
|
||||
virtual void onDataReceived(const QString &requestId, const QByteArray &data) = 0;
|
||||
virtual void onRequestFinished(const QString &requestId, bool success, const QString &error) = 0;
|
||||
|
||||
signals:
|
||||
void partialResponseReceived(const QString &requestId, const QString &partialText);
|
||||
void fullResponseReceived(const QString &requestId, const QString &fullText);
|
||||
void requestFailed(const QString &requestId, const QString &error);
|
||||
|
||||
private:
|
||||
std::unique_ptr<HttpClient> m_httpClient;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
||||
|
@ -1,202 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024-2025 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 "RequestHandler.hpp"
|
||||
#include "Logger.hpp"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkReply>
|
||||
#include <QThread>
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
RequestHandler::RequestHandler(QObject *parent)
|
||||
: RequestHandlerBase(parent)
|
||||
, m_manager(new QNetworkAccessManager(this))
|
||||
{
|
||||
connect(
|
||||
this,
|
||||
&RequestHandler::doSendRequest,
|
||||
this,
|
||||
&RequestHandler::sendLLMRequestInternal,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
this,
|
||||
&RequestHandler::doCancelRequest,
|
||||
this,
|
||||
&RequestHandler::cancelRequestInternal,
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
RequestHandler::~RequestHandler()
|
||||
{
|
||||
for (auto reply : m_activeRequests) {
|
||||
reply->abort();
|
||||
reply->deleteLater();
|
||||
}
|
||||
m_activeRequests.clear();
|
||||
m_accumulatedResponses.clear();
|
||||
}
|
||||
|
||||
void RequestHandler::sendLLMRequest(const LLMConfig &config, const QJsonObject &request)
|
||||
{
|
||||
emit doSendRequest(config, request);
|
||||
}
|
||||
|
||||
bool RequestHandler::cancelRequest(const QString &id)
|
||||
{
|
||||
emit doCancelRequest(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RequestHandler::sendLLMRequestInternal(const LLMConfig &config, const QJsonObject &request)
|
||||
{
|
||||
LOG_MESSAGE(QString("Sending request to llm: \nurl: %1\nRequest body:\n%2")
|
||||
.arg(
|
||||
config.url.toString(),
|
||||
QString::fromUtf8(
|
||||
QJsonDocument(config.providerRequest).toJson(QJsonDocument::Indented))));
|
||||
|
||||
QNetworkRequest networkRequest(config.url);
|
||||
networkRequest.setTransferTimeout(300000);
|
||||
|
||||
config.provider->prepareNetworkRequest(networkRequest);
|
||||
|
||||
QNetworkReply *reply
|
||||
= m_manager->post(networkRequest, QJsonDocument(config.providerRequest).toJson());
|
||||
if (!reply) {
|
||||
LOG_MESSAGE("Error: Failed to create network reply");
|
||||
return;
|
||||
}
|
||||
|
||||
QString requestId = request["id"].toString();
|
||||
m_activeRequests[requestId] = reply;
|
||||
|
||||
connect(reply, &QNetworkReply::readyRead, this, [this, reply, request, config]() {
|
||||
handleLLMResponse(reply, request, config);
|
||||
});
|
||||
|
||||
connect(
|
||||
reply,
|
||||
&QNetworkReply::finished,
|
||||
this,
|
||||
[this, reply, requestId]() {
|
||||
m_activeRequests.remove(requestId);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QString errorMessage = reply->errorString();
|
||||
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
LOG_MESSAGE(
|
||||
QString("Error details: %1\nStatus code: %2").arg(errorMessage).arg(statusCode));
|
||||
|
||||
emit requestFinished(requestId, false, errorMessage);
|
||||
} else {
|
||||
LOG_MESSAGE("Request finished successfully");
|
||||
emit requestFinished(requestId, true, QString());
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void RequestHandler::handleLLMResponse(
|
||||
QNetworkReply *reply, const QJsonObject &request, const LLMConfig &config)
|
||||
{
|
||||
QString &accumulatedResponse = m_accumulatedResponses[reply];
|
||||
|
||||
bool isComplete = config.provider->handleResponse(reply, accumulatedResponse);
|
||||
|
||||
if (config.requestType == RequestType::CodeCompletion) {
|
||||
if (!config.multiLineCompletion
|
||||
&& processSingleLineCompletion(reply, request, accumulatedResponse, config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isComplete) {
|
||||
auto cleanedCompletion
|
||||
= removeStopWords(accumulatedResponse, config.promptTemplate->stopWords());
|
||||
|
||||
emit completionReceived(cleanedCompletion, request, true);
|
||||
}
|
||||
} else if (config.requestType == RequestType::Chat) {
|
||||
emit completionReceived(accumulatedResponse, request, isComplete);
|
||||
}
|
||||
|
||||
if (isComplete)
|
||||
m_accumulatedResponses.remove(reply);
|
||||
}
|
||||
|
||||
void RequestHandler::cancelRequestInternal(const QString &id)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_activeRequests.contains(id)) {
|
||||
QNetworkReply *reply = m_activeRequests[id];
|
||||
|
||||
disconnect(reply, nullptr, this, nullptr);
|
||||
|
||||
reply->abort();
|
||||
m_activeRequests.remove(id);
|
||||
m_accumulatedResponses.remove(reply);
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
locker.unlock();
|
||||
|
||||
m_manager->clearConnectionCache();
|
||||
m_manager->clearAccessCache();
|
||||
|
||||
emit requestCancelled(id);
|
||||
}
|
||||
}
|
||||
|
||||
bool RequestHandler::processSingleLineCompletion(
|
||||
QNetworkReply *reply,
|
||||
const QJsonObject &request,
|
||||
const QString &accumulatedResponse,
|
||||
const LLMConfig &config)
|
||||
{
|
||||
QString cleanedResponse = accumulatedResponse;
|
||||
|
||||
int newlinePos = cleanedResponse.indexOf('\n');
|
||||
if (newlinePos != -1) {
|
||||
QString singleLineCompletion = cleanedResponse.left(newlinePos).trimmed();
|
||||
singleLineCompletion
|
||||
= removeStopWords(singleLineCompletion, config.promptTemplate->stopWords());
|
||||
emit completionReceived(singleLineCompletion, request, true);
|
||||
m_accumulatedResponses.remove(reply);
|
||||
reply->abort();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString RequestHandler::removeStopWords(const QStringView &completion, const QStringList &stopWords)
|
||||
{
|
||||
QString filteredCompletion = completion.toString();
|
||||
|
||||
for (const QString &stopWord : stopWords) {
|
||||
filteredCompletion = filteredCompletion.replace(stopWord, "");
|
||||
}
|
||||
|
||||
return filteredCompletion;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024-2025 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 <QJsonObject>
|
||||
#include <QMutex>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
|
||||
#include "RequestConfig.hpp"
|
||||
#include "RequestHandlerBase.hpp"
|
||||
|
||||
class QNetworkReply;
|
||||
|
||||
namespace QodeAssist::LLMCore {
|
||||
|
||||
class RequestHandler : public RequestHandlerBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RequestHandler(QObject *parent = nullptr);
|
||||
~RequestHandler() override;
|
||||
|
||||
void sendLLMRequest(const LLMConfig &config, const QJsonObject &request) override;
|
||||
bool cancelRequest(const QString &id) override;
|
||||
|
||||
signals:
|
||||
void doSendRequest(QodeAssist::LLMCore::LLMConfig config, QJsonObject request);
|
||||
void doCancelRequest(QString id);
|
||||
|
||||
private slots:
|
||||
void sendLLMRequestInternal(
|
||||
const QodeAssist::LLMCore::LLMConfig &config, const QJsonObject &request);
|
||||
void cancelRequestInternal(const QString &id);
|
||||
void handleLLMResponse(
|
||||
QNetworkReply *reply,
|
||||
const QJsonObject &request,
|
||||
const QodeAssist::LLMCore::LLMConfig &config);
|
||||
|
||||
private:
|
||||
QMap<QString, QNetworkReply *> m_activeRequests;
|
||||
QMap<QNetworkReply *, QString> m_accumulatedResponses;
|
||||
QNetworkAccessManager *m_manager;
|
||||
QMutex m_mutex;
|
||||
|
||||
bool processSingleLineCompletion(
|
||||
QNetworkReply *reply,
|
||||
const QJsonObject &request,
|
||||
const QString &accumulatedResponse,
|
||||
const LLMConfig &config);
|
||||
QString removeStopWords(const QStringView &completion, const QStringList &stopWords);
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::LLMCore
|
Reference in New Issue
Block a user