Move the jinja processing to mysettings and validation.

Signed-off-by: Adam Treat <treat.adam@gmail.com>
This commit is contained in:
Adam Treat 2024-08-13 13:23:12 -04:00
parent 227dbfd18b
commit 48117cda46
5 changed files with 86 additions and 126 deletions

View File

@ -31,7 +31,6 @@
#include <cmath>
#include <cstddef>
#include <functional>
#include <jinja2cpp/template.h>
#include <limits>
#include <optional>
#include <string_view>
@ -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;

View File

@ -1,6 +1,8 @@
#include "mysettings.h"
#include "../gpt4all-backend/llmodel.h"
#include "tool.h"
#include "toolmodel.h"
#include <QDebug>
#include <QDir>
@ -18,6 +20,7 @@
#include <QtLogging>
#include <algorithm>
#include <jinja2cpp/template.h>
#include <string>
#include <thread>
#include <vector>
@ -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;
}

View File

@ -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

View File

@ -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
}

View File

@ -1,95 +0,0 @@
#ifndef SOURCEEXCERT_H
#define SOURCEEXCERT_H
#include <QObject>
#include <QJsonObject>
#include <QFileInfo>
#include <QUrl>
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