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); worker.request(apiKey, query, count);
}); });
workerThread.start(); 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.quit();
workerThread.wait(); workerThread.wait();
return worker.response(); 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); 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 // This parses the response from brave and formats it in json that conforms to the de facto
// standard in SourceExcerpts::fromJson(...) // standard in SourceExcerpts::fromJson(...)
QJsonParseError err; QJsonParseError err;
QJsonDocument document = QJsonDocument::fromJson(jsonResponse, &err); QJsonDocument document = QJsonDocument::fromJson(jsonResponse, &err);
if (err.error != QJsonParseError::NoError) { 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(); return QString();
} }
@ -147,7 +155,6 @@ void BraveAPIWorker::handleFinished()
m_response = cleanBraveResponse(jsonData); m_response = cleanBraveResponse(jsonData);
} else { } else {
QByteArray jsonData = jsonReply->readAll(); QByteArray jsonData = jsonReply->readAll();
qWarning() << "ERROR: Could not search brave" << jsonReply->error() << jsonReply->errorString() << jsonData;
jsonReply->deleteLater(); jsonReply->deleteLater();
} }
emit finished(); emit finished();
@ -157,7 +164,7 @@ void BraveAPIWorker::handleErrorOccurred(QNetworkReply::NetworkError code)
{ {
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender()); QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
Q_ASSERT(reply); Q_ASSERT(reply);
qWarning().noquote() << "ERROR: BraveAPIWorker::handleErrorOccurred got HTTP Error" << code << "response:" m_error = ToolEnums::Error::UnknownError;
<< reply->errorString(); m_errorString = QString(tr("ERROR: brave search code: %1 response: %2")).arg(code).arg(reply->errorString());
emit finished(); emit finished();
} }

View File

@ -17,6 +17,8 @@ public:
virtual ~BraveAPIWorker() {} virtual ~BraveAPIWorker() {}
QString response() const { return m_response; } QString response() const { return m_response; }
ToolEnums::Error error() const { return m_error; }
QString errorString() const { return m_errorString; }
public Q_SLOTS: public Q_SLOTS:
void request(const QString &apiKey, const QString &query, int count); void request(const QString &apiKey, const QString &query, int count);
@ -25,21 +27,30 @@ Q_SIGNALS:
void finished(); void finished();
private Q_SLOTS: private Q_SLOTS:
QString cleanBraveResponse(const QByteArray& jsonResponse);
void handleFinished(); void handleFinished();
void handleErrorOccurred(QNetworkReply::NetworkError code); void handleErrorOccurred(QNetworkReply::NetworkError code);
private: private:
QNetworkAccessManager *m_networkManager; QNetworkAccessManager *m_networkManager;
QString m_response; QString m_response;
ToolEnums::Error m_error;
QString m_errorString;
}; };
class BraveSearch : public Tool { class BraveSearch : public Tool {
Q_OBJECT Q_OBJECT
public: public:
BraveSearch() : Tool() {} BraveSearch() : Tool(), m_error(ToolEnums::Error::NoError) {}
virtual ~BraveSearch() {} virtual ~BraveSearch() {}
QString run(const QJsonObject &parameters, qint64 timeout = 2000) override; 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 #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 // 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 // according to MySettings, but for now only brave search is supported
if (tool != "brave_search" || !args.contains("query")) { 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; qWarning() << "ERROR: Could not find the tool and correct parameters for " << toolCall;
return handleFailedToolCall(trimmed, elapsed); return handleFailedToolCall(trimmed, elapsed);
} }
@ -887,7 +888,7 @@ bool ChatLLM::promptInternal(const QList<QString> &collectionList, const QString
parameters.insert("query", query); parameters.insert("query", query);
parameters.insert("count", 2); 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*/); const QString braveResponse = brave.run(parameters, 2000 /*msecs to timeout*/);
QString parseError; QString parseError;

View File

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

View File

@ -27,10 +27,16 @@ private:
class LocalDocsSearch : public Tool { class LocalDocsSearch : public Tool {
Q_OBJECT Q_OBJECT
public: public:
LocalDocsSearch() : Tool() {} LocalDocsSearch() : Tool(), m_error(ToolEnums::Error::NoError) {}
virtual ~LocalDocsSearch() {} virtual ~LocalDocsSearch() {}
QString run(const QJsonObject &parameters, qint64 timeout = 2000) override; 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 #endif // LOCALDOCSSEARCH_H

View File

@ -9,15 +9,20 @@ using namespace Qt::Literals::StringLiterals;
namespace ToolEnums { namespace ToolEnums {
Q_NAMESPACE Q_NAMESPACE
enum class ConnectionType { enum class ConnectionType {
Builtin = 0, // A built-in tool with bespoke connection type BuiltinConnection = 0, // A built-in tool with bespoke connection type
Local = 1, // Starts a local process and communicates via stdin/stdout/stderr LocalConnection = 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 LocalServerConnection = 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 RemoteConnection = 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 RemoteServerConnection = 4 // Connects to an existing remote process and communicates via some networking protocol TBD
}; };
Q_ENUM_NS(ConnectionType) Q_ENUM_NS(ConnectionType)
enum class Error {
NoError = 0,
TimeoutError = 2,
UnknownError = 499,
};
} }
using namespace ToolEnums;
struct ToolInfo { struct ToolInfo {
Q_GADGET Q_GADGET
@ -25,14 +30,14 @@ struct ToolInfo {
Q_PROPERTY(QString description MEMBER description) Q_PROPERTY(QString description MEMBER description)
Q_PROPERTY(QJsonObject parameters MEMBER parameters) Q_PROPERTY(QJsonObject parameters MEMBER parameters)
Q_PROPERTY(bool isEnabled MEMBER isEnabled) Q_PROPERTY(bool isEnabled MEMBER isEnabled)
Q_PROPERTY(ConnectionType connectionType MEMBER connectionType) Q_PROPERTY(ToolEnums::ConnectionType connectionType MEMBER connectionType)
public: public:
QString name; QString name;
QString description; QString description;
QJsonObject parameters; QJsonObject parameters;
bool isEnabled; bool isEnabled;
ConnectionType connectionType; ToolEnums::ConnectionType connectionType;
// FIXME: Should we go with essentially the OpenAI/ollama consensus for these tool // 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: // 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) {} Tool() : QObject(nullptr) {}
virtual ~Tool() {} virtual ~Tool() {}
// FIXME: How to handle errors?
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 QString errorString() const { return QString(); }
}; };
#endif // TOOL_H #endif // TOOL_H