From ee5c58c26c8801a93a2a850399dd5e6b4bf8b1b6 Mon Sep 17 00:00:00 2001 From: Adam Treat Date: Wed, 26 Apr 2023 22:05:56 -0400 Subject: [PATCH] Initial support for opt-in telemetry. --- llm.cpp | 20 +++++- llm.h | 4 ++ llmodel/CMakeLists.txt | 2 +- network.cpp | 154 ++++++++++++++++++++++++++++++++++++++++- network.h | 14 ++++ 5 files changed, 190 insertions(+), 4 deletions(-) diff --git a/llm.cpp b/llm.cpp index 2e6c56b4..13fafedd 100644 --- a/llm.cpp +++ b/llm.cpp @@ -1,5 +1,6 @@ #include "llm.h" #include "download.h" +#include "network.h" #include #include @@ -43,6 +44,9 @@ LLMObject::LLMObject() { moveToThread(&m_llmThread); connect(&m_llmThread, &QThread::started, this, &LLMObject::loadModel); + connect(this, &LLMObject::sendStartup, Network::globalInstance(), &Network::sendStartup); + connect(this, &LLMObject::sendModelLoaded, Network::globalInstance(), &Network::sendModelLoaded); + connect(this, &LLMObject::sendResetContext, Network::globalInstance(), &Network::sendResetContext); m_llmThread.setObjectName("llm thread"); m_llmThread.start(); } @@ -65,11 +69,14 @@ bool LLMObject::loadModelPrivate(const QString &modelName) if (isModelLoaded() && m_modelName == modelName) return true; + bool isFirstLoad = false; if (isModelLoaded()) { - resetContext(); + resetContextPrivate(); delete m_llmodel; m_llmodel = nullptr; emit isModelLoadedChanged(); + } else { + isFirstLoad = true; } bool isGPTJ = false; @@ -93,6 +100,11 @@ bool LLMObject::loadModelPrivate(const QString &modelName) emit isModelLoadedChanged(); emit threadCountChanged(); + + if (isFirstLoad) + emit sendStartup(); + else + emit sendModelLoaded(); } if (m_llmodel) @@ -141,6 +153,12 @@ void LLMObject::resetResponse() } void LLMObject::resetContext() +{ + resetContextPrivate(); + emit sendResetContext(); +} + +void LLMObject::resetContextPrivate() { regenerateResponse(); s_ctx = LLModel::PromptContext(); diff --git a/llm.h b/llm.h index 73024b35..0bf8025e 100644 --- a/llm.h +++ b/llm.h @@ -51,8 +51,12 @@ Q_SIGNALS: void modelListChanged(); void threadCountChanged(); void recalcChanged(); + void sendStartup(); + void sendModelLoaded(); + void sendResetContext(); private: + void resetContextPrivate(); bool loadModelPrivate(const QString &modelName); bool handleResponse(int32_t token, const std::string &response); bool handleRecalculate(bool isRecalc); diff --git a/llmodel/CMakeLists.txt b/llmodel/CMakeLists.txt index 50bd5509..7f72599c 100644 --- a/llmodel/CMakeLists.txt +++ b/llmodel/CMakeLists.txt @@ -44,7 +44,7 @@ add_library(llmodel gptj.h gptj.cpp llamamodel.h llamamodel.cpp llama.cpp/examples/common.cpp - llmodel.h llmodel_c.h + llmodel.h llmodel_c.h llmodel_c.cpp utils.h utils.cpp ) diff --git a/network.cpp b/network.cpp index 130939ad..0c1c6ace 100644 --- a/network.cpp +++ b/network.cpp @@ -21,15 +21,22 @@ Network *Network::globalInstance() Network::Network() : QObject{nullptr} , m_isActive(false) + , m_isOptIn(false) + , m_shouldSendStartup(false) { QSettings settings; settings.sync(); + m_isOptIn = settings.value("track", false).toBool(); m_uniqueId = settings.value("uniqueId", generateUniqueId()).toString(); settings.setValue("uniqueId", m_uniqueId); settings.sync(); setActive(settings.value("network/isActive", false).toBool()); + if (m_isOptIn) + sendIpify(); connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this, &Network::handleSslErrors); + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, + &Network::sendShutdown); } void Network::setActive(bool b) @@ -77,9 +84,10 @@ bool Network::packageAndSendJson(const QString &ingestId, const QString &json) newDoc.setObject(object); #if defined(DEBUG) - printf("%s", qPrintable(newDoc.toJson(QJsonDocument::Indented))); + printf("%s\n", qPrintable(newDoc.toJson(QJsonDocument::Indented))); fflush(stdout); #endif + QUrl jsonUrl("https://api.gpt4all.io/v1/ingest/chat"); QNetworkRequest request(jsonUrl); QSslConfiguration conf = request.sslConfiguration(); @@ -121,7 +129,7 @@ void Network::handleJsonUploadFinished() } #if defined(DEBUG) - printf("%s", qPrintable(document.toJson(QJsonDocument::Indented))); + printf("%s\n", qPrintable(document.toJson(QJsonDocument::Indented))); fflush(stdout); #endif @@ -135,6 +143,148 @@ void Network::handleSslErrors(QNetworkReply *reply, const QList &erro qWarning() << "ERROR: Received ssl error:" << e.errorString() << "for" << url; } +void Network::sendModelLoaded() +{ + if (!m_isOptIn) + return; + sendMixpanelEvent("model_load"); +} + +void Network::sendResetContext() +{ + if (!m_isOptIn) + return; + sendMixpanelEvent("reset_context"); +} + +void Network::sendStartup() +{ + if (!m_isOptIn) + return; + m_shouldSendStartup = true; + if (m_ipify.isEmpty()) + return; // when it completes it will send + sendMixpanelEvent("startup"); +} + +void Network::sendShutdown() +{ + if (!m_isOptIn) + return; + sendMixpanelEvent("shutdown"); +} + +void Network::sendMixpanelEvent(const QString &ev) +{ + if (!m_isOptIn) + return; + + QJsonObject properties; + properties.insert("token", "ce362e568ddaee16ed243eaffb5860a2"); + properties.insert("time", QDateTime::currentSecsSinceEpoch()); + properties.insert("distinct_id", m_uniqueId); + properties.insert("$insert_id", generateUniqueId()); + properties.insert("$os", QSysInfo::prettyProductName()); + if (!m_ipify.isEmpty()) + properties.insert("ip", m_ipify); + properties.insert("name", QCoreApplication::applicationName() + " v" + + QCoreApplication::applicationVersion()); + properties.insert("model", LLM::globalInstance()->modelName()); + + QJsonObject event; + event.insert("event", ev); + event.insert("properties", properties); + + QJsonArray array; + array.append(event); + + QJsonDocument doc; + doc.setArray(array); + sendMixpanel(doc.toJson()); + +#if defined(DEBUG) + printf("%s %s\n", qPrintable(ev), qPrintable(doc.toJson(QJsonDocument::Indented))); + fflush(stdout); +#endif +} + +void Network::sendIpify() +{ + if (!m_isOptIn) + return; + + QUrl ipifyUrl("https://api.ipify.org"); + QNetworkRequest request(ipifyUrl); + QSslConfiguration conf = request.sslConfiguration(); + conf.setPeerVerifyMode(QSslSocket::VerifyNone); + request.setSslConfiguration(conf); + QNetworkReply *reply = m_networkManager.get(request); + connect(reply, &QNetworkReply::finished, this, &Network::handleIpifyFinished); +} + +void Network::sendMixpanel(const QByteArray &json) +{ + if (!m_isOptIn) + return; + + QUrl trackUrl("https://api.mixpanel.com/track"); + QNetworkRequest request(trackUrl); + QSslConfiguration conf = request.sslConfiguration(); + conf.setPeerVerifyMode(QSslSocket::VerifyNone); + request.setSslConfiguration(conf); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QNetworkReply *trackReply = m_networkManager.post(request, json); + connect(trackReply, &QNetworkReply::finished, this, &Network::handleMixpanelFinished); +} + +void Network::handleIpifyFinished() +{ + Q_ASSERT(m_isOptIn); + QNetworkReply *reply = qobject_cast(sender()); + if (!reply) + return; + + QVariant response = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + Q_ASSERT(response.isValid()); + bool ok; + int code = response.toInt(&ok); + if (!ok) + qWarning() << "ERROR: ipify invalid response."; + if (code != 200) + qWarning() << "ERROR: ipify response != 200 code:" << code; + m_ipify = qPrintable(reply->readAll()); +#if defined(DEBUG) + printf("ipify finished %s\n", m_ipify.toLatin1().constData()); + fflush(stdout); +#endif + reply->deleteLater(); + + if (m_shouldSendStartup) + sendStartup(); +} + +void Network::handleMixpanelFinished() +{ + Q_ASSERT(m_isOptIn); + QNetworkReply *reply = qobject_cast(sender()); + if (!reply) + return; + + QVariant response = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + Q_ASSERT(response.isValid()); + bool ok; + int code = response.toInt(&ok); + if (!ok) + qWarning() << "ERROR: track invalid response."; + if (code != 200) + qWarning() << "ERROR: track response != 200 code:" << code; +#if defined(DEBUG) + printf("mixpanel finished %s\n", qPrintable(reply->readAll())); + fflush(stdout); +#endif + reply->deleteLater(); +} + bool Network::sendConversation(const QString &ingestId, const QString &conversation) { return packageAndSendJson(ingestId, conversation); diff --git a/network.h b/network.h index 68cdb015..46a7f523 100644 --- a/network.h +++ b/network.h @@ -22,17 +22,31 @@ Q_SIGNALS: void activeChanged(); void healthCheckFailed(int code); +public Q_SLOTS: + void sendModelLoaded(); + void sendResetContext(); + void sendStartup(); + void sendShutdown(); + private Q_SLOTS: + void handleIpifyFinished(); void handleHealthFinished(); void handleJsonUploadFinished(); void handleSslErrors(QNetworkReply *reply, const QList &errors); + void handleMixpanelFinished(); private: void sendHealth(); + void sendIpify(); + void sendMixpanelEvent(const QString &event); + void sendMixpanel(const QByteArray &json); bool packageAndSendJson(const QString &ingestId, const QString &json); private: + bool m_isOptIn; + bool m_shouldSendStartup; bool m_isActive; + QString m_ipify; QString m_uniqueId; QNetworkAccessManager m_networkManager; QVector m_activeUploads;