Tool model.

Signed-off-by: Adam Treat <treat.adam@gmail.com>
This commit is contained in:
Adam Treat 2024-08-08 10:37:53 -04:00
parent 244b82622c
commit cedba6cd10
6 changed files with 252 additions and 51 deletions

View File

@ -124,7 +124,7 @@ qt_add_executable(chat
sourceexcerpt.h sourceexcerpt.cpp sourceexcerpt.h sourceexcerpt.cpp
server.h server.cpp server.h server.cpp
logger.h logger.cpp logger.h logger.cpp
tool.h tool.cpp tool.h tool.cpp toolmodel.h toolmodel.cpp
${APP_ICON_RESOURCE} ${APP_ICON_RESOURCE}
${CHAT_EXE_RESOURCES} ${CHAT_EXE_RESOURCES}
) )

View File

@ -8,63 +8,26 @@ using namespace Qt::Literals::StringLiterals;
namespace ToolEnums { namespace ToolEnums {
Q_NAMESPACE Q_NAMESPACE
enum class ConnectionType {
BuiltinConnection = 0, // A built-in tool with bespoke connection type
LocalConnection = 1, // Starts a local process and communicates via stdin/stdout/stderr
LocalServerConnection = 2, // Connects to an existing local process and communicates via stdin/stdout/stderr
RemoteConnection = 3, // Starts a remote process and communicates via some networking protocol TBD
RemoteServerConnection = 4 // Connects to an existing remote process and communicates via some networking protocol TBD
};
Q_ENUM_NS(ConnectionType)
enum class Error { enum class Error {
NoError = 0, NoError = 0,
TimeoutError = 2, TimeoutError = 2,
UnknownError = 499, UnknownError = 499,
}; };
Q_ENUM_NS(Error)
} }
struct ToolInfo {
Q_GADGET
Q_PROPERTY(QString name MEMBER name)
Q_PROPERTY(QString description MEMBER description)
Q_PROPERTY(QJsonObject parameters MEMBER parameters)
Q_PROPERTY(bool isEnabled MEMBER isEnabled)
Q_PROPERTY(ToolEnums::ConnectionType connectionType MEMBER connectionType)
public:
QString name;
QString description;
QJsonObject parameters;
bool isEnabled;
ToolEnums::ConnectionType connectionType;
// FIXME: Should we go with essentially the OpenAI/ollama consensus for these tool
// info files? If you install a tool in GPT4All should it need to meet the spec for these:
// https://platform.openai.com/docs/api-reference/runs/createRun#runs-createrun-tools
// https://github.com/ollama/ollama/blob/main/docs/api.md#chat-request-with-tools
QJsonObject toJson() const
{
QJsonObject result;
result.insert("name", name);
result.insert("description", description);
result.insert("parameters", parameters);
return result;
}
static ToolInfo fromJson(const QString &json);
bool operator==(const ToolInfo &other) const {
return name == other.name;
}
bool operator!=(const ToolInfo &other) const {
return !(*this == other);
}
};
Q_DECLARE_METATYPE(ToolInfo)
class Tool : public QObject { class Tool : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString name MEMBER name)
Q_PROPERTY(QString description MEMBER description)
Q_PROPERTY(QString function MEMBER function)
Q_PROPERTY(QJsonObject paramSchema MEMBER paramSchema)
Q_PROPERTY(QUrl url MEMBER url)
Q_PROPERTY(bool isEnabled MEMBER isEnabled)
Q_PROPERTY(bool isBuiltin MEMBER isBuiltin)
Q_PROPERTY(bool forceUsage MEMBER forceUsage)
Q_PROPERTY(bool excerpts MEMBER excerpts)
public: public:
Tool() : QObject(nullptr) {} Tool() : QObject(nullptr) {}
virtual ~Tool() {} virtual ~Tool() {}
@ -72,6 +35,29 @@ public:
virtual QString run(const QJsonObject &parameters, qint64 timeout = 2000) = 0; virtual QString run(const QJsonObject &parameters, qint64 timeout = 2000) = 0;
virtual ToolEnums::Error error() const { return ToolEnums::Error::NoError; } virtual ToolEnums::Error error() const { return ToolEnums::Error::NoError; }
virtual QString errorString() const { return QString(); } virtual QString errorString() const { return QString(); }
QString name; // [Required] Human readable name of the tool.
QString description; // [Required] Human readable description of the tool.
QString function; // [Required] Must be unique. Name of the function to invoke. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
QJsonObject paramSchema; // [Optional] Json schema describing the tool's parameters. An empty object specifies no parameters.
// https://json-schema.org/understanding-json-schema/
QUrl url; // [Optional] The local file or remote resource use to invoke the tool.
bool isEnabled = false; // [Optional] Whether the tool is currently enabled
bool isBuiltin = false; // [Optional] Whether the tool is built-in
bool forceUsage = false; // [Optional] Whether we should attempt to force usage of the tool rather than let the LLM decide. NOTE: Not always possible.
bool excerpts = false; // [Optional] Whether json result produces source excerpts.
// FIXME: Should we go with essentially the OpenAI/ollama consensus for these tool
// info files? If you install a tool in GPT4All should it need to meet the spec for these:
// https://platform.openai.com/docs/api-reference/runs/createRun#runs-createrun-tools
// https://github.com/ollama/ollama/blob/main/docs/api.md#chat-request-with-tools
bool operator==(const Tool &other) const {
return function == other.function;
}
bool operator!=(const Tool &other) const {
return !(*this == other);
}
}; };
#endif // TOOL_H #endif // TOOL_H

103
gpt4all-chat/toolmodel.cpp Normal file
View File

@ -0,0 +1,103 @@
#include "toolmodel.h"
#include <QGlobalStatic>
#include <QGuiApplication>
#include <QJsonDocument>
#include "bravesearch.h"
#include "localdocssearch.h"
class MyToolModel: public ToolModel { };
Q_GLOBAL_STATIC(MyToolModel, toolModelInstance)
ToolModel *ToolModel::globalInstance()
{
return toolModelInstance();
}
ToolModel::ToolModel()
: QAbstractListModel(nullptr) {
QCoreApplication::instance()->installEventFilter(this);
Tool* localDocsSearch = new LocalDocsSearch;
localDocsSearch->name = tr("LocalDocs search");
localDocsSearch->description = tr("Search the local docs");
localDocsSearch->function = "localdocs_search";
localDocsSearch->isBuiltin = true;
localDocsSearch->excerpts = true;
localDocsSearch->forceUsage = true; // FIXME: persistent setting
localDocsSearch->isEnabled = true; // FIXME: persistent setting
QString localParamSchema = R"({
"collections": {
"type": "array",
"items": {
"type": "string"
},
"description": "The collections to search",
"required": true,
"modelGenerated": false,
"userConfigured": false
},
"query": {
"type": "string",
"description": "The query to search",
"required": true
},
"count": {
"type": "integer",
"description": "The number of excerpts to return",
"required": true,
"modelGenerated": false
}
})";
QJsonDocument localJsonDoc = QJsonDocument::fromJson(localParamSchema.toUtf8());
Q_ASSERT(!localJsonDoc.isNull() && localJsonDoc.isObject());
localDocsSearch->paramSchema = localJsonDoc.object();
m_tools.append(localDocsSearch);
m_toolMap.insert(localDocsSearch->function, localDocsSearch);
Tool *braveSearch = new BraveSearch;
braveSearch->name = tr("Brave web search");
braveSearch->description = tr("Search the web using brave.com");
braveSearch->function = "brave_search";
braveSearch->isBuiltin = true;
braveSearch->excerpts = true;
braveSearch->forceUsage = false; // FIXME: persistent setting
braveSearch->isEnabled = false; // FIXME: persistent setting
QString braveParamSchema = R"({
"apiKey": {
"type": "string",
"description": "The api key to use",
"required": true,
"modelGenerated": false,
"userConfigured": true
},
"query": {
"type": "string",
"description": "The query to search",
"required": true
},
"count": {
"type": "integer",
"description": "The number of excerpts to return",
"required": true,
"modelGenerated": false
}
})";
QJsonDocument braveJsonDoc = QJsonDocument::fromJson(braveParamSchema.toUtf8());
Q_ASSERT(!braveJsonDoc.isNull() && braveJsonDoc.isObject());
braveSearch->paramSchema = braveJsonDoc.object();
m_tools.append(braveSearch);
m_toolMap.insert(braveSearch->function, braveSearch);
}
bool ToolModel::eventFilter(QObject *obj, QEvent *ev)
{
if (obj == QCoreApplication::instance() && ev->type() == QEvent::LanguageChange)
emit dataChanged(index(0, 0), index(m_tools.size() - 1, 0));
return false;
}

112
gpt4all-chat/toolmodel.h Normal file
View File

@ -0,0 +1,112 @@
#ifndef TOOLMODEL_H
#define TOOLMODEL_H
#include "tool.h"
#include <QAbstractListModel>
class ToolModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
static ToolModel *globalInstance();
enum Roles {
NameRole = Qt::UserRole + 1,
DescriptionRole,
FunctionRole,
ParametersRole,
UrlRole,
ApiKeyRole,
KeyRequiredRole,
IsEnabledRole,
IsBuiltinRole,
ForceUsageRole,
ExcerptsRole,
};
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
Q_UNUSED(parent)
return m_tools.size();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
if (!index.isValid() || index.row() < 0 || index.row() >= m_tools.size())
return QVariant();
const Tool *item = m_tools.at(index.row());
switch (role) {
case NameRole:
return item->name;
case DescriptionRole:
return item->description;
case FunctionRole:
return item->function;
case ParametersRole:
return item->paramSchema;
case UrlRole:
return item->url;
case IsEnabledRole:
return item->isEnabled;
case IsBuiltinRole:
return item->isBuiltin;
case ForceUsageRole:
return item->forceUsage;
case ExcerptsRole:
return item->excerpts;
}
return QVariant();
}
QHash<int, QByteArray> roleNames() const override
{
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[DescriptionRole] = "description";
roles[FunctionRole] = "function";
roles[ParametersRole] = "parameters";
roles[UrlRole] = "url";
roles[ApiKeyRole] = "apiKey";
roles[KeyRequiredRole] = "keyRequired";
roles[IsEnabledRole] = "isEnabled";
roles[IsBuiltinRole] = "isBuiltin";
roles[ForceUsageRole] = "forceUsage";
roles[ExcerptsRole] = "excerpts";
return roles;
}
Q_INVOKABLE Tool* get(int index) const
{
if (index < 0 || index >= m_tools.size()) return nullptr;
return m_tools.at(index);
}
Q_INVOKABLE Tool *get(const QString &id) const
{
if (!m_toolMap.contains(id)) return nullptr;
return m_toolMap.value(id);
}
int count() const { return m_tools.size(); }
Q_SIGNALS:
void countChanged();
void valueChanged(int index, const QString &value);
protected:
bool eventFilter(QObject *obj, QEvent *ev) override;
private:
explicit ToolModel();
~ToolModel() {}
friend class MyToolModel;
QList<Tool*> m_tools;
QHash<QString, Tool*> m_toolMap;
};
#endif // TOOLMODEL_H