Files
TTW_Bot/neuralnetworkmanager.cpp
T

659 lines
22 KiB
C++

// neuralnetworkmanager.cpp
#include "neuralnetworkmanager.h"
#include <QNetworkRequest>
#include <QJsonDocument>
#include <QUrl>
#include <QDebug>
#include <QDateTime>
#include <QTimer>
#include <QSslCipher>
#include <QSslSocket>
#include <QFile>
#include <QElapsedTimer>
NeuralNetworkManager::NeuralNetworkManager(QObject *parent)
: QObject(parent)
, networkManager(new QNetworkAccessManager(this))
{
// Инициализируем SSL конфигурацию
setupSSL();
// Инициализируем конфигурации сетей
setupDefaultConfigs();
// Инициализируем GigaChat токен как невалидный
gigaChatAuth.tokenExpiry = 0;
// Подключаем обработчик сетевых ответов
connect(networkManager, &QNetworkAccessManager::finished,
this, &NeuralNetworkManager::handleNetworkReply);
}
NeuralNetworkManager::~NeuralNetworkManager()
{
// Очищаем очередь сообщений
pendingMessages.clear();
}
void NeuralNetworkManager::setupSSL()
{
// Настройка стандартной SSL конфигурации
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
// Устанавливаем протокол TLS 1.2 или новее
sslConfig.setProtocol(QSsl::TlsV1_2OrLater);
sslConfig.setPeerVerifyMode(QSslSocket::VerifyPeer);
// Загружаем российские корневые сертификаты если доступны
QStringList certPaths = {
"/etc/ssl/certs/russian_trusted_root_ca.pem",
":/certs/russian_ca.pem",
"./certs/russian_ca.pem"
};
QList<QSslCertificate> russianCerts;
for (const QString &path : certPaths) {
if (QFile::exists(path)) {
QFile certFile(path);
if (certFile.open(QIODevice::ReadOnly)) {
QByteArray certData = certFile.readAll();
russianCerts += QSslCertificate::fromData(certData);
certFile.close();
}
}
}
if (!russianCerts.isEmpty()) {
QList<QSslCertificate> defaultCerts = sslConfig.caCertificates();
defaultCerts += russianCerts;
sslConfig.setCaCertificates(defaultCerts);
}
// Устанавливаем как конфигурацию по умолчанию
QSslConfiguration::setDefaultConfiguration(sslConfig);
}
void NeuralNetworkManager::setupSSLForGigaChat()
{
QSslConfiguration config = QSslConfiguration::defaultConfiguration();
// Критически важные настройки для GigaChat
config.setProtocol(QSsl::TlsV1_2OrLater);
config.setPeerVerifyMode(QSslSocket::VerifyPeer);
// Дополнительные настройки для совместимости
config.setSslOption(QSsl::SslOptionDisableSessionTickets, false);
config.setSslOption(QSsl::SslOptionDisableCompression, false);
config.setSslOption(QSsl::SslOptionDisableServerNameIndication, false);
config.setPeerVerifyDepth(2);
QSslConfiguration::setDefaultConfiguration(config);
}
void NeuralNetworkManager::setupDefaultConfigs()
{
// DeepSeek
NetworkConfig deepseekConfig;
deepseekConfig.baseUrl = "https://api.deepseek.com";
deepseekConfig.endpoint = "/chat/completions";
deepseekConfig.model = "deepseek-chat";
networkConfigs[DeepSeek] = deepseekConfig;
// GigaChat
NetworkConfig gigachatConfig;
gigachatConfig.baseUrl = "https://gigachat.devices.sberbank.ru";
gigachatConfig.endpoint = "/api/v1/chat/completions";
gigachatConfig.model = "GigaChat";
networkConfigs[GigaChat] = gigachatConfig;
// ChatGPT
NetworkConfig chatgptConfig;
chatgptConfig.baseUrl = "https://api.openai.com";
chatgptConfig.endpoint = "/v1/chat/completions";
chatgptConfig.model = "gpt-3.5-turbo";
networkConfigs[ChatGPT] = chatgptConfig;
// Ollama
NetworkConfig ollamaConfig;
ollamaConfig.baseUrl = "http://localhost:11434";
ollamaConfig.endpoint = "/api/chat";
ollamaConfig.model = "zephyr:7b";
networkConfigs[Ollama] = ollamaConfig;
}
void NeuralNetworkManager::setCurrentNetworkType(NetworkType type)
{
QMutexLocker locker(&mutex);
m_currentNetworkType = type;
}
void NeuralNetworkManager::sendMessage(const QString &message)
{
// Используем текущий тип сети
sendMessage(message, m_currentNetworkType);
}
void NeuralNetworkManager::sendMessage(const QString &message, NetworkType networkType)
{
QMutexLocker locker(&mutex);
if (!networkConfigs.contains(networkType)) {
emit errorOccurred("Неизвестный тип нейросети");
return;
}
// Для GigaChat проверяем токен
if (networkType == GigaChat) {
qint64 currentTime = QDateTime::currentSecsSinceEpoch();
if (!isGigaChatTokenValid()) {
// Добавляем сообщение в очередь
PendingMessage pendingMsg;
pendingMsg.message = message;
pendingMsg.networkType = networkType;
pendingMessages.append(pendingMsg);
// Запрашиваем новый токен если еще не запрашиваем
if (!isProcessingGigaChatToken) {
requestGigaChatToken();
}
return;
}
}
// Если токен валиден, отправляем сообщение сразу
sendMessageInternal(message, networkType);
}
void NeuralNetworkManager::sendMessageInternal(const QString &message, NetworkType networkType)
{
QElapsedTimer timer;
timer.start();
QString fullMessage = currentPrefix.isEmpty() ? message : currentPrefix + " " + message;
QNetworkRequest request = prepareRequest(networkType);
QByteArray requestData = prepareRequestBody(networkType, fullMessage);
// Устанавливаем таймаут
// request.setTransferTimeout(30000);
QNetworkReply *reply = networkManager->post(request, requestData);
reply->setProperty("networkType", networkType);
// Для GigaChat обрабатываем SSL ошибки
if (networkType == GigaChat) {
connect(reply, &QNetworkReply::sslErrors,
this, [reply](const QList<QSslError> &errors) {
for (const QSslError &error : errors) {
}
reply->ignoreSslErrors();
});
}
}
void NeuralNetworkManager::setPrefix(const QString &prefix)
{
QMutexLocker locker(&mutex);
currentPrefix = prefix;
}
void NeuralNetworkManager::setApiKey(NetworkType networkType, const QString &apiKey)
{
QMutexLocker locker(&mutex);
if (networkConfigs.contains(networkType)) {
networkConfigs[networkType].apiKey = apiKey;
}
}
void NeuralNetworkManager::setModel(NetworkType networkType, const QString &model)
{
QMutexLocker locker(&mutex);
if (networkConfigs.contains(networkType)) {
networkConfigs[networkType].model = model;
}
}
void NeuralNetworkManager::setBaseUrl(NetworkType networkType, const QString &url)
{
QMutexLocker locker(&mutex);
if (networkConfigs.contains(networkType)) {
networkConfigs[networkType].baseUrl = url;
}
}
void NeuralNetworkManager::setOllamaUrl(const QString &url)
{
setBaseUrl(Ollama, url);
}
void NeuralNetworkManager::setGigaChatCredentials(const QString &clientId,
const QString &authorizationCode)
{
QMutexLocker locker(&mutex);
gigaChatAuth.clientId = clientId;
gigaChatAuth.authorizationCode = authorizationCode;
}
void NeuralNetworkManager::refreshGigaChatToken()
{
QMutexLocker locker(&mutex);
if (!gigaChatAuth.clientId.isEmpty() && !gigaChatAuth.authorizationCode.isEmpty()) {
requestGigaChatToken();
}
}
QNetworkRequest NeuralNetworkManager::prepareRequest(NetworkType type)
{
if (!networkConfigs.contains(type)) {
qWarning() << "Попытка подготовить запрос для неизвестного типа:" << type;
return QNetworkRequest();
}
NetworkConfig config = networkConfigs[type];
// Проверяем и корректируем URL
QString baseUrl = config.baseUrl;
QString endpoint = config.endpoint;
if (baseUrl.isEmpty()) {
qWarning() << "Base URL пуст для типа" << type;
return QNetworkRequest();
}
// Убедимся, что baseUrl имеет схему
if (!baseUrl.startsWith("http://") && !baseUrl.startsWith("https://")) {
baseUrl = "https://" + baseUrl;
}
QString urlStr = baseUrl + endpoint;
QUrl url(urlStr);
if (!url.isValid()) {
qWarning() << "Некорректный URL:" << urlStr;
return QNetworkRequest();
}
QNetworkRequest request(url);
// ОСОБАЯ обработка для GigaChat
if (type == GigaChat) {
// SSL конфигурация для GigaChat
setupSSLForGigaChat();
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
request.setSslConfiguration(sslConfig);
// Заголовки для GigaChat
request.setRawHeader("Accept-Encoding", "gzip, deflate, br");
request.setRawHeader("Accept", "application/json");
request.setRawHeader("Content-Type", "application/json");
if (!gigaChatAuth.accessToken.isEmpty()) {
QString authHeader = QString("Bearer %1").arg(gigaChatAuth.accessToken);
request.setRawHeader("Authorization", authHeader.toUtf8());
} else {
qWarning() << "Токен GigaChat отсутствует!";
}
} else {
// Общая настройка для других сервисов
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
sslConfig.setPeerVerifyMode(QSslSocket::VerifyPeer);
request.setSslConfiguration(sslConfig);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
if (type == DeepSeek || type == ChatGPT) {
if (!config.apiKey.isEmpty()) {
request.setRawHeader("Authorization",
QString("Bearer %1").arg(config.apiKey).toUtf8());
}
}
}
// Общие заголовки
request.setRawHeader("User-Agent", "TwitchBot/1.0 (Qt Network)");
request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
return request;
}
QByteArray NeuralNetworkManager::prepareRequestBody(NetworkType type, const QString &message)
{
QJsonObject requestBody;
QJsonArray messages;
QJsonObject messageObj;
messageObj["role"] = "user";
messageObj["content"] = message;
messages.append(messageObj);
if (type == Ollama) {
// Формат запроса для Ollama
requestBody["model"] = networkConfigs[type].model;
requestBody["messages"] = messages;
requestBody["stream"] = false;
QJsonObject options;
options["temperature"] = 0.7;
options["num_predict"] = 200;
requestBody["options"] = options;
} else {
// Формат запроса для остальных API (OpenAI-совместимый)
requestBody["model"] = networkConfigs[type].model;
requestBody["messages"] = messages;
requestBody["max_tokens"] = 300;
requestBody["temperature"] = 0.7;
requestBody["stream"] = false;
}
return QJsonDocument(requestBody).toJson();
}
void NeuralNetworkManager::handleNetworkReply(QNetworkReply *reply)
{
QElapsedTimer timer;
timer.start();
// Определяем тип запроса
bool isTokenRequest = reply->property("isTokenRequest").toBool();
QString url = reply->url().toString();
// Если это запрос токена GigaChat
if (isTokenRequest || url.contains("oauth")) {
handleGigaChatTokenReply(reply);
reply->deleteLater();
return;
}
// Обработка обычных ответов от нейросетей
NetworkType type = static_cast<NetworkType>(reply->property("networkType").toInt());
if (reply->error() != QNetworkReply::NoError) {
QString errorMsg = QString("Ошибка сети (%1): %2")
.arg(type)
.arg(reply->errorString());
// Для GigaChat проверяем, не истек ли токен
if (type == GigaChat &&
(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 401 ||
reply->error() == QNetworkReply::AuthenticationRequiredError)) {
gigaChatAuth.tokenExpiry = 0; // Помечаем токен как устаревший
// Запрашиваем новый токен
if (!isProcessingGigaChatToken) {
requestGigaChatToken();
}
} else {
emit errorOccurred(errorMsg);
}
reply->deleteLater();
return;
}
QByteArray data = reply->readAll();
QString response = parseResponse(type, data);
if (!response.isEmpty()) {
QString truncatedResponse = truncateResponse(response);
emit responseReceived(truncatedResponse);
} else {
emit errorOccurred("Не удалось получить ответ от нейросети");
}
reply->deleteLater();
}
void NeuralNetworkManager::handleGigaChatTokenReply(QNetworkReply *reply)
{
QMutexLocker locker(&mutex);
QByteArray data = reply->readAll();
isProcessingGigaChatToken = false;
if (reply->error() != QNetworkReply::NoError) {
QString errorMsg = QString("Ошибка получения токена GigaChat: %1").arg(reply->errorString());
gigaChatAuth.tokenExpiry = 0;
emit errorOccurred(errorMsg);
reply->deleteLater();
return;
}
QJsonDocument doc = QJsonDocument::fromJson(data);
if (doc.isNull()) {
emit errorOccurred("Неверный формат ответа от GigaChat");
reply->deleteLater();
return;
}
QJsonObject json = doc.object();
if (json.contains("access_token")) {
m_tokenRetryCount = 0;
gigaChatAuth.accessToken = json["access_token"].toString();
int expiresIn = json.contains("expires_in")
? json["expires_in"].toInt(1800)
: 1800;
// Устанавливаем время истечения (текущее время + expiresIn секунд)
qint64 currentTime = QDateTime::currentSecsSinceEpoch();
gigaChatAuth.tokenExpiry = currentTime + expiresIn - 300; // Вычитаем 5 минут для запаса
emit gigaChatTokenRefreshed();
// Обрабатываем сообщения из очереди
processPendingMessages();
} else if (json.contains("error")) {
QString errorMsg = json["error"].toString();
// Очищаем очередь сообщений
emit errorOccurred("Ошибка GigaChat: " + errorMsg);
} else {
emit errorOccurred("Неизвестный формат ответа от GigaChat");
}
reply->deleteLater();
}
bool NeuralNetworkManager::isGigaChatTokenValid() const
{
qint64 currentTime = QDateTime::currentSecsSinceEpoch();
// Проверяем есть ли токен и не истек ли он
bool isValid = !gigaChatAuth.accessToken.isEmpty() &&
gigaChatAuth.tokenExpiry > currentTime;
qDebug() << "Проверка токена GigaChat:"
<< "Токен:" << (!gigaChatAuth.accessToken.isEmpty() ? "есть" : "нет")
<< "Текущее время:" << currentTime
<< "Истекает:" << gigaChatAuth.tokenExpiry
<< "Валиден:" << (isValid ? "да" : "нет");
return isValid;
}
bool NeuralNetworkManager::shouldRefreshGigaChatToken() const
{
qint64 currentTime = QDateTime::currentSecsSinceEpoch();
// Проверяем, нужно ли обновить токен (осталось меньше 5 минут)
bool shouldRefresh = gigaChatAuth.tokenExpiry > 0 &&
(gigaChatAuth.tokenExpiry - currentTime) < 300;
qDebug() << "Нужно ли обновить токен:"
<< "Текущее время:" << currentTime
<< "Истекает:" << gigaChatAuth.tokenExpiry
<< "Осталось секунд:" << (gigaChatAuth.tokenExpiry - currentTime)
<< "Обновить:" << (shouldRefresh ? "да" : "нет");
return shouldRefresh;
}
void NeuralNetworkManager::requestGigaChatToken()
{
if (isProcessingGigaChatToken) {
return;
}
// Проверяем количество попыток
if (m_tokenRetryCount >= MAX_TOKEN_RETRIES) {
qDebug() << "Превышено максимальное количество попыток получения токена";
m_tokenRetryCount = 0;
pendingMessages.clear(); // Только после превышения лимита очищаем очередь
emit errorOccurred("Не удалось получить токен GigaChat после нескольких попыток");
return;
}
m_tokenRetryCount++;
isProcessingGigaChatToken = true;
if (gigaChatAuth.clientId.isEmpty() || gigaChatAuth.authorizationCode.isEmpty()) {
emit errorOccurred("Не установлены учетные данные GigaChat");
// Очищаем очередь сообщений
pendingMessages.clear();
return;
}
isProcessingGigaChatToken = true;
QString tokenUrl = "https://ngw.devices.sberbank.ru:9443/api/v2/oauth";
QNetworkRequest request((QUrl(tokenUrl)));
// Настройка SSL для GigaChat
setupSSLForGigaChat();
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
request.setSslConfiguration(sslConfig);
// Заголовки
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setRawHeader("Accept", "application/json");
request.setRawHeader("User-Agent",
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
request.setRawHeader("RqUID", gigaChatAuth.clientId.toUtf8());
request.setRawHeader("Authorization",
QString("Basic %1").arg(gigaChatAuth.authorizationCode).toUtf8());
// Тело запроса
QByteArray postData = "scope=" + gigaChatScope.toUtf8();
QNetworkReply *reply = networkManager->post(request, postData);
reply->setProperty("isTokenRequest", true);
// Обработка SSL ошибок
connect(reply, &QNetworkReply::sslErrors,
this, [reply](const QList<QSslError> &errors) {
reply->ignoreSslErrors();
});
}
void NeuralNetworkManager::processPendingMessages()
{
while (!pendingMessages.isEmpty()) {
PendingMessage pendingMsg = pendingMessages.takeFirst();
// Проверяем, что токен все еще валиден
if (isGigaChatTokenValid()) {
sendMessageInternal(pendingMsg.message, pendingMsg.networkType);
} else {
pendingMessages.prepend(pendingMsg); // Возвращаем сообщение в начало очереди
break;
}
}
}
QString NeuralNetworkManager::parseResponse(NetworkType type, const QByteArray &data)
{
QJsonDocument doc = QJsonDocument::fromJson(data);
if (doc.isNull()) {
return QString();
}
QJsonObject json = doc.object();
if (type == Ollama) {
// Парсинг ответа Ollama
if (json.contains("message")) {
QJsonObject message = json["message"].toObject();
if (message.contains("content")) {
return message["content"].toString().trimmed();
}
}
} else {
// Парсинг ответа OpenAI-совместимых API
if (json.contains("choices") && json["choices"].isArray()) {
QJsonArray choices = json["choices"].toArray();
if (!choices.isEmpty()) {
QJsonObject choice = choices[0].toObject();
if (choice.contains("message")) {
QJsonObject message = choice["message"].toObject();
if (message.contains("content")) {
return message["content"].toString().trimmed();
}
}
}
}
// Проверка на ошибки API
if (json.contains("error")) {
QJsonObject error = json["error"].toObject();
QString errorMsg = error.contains("message")
? error["message"].toString()
: "Неизвестная ошибка API";
return QString();
}
}
return QString();
}
QString NeuralNetworkManager::truncateResponse(const QString &response)
{
// Ограничиваем длину ответа 400 символами
if (response.length() > 400) {
QString truncated = response.left(400);
// Пытаемся обрезать по последнему предложению
int lastDot = truncated.lastIndexOf('.');
int lastExcl = truncated.lastIndexOf('!');
int lastQuest = truncated.lastIndexOf('?');
int lastBreak = qMax(lastDot, qMax(lastExcl, lastQuest));
if (lastBreak > 300) {
truncated = truncated.left(lastBreak + 1);
} else {
truncated += "...";
}
return truncated;
}
return response;
}