mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-05-30 02:49:12 -04:00
fix: Add checking model support for tool calling (#350)
This commit is contained in:
@@ -15,6 +15,7 @@ public:
|
||||
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::Chat; }
|
||||
QString name() const override { return "Claude"; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
bool supportsToolHistory() const override { return true; }
|
||||
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
@@ -24,9 +25,48 @@ public:
|
||||
}
|
||||
|
||||
if (context.history) {
|
||||
int toolResultUserIdx = -1;
|
||||
for (const auto &msg : context.history.value()) {
|
||||
if (msg.role == "system") continue;
|
||||
|
||||
|
||||
if (!msg.toolCalls.isEmpty()) {
|
||||
toolResultUserIdx = -1;
|
||||
QJsonArray content;
|
||||
if (!msg.content.isEmpty()) {
|
||||
content.append(QJsonObject{{"type", "text"}, {"text", msg.content}});
|
||||
}
|
||||
for (const auto &call : msg.toolCalls) {
|
||||
content.append(QJsonObject{
|
||||
{"type", "tool_use"},
|
||||
{"id", call.id},
|
||||
{"name", call.name},
|
||||
{"input", call.arguments}});
|
||||
}
|
||||
messages.append(QJsonObject{{"role", "assistant"}, {"content", content}});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msg.role == "tool") {
|
||||
QJsonObject resultBlock{
|
||||
{"type", "tool_result"},
|
||||
{"tool_use_id", msg.toolCallId},
|
||||
{"content", msg.content}};
|
||||
if (toolResultUserIdx >= 0) {
|
||||
QJsonObject userMsg = messages[toolResultUserIdx].toObject();
|
||||
QJsonArray content = userMsg["content"].toArray();
|
||||
content.append(resultBlock);
|
||||
userMsg["content"] = content;
|
||||
messages[toolResultUserIdx] = userMsg;
|
||||
} else {
|
||||
messages.append(QJsonObject{
|
||||
{"role", "user"}, {"content", QJsonArray{resultBlock}}});
|
||||
toolResultUserIdx = messages.size() - 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
toolResultUserIdx = -1;
|
||||
|
||||
if (msg.isThinking) {
|
||||
// Claude API requires signature for thinking blocks
|
||||
if (msg.signature.isEmpty()) {
|
||||
|
||||
@@ -16,6 +16,7 @@ public:
|
||||
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::Chat; }
|
||||
QString name() const override { return "Google AI"; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
bool supportsToolHistory() const override { return true; }
|
||||
|
||||
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
@@ -26,7 +27,45 @@ public:
|
||||
{"parts", QJsonObject{{"text", context.systemPrompt.value()}}}};
|
||||
}
|
||||
|
||||
int toolResultIdx = -1;
|
||||
for (const auto &msg : context.history.value()) {
|
||||
if (!msg.toolCalls.isEmpty()) {
|
||||
toolResultIdx = -1;
|
||||
QJsonArray callParts;
|
||||
if (!msg.content.isEmpty()) {
|
||||
callParts.append(QJsonObject{{"text", msg.content}});
|
||||
}
|
||||
for (const auto &call : msg.toolCalls) {
|
||||
callParts.append(QJsonObject{
|
||||
{"functionCall",
|
||||
QJsonObject{{"name", call.name}, {"args", call.arguments}}}});
|
||||
}
|
||||
contents.append(QJsonObject{{"role", "model"}, {"parts", callParts}});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msg.role == "tool") {
|
||||
QJsonObject responsePart{
|
||||
{"functionResponse",
|
||||
QJsonObject{
|
||||
{"name", msg.toolName},
|
||||
{"response", QJsonObject{{"result", msg.content}}}}}};
|
||||
if (toolResultIdx >= 0) {
|
||||
QJsonObject fnMsg = contents[toolResultIdx].toObject();
|
||||
QJsonArray fnParts = fnMsg["parts"].toArray();
|
||||
fnParts.append(responsePart);
|
||||
fnMsg["parts"] = fnParts;
|
||||
contents[toolResultIdx] = fnMsg;
|
||||
} else {
|
||||
contents.append(
|
||||
QJsonObject{{"role", "function"}, {"parts", QJsonArray{responsePart}}});
|
||||
toolResultIdx = contents.size() - 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
toolResultIdx = -1;
|
||||
|
||||
QJsonObject content;
|
||||
QJsonArray parts;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "pluginllmcore/PromptTemplate.hpp"
|
||||
#include "templates/ToolMessages.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
@@ -47,6 +48,7 @@ public:
|
||||
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::Chat; }
|
||||
QString name() const override { return "Mistral AI Chat"; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
bool supportsToolHistory() const override { return true; }
|
||||
|
||||
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
@@ -59,6 +61,9 @@ public:
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
if (appendOpenAIToolMessage(messages, msg)) {
|
||||
continue;
|
||||
}
|
||||
if (msg.images && !msg.images->isEmpty()) {
|
||||
QJsonArray content;
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::Chat; }
|
||||
QString name() const override { return "Ollama Chat"; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
bool supportsToolHistory() const override { return true; }
|
||||
|
||||
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
@@ -63,8 +64,28 @@ public:
|
||||
for (const auto &msg : context.history.value()) {
|
||||
QJsonObject messageObj;
|
||||
messageObj["role"] = msg.role;
|
||||
messageObj["content"] = msg.content;
|
||||
|
||||
|
||||
if (!msg.toolCalls.isEmpty()) {
|
||||
QJsonArray toolCalls;
|
||||
for (const auto &call : msg.toolCalls) {
|
||||
toolCalls.append(QJsonObject{
|
||||
{"type", "function"},
|
||||
{"function",
|
||||
QJsonObject{{"name", call.name}, {"arguments", call.arguments}}}});
|
||||
}
|
||||
messageObj["tool_calls"] = toolCalls;
|
||||
if (!msg.content.isEmpty()) {
|
||||
messageObj["content"] = msg.content;
|
||||
}
|
||||
} else {
|
||||
messageObj["content"] = msg.content;
|
||||
// Ollama correlates a tool result to its originating
|
||||
// call by tool_name; omitting it breaks multi-tool turns.
|
||||
if (msg.role == QLatin1String("tool") && !msg.toolName.isEmpty()) {
|
||||
messageObj["tool_name"] = msg.toolName;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.images && !msg.images->isEmpty()) {
|
||||
QJsonArray images;
|
||||
for (const auto &image : msg.images.value()) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "pluginllmcore/PromptTemplate.hpp"
|
||||
#include "templates/ToolMessages.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
@@ -15,6 +16,7 @@ public:
|
||||
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::Chat; }
|
||||
QString name() const override { return "OpenAI"; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
bool supportsToolHistory() const override { return true; }
|
||||
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
@@ -26,6 +28,9 @@ public:
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
if (appendOpenAIToolMessage(messages, msg)) {
|
||||
continue;
|
||||
}
|
||||
if (msg.images && !msg.images->isEmpty()) {
|
||||
QJsonArray content;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "pluginllmcore/PromptTemplate.hpp"
|
||||
#include "templates/ToolMessages.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
@@ -15,6 +16,7 @@ public:
|
||||
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::Chat; }
|
||||
QString name() const override { return "OpenAI Compatible"; }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
bool supportsToolHistory() const override { return true; }
|
||||
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
@@ -26,6 +28,9 @@ public:
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
if (appendOpenAIToolMessage(messages, msg)) {
|
||||
continue;
|
||||
}
|
||||
if (msg.images && !msg.images->isEmpty()) {
|
||||
QJsonArray content;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "pluginllmcore/PromptTemplate.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
@@ -22,6 +23,8 @@ public:
|
||||
|
||||
QStringList stopWords() const override { return {}; }
|
||||
|
||||
bool supportsToolHistory() const override { return true; }
|
||||
|
||||
void prepareRequest(
|
||||
QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
@@ -39,6 +42,30 @@ public:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!msg.toolCalls.isEmpty()) {
|
||||
if (!msg.content.isEmpty()) {
|
||||
input.append(QJsonObject{{"role", "assistant"}, {"content", msg.content}});
|
||||
}
|
||||
for (const auto &call : msg.toolCalls) {
|
||||
input.append(QJsonObject{
|
||||
{"type", "function_call"},
|
||||
{"call_id", call.id},
|
||||
{"name", call.name},
|
||||
{"arguments",
|
||||
QString::fromUtf8(
|
||||
QJsonDocument(call.arguments).toJson(QJsonDocument::Compact))}});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msg.role == "tool") {
|
||||
input.append(QJsonObject{
|
||||
{"type", "function_call_output"},
|
||||
{"call_id", msg.toolCallId},
|
||||
{"output", msg.content}});
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject message;
|
||||
message["role"] = msg.role;
|
||||
|
||||
|
||||
45
templates/ToolMessages.hpp
Normal file
45
templates/ToolMessages.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "pluginllmcore/ContextData.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
inline bool appendOpenAIToolMessage(QJsonArray &messages, const PluginLLMCore::Message &msg)
|
||||
{
|
||||
if (!msg.toolCalls.isEmpty()) {
|
||||
QJsonArray toolCalls;
|
||||
for (const auto &call : msg.toolCalls) {
|
||||
toolCalls.append(QJsonObject{
|
||||
{"id", call.id},
|
||||
{"type", "function"},
|
||||
{"function",
|
||||
QJsonObject{
|
||||
{"name", call.name},
|
||||
{"arguments",
|
||||
QString::fromUtf8(
|
||||
QJsonDocument(call.arguments).toJson(QJsonDocument::Compact))}}}});
|
||||
}
|
||||
QJsonObject toolMessage{{"role", "assistant"}, {"tool_calls", toolCalls}};
|
||||
toolMessage["content"] = msg.content.isEmpty() ? QJsonValue() : QJsonValue(msg.content);
|
||||
messages.append(toolMessage);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (msg.role == QLatin1String("tool")) {
|
||||
messages.append(QJsonObject{
|
||||
{"role", "tool"}, {"tool_call_id", msg.toolCallId}, {"content", msg.content}});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
Reference in New Issue
Block a user