diff --git a/src/base/ai/abstractllm.h b/src/base/ai/abstractllm.h index 2117146ad..7a762281e 100644 --- a/src/base/ai/abstractllm.h +++ b/src/base/ai/abstractllm.h @@ -25,6 +25,7 @@ class AbstractLLM : public QObject explicit AbstractLLM(QObject *parent = nullptr); virtual ~AbstractLLM() {} + virtual QString modelName() const = 0; virtual QString modelPath() const = 0; virtual bool checkValid(QString *errStr) = 0; virtual QJsonObject create(const Conversation &conversation) = 0; @@ -37,6 +38,7 @@ class AbstractLLM : public QObject virtual void cancel() = 0; virtual void setMaxTokens(int maxToken) = 0; virtual Conversation *getCurrentConversation() = 0; + virtual bool isIdle() = 0; signals: void dataReceived(const QString &data, ResponseState statu); diff --git a/src/common/util/eventdefinitions.h b/src/common/util/eventdefinitions.h index 0ddbef114..8cd6f5682 100644 --- a/src/common/util/eventdefinitions.h +++ b/src/common/util/eventdefinitions.h @@ -133,6 +133,9 @@ OPI_OBJECT(session, OPI_INTERFACE(sessionRenamed, "oldName", "newName") OPI_INTERFACE(sessionRemoved, "session") ) +OPI_OBJECT(ai, + OPI_INTERFACE(LLMChanged) + ) struct AnalysedData { diff --git a/src/plugins/aimanager/aimanager.cpp b/src/plugins/aimanager/aimanager.cpp index ea3800ebc..14b83fb91 100644 --- a/src/plugins/aimanager/aimanager.cpp +++ b/src/plugins/aimanager/aimanager.cpp @@ -7,6 +7,7 @@ #include "openai/openaicompatiblellm.h" #include "services/option/optionmanager.h" #include "option/detailwidget.h" +#include "common/util/eventdefinitions.h" #include @@ -83,10 +84,22 @@ void AiManager::removeModel(const LLMInfo &info) void AiManager::readLLMFromOption() { + auto currentModels = d->models; + bool changed = false; d->models.clear(); + QMap map = OptionManager::getInstance()->getValue(kCATEGORY_CUSTOMMODELS, kCATEGORY_OPTIONKEY).toMap(); auto LLMs = map.value(kCATEGORY_CUSTOMMODELS); + if (LLMs.toList().size() != currentModels.size()) + changed = true; + for (auto llmInfo : LLMs.toList()) { - appendModel(LLMInfo::fromVariantMap(llmInfo.toMap())); + LLMInfo info = LLMInfo::fromVariantMap(llmInfo.toMap()); + if (!currentModels.contains(info)) + changed = true; + appendModel(info); } + + if (changed) + ai.LLMChanged(); } diff --git a/src/plugins/aimanager/openai/openaicompatibleconversation.cpp b/src/plugins/aimanager/openai/openaicompatibleconversation.cpp index 147552a1a..befb47834 100644 --- a/src/plugins/aimanager/openai/openaicompatibleconversation.cpp +++ b/src/plugins/aimanager/openai/openaicompatibleconversation.cpp @@ -19,6 +19,9 @@ QJsonObject OpenAiCompatibleConversation::parseContentString(const QString &cont QRegularExpressionMatchIterator iter = regex.globalMatch(content); QString finishReason = ""; + QJsonObject functionCall; + QMap toolCallMaps; + while (iter.hasNext()) { QRegularExpressionMatch match = iter.next(); QString matchString = match.captured(0); @@ -42,6 +45,52 @@ QJsonObject OpenAiCompatibleConversation::parseContentString(const QString &cont const QString &deltaData = delta["content"].toString(); deltacontent += deltaData; } + if (delta.contains("function_call")) { + const QJsonObject &function_call = delta.value("function_call").toObject(); + if (function_call.contains("name")) { + functionCall["name"] = functionCall["name"].toString() + function_call.value("name").toString(); + } + + if (function_call.contains("arguments")) { + functionCall["arguments"] = functionCall["arguments"].toString() + function_call.value("arguments").toString(); + } + } + + if (delta.contains("tool_calls")) { + const QJsonArray &tool_calls = delta.value("tool_calls").toArray(); + for (const QJsonValue &tool_call : tool_calls) { + const QJsonObject &toolCallObj = tool_call.toObject(); + + int index = toolCallObj["index"].toInt(); + if (!toolCallMaps[index].contains("function")) { + toolCallMaps[index]["function"] = QJsonObject(); + } + + toolCallMaps[index]["index"] = index; + + if (toolCallObj.contains("id")) { + toolCallMaps[index]["id"] = toolCallObj.value("id"); + } + + if (toolCallObj.contains("type")) { + toolCallMaps[index]["type"] = toolCallObj.value("type"); + } + + if (toolCallObj.contains("function")) { + QJsonObject toolFun = toolCallMaps[index]["function"].toObject(); + const QJsonObject &tmpToolFun = toolCallObj.value("function").toObject(); + if (tmpToolFun.contains("name")) { + toolFun["name"] = toolFun["name"].toString() + tmpToolFun.value("name").toString(); + } + + if (tmpToolFun.contains("arguments")) { + toolFun["arguments"] = toolFun["arguments"].toString() + tmpToolFun.value("arguments").toString(); + } + + toolCallMaps[index]["function"] = toolFun; + } + } + } } else if (cj.contains("text")) { deltacontent += cj["text"].toString(); } @@ -55,6 +104,22 @@ QJsonObject OpenAiCompatibleConversation::parseContentString(const QString &cont response["content"] = deltacontent; } + QJsonObject tools; + if (!functionCall.isEmpty()) { + tools["function_call"] = functionCall; + response["tools"] = tools; + } + + QJsonArray toolCalls; + for (auto iter = toolCallMaps.begin(); iter != toolCallMaps.end(); iter++) { + toolCalls << iter.value(); + } + + if (!toolCalls.isEmpty()) { + tools["tool_calls"] = toolCalls; + response["tools"] = tools; + } + if (!finishReason.isEmpty()) response["finish_reason"] = finishReason; diff --git a/src/plugins/aimanager/openai/openaicompatiblellm.cpp b/src/plugins/aimanager/openai/openaicompatiblellm.cpp index fe0a8a85d..9cb510b9c 100644 --- a/src/plugins/aimanager/openai/openaicompatiblellm.cpp +++ b/src/plugins/aimanager/openai/openaicompatiblellm.cpp @@ -119,6 +119,11 @@ OpenAiCompatibleLLM::~OpenAiCompatibleLLM() delete d; } +QString OpenAiCompatibleLLM::modelName() const +{ + return d->modelName; +} + QString OpenAiCompatibleLLM::modelPath() const { return d->modelPath; @@ -196,6 +201,9 @@ QJsonObject OpenAiCompatibleLLM::create(const Conversation &conversation) void OpenAiCompatibleLLM::request(const QJsonObject &data) { + if (d->waitingResponse) + return; + QByteArray body = QJsonDocument(data).toJson(); d->httpResult.clear(); d->waitingResponse = true; @@ -339,3 +347,8 @@ void OpenAiCompatibleLLM::setMaxTokens(int maxTokens) { d->maxTokens = maxTokens; } + +bool OpenAiCompatibleLLM::isIdle() +{ + return !d->waitingResponse; +} diff --git a/src/plugins/aimanager/openai/openaicompatiblellm.h b/src/plugins/aimanager/openai/openaicompatiblellm.h index fd35b0685..b83becd38 100644 --- a/src/plugins/aimanager/openai/openaicompatiblellm.h +++ b/src/plugins/aimanager/openai/openaicompatiblellm.h @@ -20,6 +20,7 @@ class OpenAiCompatibleLLM : public AbstractLLM void setModelPath(const QString &path); void setApiKey(const QString &apiKey); + QString modelName() const override; QString modelPath() const override; bool checkValid(QString *errStr) override; QJsonObject create(const Conversation &conversation) override; @@ -31,6 +32,7 @@ class OpenAiCompatibleLLM : public AbstractLLM void processResponse(QNetworkReply *reply) override; void cancel() override; void setMaxTokens(int maxTokens) override; + bool isIdle() override; signals: void requstCancel();