From 48117cda46ceb75c4ef0f1871b180eef8797829a Mon Sep 17 00:00:00 2001 From: Adam Treat Date: Tue, 13 Aug 2024 13:23:12 -0400 Subject: [PATCH] Move the jinja processing to mysettings and validation. Signed-off-by: Adam Treat --- gpt4all-chat/chatllm.cpp | 31 ++-------- gpt4all-chat/mysettings.cpp | 45 ++++++++++++++ gpt4all-chat/mysettings.h | 6 ++ gpt4all-chat/qml/ModelSettings.qml | 35 +++++++++-- gpt4all-chat/toolinfo.h | 95 ------------------------------ 5 files changed, 86 insertions(+), 126 deletions(-) delete mode 100644 gpt4all-chat/toolinfo.h diff --git a/gpt4all-chat/chatllm.cpp b/gpt4all-chat/chatllm.cpp index 5a0e03ff..00bf86a7 100644 --- a/gpt4all-chat/chatllm.cpp +++ b/gpt4all-chat/chatllm.cpp @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -1337,34 +1336,14 @@ void ChatLLM::processSystemPrompt() if (!isModelLoaded() || m_processedSystemPrompt || m_restoreStateFromText || m_isServer) return; - const std::string systemPromptTemplate = MySettings::globalInstance()->modelSystemPromptTemplate(m_modelInfo).toStdString(); - - // FIXME: This needs to be moved to settings probably and the same code used for validation - jinja2::ValuesMap params; - params.insert({"currentDate", QDate::currentDate().toString().toStdString()}); - - jinja2::ValuesList toolList; - int c = ToolModel::globalInstance()->count(); - for (int i = 0; i < c; ++i) { - Tool *t = ToolModel::globalInstance()->get(i); - if (t->usageMode() == ToolEnums::UsageMode::Enabled) - toolList.push_back(t->jinjaValue()); - } - params.insert({"toolList", toolList}); - - std::string systemPrompt; - - jinja2::Template t; - t.Load(systemPromptTemplate); - const auto renderResult = t.RenderAsString(params); + QString error; + const std::string systemPrompt = MySettings::globalInstance()->modelSystemPrompt(m_modelInfo, error).toStdString(); // The GUI should not allow setting an improper template, but it is always possible someone hand // edits the settings file to produce an improper one. - Q_ASSERT(renderResult); - if (renderResult) - systemPrompt = renderResult.value(); - else - qWarning() << "ERROR: Could not parse system prompt template:" << renderResult.error().ToString(); + Q_ASSERT(error.isEmpty()); + if (!error.isEmpty()) + qWarning() << "ERROR: Could not parse system prompt template:" << error; if (QString::fromStdString(systemPrompt).trimmed().isEmpty()) { m_processedSystemPrompt = true; diff --git a/gpt4all-chat/mysettings.cpp b/gpt4all-chat/mysettings.cpp index b9bb76b5..0d642979 100644 --- a/gpt4all-chat/mysettings.cpp +++ b/gpt4all-chat/mysettings.cpp @@ -1,6 +1,8 @@ #include "mysettings.h" #include "../gpt4all-backend/llmodel.h" +#include "tool.h" +#include "toolmodel.h" #include #include @@ -18,6 +20,7 @@ #include #include +#include #include #include #include @@ -676,3 +679,45 @@ void MySettings::setLanguageAndLocale(const QString &bcp47Name) QLocale::setDefault(locale); emit languageAndLocaleChanged(); } + +QString MySettings::validateModelSystemPromptTemplate(const QString &proposedTemplate) +{ + QString error; + systemPromptInternal(proposedTemplate, error); + return error; +} + +QString MySettings::modelSystemPrompt(const ModelInfo &info, QString &error) +{ + return systemPromptInternal(modelSystemPromptTemplate(info), error); +} + +QString MySettings::systemPromptInternal(const QString &proposedTemplate, QString &error) +{ + jinja2::ValuesMap params; + params.insert({"currentDate", QDate::currentDate().toString().toStdString()}); + + jinja2::ValuesList toolList; + int c = ToolModel::globalInstance()->count(); + for (int i = 0; i < c; ++i) { + Tool *t = ToolModel::globalInstance()->get(i); + if (t->usageMode() == ToolEnums::UsageMode::Enabled) + toolList.push_back(t->jinjaValue()); + } + params.insert({"toolList", toolList}); + + QString systemPrompt; + jinja2::Template t; + const auto loadResult = t.Load(proposedTemplate.toStdString(), "systemPromptTemplate" /*Used in error messages*/); + if (!loadResult) { + error = QString::fromStdString(loadResult.error().ToString()); + return systemPrompt; + } + + const auto renderResult = t.RenderAsString(params); + if (renderResult) + systemPrompt = QString::fromStdString(renderResult.value()); + else + error = QString::fromStdString(renderResult.error().ToString()); + return systemPrompt; +} diff --git a/gpt4all-chat/mysettings.h b/gpt4all-chat/mysettings.h index 09da6681..6e1e8056 100644 --- a/gpt4all-chat/mysettings.h +++ b/gpt4all-chat/mysettings.h @@ -204,6 +204,10 @@ public: int networkPort() const; void setNetworkPort(int value); + // Jinja aware methods for validating and parsing/rendering the system prompt + Q_INVOKABLE QString validateModelSystemPromptTemplate(const QString &proposedTemplate); + QString modelSystemPrompt(const ModelInfo &info, QString &error); + Q_SIGNALS: void nameChanged(const ModelInfo &info); void filenameChanged(const ModelInfo &info); @@ -269,6 +273,8 @@ private: void setModelSetting(const QString &name, const ModelInfo &info, const QVariant &value, bool force, bool signal = false); QString filePathForLocale(const QLocale &locale); + QString systemPromptInternal(const QString &proposedTemplate, QString &error); + }; #endif // MYSETTINGS_H diff --git a/gpt4all-chat/qml/ModelSettings.qml b/gpt4all-chat/qml/ModelSettings.qml index d2f47976..1f96807d 100644 --- a/gpt4all-chat/qml/ModelSettings.qml +++ b/gpt4all-chat/qml/ModelSettings.qml @@ -153,13 +153,30 @@ MySettingsTab { Layout.fillWidth: true } - MySettingsLabel { - visible: !root.currentModelInfo.isOnline - text: qsTr("System Prompt") - helpText: qsTr("Prefixed at the beginning of every conversation. Must contain the appropriate framing tokens.") + RowLayout { Layout.row: 7 Layout.column: 0 + Layout.columnSpan: 2 Layout.topMargin: 15 + spacing: 10 + MySettingsLabel { + text: qsTr("System Prompt Template") + helpText: qsTr("Prefixed at the beginning of every conversation. Must contain the appropriate framing tokens.") + } + MySettingsLabel { + id: systemPromptTemplateError + color: theme.textErrorColor + wrapMode: TextArea.Wrap + Timer { + id: errorTimer + interval: 500 // 500 ms delay + repeat: false + property string text: "" + onTriggered: { + systemPromptTemplateError.text = errorTimer.text; + } + } + } } Rectangle { @@ -188,7 +205,15 @@ MySettingsTab { } } onTextChanged: { - MySettings.setModelSystemPromptTemplate(root.currentModelInfo, text) + var errorString = MySettings.validateModelSystemPromptTemplate(text); + if (errorString === "") { + errorTimer.stop(); + systemPromptTemplateError.text = ""; // Clear any previous error + MySettings.setModelSystemPromptTemplate(root.currentModelInfo, text); + } else { + errorTimer.text = errorString; + errorTimer.restart(); + } } Accessible.role: Accessible.EditableText } diff --git a/gpt4all-chat/toolinfo.h b/gpt4all-chat/toolinfo.h deleted file mode 100644 index 91497e9d..00000000 --- a/gpt4all-chat/toolinfo.h +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef SOURCEEXCERT_H -#define SOURCEEXCERT_H - -#include -#include -#include -#include - -using namespace Qt::Literals::StringLiterals; - -struct SourceExcerpt { - Q_GADGET - Q_PROPERTY(QString date MEMBER date) - Q_PROPERTY(QString text MEMBER text) - Q_PROPERTY(QString collection MEMBER collection) - Q_PROPERTY(QString path MEMBER path) - Q_PROPERTY(QString file MEMBER file) - Q_PROPERTY(QString url MEMBER url) - Q_PROPERTY(QString favicon MEMBER favicon) - Q_PROPERTY(QString title MEMBER title) - Q_PROPERTY(QString author MEMBER author) - Q_PROPERTY(int page MEMBER page) - Q_PROPERTY(int from MEMBER from) - Q_PROPERTY(int to MEMBER to) - Q_PROPERTY(QString fileUri READ fileUri STORED false) - -public: - QString date; // [Required] The creation or the last modification date whichever is latest - QString text; // [Required] The text actually used in the augmented context - QString collection; // [Optional] The name of the collection - QString path; // [Optional] The full path - QString file; // [Optional] The name of the file, but not the full path - QString url; // [Optional] The name of the remote url - QString favicon; // [Optional] The favicon - QString title; // [Optional] The title of the document - QString author; // [Optional] The author of the document - int page = -1; // [Optional] The page where the text was found - int from = -1; // [Optional] The line number where the text begins - int to = -1; // [Optional] The line number where the text ends - - QString fileUri() const { - // QUrl reserved chars that are not UNSAFE_PATH according to glib/gconvert.c - static const QByteArray s_exclude = "!$&'()*+,/:=@~"_ba; - - Q_ASSERT(!QFileInfo(path).isRelative()); -#ifdef Q_OS_WINDOWS - Q_ASSERT(!path.contains('\\')); // Qt normally uses forward slash as path separator -#endif - - auto escaped = QString::fromUtf8(QUrl::toPercentEncoding(path, s_exclude)); - if (escaped.front() != '/') - escaped = '/' + escaped; - return u"file://"_s + escaped; - } - - QJsonObject toJson() const - { - QJsonObject result; - result.insert("date", date); - result.insert("text", text); - result.insert("collection", collection); - result.insert("path", path); - result.insert("file", file); - result.insert("url", url); - result.insert("favicon", favicon); - result.insert("title", title); - result.insert("author", author); - result.insert("page", page); - result.insert("from", from); - result.insert("to", to); - return result; - } - - bool operator==(const SourceExcerpt &other) const { - return date == other.date && - text == other.text && - collection == other.collection && - path == other.path && - file == other.file && - url == other.url && - favicon == other.favicon && - title == other.title && - author == other.author && - page == other.page && - from == other.from && - to == other.to; - } - bool operator!=(const SourceExcerpt &other) const { - return !(*this == other); - } -}; - -Q_DECLARE_METATYPE(SourceExcerpt) - -#endif // SOURCEEXCERT_H