diff --git a/gpt4all-backend-test/src/main.cpp b/gpt4all-backend-test/src/main.cpp index a5098dc9..130d3b4a 100644 --- a/gpt4all-backend-test/src/main.cpp +++ b/gpt4all-backend-test/src/main.cpp @@ -1,6 +1,9 @@ #include "config.h" +#include "pretty.h" + #include // IWYU pragma: keep +#include #include #include // IWYU pragma: keep #include @@ -14,35 +17,43 @@ #include #include +namespace json = boost::json; +using namespace Qt::Literals::StringLiterals; using gpt4all::backend::OllamaClient; +template +static std::string to_json(const T &value) +{ return pretty_print(json::value_from(value)); } + static void run() { fmt::print("Connecting to server at {}\n", OLLAMA_URL); OllamaClient provider(OLLAMA_URL); - auto versionResp = QCoro::waitFor(provider.getVersion()); + auto versionResp = QCoro::waitFor(provider.version()); if (versionResp) { - fmt::print("Server version: {}\n", versionResp->version); + fmt::print("Version response: {}\n", to_json(*versionResp)); } else { fmt::print("Error retrieving version: {}\n", versionResp.error().errorString); return QCoreApplication::exit(1); } - auto modelsResponse = QCoro::waitFor(provider.listModels()); + auto modelsResponse = QCoro::waitFor(provider.list()); if (modelsResponse) { fmt::print("Available models:\n"); for (const auto & model : modelsResponse->models) fmt::print("{}\n", model.model); + if (!modelsResponse->models.empty()) + fmt::print("First model: {}\n", to_json(modelsResponse->models.front())); } else { fmt::print("Error retrieving available models: {}\n", modelsResponse.error().errorString); return QCoreApplication::exit(1); } - auto showResponse = QCoro::waitFor(provider.showModelInfo({ .model = "DeepSeek-R1-Distill-Llama-70B-Q4_K_S" })); + auto showResponse = QCoro::waitFor(provider.show({ .model = "DeepSeek-R1-Distill-Llama-70B-Q4_K_S" })); if (showResponse) { - fmt::print("Model family: {}\n", showResponse->details.family); + fmt::print("Show response: {}\n", to_json(*showResponse)); } else { fmt::print("Error retrieving model info: {}\n", showResponse.error().errorString); return QCoreApplication::exit(1); @@ -51,7 +62,6 @@ static void run() QCoreApplication::exit(0); } - int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); diff --git a/gpt4all-backend-test/src/pretty.h b/gpt4all-backend-test/src/pretty.h new file mode 100644 index 00000000..cd817caf --- /dev/null +++ b/gpt4all-backend-test/src/pretty.h @@ -0,0 +1,95 @@ +#pragma once + +#include + +#include +#include + + +inline void pretty_print( std::ostream& os, boost::json::value const& jv, std::string* indent = nullptr ) +{ + std::string indent_; + if(! indent) + indent = &indent_; + switch(jv.kind()) + { + case boost::json::kind::object: + { + os << "{\n"; + indent->append(4, ' '); + auto const& obj = jv.get_object(); + if(! obj.empty()) + { + auto it = obj.begin(); + for(;;) + { + os << *indent << boost::json::serialize(it->key()) << ": "; + pretty_print(os, it->value(), indent); + if(++it == obj.end()) + break; + os << ",\n"; + } + } + os << "\n"; + indent->resize(indent->size() - 4); + os << *indent << "}"; + break; + } + + case boost::json::kind::array: + { + os << "[\n"; + indent->append(4, ' '); + auto const& arr = jv.get_array(); + if(! arr.empty()) + { + auto it = arr.begin(); + for(;;) + { + os << *indent; + pretty_print( os, *it, indent); + if(++it == arr.end()) + break; + os << ",\n"; + } + } + os << "\n"; + indent->resize(indent->size() - 4); + os << *indent << "]"; + break; + } + + case boost::json::kind::string: + { + os << boost::json::serialize(jv.get_string()); + break; + } + + case boost::json::kind::uint64: + case boost::json::kind::int64: + case boost::json::kind::double_: + os << jv; + break; + + case boost::json::kind::bool_: + if(jv.get_bool()) + os << "true"; + else + os << "false"; + break; + + case boost::json::kind::null: + os << "null"; + break; + } + + if(indent->empty()) + os << "\n"; +} + +inline std::string pretty_print( boost::json::value const& jv, std::string* indent = nullptr ) +{ + std::ostringstream ss; + pretty_print(ss, jv, indent); + return ss.str(); +} diff --git a/gpt4all-backend/CMakeLists.txt b/gpt4all-backend/CMakeLists.txt index d59d3116..eba241b4 100644 --- a/gpt4all-backend/CMakeLists.txt +++ b/gpt4all-backend/CMakeLists.txt @@ -13,6 +13,7 @@ add_subdirectory(src) target_sources(gpt4all-backend PUBLIC FILE_SET public_headers TYPE HEADERS BASE_DIRS include FILES include/gpt4all-backend/formatters.h + include/gpt4all-backend/json-helpers.h include/gpt4all-backend/ollama-client.h include/gpt4all-backend/ollama-types.h ) diff --git a/gpt4all-backend/src/json-helpers.h b/gpt4all-backend/include/gpt4all-backend/json-helpers.h similarity index 100% rename from gpt4all-backend/src/json-helpers.h rename to gpt4all-backend/include/gpt4all-backend/json-helpers.h diff --git a/gpt4all-backend/include/gpt4all-backend/ollama-client.h b/gpt4all-backend/include/gpt4all-backend/ollama-client.h index f288d35e..da5e448c 100644 --- a/gpt4all-backend/include/gpt4all-backend/ollama-client.h +++ b/gpt4all-backend/include/gpt4all-backend/ollama-client.h @@ -3,6 +3,8 @@ #include "ollama-types.h" #include // IWYU pragma: keep +#include // IWYU pragma: keep +#include #include #include @@ -15,14 +17,20 @@ #include class QNetworkRequest; -namespace boost::json { class value; } namespace gpt4all::backend { struct ResponseError { - QNetworkReply::NetworkError error; - QString errorString; +private: + using ErrorCode = std::variant< + QNetworkReply::NetworkError, + boost::system::error_code + >; + +public: + ErrorCode error; + QString errorString; ResponseError(const QNetworkReply *reply) : error(reply->error()) @@ -30,6 +38,13 @@ struct ResponseError { { assert(reply->error()); } + + ResponseError(const boost::system::system_error &e) + : error(e.code()) + , errorString(QString::fromUtf8(e.what())) + { + assert(e.code()); + } }; template @@ -45,40 +60,44 @@ public: const QUrl &baseUrl() const { return m_baseUrl; } void getBaseUrl(QUrl value) { m_baseUrl = std::move(value); } - /// Returns the version of the Ollama server. - auto getVersion() -> QCoro::Task> + /// Returns the Ollama server version as a string. + auto version() -> QCoro::Task> { return get(QStringLiteral("version")); } - /// List models that are available locally. - auto listModels() -> QCoro::Task> - { return get(QStringLiteral("tags")); } + /// Lists models that are available locally. + auto list() -> QCoro::Task> + { return get(QStringLiteral("tags")); } - /// Show details about a model including modelfile, template, parameters, license, and system prompt. - auto showModelInfo(const ollama::ModelInfoRequest &req) -> QCoro::Task> - { return post(QStringLiteral("show"), req); } + /// Obtains model information, including details, modelfile, license etc. + auto show(const ollama::ShowRequest &req) -> QCoro::Task> + { return post(QStringLiteral("show"), req); } private: QNetworkRequest makeRequest(const QString &path) const; + auto processResponse(QNetworkReply &reply) -> QCoro::Task>; + template auto get(const QString &path) -> QCoro::Task>; template - auto post(const QString &path, Req const &req) -> QCoro::Task>; + auto post(const QString &path, Req const &body) -> QCoro::Task>; auto getJson(const QString &path) -> QCoro::Task>; - auto postJson(const QString &path, const boost::json::value &req) + auto postJson(const QString &path, const boost::json::value &body) -> QCoro::Task>; private: - QUrl m_baseUrl; - QString m_userAgent; - QNetworkAccessManager m_nam; + QUrl m_baseUrl; + QString m_userAgent; + QNetworkAccessManager m_nam; + boost::json::stream_parser m_parser; }; extern template auto OllamaClient::get(const QString &) -> QCoro::Task>; -extern template auto OllamaClient::get(const QString &) -> QCoro::Task>; +extern template auto OllamaClient::get(const QString &) -> QCoro::Task>; + +extern template auto OllamaClient::post(const QString &, const ollama::ShowRequest &) + -> QCoro::Task>; -extern template auto OllamaClient::post(const QString &, const ollama::ModelInfoRequest &) - -> QCoro::Task>; } // namespace gpt4all::backend diff --git a/gpt4all-backend/include/gpt4all-backend/ollama-types.h b/gpt4all-backend/include/gpt4all-backend/ollama-types.h index 622a3307..4cc3c2e9 100644 --- a/gpt4all-backend/include/gpt4all-backend/ollama-types.h +++ b/gpt4all-backend/include/gpt4all-backend/ollama-types.h @@ -1,131 +1,128 @@ #pragma once -#ifdef G4A_BACKEND_IMPL -# include -# include -#endif +#include "json-helpers.h" // IWYU pragma: keep + +#include #include // IWYU pragma: keep +#include #include #include +#include #include #include namespace gpt4all::backend::ollama { + // // basic types // -/// Details about a model. +struct Time : std::chrono::sys_time {}; + +void tag_invoke(const boost::json::value_from_tag &, boost::json::value &value, Time time); +Time tag_invoke(const boost::json::value_to_tag