mirror of
https://github.com/nomic-ai/gpt4all.git
synced 2025-06-27 15:58:25 +00:00
stuff is working now
This commit is contained in:
parent
068845e1a2
commit
d20cfbbec9
@ -1,6 +1,9 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "pretty.h"
|
||||||
|
|
||||||
#include <QCoro/QCoroTask> // IWYU pragma: keep
|
#include <QCoro/QCoroTask> // IWYU pragma: keep
|
||||||
|
#include <boost/json.hpp>
|
||||||
#include <fmt/base.h>
|
#include <fmt/base.h>
|
||||||
#include <gpt4all-backend/formatters.h> // IWYU pragma: keep
|
#include <gpt4all-backend/formatters.h> // IWYU pragma: keep
|
||||||
#include <gpt4all-backend/ollama-client.h>
|
#include <gpt4all-backend/ollama-client.h>
|
||||||
@ -14,35 +17,43 @@
|
|||||||
#include <expected>
|
#include <expected>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
|
namespace json = boost::json;
|
||||||
|
using namespace Qt::Literals::StringLiterals;
|
||||||
using gpt4all::backend::OllamaClient;
|
using gpt4all::backend::OllamaClient;
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static std::string to_json(const T &value)
|
||||||
|
{ return pretty_print(json::value_from(value)); }
|
||||||
|
|
||||||
static void run()
|
static void run()
|
||||||
{
|
{
|
||||||
fmt::print("Connecting to server at {}\n", OLLAMA_URL);
|
fmt::print("Connecting to server at {}\n", OLLAMA_URL);
|
||||||
OllamaClient provider(OLLAMA_URL);
|
OllamaClient provider(OLLAMA_URL);
|
||||||
|
|
||||||
auto versionResp = QCoro::waitFor(provider.getVersion());
|
auto versionResp = QCoro::waitFor(provider.version());
|
||||||
if (versionResp) {
|
if (versionResp) {
|
||||||
fmt::print("Server version: {}\n", versionResp->version);
|
fmt::print("Version response: {}\n", to_json(*versionResp));
|
||||||
} else {
|
} else {
|
||||||
fmt::print("Error retrieving version: {}\n", versionResp.error().errorString);
|
fmt::print("Error retrieving version: {}\n", versionResp.error().errorString);
|
||||||
return QCoreApplication::exit(1);
|
return QCoreApplication::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto modelsResponse = QCoro::waitFor(provider.listModels());
|
auto modelsResponse = QCoro::waitFor(provider.list());
|
||||||
if (modelsResponse) {
|
if (modelsResponse) {
|
||||||
fmt::print("Available models:\n");
|
fmt::print("Available models:\n");
|
||||||
for (const auto & model : modelsResponse->models)
|
for (const auto & model : modelsResponse->models)
|
||||||
fmt::print("{}\n", model.model);
|
fmt::print("{}\n", model.model);
|
||||||
|
if (!modelsResponse->models.empty())
|
||||||
|
fmt::print("First model: {}\n", to_json(modelsResponse->models.front()));
|
||||||
} else {
|
} else {
|
||||||
fmt::print("Error retrieving available models: {}\n", modelsResponse.error().errorString);
|
fmt::print("Error retrieving available models: {}\n", modelsResponse.error().errorString);
|
||||||
return QCoreApplication::exit(1);
|
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) {
|
if (showResponse) {
|
||||||
fmt::print("Model family: {}\n", showResponse->details.family);
|
fmt::print("Show response: {}\n", to_json(*showResponse));
|
||||||
} else {
|
} else {
|
||||||
fmt::print("Error retrieving model info: {}\n", showResponse.error().errorString);
|
fmt::print("Error retrieving model info: {}\n", showResponse.error().errorString);
|
||||||
return QCoreApplication::exit(1);
|
return QCoreApplication::exit(1);
|
||||||
@ -51,7 +62,6 @@ static void run()
|
|||||||
QCoreApplication::exit(0);
|
QCoreApplication::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
QCoreApplication app(argc, argv);
|
QCoreApplication app(argc, argv);
|
||||||
|
95
gpt4all-backend-test/src/pretty.h
Normal file
95
gpt4all-backend-test/src/pretty.h
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/json.hpp>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
@ -13,6 +13,7 @@ add_subdirectory(src)
|
|||||||
target_sources(gpt4all-backend PUBLIC
|
target_sources(gpt4all-backend PUBLIC
|
||||||
FILE_SET public_headers TYPE HEADERS BASE_DIRS include FILES
|
FILE_SET public_headers TYPE HEADERS BASE_DIRS include FILES
|
||||||
include/gpt4all-backend/formatters.h
|
include/gpt4all-backend/formatters.h
|
||||||
|
include/gpt4all-backend/json-helpers.h
|
||||||
include/gpt4all-backend/ollama-client.h
|
include/gpt4all-backend/ollama-client.h
|
||||||
include/gpt4all-backend/ollama-types.h
|
include/gpt4all-backend/ollama-types.h
|
||||||
)
|
)
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
#include "ollama-types.h"
|
#include "ollama-types.h"
|
||||||
|
|
||||||
#include <QCoro/QCoroTask> // IWYU pragma: keep
|
#include <QCoro/QCoroTask> // IWYU pragma: keep
|
||||||
|
#include <boost/json.hpp> // IWYU pragma: keep
|
||||||
|
#include <boost/system.hpp>
|
||||||
|
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
@ -15,13 +17,19 @@
|
|||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
class QNetworkRequest;
|
class QNetworkRequest;
|
||||||
namespace boost::json { class value; }
|
|
||||||
|
|
||||||
|
|
||||||
namespace gpt4all::backend {
|
namespace gpt4all::backend {
|
||||||
|
|
||||||
struct ResponseError {
|
struct ResponseError {
|
||||||
QNetworkReply::NetworkError error;
|
private:
|
||||||
|
using ErrorCode = std::variant<
|
||||||
|
QNetworkReply::NetworkError,
|
||||||
|
boost::system::error_code
|
||||||
|
>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ErrorCode error;
|
||||||
QString errorString;
|
QString errorString;
|
||||||
|
|
||||||
ResponseError(const QNetworkReply *reply)
|
ResponseError(const QNetworkReply *reply)
|
||||||
@ -30,6 +38,13 @@ struct ResponseError {
|
|||||||
{
|
{
|
||||||
assert(reply->error());
|
assert(reply->error());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResponseError(const boost::system::system_error &e)
|
||||||
|
: error(e.code())
|
||||||
|
, errorString(QString::fromUtf8(e.what()))
|
||||||
|
{
|
||||||
|
assert(e.code());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -45,40 +60,44 @@ public:
|
|||||||
const QUrl &baseUrl() const { return m_baseUrl; }
|
const QUrl &baseUrl() const { return m_baseUrl; }
|
||||||
void getBaseUrl(QUrl value) { m_baseUrl = std::move(value); }
|
void getBaseUrl(QUrl value) { m_baseUrl = std::move(value); }
|
||||||
|
|
||||||
/// Returns the version of the Ollama server.
|
/// Returns the Ollama server version as a string.
|
||||||
auto getVersion() -> QCoro::Task<DataOrRespErr<ollama::VersionResponse>>
|
auto version() -> QCoro::Task<DataOrRespErr<ollama::VersionResponse>>
|
||||||
{ return get<ollama::VersionResponse>(QStringLiteral("version")); }
|
{ return get<ollama::VersionResponse>(QStringLiteral("version")); }
|
||||||
|
|
||||||
/// List models that are available locally.
|
/// Lists models that are available locally.
|
||||||
auto listModels() -> QCoro::Task<DataOrRespErr<ollama::ModelsResponse>>
|
auto list() -> QCoro::Task<DataOrRespErr<ollama::ListResponse>>
|
||||||
{ return get<ollama::ModelsResponse>(QStringLiteral("tags")); }
|
{ return get<ollama::ListResponse>(QStringLiteral("tags")); }
|
||||||
|
|
||||||
/// Show details about a model including modelfile, template, parameters, license, and system prompt.
|
/// Obtains model information, including details, modelfile, license etc.
|
||||||
auto showModelInfo(const ollama::ModelInfoRequest &req) -> QCoro::Task<DataOrRespErr<ollama::ModelInfo>>
|
auto show(const ollama::ShowRequest &req) -> QCoro::Task<DataOrRespErr<ollama::ShowResponse>>
|
||||||
{ return post<ollama::ModelInfo>(QStringLiteral("show"), req); }
|
{ return post<ollama::ShowResponse>(QStringLiteral("show"), req); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QNetworkRequest makeRequest(const QString &path) const;
|
QNetworkRequest makeRequest(const QString &path) const;
|
||||||
|
|
||||||
|
auto processResponse(QNetworkReply &reply) -> QCoro::Task<DataOrRespErr<boost::json::value>>;
|
||||||
|
|
||||||
template <typename Resp>
|
template <typename Resp>
|
||||||
auto get(const QString &path) -> QCoro::Task<DataOrRespErr<Resp>>;
|
auto get(const QString &path) -> QCoro::Task<DataOrRespErr<Resp>>;
|
||||||
template <typename Resp, typename Req>
|
template <typename Resp, typename Req>
|
||||||
auto post(const QString &path, Req const &req) -> QCoro::Task<DataOrRespErr<Resp>>;
|
auto post(const QString &path, Req const &body) -> QCoro::Task<DataOrRespErr<Resp>>;
|
||||||
|
|
||||||
auto getJson(const QString &path) -> QCoro::Task<DataOrRespErr<boost::json::value>>;
|
auto getJson(const QString &path) -> QCoro::Task<DataOrRespErr<boost::json::value>>;
|
||||||
auto postJson(const QString &path, const boost::json::value &req)
|
auto postJson(const QString &path, const boost::json::value &body)
|
||||||
-> QCoro::Task<DataOrRespErr<boost::json::value>>;
|
-> QCoro::Task<DataOrRespErr<boost::json::value>>;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QUrl m_baseUrl;
|
QUrl m_baseUrl;
|
||||||
QString m_userAgent;
|
QString m_userAgent;
|
||||||
QNetworkAccessManager m_nam;
|
QNetworkAccessManager m_nam;
|
||||||
|
boost::json::stream_parser m_parser;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern template auto OllamaClient::get(const QString &) -> QCoro::Task<DataOrRespErr<ollama::VersionResponse>>;
|
extern template auto OllamaClient::get(const QString &) -> QCoro::Task<DataOrRespErr<ollama::VersionResponse>>;
|
||||||
extern template auto OllamaClient::get(const QString &) -> QCoro::Task<DataOrRespErr<ollama::ModelsResponse>>;
|
extern template auto OllamaClient::get(const QString &) -> QCoro::Task<DataOrRespErr<ollama::ListResponse>>;
|
||||||
|
|
||||||
|
extern template auto OllamaClient::post(const QString &, const ollama::ShowRequest &)
|
||||||
|
-> QCoro::Task<DataOrRespErr<ollama::ShowResponse>>;
|
||||||
|
|
||||||
extern template auto OllamaClient::post(const QString &, const ollama::ModelInfoRequest &)
|
|
||||||
-> QCoro::Task<DataOrRespErr<ollama::ModelInfo>>;
|
|
||||||
|
|
||||||
} // namespace gpt4all::backend
|
} // namespace gpt4all::backend
|
||||||
|
@ -1,131 +1,128 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef G4A_BACKEND_IMPL
|
#include "json-helpers.h" // IWYU pragma: keep
|
||||||
|
|
||||||
#include <boost/describe/class.hpp>
|
#include <boost/describe/class.hpp>
|
||||||
# include <boost/describe/enum.hpp>
|
|
||||||
#endif
|
|
||||||
#include <boost/json.hpp> // IWYU pragma: keep
|
#include <boost/json.hpp> // IWYU pragma: keep
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QtTypes>
|
#include <QtTypes>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
namespace gpt4all::backend::ollama {
|
namespace gpt4all::backend::ollama {
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// basic types
|
// basic types
|
||||||
//
|
//
|
||||||
|
|
||||||
/// Details about a model.
|
struct Time : std::chrono::sys_time<std::chrono::nanoseconds> {};
|
||||||
|
|
||||||
|
void tag_invoke(const boost::json::value_from_tag &, boost::json::value &value, Time time);
|
||||||
|
Time tag_invoke(const boost::json::value_to_tag<Time> &, const boost::json::value &value);
|
||||||
|
|
||||||
|
/// ImageData represents the raw binary data of an image file.
|
||||||
|
struct ImageData : QByteArray {};
|
||||||
|
|
||||||
|
void tag_invoke(const boost::json::value_from_tag &, boost::json::value &value, const ImageData &image);
|
||||||
|
ImageData tag_invoke(const boost::json::value_to_tag<ImageData> &, const boost::json::value &value);
|
||||||
|
|
||||||
struct ModelDetails {
|
struct ModelDetails {
|
||||||
QString parent_model; /// The parent of the model.
|
QString parent_model;
|
||||||
QString format; /// The format of the model.
|
QString format;
|
||||||
QString family; /// The family of the model.
|
QString family;
|
||||||
std::vector<QString> families; /// The families of the model.
|
std::vector<QString> families;
|
||||||
QString parameter_size; /// The size of the model's parameters.
|
QString parameter_size;
|
||||||
QString quantization_level; /// The quantization level of the model.
|
QString quantization_level;
|
||||||
};
|
};
|
||||||
#ifdef G4A_BACKEND_IMPL
|
|
||||||
BOOST_DESCRIBE_STRUCT(ModelDetails, (), (parent_model, format, family, families, parameter_size, quantization_level))
|
BOOST_DESCRIBE_STRUCT(ModelDetails, (), (parent_model, format, family, families, parameter_size, quantization_level))
|
||||||
#endif
|
|
||||||
|
|
||||||
/// A model available locally.
|
/// ListModelResponse is a single model description in ListResponse.
|
||||||
struct Model {
|
struct ListModelResponse {
|
||||||
QString model; /// The model name.
|
QString name;
|
||||||
QString modified_at; /// Model modification date.
|
QString model;
|
||||||
quint64 size; /// Size of the model on disk.
|
Time modified_at;
|
||||||
QString digest; /// The model's digest.
|
qint64 size; /// Size of the model on disk.
|
||||||
ModelDetails details; /// The model's details.
|
QString digest;
|
||||||
|
std::optional<ModelDetails> details;
|
||||||
};
|
};
|
||||||
#ifdef G4A_BACKEND_IMPL
|
BOOST_DESCRIBE_STRUCT(ListModelResponse, (), (model, modified_at, size, digest, details))
|
||||||
BOOST_DESCRIBE_STRUCT(Model, (), (model, modified_at, size, digest, details))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum MessageRole {
|
using ToolCallFunctionArguments = boost::json::object;
|
||||||
system,
|
|
||||||
user,
|
|
||||||
assistant,
|
|
||||||
tool,
|
|
||||||
};
|
|
||||||
#ifdef G4A_BACKEND_IMPL
|
|
||||||
BOOST_DESCRIBE_ENUM(MessageRole, system, user, assistant, tool)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct ToolCallFunction {
|
struct ToolCallFunction {
|
||||||
QString name; /// The name of the function to be called.
|
std::optional<int> index;
|
||||||
boost::json::object arguments; /// The arguments to pass to the function.
|
QString name;
|
||||||
|
ToolCallFunctionArguments arguments;
|
||||||
};
|
};
|
||||||
#ifdef G4A_BACKEND_IMPL
|
BOOST_DESCRIBE_STRUCT(ToolCallFunction, (), (index, name, arguments))
|
||||||
BOOST_DESCRIBE_STRUCT(ToolCallFunction, (), (name, arguments))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct ToolCall {
|
struct ToolCall {
|
||||||
ToolCallFunction function; /// The function the model wants to call.
|
ToolCallFunction function;
|
||||||
};
|
};
|
||||||
#ifdef G4A_BACKEND_IMPL
|
|
||||||
BOOST_DESCRIBE_STRUCT(ToolCall, (), (function))
|
BOOST_DESCRIBE_STRUCT(ToolCall, (), (function))
|
||||||
#endif
|
|
||||||
|
|
||||||
/// A message in the chat endpoint
|
/// Message is a single message in a chat sequence. The message contains the
|
||||||
|
/// role ("system", "user", or "assistant"), the content and an optional list
|
||||||
|
/// of images.
|
||||||
struct Message {
|
struct Message {
|
||||||
MessageRole role; /// The role of the message
|
QString role;
|
||||||
QString content; /// The content of the message
|
QString content;
|
||||||
std::vector<QString> images; /// (optional) a list of Base64-encoded images to include in the message
|
std::optional<std::vector<ImageData>> images;
|
||||||
std::vector<ToolCall> tool_calls; /// A list of tool calls the model wants to call.
|
std::optional<std::vector<ToolCall>> tool_calls;
|
||||||
};
|
};
|
||||||
#ifdef G4A_BACKEND_IMPL
|
|
||||||
BOOST_DESCRIBE_STRUCT(Message, (), (role, content, images, tool_calls))
|
BOOST_DESCRIBE_STRUCT(Message, (), (role, content, images, tool_calls))
|
||||||
#endif
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// request types
|
// request types
|
||||||
//
|
//
|
||||||
|
|
||||||
/// Request class for the show model info endpoint.
|
/// ShowRequest is the request passed to OllamaClient::show().
|
||||||
struct ModelInfoRequest {
|
struct ShowRequest {
|
||||||
QString model; /// The model name.
|
QString model;
|
||||||
|
std::optional<QString> system {};
|
||||||
|
std::optional<bool> verbose {};
|
||||||
|
std::optional<boost::json::object> options {};
|
||||||
};
|
};
|
||||||
#ifdef G4A_BACKEND_IMPL
|
BOOST_DESCRIBE_STRUCT(ShowRequest, (), (model, system, verbose, options))
|
||||||
BOOST_DESCRIBE_STRUCT(ModelInfoRequest, (), (model))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// response types
|
// response types
|
||||||
//
|
//
|
||||||
|
|
||||||
/// The response class for the version endpoint.
|
/// VersionRepsonse is the response from OllamaClient::version().
|
||||||
struct VersionResponse {
|
struct VersionResponse {
|
||||||
QString version; /// The version of the Ollama server.
|
QString version; /// The version of the Ollama server.
|
||||||
};
|
};
|
||||||
#ifdef G4A_BACKEND_IMPL
|
|
||||||
BOOST_DESCRIBE_STRUCT(VersionResponse, (), (version))
|
BOOST_DESCRIBE_STRUCT(VersionResponse, (), (version))
|
||||||
#endif
|
|
||||||
|
|
||||||
/// Response class for the list models endpoint.
|
/// ShowResponse is the response from OllamaClient::show().
|
||||||
struct ModelsResponse {
|
struct ShowResponse {
|
||||||
std::vector<Model> models; /// List of models available locally.
|
std::optional<QString> license;
|
||||||
};
|
|
||||||
#ifdef G4A_BACKEND_IMPL
|
|
||||||
BOOST_DESCRIBE_STRUCT(ModelsResponse, (), (models))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// Details about a model including modelfile, template, parameters, license, and system prompt.
|
|
||||||
struct ModelInfo {
|
|
||||||
std::optional<QString> license; /// The model's license.
|
|
||||||
std::optional<QString> modelfile; /// The modelfile associated with the model.
|
std::optional<QString> modelfile; /// The modelfile associated with the model.
|
||||||
std::optional<QString> parameters; /// The model parameters.
|
std::optional<QString> parameters;
|
||||||
std::optional<QString> template_; /// The prompt template for the model.
|
std::optional<QString> template_; /// The prompt template for the model.
|
||||||
std::optional<QString> system; /// The system prompt for the model.
|
std::optional<QString> system; /// The system prompt for the model.
|
||||||
ModelDetails details;
|
std::optional<ModelDetails> details;
|
||||||
boost::json::object model_info;
|
|
||||||
std::optional<std::vector<Message>> messages; /// The default messages for the model.
|
std::optional<std::vector<Message>> messages; /// The default messages for the model.
|
||||||
|
std::optional<boost::json::object> model_info;
|
||||||
|
std::optional<boost::json::object> projector_info;
|
||||||
|
std::optional<Time> modified_at;
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef G4A_BACKEND_IMPL
|
void tag_invoke(const boost::json::value_from_tag &, boost::json::value &value, const ShowResponse &resp);
|
||||||
ModelInfo tag_invoke(const boost::json::value_to_tag<ModelInfo> &, const boost::json::value &value);
|
ShowResponse tag_invoke(const boost::json::value_to_tag<ShowResponse> &, const boost::json::value &value);
|
||||||
#endif
|
|
||||||
|
/// ListResponse is the response from OllamaClient::list().
|
||||||
|
struct ListResponse {
|
||||||
|
std::vector<ListModelResponse> models; /// List of available models.
|
||||||
|
};
|
||||||
|
BOOST_DESCRIBE_STRUCT(ListResponse, (), (models))
|
||||||
|
|
||||||
|
|
||||||
} // namespace gpt4all::backend::ollama
|
} // namespace gpt4all::backend::ollama
|
||||||
|
@ -7,7 +7,6 @@ add_library(${TARGET} STATIC
|
|||||||
qt-json-stream.cpp
|
qt-json-stream.cpp
|
||||||
)
|
)
|
||||||
target_compile_features(${TARGET} PUBLIC cxx_std_23)
|
target_compile_features(${TARGET} PUBLIC cxx_std_23)
|
||||||
target_compile_definitions(${TARGET} PRIVATE G4A_BACKEND_IMPL)
|
|
||||||
gpt4all_add_warning_options(${TARGET})
|
gpt4all_add_warning_options(${TARGET})
|
||||||
target_include_directories(${TARGET} PRIVATE
|
target_include_directories(${TARGET} PRIVATE
|
||||||
.
|
.
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include <QCoro/QCoroIODevice> // IWYU pragma: keep
|
#include <QCoro/QCoroIODevice> // IWYU pragma: keep
|
||||||
#include <QCoro/QCoroNetworkReply> // IWYU pragma: keep
|
#include <QCoro/QCoroNetworkReply> // IWYU pragma: keep
|
||||||
#include <boost/json.hpp> // IWYU pragma: keep
|
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
@ -22,24 +21,6 @@ namespace json = boost::json;
|
|||||||
namespace gpt4all::backend {
|
namespace gpt4all::backend {
|
||||||
|
|
||||||
|
|
||||||
static auto processResponse(QNetworkReply &reply) -> QCoro::Task<DataOrRespErr<json::value>>
|
|
||||||
{
|
|
||||||
if (reply.error())
|
|
||||||
co_return std::unexpected(&reply);
|
|
||||||
|
|
||||||
json::parser p;
|
|
||||||
auto coroReply = qCoro(reply);
|
|
||||||
do {
|
|
||||||
auto chunk = co_await coroReply.readAll();
|
|
||||||
if (reply.error())
|
|
||||||
co_return std::unexpected(&reply);
|
|
||||||
p.write(chunk.data(), chunk.size());
|
|
||||||
} while (!reply.atEnd());
|
|
||||||
|
|
||||||
co_return p.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QNetworkRequest OllamaClient::makeRequest(const QString &path) const
|
QNetworkRequest OllamaClient::makeRequest(const QString &path) const
|
||||||
{
|
{
|
||||||
QNetworkRequest req(m_baseUrl.resolved(QUrl(path)));
|
QNetworkRequest req(m_baseUrl.resolved(QUrl(path)));
|
||||||
@ -47,29 +28,56 @@ QNetworkRequest OllamaClient::makeRequest(const QString &path) const
|
|||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto OllamaClient::processResponse(QNetworkReply &reply) -> QCoro::Task<DataOrRespErr<json::value>>
|
||||||
|
{
|
||||||
|
if (reply.error())
|
||||||
|
co_return std::unexpected(&reply);
|
||||||
|
|
||||||
|
auto coroReply = qCoro(reply);
|
||||||
|
do {
|
||||||
|
auto chunk = co_await coroReply.readAll();
|
||||||
|
if (reply.error())
|
||||||
|
co_return std::unexpected(&reply);
|
||||||
|
m_parser.write(chunk.data(), chunk.size());
|
||||||
|
} while (!reply.atEnd());
|
||||||
|
|
||||||
|
m_parser.finish();
|
||||||
|
co_return m_parser.release();
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Resp>
|
template <typename Resp>
|
||||||
auto OllamaClient::get(const QString &path) -> QCoro::Task<DataOrRespErr<Resp>>
|
auto OllamaClient::get(const QString &path) -> QCoro::Task<DataOrRespErr<Resp>>
|
||||||
{
|
{
|
||||||
|
// get() should not throw exceptions
|
||||||
|
try {
|
||||||
auto value = co_await getJson(path);
|
auto value = co_await getJson(path);
|
||||||
if (value)
|
if (value)
|
||||||
co_return json::value_to<Resp>(*value);
|
co_return json::value_to<Resp>(*value);
|
||||||
co_return std::unexpected(value.error());
|
co_return std::unexpected(value.error());
|
||||||
|
} catch (const boost::system::system_error &e) {
|
||||||
|
co_return std::unexpected(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template auto OllamaClient::get(const QString &) -> QCoro::Task<DataOrRespErr<VersionResponse>>;
|
template auto OllamaClient::get(const QString &) -> QCoro::Task<DataOrRespErr<VersionResponse>>;
|
||||||
template auto OllamaClient::get(const QString &) -> QCoro::Task<DataOrRespErr<ModelsResponse>>;
|
template auto OllamaClient::get(const QString &) -> QCoro::Task<DataOrRespErr<ListResponse>>;
|
||||||
|
|
||||||
template <typename Resp, typename Req>
|
template <typename Resp, typename Req>
|
||||||
auto OllamaClient::post(const QString &path, const Req &req) -> QCoro::Task<DataOrRespErr<Resp>>
|
auto OllamaClient::post(const QString &path, const Req &body) -> QCoro::Task<DataOrRespErr<Resp>>
|
||||||
{
|
{
|
||||||
auto reqJson = json::value_from(req);
|
// post() should not throw exceptions
|
||||||
|
try {
|
||||||
|
auto reqJson = json::value_from(body);
|
||||||
auto value = co_await postJson(path, reqJson);
|
auto value = co_await postJson(path, reqJson);
|
||||||
if (value)
|
if (value)
|
||||||
co_return json::value_to<Resp>(*value);
|
co_return json::value_to<Resp>(*value);
|
||||||
co_return std::unexpected(value.error());
|
co_return std::unexpected(value.error());
|
||||||
|
} catch (const boost::system::system_error &e) {
|
||||||
|
co_return std::unexpected(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template auto OllamaClient::post(const QString &, const ModelInfoRequest &) -> QCoro::Task<DataOrRespErr<ModelInfo>>;
|
template auto OllamaClient::post(const QString &, const ShowRequest &) -> QCoro::Task<DataOrRespErr<ShowResponse>>;
|
||||||
|
|
||||||
auto OllamaClient::getJson(const QString &path) -> QCoro::Task<DataOrRespErr<json::value>>
|
auto OllamaClient::getJson(const QString &path) -> QCoro::Task<DataOrRespErr<json::value>>
|
||||||
{
|
{
|
||||||
@ -77,12 +85,12 @@ auto OllamaClient::getJson(const QString &path) -> QCoro::Task<DataOrRespErr<jso
|
|||||||
co_return co_await processResponse(*reply);
|
co_return co_await processResponse(*reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto OllamaClient::postJson(const QString &path, const json::value &req) -> QCoro::Task<DataOrRespErr<json::value>>
|
auto OllamaClient::postJson(const QString &path, const json::value &body) -> QCoro::Task<DataOrRespErr<json::value>>
|
||||||
{
|
{
|
||||||
JsonStreamDevice reqStream(&req);
|
JsonStreamDevice stream(&body);
|
||||||
std::unique_ptr<QNetworkReply> reply(
|
auto req = makeRequest(path);
|
||||||
m_nam.post(makeRequest(path), &reqStream)
|
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"_ba);
|
||||||
);
|
std::unique_ptr<QNetworkReply> reply(m_nam.post(req, &stream));
|
||||||
co_return co_await processResponse(*reply);
|
co_return co_await processResponse(*reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,30 +2,97 @@
|
|||||||
|
|
||||||
#include "json-helpers.h"
|
#include "json-helpers.h"
|
||||||
|
|
||||||
|
#include <fmt/chrono.h> // IWYU pragma: keep
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <system_error>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace json = boost::json;
|
namespace json = boost::json;
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static T get_optional(const json::object &o, json::string_view key)
|
||||||
|
{
|
||||||
|
if (auto *p = o.if_contains(key))
|
||||||
|
return value_to<typename T::value_type>(*p);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
namespace gpt4all::backend::ollama {
|
namespace gpt4all::backend::ollama {
|
||||||
|
|
||||||
ModelInfo tag_invoke(const boost::json::value_to_tag<ModelInfo> &, const boost::json::value &value)
|
|
||||||
|
void tag_invoke(const json::value_from_tag &, json::value &value, Time time)
|
||||||
|
{
|
||||||
|
value = json::value_from(fmt::format(
|
||||||
|
"{:%FT%T}Z",
|
||||||
|
static_cast<const std::chrono::sys_time<std::chrono::nanoseconds> &>(time)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Time tag_invoke(const json::value_to_tag<Time> &, const json::value &value)
|
||||||
|
{
|
||||||
|
namespace sys = boost::system;
|
||||||
|
|
||||||
|
Time time;
|
||||||
|
std::istringstream iss(json::string_view(value.as_string()));
|
||||||
|
iss >> std::chrono::parse("%FT%T%Ez", time);
|
||||||
|
if (!iss && !iss.eof())
|
||||||
|
throw sys::system_error(std::make_error_code(std::errc::invalid_argument), __func__);
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tag_invoke(const json::value_from_tag &, json::value &value, const ImageData &image)
|
||||||
|
{
|
||||||
|
auto base64 = image.toBase64();
|
||||||
|
value = json::value_from(json::string_view(base64.data(), base64.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageData tag_invoke(const json::value_to_tag<ImageData> &, const json::value &value)
|
||||||
|
{
|
||||||
|
auto &str = value.as_string();
|
||||||
|
return ImageData(QByteArray::fromBase64(QByteArray::fromRawData(str.data(), str.size())));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tag_invoke(const json::value_from_tag &, json::value &value, const ShowResponse &resp)
|
||||||
|
{
|
||||||
|
auto &o = value.emplace_object();
|
||||||
|
|
||||||
|
auto maybe_add = [&o](json::string_view key, auto &v) { if (v) o[key] = json::value_from(*v); };
|
||||||
|
maybe_add("license", resp.license );
|
||||||
|
maybe_add("modelfile", resp.modelfile );
|
||||||
|
maybe_add("parameters", resp.parameters );
|
||||||
|
maybe_add("template", resp.template_ );
|
||||||
|
maybe_add("system", resp.system );
|
||||||
|
maybe_add("details", resp.details );
|
||||||
|
maybe_add("messages", resp.messages );
|
||||||
|
maybe_add("model_info", resp.model_info );
|
||||||
|
maybe_add("projector_info", resp.projector_info);
|
||||||
|
maybe_add("modified_at", resp.modified_at );
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowResponse tag_invoke(const json::value_to_tag<ShowResponse> &, const json::value &value)
|
||||||
{
|
{
|
||||||
using namespace json;
|
|
||||||
auto &o = value.as_object();
|
auto &o = value.as_object();
|
||||||
return {
|
return {
|
||||||
#define T(name) std::remove_reference_t<decltype(std::declval<ModelInfo>().name)>
|
#define T(name) std::remove_reference_t<decltype(std::declval<ShowResponse>().name)>
|
||||||
.license = value_to<T(license )>(o.at("license" )),
|
.license = get_optional<T(license )>(o, "license" ),
|
||||||
.modelfile = value_to<T(modelfile )>(o.at("modelfile" )),
|
.modelfile = get_optional<T(modelfile )>(o, "modelfile" ),
|
||||||
.parameters = value_to<T(parameters)>(o.at("parameters")),
|
.parameters = get_optional<T(parameters )>(o, "parameters" ),
|
||||||
.template_ = value_to<T(template_ )>(o.at("template" )), // :(
|
.template_ = get_optional<T(template_ )>(o, "template" ), // :(
|
||||||
.system = value_to<T(system )>(o.at("system" )),
|
.system = get_optional<T(system )>(o, "system" ),
|
||||||
.details = value_to<T(details )>(o.at("details" )),
|
.details = get_optional<T(details )>(o, "details" ),
|
||||||
.model_info = value_to<T(model_info)>(o.at("model_info")),
|
.messages = get_optional<T(messages )>(o, "messages" ),
|
||||||
.messages = value_to<T(messages )>(o.at("messages" )),
|
.model_info = get_optional<T(model_info )>(o, "model_info" ),
|
||||||
|
.projector_info = get_optional<T(projector_info)>(o, "projector_info"),
|
||||||
|
.modified_at = get_optional<T(modified_at )>(o, "modified_at" ),
|
||||||
#undef T
|
#undef T
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace gpt4all::backend::ollama
|
} // namespace gpt4all::backend::ollama
|
||||||
|
@ -10,6 +10,7 @@ namespace json = boost::json;
|
|||||||
|
|
||||||
namespace gpt4all::backend {
|
namespace gpt4all::backend {
|
||||||
|
|
||||||
|
|
||||||
JsonStreamDevice::JsonStreamDevice(const json::value *jv, QObject *parent)
|
JsonStreamDevice::JsonStreamDevice(const json::value *jv, QObject *parent)
|
||||||
: QIODevice(parent)
|
: QIODevice(parent)
|
||||||
{
|
{
|
||||||
@ -31,4 +32,5 @@ qint64 JsonStreamDevice::writeData(const char *data, qint64 maxSize)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace gpt4all::backend
|
} // namespace gpt4all::backend
|
||||||
|
@ -10,6 +10,7 @@ class QObject;
|
|||||||
|
|
||||||
namespace gpt4all::backend {
|
namespace gpt4all::backend {
|
||||||
|
|
||||||
|
|
||||||
class JsonStreamDevice : public QIODevice
|
class JsonStreamDevice : public QIODevice
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -25,4 +26,5 @@ private:
|
|||||||
boost::json::serializer m_sr;
|
boost::json::serializer m_sr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace gpt4all::backend
|
} // namespace gpt4all::backend
|
||||||
|
Loading…
Reference in New Issue
Block a user