Implement error handling for tool calls.

Signed-off-by: Adam Treat <treat.adam@gmail.com>
This commit is contained in:
Adam Treat 2024-08-07 10:44:52 -04:00
parent 5fc2ff8e69
commit 244b82622c
6 changed files with 55 additions and 18 deletions

View File

@ -29,7 +29,14 @@ QString BraveSearch::run(const QJsonObject &parameters, qint64 timeout)
worker.request(apiKey, query, count);
});
workerThread.start();
workerThread.wait(timeout);
bool timedOut = !workerThread.wait(timeout);
if (timedOut) {
m_error = ToolEnums::Error::TimeoutError;
m_errorString = tr("ERROR: brave search timeout");
} else {
m_error = worker.error();
m_errorString = worker.errorString();
}
workerThread.quit();
workerThread.wait();
return worker.response();
@ -62,14 +69,15 @@ void BraveAPIWorker::request(const QString &apiKey, const QString &query, int co
connect(reply, &QNetworkReply::errorOccurred, this, &BraveAPIWorker::handleErrorOccurred);
}
static QString cleanBraveResponse(const QByteArray& jsonResponse)
QString BraveAPIWorker::cleanBraveResponse(const QByteArray& jsonResponse)
{
// This parses the response from brave and formats it in json that conforms to the de facto
// standard in SourceExcerpts::fromJson(...)
QJsonParseError err;
QJsonDocument document = QJsonDocument::fromJson(jsonResponse, &err);
if (err.error != QJsonParseError::NoError) {
qWarning() << "ERROR: Couldn't parse brave response: " << jsonResponse << err.errorString();
m_error = ToolEnums::Error::UnknownError;
m_errorString = QString(tr("ERROR: brave search could not parse json response: %1")).arg(jsonResponse);
return QString();
}
@ -147,7 +155,6 @@ void BraveAPIWorker::handleFinished()
m_response = cleanBraveResponse(jsonData);
} else {
QByteArray jsonData = jsonReply->readAll();
qWarning() << "ERROR: Could not search brave" << jsonReply->error() << jsonReply->errorString() << jsonData;
jsonReply->deleteLater();
}
emit finished();
@ -157,7 +164,7 @@ void BraveAPIWorker::handleErrorOccurred(QNetworkReply::NetworkError code)
{
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
Q_ASSERT(reply);
qWarning().noquote() << "ERROR: BraveAPIWorker::handleErrorOccurred got HTTP Error" << code << "response:"
<< reply->errorString();
m_error = ToolEnums::Error::UnknownError;
m_errorString = QString(tr("ERROR: brave search code: %1 response: %2")).arg(code).arg(reply->errorString());
emit finished();
}

View File

@ -17,6 +17,8 @@ public:
virtual ~BraveAPIWorker() {}
QString response() const { return m_response; }
ToolEnums::Error error() const { return m_error; }
QString errorString() const { return m_errorString; }
public Q_SLOTS:
void request(const QString &apiKey, const QString &query, int count);
@ -25,21 +27,30 @@ Q_SIGNALS:
void finished();
private Q_SLOTS:
QString cleanBraveResponse(const QByteArray& jsonResponse);
void handleFinished();
void handleErrorOccurred(QNetworkReply::NetworkError code);
private:
QNetworkAccessManager *m_networkManager;
QString m_response;
ToolEnums::Error m_error;
QString m_errorString;
};
class BraveSearch : public Tool {
Q_OBJECT
public:
BraveSearch() : Tool() {}
BraveSearch() : Tool(), m_error(ToolEnums::Error::NoError) {}
virtual ~BraveSearch() {}
QString run(const QJsonObject &parameters, qint64 timeout = 2000) override;
ToolEnums::Error error() const override { return m_error; }
QString errorString() const override { return m_errorString; }
private:
ToolEnums::Error m_error;
QString m_errorString;
};
#endif // BRAVESEARCH_H

View File

@ -871,6 +871,7 @@ bool ChatLLM::promptInternal(const QList<QString> &collectionList, const QString
// FIXME: In the future this will try to match the tool call to a list of tools that are supported
// according to MySettings, but for now only brave search is supported
if (tool != "brave_search" || !args.contains("query")) {
// FIXME: Need to surface errors to the UI
qWarning() << "ERROR: Could not find the tool and correct parameters for " << toolCall;
return handleFailedToolCall(trimmed, elapsed);
}
@ -887,7 +888,7 @@ bool ChatLLM::promptInternal(const QList<QString> &collectionList, const QString
parameters.insert("query", query);
parameters.insert("count", 2);
// FIXME: This has to handle errors of the tool call
// FIXME: Need to surface errors to the UI
const QString braveResponse = brave.run(parameters, 2000 /*msecs to timeout*/);
QString parseError;

View File

@ -27,6 +27,12 @@ QString LocalDocsSearch::run(const QJsonObject &parameters, qint64 timeout)
worker.request(collections, text, count);
});
workerThread.start();
bool timedOut = !workerThread.wait(timeout);
if (timedOut) {
m_error = ToolEnums::Error::TimeoutError;
m_errorString = tr("ERROR: localdocs timeout");
}
workerThread.wait(timeout);
workerThread.quit();
workerThread.wait();

View File

@ -27,10 +27,16 @@ private:
class LocalDocsSearch : public Tool {
Q_OBJECT
public:
LocalDocsSearch() : Tool() {}
LocalDocsSearch() : Tool(), m_error(ToolEnums::Error::NoError) {}
virtual ~LocalDocsSearch() {}
QString run(const QJsonObject &parameters, qint64 timeout = 2000) override;
ToolEnums::Error error() const override { return m_error; }
QString errorString() const override { return m_errorString; }
private:
ToolEnums::Error m_error;
QString m_errorString;
};
#endif // LOCALDOCSSEARCH_H

View File

@ -9,15 +9,20 @@ using namespace Qt::Literals::StringLiterals;
namespace ToolEnums {
Q_NAMESPACE
enum class ConnectionType {
Builtin = 0, // A built-in tool with bespoke connection type
Local = 1, // Starts a local process and communicates via stdin/stdout/stderr
LocalServer = 2, // Connects to an existing local process and communicates via stdin/stdout/stderr
Remote = 3, // Starts a remote process and communicates via some networking protocol TBD
RemoteServer = 4 // Connects to an existing remote process and communicates via some networking protocol TBD
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 {
NoError = 0,
TimeoutError = 2,
UnknownError = 499,
};
}
using namespace ToolEnums;
struct ToolInfo {
Q_GADGET
@ -25,14 +30,14 @@ struct ToolInfo {
Q_PROPERTY(QString description MEMBER description)
Q_PROPERTY(QJsonObject parameters MEMBER parameters)
Q_PROPERTY(bool isEnabled MEMBER isEnabled)
Q_PROPERTY(ConnectionType connectionType MEMBER connectionType)
Q_PROPERTY(ToolEnums::ConnectionType connectionType MEMBER connectionType)
public:
QString name;
QString description;
QJsonObject parameters;
bool isEnabled;
ConnectionType connectionType;
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:
@ -64,8 +69,9 @@ public:
Tool() : QObject(nullptr) {}
virtual ~Tool() {}
// FIXME: How to handle errors?
virtual QString run(const QJsonObject &parameters, qint64 timeout = 2000) = 0;
virtual ToolEnums::Error error() const { return ToolEnums::Error::NoError; }
virtual QString errorString() const { return QString(); }
};
#endif // TOOL_H