mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-06-14 02:09:22 -04:00
refactor: add to template agent roles
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
list(APPEND CMAKE_PREFIX_PATH "/Users/palm1r/Qt/Qt Creator.sdk/lib/cmake/QtCreator")
|
||||
|
||||
project(QodeAssist)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
@@ -38,7 +40,6 @@ add_definitions(
|
||||
|
||||
add_subdirectory(sources)
|
||||
add_subdirectory(logger)
|
||||
add_subdirectory(pluginllmcore)
|
||||
add_subdirectory(settings)
|
||||
add_subdirectory(UIControls)
|
||||
add_subdirectory(ChatView)
|
||||
@@ -70,7 +71,6 @@ add_qtc_plugin(QodeAssist
|
||||
QtCreator::Utils
|
||||
QtCreator::CPlusPlus
|
||||
LLMQore
|
||||
PluginLLMCore
|
||||
ProvidersConfig
|
||||
Agents
|
||||
Skills
|
||||
@@ -84,42 +84,6 @@ add_qtc_plugin(QodeAssist
|
||||
QodeAssisttr.h
|
||||
LLMClientInterface.hpp LLMClientInterface.cpp
|
||||
RefactorContextHelper.hpp
|
||||
templates/Templates.hpp
|
||||
templates/CodeLlamaFim.hpp
|
||||
templates/Ollama.hpp
|
||||
templates/Claude.hpp
|
||||
templates/OpenAI.hpp
|
||||
templates/MistralAI.hpp
|
||||
templates/StarCoder2Fim.hpp
|
||||
templates/Qwen25CoderFIM.hpp
|
||||
templates/OpenAICompatible.hpp
|
||||
templates/Llama3.hpp
|
||||
templates/ChatML.hpp
|
||||
templates/Alpaca.hpp
|
||||
templates/Llama2.hpp
|
||||
templates/CodeLlamaQMLFim.hpp
|
||||
templates/GoogleAI.hpp
|
||||
templates/LlamaCppFim.hpp
|
||||
templates/Qwen3CoderFIM.hpp
|
||||
templates/OpenAIResponses.hpp
|
||||
providers/Providers.hpp
|
||||
providers/ProviderUrlUtils.hpp
|
||||
providers/OllamaProvider.hpp providers/OllamaProvider.cpp
|
||||
providers/OllamaCompatProvider.hpp providers/OllamaCompatProvider.cpp
|
||||
providers/ClaudeProvider.hpp providers/ClaudeProvider.cpp
|
||||
providers/OpenAIProvider.hpp providers/OpenAIProvider.cpp
|
||||
providers/MistralAIProvider.hpp providers/MistralAIProvider.cpp
|
||||
providers/LMStudioProvider.hpp providers/LMStudioProvider.cpp
|
||||
providers/LMStudioResponsesProvider.hpp providers/LMStudioResponsesProvider.cpp
|
||||
providers/OpenAICompatProvider.hpp providers/OpenAICompatProvider.cpp
|
||||
providers/OpenRouterAIProvider.hpp providers/OpenRouterAIProvider.cpp
|
||||
providers/GoogleAIProvider.hpp providers/GoogleAIProvider.cpp
|
||||
providers/LlamaCppProvider.hpp providers/LlamaCppProvider.cpp
|
||||
providers/CodestralProvider.hpp providers/CodestralProvider.cpp
|
||||
providers/OpenAIResponsesProvider.hpp providers/OpenAIResponsesProvider.cpp
|
||||
providers/QwenProvider.hpp providers/QwenProvider.cpp
|
||||
providers/QwenResponsesProvider.hpp providers/QwenResponsesProvider.cpp
|
||||
providers/DeepSeekProvider.hpp providers/DeepSeekProvider.cpp
|
||||
QodeAssist.qrc
|
||||
LSPCompletion.hpp
|
||||
LLMSuggestion.hpp LLMSuggestion.cpp
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <SessionManager.hpp>
|
||||
|
||||
#include "ChatAgentController.hpp"
|
||||
#include "AgentRole.hpp"
|
||||
#include "ChatAssistantSettings.hpp"
|
||||
#include "ChatCompressor.hpp"
|
||||
#include "ChatHistoryStore.hpp"
|
||||
@@ -393,6 +394,43 @@ void ChatRootView::setCurrentChatAgent(const QString &name)
|
||||
m_agentController->setCurrentAgent(name);
|
||||
}
|
||||
|
||||
QStringList ChatRootView::availableRoles() const
|
||||
{
|
||||
return m_availableRoles;
|
||||
}
|
||||
|
||||
QString ChatRootView::currentRole() const
|
||||
{
|
||||
return m_currentRole;
|
||||
}
|
||||
|
||||
void ChatRootView::setCurrentRole(const QString &roleId)
|
||||
{
|
||||
if (m_currentRole == roleId)
|
||||
return;
|
||||
m_currentRole = roleId;
|
||||
emit currentRoleChanged();
|
||||
}
|
||||
|
||||
void ChatRootView::loadAvailableRoles()
|
||||
{
|
||||
QStringList ids;
|
||||
const QList<Settings::AgentRole> roles = Settings::AgentRolesManager::loadAllRoles();
|
||||
ids.reserve(roles.size());
|
||||
for (const auto &r : roles)
|
||||
ids << r.id;
|
||||
|
||||
if (ids != m_availableRoles) {
|
||||
m_availableRoles = ids;
|
||||
emit availableRolesChanged();
|
||||
}
|
||||
|
||||
if (!m_availableRoles.isEmpty() && !m_availableRoles.contains(m_currentRole))
|
||||
setCurrentRole(m_availableRoles.contains(QStringLiteral("developer"))
|
||||
? QStringLiteral("developer")
|
||||
: m_availableRoles.first());
|
||||
}
|
||||
|
||||
QVariantList ChatRootView::searchSkills(const QString &query) const
|
||||
{
|
||||
QVariantList results;
|
||||
@@ -501,6 +539,7 @@ void ChatRootView::dispatchSend(
|
||||
m_clientInterface->setSkillsManager(skillsManager());
|
||||
m_clientInterface->setSessionManager(sessionManager());
|
||||
m_clientInterface->setActiveAgent(currentChatAgent());
|
||||
m_clientInterface->setActiveRole(currentRole());
|
||||
m_clientInterface->sendMessage(message, attachments, linkedFiles);
|
||||
|
||||
m_fileManager->clearIntermediateStorage();
|
||||
|
||||
@@ -59,6 +59,8 @@ class ChatRootView : public QQuickItem
|
||||
Q_PROPERTY(bool isThinkingSupport READ isThinkingSupport NOTIFY isThinkingSupportChanged FINAL)
|
||||
Q_PROPERTY(QStringList availableChatAgents READ availableChatAgents NOTIFY availableChatAgentsChanged FINAL)
|
||||
Q_PROPERTY(QString currentChatAgent READ currentChatAgent WRITE setCurrentChatAgent NOTIFY currentChatAgentChanged FINAL)
|
||||
Q_PROPERTY(QStringList availableRoles READ availableRoles NOTIFY availableRolesChanged FINAL)
|
||||
Q_PROPERTY(QString currentRole READ currentRole WRITE setCurrentRole NOTIFY currentRoleChanged FINAL)
|
||||
Q_PROPERTY(bool isCompressing READ isCompressing NOTIFY isCompressingChanged FINAL)
|
||||
Q_PROPERTY(bool isInEditor READ isInEditor NOTIFY isInEditorChanged FINAL)
|
||||
Q_PROPERTY(QString chatTitle READ chatTitle NOTIFY chatTitleChanged FINAL)
|
||||
@@ -155,6 +157,11 @@ public:
|
||||
QString currentChatAgent() const;
|
||||
void setCurrentChatAgent(const QString &name);
|
||||
|
||||
Q_INVOKABLE void loadAvailableRoles();
|
||||
QStringList availableRoles() const;
|
||||
QString currentRole() const;
|
||||
void setCurrentRole(const QString &roleId);
|
||||
|
||||
int currentMessageTotalEdits() const;
|
||||
int currentMessageAppliedEdits() const;
|
||||
int currentMessagePendingEdits() const;
|
||||
@@ -208,6 +215,8 @@ signals:
|
||||
|
||||
void availableChatAgentsChanged();
|
||||
void currentChatAgentChanged();
|
||||
void availableRolesChanged();
|
||||
void currentRoleChanged();
|
||||
|
||||
void isCompressingChanged();
|
||||
void compressionCompleted(const QString &compressedChatPath);
|
||||
@@ -262,6 +271,9 @@ private:
|
||||
|
||||
QString m_lastInfoMessage;
|
||||
|
||||
QString m_currentRole = QStringLiteral("developer");
|
||||
QStringList m_availableRoles;
|
||||
|
||||
ChatCompressor *m_chatCompressor;
|
||||
ChatAgentController *m_agentController;
|
||||
FileEditController *m_fileEditController;
|
||||
|
||||
@@ -30,7 +30,10 @@
|
||||
|
||||
#include <ConversationHistory.hpp>
|
||||
#include <Message.hpp>
|
||||
#include <ContextRenderer.hpp>
|
||||
#include <Session.hpp>
|
||||
|
||||
#include <QDir>
|
||||
#include <SessionManager.hpp>
|
||||
#include <SystemPromptBuilder.hpp>
|
||||
|
||||
@@ -75,6 +78,11 @@ void ClientInterface::setActiveAgent(const QString &agentName)
|
||||
m_activeAgent = agentName;
|
||||
}
|
||||
|
||||
void ClientInterface::setActiveRole(const QString &roleId)
|
||||
{
|
||||
m_activeRoleId = roleId;
|
||||
}
|
||||
|
||||
void ClientInterface::sendMessage(
|
||||
const QString &message,
|
||||
const QList<QString> &attachments,
|
||||
@@ -175,9 +183,15 @@ void ClientInterface::sendMessage(
|
||||
return;
|
||||
}
|
||||
|
||||
Tools::registerQodeAssistTools(client->tools());
|
||||
if (m_skillsManager)
|
||||
Tools::registerSkillTool(client->tools(), m_skillsManager);
|
||||
auto *project = ProjectExplorer::ProjectManager::startupProject();
|
||||
Templates::ContextRenderer::Bindings bindings;
|
||||
bindings.projectDir = project ? project->projectDirectory().toFSPathString() : QString();
|
||||
bindings.homeDir = QDir::homePath();
|
||||
bindings.roleId = m_activeRoleId;
|
||||
session->setContextBindings(bindings);
|
||||
|
||||
if (m_sessionManager)
|
||||
m_sessionManager->toolContributors().contribute(client->tools());
|
||||
client->setMaxToolContinuations(Settings::toolsSettings().maxToolContinuations());
|
||||
client->setTransferTimeout(
|
||||
static_cast<int>(Settings::generalSettings().requestTimeout() * 1000));
|
||||
|
||||
@@ -37,6 +37,7 @@ public:
|
||||
void setSkillsManager(Skills::SkillsManager *skillsManager);
|
||||
void setSessionManager(SessionManager *sessionManager);
|
||||
void setActiveAgent(const QString &agentName);
|
||||
void setActiveRole(const QString &roleId);
|
||||
|
||||
void sendMessage(
|
||||
const QString &message,
|
||||
@@ -98,6 +99,7 @@ private:
|
||||
Skills::SkillsManager *m_skillsManager = nullptr;
|
||||
QPointer<SessionManager> m_sessionManager;
|
||||
QString m_activeAgent;
|
||||
QString m_activeRoleId;
|
||||
QString m_chatFilePath;
|
||||
|
||||
QHash<QString, RequestContext> m_activeRequests;
|
||||
|
||||
@@ -152,6 +152,19 @@ ChatRootView {
|
||||
root.loadAvailableChatAgents()
|
||||
}
|
||||
}
|
||||
roleSelector {
|
||||
model: root.availableRoles
|
||||
displayText: root.currentRole
|
||||
onActivated: function(index) {
|
||||
root.currentRole = root.availableRoles[index]
|
||||
}
|
||||
|
||||
Component.onCompleted: root.loadAvailableRoles()
|
||||
|
||||
popup.onAboutToShow: {
|
||||
root.loadAvailableRoles()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
|
||||
@@ -25,6 +25,7 @@ Rectangle {
|
||||
property alias contextButton: contextButtonId
|
||||
property alias settingsButton: settingsButtonId
|
||||
property alias agentSelector: agentSelectorId
|
||||
property alias roleSelector: roleSelectorId
|
||||
property alias relocateTooltip: relocateTooltipId
|
||||
|
||||
color: palette.window.hslLightness > 0.5 ?
|
||||
@@ -141,7 +142,22 @@ Rectangle {
|
||||
QoAToolTip {
|
||||
visible: agentSelectorId.hovered
|
||||
delay: 250
|
||||
text: qsTr("Select chat agent (provider, model and role come from the agent)")
|
||||
text: qsTr("Select chat agent (provider and model come from the agent)")
|
||||
}
|
||||
}
|
||||
|
||||
QoAComboBox {
|
||||
id: roleSelectorId
|
||||
|
||||
implicitHeight: 25
|
||||
|
||||
model: []
|
||||
currentIndex: 0
|
||||
|
||||
QoAToolTip {
|
||||
visible: roleSelectorId.hovered
|
||||
delay: 250
|
||||
text: qsTr("Select the role (system prompt) for the chat")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,14 +334,7 @@ Templates::ContextData LLMClientInterface::prepareContext(
|
||||
|
||||
Context::DocumentContextReader
|
||||
reader(documentInfo.document, documentInfo.mimeType, documentInfo.filePath);
|
||||
const PluginLLMCore::ContextData legacy
|
||||
= reader.prepareContext(lineNumber, cursorPosition, m_completeSettings);
|
||||
|
||||
Templates::ContextData context;
|
||||
context.prefix = legacy.prefix;
|
||||
context.suffix = legacy.suffix;
|
||||
context.fileContext = legacy.fileContext;
|
||||
return context;
|
||||
return reader.prepareContext(lineNumber, cursorPosition, m_completeSettings);
|
||||
}
|
||||
|
||||
Context::ContextManager *LLMClientInterface::contextManager() const
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
#include "widgets/EditorChatButtonHandler.hpp"
|
||||
#include "widgets/RefactorWidgetHandler.hpp"
|
||||
#include <languageclient/client.h>
|
||||
#include <pluginllmcore/IPromptProvider.hpp>
|
||||
#include <pluginllmcore/IProviderRegistry.hpp>
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#include <context/DocumentReaderQtCreator.hpp>
|
||||
#include <context/Utils.hpp>
|
||||
#include <logger/Logger.hpp>
|
||||
#include <pluginllmcore/ResponseCleaner.hpp>
|
||||
#include <sources/common/ResponseCleaner.hpp>
|
||||
#include <settings/QuickRefactorSettings.hpp>
|
||||
#include <settings/ToolsSettings.hpp>
|
||||
|
||||
@@ -378,7 +378,7 @@ void QuickRefactorHandler::handleLLMResponse(
|
||||
|
||||
if (isComplete) {
|
||||
m_isRefactoringInProgress = false;
|
||||
QString cleanedResponse = PluginLLMCore::ResponseCleaner::clean(response);
|
||||
QString cleanedResponse = ResponseCleaner::clean(response);
|
||||
|
||||
RefactorResult result;
|
||||
result.newText = cleanedResponse;
|
||||
|
||||
@@ -21,7 +21,7 @@ target_link_libraries(Context
|
||||
QtCreator::Utils
|
||||
QtCreator::ProjectExplorer
|
||||
PRIVATE
|
||||
PluginLLMCore
|
||||
Common
|
||||
QodeAssistSettings
|
||||
)
|
||||
|
||||
|
||||
@@ -254,7 +254,7 @@ CopyrightInfo DocumentContextReader::copyrightInfo() const
|
||||
return m_copyrightInfo;
|
||||
}
|
||||
|
||||
PluginLLMCore::ContextData DocumentContextReader::prepareContext(
|
||||
Templates::ContextData DocumentContextReader::prepareContext(
|
||||
int lineNumber, int cursorPosition, const Settings::CodeCompletionSettings &settings) const
|
||||
{
|
||||
QString contextBefore;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <QTextDocument>
|
||||
|
||||
#include <pluginllmcore/ContextData.hpp>
|
||||
#include <sources/common/ContextData.hpp>
|
||||
#include <settings/CodeCompletionSettings.hpp>
|
||||
|
||||
namespace QodeAssist::Context {
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
|
||||
CopyrightInfo copyrightInfo() const;
|
||||
|
||||
PluginLLMCore::ContextData prepareContext(
|
||||
Templates::ContextData prepareContext(
|
||||
int lineNumber, int cursorPosition, const Settings::CodeCompletionSettings &settings) const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -43,18 +43,26 @@ exactly like the curl body.
|
||||
|
||||
No runtime toggles: thinking / tools / streaming are **fixed per agent**. A thinking
|
||||
agent literally carries the `thinking` fields; a non-thinking variant is a separate
|
||||
file. There is no `{% if thinking %}` and no `thinkingEnabled` flag threaded into
|
||||
rendering. `system` uses `{% if existsIn(ctx, "system_prompt") %}` only because that
|
||||
is about *presence of data*, not a mode toggle.
|
||||
file. There is no `{% if thinking %}` in the body. `system` uses
|
||||
`{% if existsIn(ctx, "system_prompt") %}` only because that is about *presence of
|
||||
data*, not a mode toggle. `enable_thinking` / `enable_tools` are **capability hints**
|
||||
(used for UI badges and to decide tool-definition injection) — the body is the source
|
||||
of truth for what is actually sent, so a thinking agent's body must carry the thinking
|
||||
fields regardless of the flag.
|
||||
|
||||
Outside the body:
|
||||
- `model` — supplied by the **client** from its own settings; never in the profile.
|
||||
Google embeds the model in the URL, so its `endpoint` uses a `${MODEL}` placeholder
|
||||
the client resolves (same substitution style as `${PROJECT_DIR}` / `${HOME}`).
|
||||
- `model` — the TOML `model` is the **default**; a per-agent override chosen in
|
||||
QodeAssist settings wins. Overrides are stored in `agent_models.json`
|
||||
(agentName → model) and applied by `AgentFactory` when it builds the agent
|
||||
(`AgentFactory::effectiveModel`/`setModelOverride`); `Session` still seeds the
|
||||
payload `model` from the resolved `cfg.model`. URL-model providers (Google) put a
|
||||
`${MODEL}` placeholder in `endpoint`; `Session` substitutes the resolved model into
|
||||
the endpoint before sending (same substitution style as `${PROJECT_DIR}`/`${HOME}`),
|
||||
so the override drives the URL too.
|
||||
- `tools` — injected by the **provider** when `enable_tools` is set (tool
|
||||
definitions are dynamic, from `ToolsManager`; they can't be authored in TOML).
|
||||
- `stream` — always on. Literal `"stream": true` in the body for OpenAI / Claude /
|
||||
Mistral; encoded in the `endpoint` URL for Google.
|
||||
Mistral / Responses / Ollama; encoded in the `endpoint` URL for Google.
|
||||
|
||||
### 2. `include` re-enabled as whitelisted partials
|
||||
|
||||
@@ -71,19 +79,20 @@ A missing/typo'd partial is a **load-time** error.
|
||||
### 3. `extends` shares config down a hierarchy
|
||||
|
||||
`extends` already exists (`resolveExtends` + `deepMerge` + `abstract`/`hidden`); it
|
||||
keeps doing what it does, now over the structured `[body]` too. Typical 2–3 levels:
|
||||
keeps doing what it does, now over the structured `[body]` too. A flat 2 levels — each
|
||||
provider base sets `system_prompt = """{{ agent_role("developer") }}"""` (the role text
|
||||
comes from the role JSON via the `agent_role` callback; see below). No shared root base:
|
||||
|
||||
```
|
||||
chat_base (abstract) → system_prompt (shared by all)
|
||||
├─ openai_base (abstract) → provider/endpoint/enable_tools + [body]
|
||||
│ ├─ openai_chat → name
|
||||
│ ├─ mistral_chat → name, provider, endpoint
|
||||
│ └─ mistral_reasoning → + [body].reasoning_effort
|
||||
├─ anthropic_base (abstract) → provider/endpoint/thinking + [body]
|
||||
│ ├─ claude_chat → name
|
||||
│ └─ claude_sonnet → + [body.output_config].effort
|
||||
└─ google_base (abstract) → provider/endpoint + [body]
|
||||
└─ gemini_chat → name
|
||||
openai_base (abstract) → system_prompt + provider/endpoint/enable_tools + [body]
|
||||
├─ openai_chat → name
|
||||
├─ mistral_chat → name, provider, endpoint
|
||||
└─ mistral_reasoning → + enable_thinking
|
||||
anthropic_base (abstract) → system_prompt + provider/endpoint + [body]
|
||||
├─ claude_chat → name
|
||||
└─ claude_sonnet → + [body] thinking / output_config
|
||||
google_base (abstract) → system_prompt + provider + [body]
|
||||
└─ gemini_chat → endpoint (${MODEL}) + [body.generationConfig] thinkingConfig
|
||||
```
|
||||
|
||||
Notes:
|
||||
@@ -98,21 +107,33 @@ Notes:
|
||||
by `model` become one agent (the client picks the model). A separate file is only
|
||||
needed when the body genuinely differs (effort, no-thinking, …).
|
||||
|
||||
### `role` + `context` merged into `system_prompt`
|
||||
### System prompt — a composable template with building blocks
|
||||
|
||||
The old `role` (static) and `context` (jinja, reads files) are two layers of the
|
||||
same system prompt (`SystemPromptBuilder` layers `agent.role` / `agent.context`).
|
||||
Merge into one `system_prompt` field, always rendered through `ContextRenderer`
|
||||
(static text passes through; dynamic parts use `{% %}`), e.g. README via
|
||||
`file_exists` instead of the `set readme / if length` dance. `Session` collapses the
|
||||
two layers into one rendered layer.
|
||||
The old `role` (static text) and `context` (jinja) layers collapse into one
|
||||
`agent.system` layer in `Session`, rendered through `ContextRenderer`. The agent's
|
||||
`system_prompt` field IS that template, and the user edits it (in the profile) to
|
||||
compose the prompt from building-block callbacks:
|
||||
|
||||
- `{{ agent_role("<id>") }}` — insert a role's text (Developer/Reviewer/Researcher…).
|
||||
Implemented as a `ContextRenderer` callback (`registerAgentRole`) that reads
|
||||
`userResourcePath("qodeassist/agent_roles/<id>.json")["systemPrompt"]`. Returns "" if
|
||||
missing. Lives in `sources/agents` (no dependency on `settings/`), so it works in the
|
||||
plugin and bench. The role text lives once in the role JSON (managed by the settings
|
||||
Roles UI); the chat bases just carry `system_prompt = """{{ agent_role("developer") }}"""`.
|
||||
- `{{ read_file("...") }}` / `file_exists` / `${PROJECT_DIR}` / `${HOME}` — existing
|
||||
`ContextRenderer` helpers, composable in the same template.
|
||||
|
||||
So a profile can do `system_prompt = """{{ agent_role("developer") }}\n\n{{ read_file("…") }}"""`.
|
||||
`qodeassist.cpp` calls `AgentRolesManager::ensureDefaultRoles()` at startup so the default
|
||||
role JSONs exist before agents load. There is NO per-agent settings override — the edit
|
||||
point is the profile's `system_prompt`. Code-completion/FIM agents set no `system_prompt`.
|
||||
|
||||
## Worked examples
|
||||
|
||||
OpenAI base:
|
||||
```toml
|
||||
extends = "Chat Base"
|
||||
abstract = true
|
||||
system_prompt = """{{ agent_role("developer") }}"""
|
||||
provider_instance = "OpenAI (Chat Completions)"
|
||||
endpoint = "/chat/completions"
|
||||
enable_tools = true
|
||||
@@ -140,8 +161,8 @@ reasoning_effort = "medium"
|
||||
|
||||
Claude base (literally the curl body):
|
||||
```toml
|
||||
extends = "Chat Base"
|
||||
abstract = true
|
||||
system_prompt = """{{ agent_role("developer") }}"""
|
||||
provider_instance = "Claude"
|
||||
endpoint = "/v1/messages"
|
||||
enable_thinking = true
|
||||
@@ -170,8 +191,8 @@ effort = "medium"
|
||||
|
||||
Google base (`${MODEL}` in endpoint; streaming in the URL):
|
||||
```toml
|
||||
extends = "Chat Base"
|
||||
abstract = true
|
||||
system_prompt = """{{ agent_role("developer") }}"""
|
||||
provider_instance = "Google AI"
|
||||
endpoint = "/models/${MODEL}:streamGenerateContent?alt=sse"
|
||||
enable_thinking = true
|
||||
@@ -337,10 +358,16 @@ In `AgentConfig` / `AgentLoader`:
|
||||
as JSON (render once against a synthetic context with tool_use / tool_result /
|
||||
image). Catches breakage at startup, not mid-conversation.
|
||||
|
||||
In the client / provider layer:
|
||||
- The client sets `model` from its settings (and resolves `${MODEL}` in the
|
||||
endpoint); `Session` no longer seeds the payload with `cfg.model`.
|
||||
- The provider keeps injecting `tools` when `enable_tools` is set.
|
||||
Model selection (per-agent override):
|
||||
- `AgentFactory` owns an agentName → model map loaded from `agent_models.json`
|
||||
(`loadModelOverrides`/`saveModelOverrides`). `create()`/`createFromFile()` apply the
|
||||
override into the built `AgentConfig`; `effectiveModel()` exposes the resolved value;
|
||||
`setModelOverride()` persists. The settings UI (`AgentDetailPane`) edits it via an
|
||||
editable Model field; list/roster widgets display `effectiveModel`.
|
||||
- `Session` substitutes `${MODEL}` in `cfg.endpoint` with the resolved model before
|
||||
`sendRequest` (covers Google, whose model lives in the URL), and still seeds the
|
||||
payload `model` from `cfg.model`. The provider keeps injecting `tools` when
|
||||
`enable_tools` is set.
|
||||
|
||||
In `Session`:
|
||||
- Collapse the `agent.role` + `agent.context` system-prompt layers into one rendered
|
||||
@@ -353,6 +380,7 @@ In `Session`:
|
||||
2. `[body]`-table model in `JsonPromptTemplate` + loader; delete
|
||||
sampling/thinking/`applySampling`; drop `thinkingEnabled`.
|
||||
3. `system_prompt` merge in loader + `Session`.
|
||||
4. `model` from client (+ `${MODEL}` endpoint substitution); convert bundled agents
|
||||
to the base/partials/`extends` layout.
|
||||
4. Per-agent model override in `AgentFactory` (`agent_models.json`) + `${MODEL}`
|
||||
endpoint substitution in `Session`; editable Model field in settings; convert
|
||||
bundled agents to the base/partials/`extends` layout.
|
||||
5. Load-time validation (partial resolves, body parses).
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
#include <QSaveFile>
|
||||
#include <QTimer>
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
#include <logger/Logger.hpp>
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
#include <pluginllmcore/ProvidersManager.hpp>
|
||||
#include <settings/McpSettings.hpp>
|
||||
|
||||
namespace QodeAssist::Mcp {
|
||||
@@ -176,18 +175,14 @@ QList<McpServerConnection *> McpClientsManager::connections() const
|
||||
return m_connections;
|
||||
}
|
||||
|
||||
QList<PluginLLMCore::Provider *> McpClientsManager::toolsCapableProviders() const
|
||||
void McpClientsManager::registerToolsOn(::LLMQore::ToolsManager *tools) const
|
||||
{
|
||||
QList<PluginLLMCore::Provider *> out;
|
||||
auto &pm = PluginLLMCore::ProvidersManager::instance();
|
||||
for (const QString &name : pm.providersNames()) {
|
||||
auto *p = pm.getProviderByName(name);
|
||||
if (!p)
|
||||
continue;
|
||||
if (p->capabilities().testFlag(PluginLLMCore::ProviderCapability::Tools))
|
||||
out.append(p);
|
||||
if (!tools)
|
||||
return;
|
||||
for (auto *c : m_connections) {
|
||||
if (c)
|
||||
c->registerToolsOn(tools);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QJsonObject McpClientsManager::builtinServers()
|
||||
@@ -319,8 +314,6 @@ void McpClientsManager::loadFromDisk()
|
||||
newConfigs.append(McpServerConfig::fromJson(it.key(), it.value().toObject()));
|
||||
}
|
||||
|
||||
const auto providers = toolsCapableProviders();
|
||||
|
||||
const bool masterEnabled = Settings::mcpSettings().enableMcpClients();
|
||||
|
||||
QList<McpServerConnection *> keep;
|
||||
@@ -350,7 +343,6 @@ void McpClientsManager::loadFromDisk()
|
||||
existing->deleteLater();
|
||||
}
|
||||
c = new McpServerConnection(cfg, this);
|
||||
c->setProviders(providers);
|
||||
connect(
|
||||
c,
|
||||
&McpServerConnection::stateChanged,
|
||||
|
||||
@@ -35,6 +35,8 @@ public:
|
||||
bool removeServer(const QString &name);
|
||||
void reload();
|
||||
|
||||
void registerToolsOn(::LLMQore::ToolsManager *tools) const;
|
||||
|
||||
signals:
|
||||
void serversChanged();
|
||||
void writeFailed(const QString &reason);
|
||||
@@ -50,7 +52,6 @@ private:
|
||||
void setupWatcher();
|
||||
void updateWatchedPaths();
|
||||
|
||||
QList<PluginLLMCore::Provider *> toolsCapableProviders() const;
|
||||
static QJsonObject builtinServers();
|
||||
QJsonObject readRoot() const;
|
||||
bool writeRoot(const QJsonObject &root);
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include <logger/Logger.hpp>
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
#include <settings/McpSettings.hpp>
|
||||
|
||||
namespace QodeAssist::Mcp {
|
||||
@@ -35,13 +34,6 @@ QString transportToString(McpTransportKind k)
|
||||
return k == McpTransportKind::Http ? QStringLiteral("http") : QStringLiteral("stdio");
|
||||
}
|
||||
|
||||
bool providerSupportsTools(PluginLLMCore::Provider *p)
|
||||
{
|
||||
if (!p)
|
||||
return false;
|
||||
return p->capabilities().testFlag(PluginLLMCore::ProviderCapability::Tools);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
McpServerConfig McpServerConfig::fromJson(const QString &name, const QJsonObject &obj)
|
||||
@@ -133,15 +125,6 @@ McpServerConnection::~McpServerConnection()
|
||||
disconnectFromServer();
|
||||
}
|
||||
|
||||
void McpServerConnection::setProviders(const QList<PluginLLMCore::Provider *> &providers)
|
||||
{
|
||||
m_providers.clear();
|
||||
for (auto *p : providers) {
|
||||
if (providerSupportsTools(p))
|
||||
m_providers.append(p);
|
||||
}
|
||||
}
|
||||
|
||||
::LLMQore::Mcp::McpTransport *McpServerConnection::createTransport()
|
||||
{
|
||||
if (m_config.transport == McpTransportKind::Http) {
|
||||
@@ -293,40 +276,20 @@ void McpServerConnection::fetchAndRegisterTools()
|
||||
[this](const QList<::LLMQore::Mcp::ToolInfo> &tools) {
|
||||
if (m_listToolsWatchdog)
|
||||
m_listToolsWatchdog->stop();
|
||||
if (m_providers.isEmpty()) {
|
||||
LOG_MESSAGE(QString("MCP client [%1]: no tools-capable providers to "
|
||||
"register %2 tools into")
|
||||
.arg(m_config.name)
|
||||
.arg(tools.size()));
|
||||
setState(
|
||||
McpConnectionState::Connected,
|
||||
QStringLiteral("Connected (%1 tools)").arg(tools.size()));
|
||||
return;
|
||||
}
|
||||
|
||||
m_tools.clear();
|
||||
for (const auto &info : tools) {
|
||||
if (info.name.isEmpty())
|
||||
continue;
|
||||
m_toolIds.append(info.name);
|
||||
for (const auto &p : m_providers) {
|
||||
if (!p)
|
||||
continue;
|
||||
auto *tm = p->toolsManager();
|
||||
if (!tm)
|
||||
continue;
|
||||
auto *remote = new ::LLMQore::Mcp::McpRemoteTool(
|
||||
m_client.data(), info, tm);
|
||||
tm->addTool(remote);
|
||||
}
|
||||
m_tools.append(info);
|
||||
}
|
||||
|
||||
LOG_MESSAGE(QString("MCP client [%1]: registered %2 tools across %3 providers")
|
||||
LOG_MESSAGE(QString("MCP client [%1]: discovered %2 tools")
|
||||
.arg(m_config.name)
|
||||
.arg(tools.size())
|
||||
.arg(m_providers.size()));
|
||||
.arg(m_tools.size()));
|
||||
setState(
|
||||
McpConnectionState::Connected,
|
||||
QStringLiteral("Connected (%1 tools)").arg(tools.size()));
|
||||
QStringLiteral("Connected (%1 tools)").arg(m_tools.size()));
|
||||
})
|
||||
.onFailed(this, [this](const std::exception &e) {
|
||||
if (m_listToolsWatchdog)
|
||||
@@ -337,21 +300,19 @@ void McpServerConnection::fetchAndRegisterTools()
|
||||
});
|
||||
}
|
||||
|
||||
void McpServerConnection::registerToolsOn(::LLMQore::ToolsManager *tools)
|
||||
{
|
||||
if (!tools || !m_client || m_state != McpConnectionState::Connected)
|
||||
return;
|
||||
for (const auto &info : m_tools) {
|
||||
auto *remote = new ::LLMQore::Mcp::McpRemoteTool(m_client.data(), info, tools);
|
||||
tools->addTool(remote);
|
||||
}
|
||||
}
|
||||
|
||||
void McpServerConnection::unregisterTools()
|
||||
{
|
||||
if (m_toolIds.isEmpty())
|
||||
return;
|
||||
|
||||
for (const auto &p : m_providers) {
|
||||
if (!p)
|
||||
continue;
|
||||
auto *tm = p->toolsManager();
|
||||
if (!tm)
|
||||
continue;
|
||||
for (const QString &id : m_toolIds)
|
||||
tm->removeTool(id);
|
||||
}
|
||||
m_toolIds.clear();
|
||||
m_tools.clear();
|
||||
}
|
||||
|
||||
void McpServerConnection::disconnectFromServer()
|
||||
|
||||
@@ -14,15 +14,17 @@
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
|
||||
#include <LLMQore/McpTypes.hpp>
|
||||
|
||||
namespace LLMQore {
|
||||
class ToolsManager;
|
||||
}
|
||||
|
||||
namespace LLMQore::Mcp {
|
||||
class McpClient;
|
||||
class McpTransport;
|
||||
} // namespace LLMQore::Mcp
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
class Provider;
|
||||
}
|
||||
|
||||
namespace QodeAssist::Mcp {
|
||||
|
||||
enum class McpTransportKind { Http, Stdio };
|
||||
@@ -61,10 +63,17 @@ public:
|
||||
const McpServerConfig &config() const { return m_config; }
|
||||
McpConnectionState state() const { return m_state; }
|
||||
QString statusText() const { return m_statusText; }
|
||||
int toolCount() const { return m_toolIds.size(); }
|
||||
QStringList toolNames() const { return m_toolIds; }
|
||||
int toolCount() const { return m_tools.size(); }
|
||||
QStringList toolNames() const
|
||||
{
|
||||
QStringList names;
|
||||
names.reserve(m_tools.size());
|
||||
for (const auto &tool : m_tools)
|
||||
names << tool.name;
|
||||
return names;
|
||||
}
|
||||
|
||||
void setProviders(const QList<PluginLLMCore::Provider *> &providers);
|
||||
void registerToolsOn(::LLMQore::ToolsManager *tools);
|
||||
|
||||
void connectToServer();
|
||||
void disconnectFromServer();
|
||||
@@ -75,7 +84,6 @@ signals:
|
||||
private:
|
||||
void setState(McpConnectionState state, const QString &text = {});
|
||||
void fetchAndRegisterTools();
|
||||
void registerTools(const QList<::LLMQore::Mcp::McpClient *> & /*unused*/);
|
||||
void unregisterTools();
|
||||
::LLMQore::Mcp::McpTransport *createTransport();
|
||||
|
||||
@@ -87,8 +95,7 @@ private:
|
||||
QPointer<::LLMQore::Mcp::McpTransport> m_transport;
|
||||
QPointer<QTimer> m_listToolsWatchdog;
|
||||
|
||||
QList<QPointer<PluginLLMCore::Provider>> m_providers;
|
||||
QStringList m_toolIds;
|
||||
QList<::LLMQore::Mcp::ToolInfo> m_tools;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Mcp
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
add_library(PluginLLMCore STATIC
|
||||
RequestType.hpp
|
||||
Provider.hpp Provider.cpp
|
||||
ProvidersManager.hpp ProvidersManager.cpp
|
||||
ContextData.hpp
|
||||
IPromptProvider.hpp
|
||||
IProviderRegistry.hpp
|
||||
PromptProviderChat.hpp
|
||||
PromptProviderFim.hpp
|
||||
PromptTemplate.hpp
|
||||
PromptTemplateManager.hpp PromptTemplateManager.cpp
|
||||
ProviderID.hpp
|
||||
ResponseCleaner.hpp
|
||||
)
|
||||
|
||||
target_link_libraries(PluginLLMCore
|
||||
PUBLIC
|
||||
Qt::Core
|
||||
Qt::Network
|
||||
QtCreator::Core
|
||||
QtCreator::Utils
|
||||
QtCreator::ExtensionSystem
|
||||
LLMQore
|
||||
PRIVATE
|
||||
QodeAssistLogger
|
||||
)
|
||||
|
||||
target_include_directories(PluginLLMCore PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
|
||||
struct ImageAttachment
|
||||
{
|
||||
QString data; // Base64 encoded data or URL
|
||||
QString mediaType; // e.g., "image/png", "image/jpeg", "image/webp", "image/gif"
|
||||
bool isUrl = false; // true if data is URL, false if base64
|
||||
|
||||
bool operator==(const ImageAttachment &) const = default;
|
||||
};
|
||||
|
||||
struct ToolCall
|
||||
{
|
||||
QString id;
|
||||
QString name;
|
||||
QJsonObject arguments;
|
||||
|
||||
bool operator==(const ToolCall &) const = default;
|
||||
};
|
||||
|
||||
struct Message
|
||||
{
|
||||
QString role;
|
||||
QString content;
|
||||
QString signature;
|
||||
bool isThinking = false;
|
||||
bool isRedacted = false;
|
||||
std::optional<QVector<ImageAttachment>> images;
|
||||
|
||||
QVector<ToolCall> toolCalls;
|
||||
QString toolCallId;
|
||||
QString toolName;
|
||||
|
||||
// clang-format off
|
||||
bool operator==(const Message&) const = default;
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
struct FileMetadata
|
||||
{
|
||||
QString filePath;
|
||||
QString content;
|
||||
bool operator==(const FileMetadata &) const = default;
|
||||
};
|
||||
|
||||
struct ContextData
|
||||
{
|
||||
std::optional<QString> systemPrompt;
|
||||
std::optional<QString> prefix;
|
||||
std::optional<QString> suffix;
|
||||
std::optional<QString> fileContext;
|
||||
std::optional<QVector<Message>> history;
|
||||
std::optional<QList<FileMetadata>> filesMetadata;
|
||||
|
||||
bool operator==(const ContextData &) const = default;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::PluginLLMCore
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PromptTemplate.hpp"
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
|
||||
class IPromptProvider
|
||||
{
|
||||
public:
|
||||
virtual ~IPromptProvider() = default;
|
||||
|
||||
virtual PromptTemplate *getTemplateByName(const QString &templateName) const = 0;
|
||||
|
||||
virtual QStringList templatesNames() const = 0;
|
||||
|
||||
virtual QStringList getTemplatesForProvider(ProviderID id) const = 0;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::PluginLLMCore
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Provider.hpp"
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
|
||||
class IProviderRegistry
|
||||
{
|
||||
public:
|
||||
virtual ~IProviderRegistry() = default;
|
||||
|
||||
virtual Provider *getProviderByName(const QString &providerName) = 0;
|
||||
|
||||
virtual QStringList providersNames() const = 0;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::PluginLLMCore
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IPromptProvider.hpp"
|
||||
#include "PromptTemplate.hpp"
|
||||
#include "PromptTemplateManager.hpp"
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
|
||||
class PromptProviderChat : public IPromptProvider
|
||||
{
|
||||
public:
|
||||
explicit PromptProviderChat(PromptTemplateManager &templateManager)
|
||||
: m_templateManager(templateManager)
|
||||
{}
|
||||
|
||||
~PromptProviderChat() = default;
|
||||
|
||||
PromptTemplate *getTemplateByName(const QString &templateName) const override
|
||||
{
|
||||
return m_templateManager.getChatTemplateByName(templateName);
|
||||
}
|
||||
|
||||
QStringList templatesNames() const override { return m_templateManager.chatTemplatesNames(); }
|
||||
|
||||
QStringList getTemplatesForProvider(ProviderID id) const override
|
||||
{
|
||||
return m_templateManager.getChatTemplatesForProvider(id);
|
||||
}
|
||||
|
||||
private:
|
||||
PromptTemplateManager &m_templateManager;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::PluginLLMCore
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IPromptProvider.hpp"
|
||||
#include "PromptTemplateManager.hpp"
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
|
||||
class PromptProviderFim : public IPromptProvider
|
||||
{
|
||||
public:
|
||||
explicit PromptProviderFim(PromptTemplateManager &templateManager)
|
||||
: m_templateManager(templateManager)
|
||||
{}
|
||||
|
||||
~PromptProviderFim() = default;
|
||||
|
||||
PromptTemplate *getTemplateByName(const QString &templateName) const override
|
||||
{
|
||||
return m_templateManager.getFimTemplateByName(templateName);
|
||||
}
|
||||
|
||||
QStringList templatesNames() const override { return m_templateManager.fimTemplatesNames(); }
|
||||
|
||||
QStringList getTemplatesForProvider(ProviderID id) const override
|
||||
{
|
||||
return m_templateManager.getFimTemplatesForProvider(id);
|
||||
}
|
||||
|
||||
private:
|
||||
PromptTemplateManager &m_templateManager;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::PluginLLMCore
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
#include "ContextData.hpp"
|
||||
#include "ProviderID.hpp"
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
|
||||
enum class TemplateType { Chat, FIM, FIMOnChat };
|
||||
|
||||
class PromptTemplate
|
||||
{
|
||||
public:
|
||||
virtual ~PromptTemplate() = default;
|
||||
virtual TemplateType type() const = 0;
|
||||
virtual QString name() const = 0;
|
||||
virtual QStringList stopWords() const = 0;
|
||||
virtual void prepareRequest(QJsonObject &request, const ContextData &context) const = 0;
|
||||
virtual QString description() const = 0;
|
||||
virtual bool isSupportProvider(ProviderID id) const = 0;
|
||||
|
||||
virtual QString endpoint() const { return {}; }
|
||||
|
||||
virtual bool supportsToolHistory() const { return false; }
|
||||
};
|
||||
} // namespace QodeAssist::PluginLLMCore
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "PromptTemplateManager.hpp"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
|
||||
PromptTemplateManager &PromptTemplateManager::instance()
|
||||
{
|
||||
static PromptTemplateManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
QStringList PromptTemplateManager::fimTemplatesNames() const
|
||||
{
|
||||
return m_fimTemplates.keys();
|
||||
}
|
||||
|
||||
QStringList PromptTemplateManager::chatTemplatesNames() const
|
||||
{
|
||||
return m_chatTemplates.keys();
|
||||
}
|
||||
|
||||
QStringList PromptTemplateManager::getFimTemplatesForProvider(ProviderID id)
|
||||
{
|
||||
QStringList templateList;
|
||||
|
||||
for (const auto tmpl : m_fimTemplates) {
|
||||
if (tmpl->isSupportProvider(id)) {
|
||||
templateList.append(tmpl->name());
|
||||
}
|
||||
}
|
||||
|
||||
return templateList;
|
||||
}
|
||||
|
||||
QStringList PromptTemplateManager::getChatTemplatesForProvider(ProviderID id)
|
||||
{
|
||||
QStringList templateList;
|
||||
|
||||
for (const auto tmpl : m_chatTemplates) {
|
||||
if (tmpl->isSupportProvider(id)) {
|
||||
templateList.append(tmpl->name());
|
||||
}
|
||||
}
|
||||
|
||||
return templateList;
|
||||
}
|
||||
|
||||
PromptTemplateManager::~PromptTemplateManager()
|
||||
{
|
||||
qDeleteAll(m_fimTemplates);
|
||||
}
|
||||
|
||||
PromptTemplate *PromptTemplateManager::getFimTemplateByName(const QString &templateName)
|
||||
{
|
||||
if (!m_fimTemplates.contains(templateName)) {
|
||||
QMessageBox::warning(
|
||||
nullptr,
|
||||
QObject::tr("Template Not Found"),
|
||||
QObject::tr("Template '%1' was not found or has been updated. Please re-set new one.")
|
||||
.arg(templateName));
|
||||
return m_fimTemplates.first();
|
||||
}
|
||||
return m_fimTemplates[templateName];
|
||||
}
|
||||
|
||||
PromptTemplate *PromptTemplateManager::getChatTemplateByName(const QString &templateName)
|
||||
{
|
||||
if (!m_chatTemplates.contains(templateName)) {
|
||||
QMessageBox::warning(
|
||||
nullptr,
|
||||
QObject::tr("Template Not Found"),
|
||||
QObject::tr("Template '%1' was not found or has been updated. Please re-set new one.")
|
||||
.arg(templateName));
|
||||
return m_chatTemplates.first();
|
||||
}
|
||||
return m_chatTemplates[templateName];
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::PluginLLMCore
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#include "PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
|
||||
class PromptTemplateManager
|
||||
{
|
||||
public:
|
||||
static PromptTemplateManager &instance();
|
||||
~PromptTemplateManager();
|
||||
|
||||
template<typename T>
|
||||
void registerTemplate()
|
||||
{
|
||||
static_assert(std::is_base_of<PromptTemplate, T>::value, "T must inherit from PromptTemplate");
|
||||
T *template_ptr = new T();
|
||||
QString name = template_ptr->name();
|
||||
m_fimTemplates[name] = template_ptr;
|
||||
if (template_ptr->type() == TemplateType::Chat) {
|
||||
m_chatTemplates[name] = template_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
PromptTemplate *getFimTemplateByName(const QString &templateName);
|
||||
PromptTemplate *getChatTemplateByName(const QString &templateName);
|
||||
|
||||
QStringList fimTemplatesNames() const;
|
||||
QStringList chatTemplatesNames() const;
|
||||
|
||||
QStringList getFimTemplatesForProvider(ProviderID id);
|
||||
QStringList getChatTemplatesForProvider(ProviderID id);
|
||||
|
||||
private:
|
||||
PromptTemplateManager() = default;
|
||||
PromptTemplateManager(const PromptTemplateManager &) = delete;
|
||||
PromptTemplateManager &operator=(const PromptTemplateManager &) = delete;
|
||||
|
||||
QMap<QString, PromptTemplate *> m_fimTemplates;
|
||||
QMap<QString, PromptTemplate *> m_chatTemplates;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::PluginLLMCore
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "Provider.hpp"
|
||||
|
||||
#include <LLMQore/BaseClient.hpp>
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include <Logger.hpp>
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
|
||||
Provider::Provider(QObject *parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
LLMQore::RequestID Provider::sendRequest(
|
||||
const QUrl &url, const QJsonObject &payload, const QString &endpoint)
|
||||
{
|
||||
auto *c = client();
|
||||
|
||||
c->setUrl(url.toString());
|
||||
c->setApiKey(apiKey());
|
||||
|
||||
auto requestId = c->sendMessage(payload, endpoint);
|
||||
|
||||
LOG_MESSAGE(
|
||||
QString("%1: Sending request %2 to %3%4").arg(name(), requestId, url.toString(), endpoint));
|
||||
LOG_MESSAGE(
|
||||
QString("%1: Payload:\n%2")
|
||||
.arg(name(), QString::fromUtf8(QJsonDocument(payload).toJson(QJsonDocument::Indented))));
|
||||
|
||||
return requestId;
|
||||
}
|
||||
|
||||
void Provider::cancelRequest(const LLMQore::RequestID &requestId)
|
||||
{
|
||||
LOG_MESSAGE(QString("%1: Cancelling request %2").arg(name(), requestId));
|
||||
client()->cancelRequest(requestId);
|
||||
}
|
||||
|
||||
::LLMQore::ToolsManager *Provider::toolsManager() const
|
||||
{
|
||||
return client()->tools();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::PluginLLMCore
|
||||
@@ -1,67 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFlags>
|
||||
#include <QFuture>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <utils/environment.h>
|
||||
|
||||
#include "ContextData.hpp"
|
||||
#include "PromptTemplate.hpp"
|
||||
#include "LLMQore/BaseClient.hpp"
|
||||
#include "RequestType.hpp"
|
||||
|
||||
namespace LLMQore {
|
||||
class BaseClient;
|
||||
class ToolsManager;
|
||||
}
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
|
||||
enum class ProviderCapability {
|
||||
Tools = 0x1,
|
||||
Thinking = 0x2,
|
||||
Image = 0x4,
|
||||
ModelListing = 0x8,
|
||||
};
|
||||
Q_DECLARE_FLAGS(ProviderCapabilities, ProviderCapability)
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(ProviderCapabilities)
|
||||
|
||||
class Provider : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Provider(QObject *parent = nullptr);
|
||||
|
||||
virtual ~Provider() = default;
|
||||
|
||||
virtual QString name() const = 0;
|
||||
virtual QString url() const = 0;
|
||||
virtual void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
= 0;
|
||||
virtual QFuture<QList<QString>> getInstalledModels(const QString &url) = 0;
|
||||
virtual ProviderID providerID() const = 0;
|
||||
virtual ProviderCapabilities capabilities() const { return {}; }
|
||||
|
||||
virtual ::LLMQore::BaseClient *client() const = 0;
|
||||
virtual QString apiKey() const = 0;
|
||||
|
||||
virtual LLMQore::RequestID sendRequest(
|
||||
const QUrl &url, const QJsonObject &payload, const QString &endpoint);
|
||||
void cancelRequest(const LLMQore::RequestID &requestId);
|
||||
::LLMQore::ToolsManager *toolsManager() const;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::PluginLLMCore
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
|
||||
enum class ProviderID {
|
||||
Any,
|
||||
Ollama,
|
||||
LMStudio,
|
||||
Claude,
|
||||
OpenAI,
|
||||
OpenAICompatible,
|
||||
OpenAIResponses,
|
||||
MistralAI,
|
||||
OpenRouter,
|
||||
GoogleAI,
|
||||
LlamaCpp,
|
||||
Qwen,
|
||||
DeepSeek
|
||||
};
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "ProvidersManager.hpp"
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
|
||||
ProvidersManager &ProvidersManager::instance()
|
||||
{
|
||||
static ProvidersManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
QStringList ProvidersManager::providersNames() const
|
||||
{
|
||||
return m_providers.keys();
|
||||
}
|
||||
|
||||
ProvidersManager::~ProvidersManager()
|
||||
{
|
||||
qDeleteAll(m_providers);
|
||||
}
|
||||
|
||||
Provider *ProvidersManager::getProviderByName(const QString &providerName)
|
||||
{
|
||||
if (!m_providers.contains(providerName))
|
||||
return m_providers.first();
|
||||
return m_providers[providerName];
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::PluginLLMCore
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "IProviderRegistry.hpp"
|
||||
#include <QMap>
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
|
||||
class ProvidersManager : public IProviderRegistry
|
||||
{
|
||||
public:
|
||||
static ProvidersManager &instance();
|
||||
~ProvidersManager();
|
||||
|
||||
template<typename T>
|
||||
void registerProvider()
|
||||
{
|
||||
static_assert(std::is_base_of<Provider, T>::value, "T must inherit from Provider");
|
||||
T *provider = new T();
|
||||
QString name = provider->name();
|
||||
m_providers[name] = provider;
|
||||
}
|
||||
|
||||
Provider *getProviderByName(const QString &providerName) override;
|
||||
|
||||
QStringList providersNames() const override;
|
||||
|
||||
private:
|
||||
ProvidersManager() = default;
|
||||
ProvidersManager(const ProvidersManager &) = delete;
|
||||
ProvidersManager &operator=(const ProvidersManager &) = delete;
|
||||
|
||||
QMap<QString, Provider *> m_providers;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::PluginLLMCore
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include <QString>
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
|
||||
enum RequestType { CodeCompletion, Chat, Embedding, QuickRefactoring };
|
||||
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::Providers::ClaudeCacheControl {
|
||||
|
||||
inline QJsonObject buildBreakpoint(bool extendedTtl)
|
||||
{
|
||||
QJsonObject cacheControl{{"type", "ephemeral"}};
|
||||
if (extendedTtl)
|
||||
cacheControl["ttl"] = "1h";
|
||||
return cacheControl;
|
||||
}
|
||||
|
||||
inline void markLastBlock(QJsonArray &blocks, const QJsonObject &cacheControl)
|
||||
{
|
||||
if (blocks.isEmpty())
|
||||
return;
|
||||
QJsonObject last = blocks.last().toObject();
|
||||
last["cache_control"] = cacheControl;
|
||||
blocks.replace(blocks.size() - 1, last);
|
||||
}
|
||||
|
||||
inline void applyToSystem(QJsonObject &request, const QJsonObject &cacheControl)
|
||||
{
|
||||
if (!request.contains("system"))
|
||||
return;
|
||||
|
||||
const QJsonValue sys = request.value("system");
|
||||
if (sys.isString()) {
|
||||
const QString text = sys.toString();
|
||||
if (!text.isEmpty()) {
|
||||
request["system"] = QJsonArray{QJsonObject{
|
||||
{"type", "text"}, {"text", text}, {"cache_control", cacheControl}}};
|
||||
}
|
||||
} else if (sys.isArray()) {
|
||||
QJsonArray blocks = sys.toArray();
|
||||
markLastBlock(blocks, cacheControl);
|
||||
request["system"] = blocks;
|
||||
}
|
||||
}
|
||||
|
||||
inline void applyToTools(QJsonObject &request, const QJsonObject &cacheControl)
|
||||
{
|
||||
if (!request.contains("tools"))
|
||||
return;
|
||||
QJsonArray tools = request.value("tools").toArray();
|
||||
markLastBlock(tools, cacheControl);
|
||||
request["tools"] = tools;
|
||||
}
|
||||
|
||||
inline void applyToHistory(QJsonObject &request, const QJsonObject &cacheControl)
|
||||
{
|
||||
if (!request.contains("messages"))
|
||||
return;
|
||||
QJsonArray messages = request.value("messages").toArray();
|
||||
if (messages.size() < 2)
|
||||
return;
|
||||
|
||||
const int idx = messages.size() - 2;
|
||||
QJsonObject msg = messages[idx].toObject();
|
||||
const QJsonValue content = msg.value("content");
|
||||
if (content.isString()) {
|
||||
msg["content"] = QJsonArray{QJsonObject{
|
||||
{"type", "text"}, {"text", content.toString()}, {"cache_control", cacheControl}}};
|
||||
} else if (content.isArray()) {
|
||||
QJsonArray blocks = content.toArray();
|
||||
markLastBlock(blocks, cacheControl);
|
||||
msg["content"] = blocks;
|
||||
}
|
||||
messages.replace(idx, msg);
|
||||
request["messages"] = messages;
|
||||
}
|
||||
|
||||
inline void apply(QJsonObject &request, bool extendedTtl)
|
||||
{
|
||||
const QJsonObject cacheControl = buildBreakpoint(extendedTtl);
|
||||
applyToSystem(request, cacheControl);
|
||||
applyToTools(request, cacheControl);
|
||||
applyToHistory(request, cacheControl);
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers::ClaudeCacheControl
|
||||
@@ -1,159 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "ClaudeProvider.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
|
||||
#include "ClaudeCacheControl.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
ClaudeProvider::ClaudeProvider(QObject *parent)
|
||||
: PluginLLMCore::Provider(parent)
|
||||
, m_client(new ::LLMQore::ClaudeClient(QString(), QString(), QString(), this))
|
||||
{
|
||||
Tools::registerQodeAssistTools(m_client->tools());
|
||||
}
|
||||
|
||||
QString ClaudeProvider::name() const
|
||||
{
|
||||
return "Claude";
|
||||
}
|
||||
|
||||
QString ClaudeProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().claudeApiKey();
|
||||
}
|
||||
|
||||
QString ClaudeProvider::url() const
|
||||
{
|
||||
return "https://api.anthropic.com";
|
||||
}
|
||||
|
||||
void ClaudeProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
if (settings.useTopP())
|
||||
request["top_p"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
request["top_k"] = settings.topK();
|
||||
request["stream"] = true;
|
||||
};
|
||||
|
||||
auto applyThinkingMode = [&request](const auto &settings) {
|
||||
const QString model = request.value("model").toString().toLower();
|
||||
const bool useAdaptiveThinking = model.contains("opus-4-8") || model.contains("opus-4-7")
|
||||
|| model.contains("opus-4-6") || model.contains("sonnet-4-6");
|
||||
|
||||
QJsonObject thinkingObj;
|
||||
if (useAdaptiveThinking) {
|
||||
thinkingObj["type"] = "adaptive";
|
||||
|
||||
const int budget = settings.thinkingBudgetTokens();
|
||||
QString effort = "high";
|
||||
if (budget < 8000)
|
||||
effort = "low";
|
||||
else if (budget < 24000)
|
||||
effort = "medium";
|
||||
|
||||
QJsonObject outputConfig;
|
||||
outputConfig["effort"] = effort;
|
||||
request["output_config"] = outputConfig;
|
||||
} else {
|
||||
thinkingObj["type"] = "enabled";
|
||||
thinkingObj["budget_tokens"] = settings.thinkingBudgetTokens();
|
||||
}
|
||||
request["thinking"] = thinkingObj;
|
||||
request["max_tokens"] = settings.thinkingMaxTokens();
|
||||
request["temperature"] = 1.0;
|
||||
};
|
||||
|
||||
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||
const auto &qrSettings = Settings::quickRefactorSettings();
|
||||
applyModelParams(qrSettings);
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode(qrSettings);
|
||||
} else {
|
||||
request["temperature"] = qrSettings.temperature();
|
||||
}
|
||||
} else {
|
||||
const auto &chatSettings = Settings::chatAssistantSettings();
|
||||
applyModelParams(chatSettings);
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode(chatSettings);
|
||||
} else {
|
||||
request["temperature"] = chatSettings.temperature();
|
||||
}
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
|
||||
|
||||
if (!toolsDefinitions.isEmpty()) {
|
||||
request["tools"] = toolsDefinitions;
|
||||
LOG_MESSAGE(QString("Added %1 tools to Claude request").arg(toolsDefinitions.size()));
|
||||
}
|
||||
}
|
||||
|
||||
const auto &ps = Settings::providerSettings();
|
||||
const bool cachingOn = ps.claudeEnablePromptCaching()
|
||||
&& type != PluginLLMCore::RequestType::CodeCompletion;
|
||||
m_client->setUseExtendedCacheTTL(cachingOn && ps.claudeUseExtendedCacheTTL());
|
||||
if (cachingOn) {
|
||||
ClaudeCacheControl::apply(request, ps.claudeUseExtendedCacheTTL());
|
||||
}
|
||||
}
|
||||
|
||||
QFuture<QList<QString>> ClaudeProvider::getInstalledModels(const QString &baseUrl)
|
||||
{
|
||||
m_client->setUrl(baseUrl);
|
||||
m_client->setApiKey(apiKey());
|
||||
return m_client->listModels();
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID ClaudeProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::Claude;
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities ClaudeProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Thinking
|
||||
| PluginLLMCore::ProviderCapability::Image
|
||||
| PluginLLMCore::ProviderCapability::ModelListing;
|
||||
}
|
||||
|
||||
::LLMQore::BaseClient *ClaudeProvider::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
|
||||
#include <LLMQore/ClaudeClient.hpp>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class ClaudeProvider : public PluginLLMCore::Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ClaudeProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
|
||||
::LLMQore::BaseClient *client() const override;
|
||||
QString apiKey() const override;
|
||||
|
||||
private:
|
||||
::LLMQore::ClaudeClient *m_client;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright (C) 2025-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "CodestralProvider.hpp"
|
||||
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
CodestralProvider::CodestralProvider(QObject *parent)
|
||||
: MistralAIProvider(parent)
|
||||
{}
|
||||
|
||||
QString CodestralProvider::name() const
|
||||
{
|
||||
return "Codestral";
|
||||
}
|
||||
|
||||
QString CodestralProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().codestralApiKey();
|
||||
}
|
||||
|
||||
QString CodestralProvider::url() const
|
||||
{
|
||||
return "https://codestral.mistral.ai";
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities CodestralProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (C) 2025-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MistralAIProvider.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class CodestralProvider : public MistralAIProvider
|
||||
{
|
||||
public:
|
||||
explicit CodestralProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
QString apiKey() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,109 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "DeepSeekProvider.hpp"
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
DeepSeekProvider::DeepSeekProvider(QObject *parent)
|
||||
: PluginLLMCore::Provider(parent)
|
||||
, m_client(new ::LLMQore::OpenAIClient(QString(), QString(), QString(), this))
|
||||
{
|
||||
Tools::registerQodeAssistTools(m_client->tools());
|
||||
}
|
||||
|
||||
QString DeepSeekProvider::name() const
|
||||
{
|
||||
return "DeepSeek";
|
||||
}
|
||||
|
||||
QString DeepSeekProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().deepSeekApiKey();
|
||||
}
|
||||
|
||||
QString DeepSeekProvider::url() const
|
||||
{
|
||||
return "https://api.deepseek.com";
|
||||
}
|
||||
|
||||
QFuture<QList<QString>> DeepSeekProvider::getInstalledModels(const QString &url)
|
||||
{
|
||||
m_client->setUrl(url);
|
||||
m_client->setApiKey(apiKey());
|
||||
return m_client->listModels();
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID DeepSeekProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::DeepSeek;
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities DeepSeekProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools
|
||||
| PluginLLMCore::ProviderCapability::Thinking
|
||||
| PluginLLMCore::ProviderCapability::ModelListing;
|
||||
}
|
||||
|
||||
void DeepSeekProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
|
||||
if (settings.useTopP())
|
||||
request["top_p"] = settings.topP();
|
||||
if (settings.useFrequencyPenalty())
|
||||
request["frequency_penalty"] = settings.frequencyPenalty();
|
||||
if (settings.usePresencePenalty())
|
||||
request["presence_penalty"] = settings.presencePenalty();
|
||||
};
|
||||
|
||||
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||
applyModelParams(Settings::quickRefactorSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
|
||||
if (!toolsDefinitions.isEmpty()) {
|
||||
request["tools"] = toolsDefinitions;
|
||||
LOG_MESSAGE(QString("Added %1 tools to DeepSeek request").arg(toolsDefinitions.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::LLMQore::BaseClient *DeepSeekProvider::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LLMQore/OpenAIClient.hpp>
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class DeepSeekProvider : public PluginLLMCore::Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeepSeekProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
|
||||
::LLMQore::BaseClient *client() const override;
|
||||
QString apiKey() const override;
|
||||
|
||||
private:
|
||||
::LLMQore::OpenAIClient *m_client;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,159 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "GoogleAIProvider.hpp"
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
|
||||
#include <QJsonArray>
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QtCore/qurlquery.h>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
GoogleAIProvider::GoogleAIProvider(QObject *parent)
|
||||
: PluginLLMCore::Provider(parent)
|
||||
, m_client(new ::LLMQore::GoogleAIClient(QString(), QString(), QString(), this))
|
||||
{
|
||||
Tools::registerQodeAssistTools(m_client->tools());
|
||||
}
|
||||
|
||||
QString GoogleAIProvider::name() const
|
||||
{
|
||||
return "Google AI";
|
||||
}
|
||||
|
||||
QString GoogleAIProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().googleAiApiKey();
|
||||
}
|
||||
|
||||
QString GoogleAIProvider::url() const
|
||||
{
|
||||
return "https://generativelanguage.googleapis.com/v1beta";
|
||||
}
|
||||
|
||||
void GoogleAIProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
QJsonObject generationConfig;
|
||||
generationConfig["maxOutputTokens"] = settings.maxTokens();
|
||||
generationConfig["temperature"] = settings.temperature();
|
||||
|
||||
if (settings.useTopP())
|
||||
generationConfig["topP"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
generationConfig["topK"] = settings.topK();
|
||||
|
||||
request["generationConfig"] = generationConfig;
|
||||
};
|
||||
|
||||
auto applyThinkingMode = [&request](const auto &settings) {
|
||||
QJsonObject generationConfig;
|
||||
generationConfig["maxOutputTokens"] = settings.thinkingMaxTokens();
|
||||
|
||||
if (settings.useTopP())
|
||||
generationConfig["topP"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
generationConfig["topK"] = settings.topK();
|
||||
|
||||
generationConfig["temperature"] = 1.0;
|
||||
|
||||
QJsonObject thinkingConfig;
|
||||
thinkingConfig["includeThoughts"] = true;
|
||||
int budgetTokens = settings.thinkingBudgetTokens();
|
||||
if (budgetTokens != -1) {
|
||||
thinkingConfig["thinkingBudget"] = budgetTokens;
|
||||
}
|
||||
|
||||
generationConfig["thinkingConfig"] = thinkingConfig;
|
||||
request["generationConfig"] = generationConfig;
|
||||
};
|
||||
|
||||
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||
const auto &qrSettings = Settings::quickRefactorSettings();
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode(qrSettings);
|
||||
} else {
|
||||
applyModelParams(qrSettings);
|
||||
}
|
||||
} else {
|
||||
const auto &chatSettings = Settings::chatAssistantSettings();
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode(chatSettings);
|
||||
} else {
|
||||
applyModelParams(chatSettings);
|
||||
}
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
|
||||
if (!toolsDefinitions.isEmpty()) {
|
||||
request["tools"] = toolsDefinitions;
|
||||
LOG_MESSAGE(QString("Added %1 tools to Google AI request").arg(toolsDefinitions.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QFuture<QList<QString>> GoogleAIProvider::getInstalledModels(const QString &baseUrl)
|
||||
{
|
||||
m_client->setUrl(baseUrl);
|
||||
m_client->setApiKey(apiKey());
|
||||
return m_client->listModels();
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID GoogleAIProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::GoogleAI;
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities GoogleAIProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Thinking
|
||||
| PluginLLMCore::ProviderCapability::Image
|
||||
| PluginLLMCore::ProviderCapability::ModelListing;
|
||||
}
|
||||
|
||||
LLMQore::RequestID GoogleAIProvider::sendRequest(
|
||||
const QUrl &url, const QJsonObject &payload, const QString &endpoint)
|
||||
{
|
||||
QJsonObject cleaned = payload;
|
||||
if (cleaned.contains("model")) {
|
||||
m_client->setModel(cleaned["model"].toString());
|
||||
cleaned.remove("model");
|
||||
}
|
||||
cleaned.remove("stream");
|
||||
|
||||
return PluginLLMCore::Provider::sendRequest(url, cleaned, endpoint);
|
||||
}
|
||||
|
||||
::LLMQore::BaseClient *GoogleAIProvider::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
|
||||
#include <LLMQore/GoogleAIClient.hpp>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class GoogleAIProvider : public PluginLLMCore::Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GoogleAIProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
|
||||
LLMQore::RequestID sendRequest(
|
||||
const QUrl &url, const QJsonObject &payload, const QString &endpoint) override;
|
||||
|
||||
::LLMQore::BaseClient *client() const override;
|
||||
QString apiKey() const override;
|
||||
|
||||
private:
|
||||
::LLMQore::GoogleAIClient *m_client;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,120 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "LMStudioProvider.hpp"
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
|
||||
#include "providers/ProviderUrlUtils.hpp"
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
LMStudioProvider::LMStudioProvider(QObject *parent)
|
||||
: PluginLLMCore::Provider(parent)
|
||||
, m_client(new ::LLMQore::OpenAIClient(QString(), QString(), QString(), this))
|
||||
{
|
||||
Tools::registerQodeAssistTools(m_client->tools());
|
||||
}
|
||||
|
||||
QString LMStudioProvider::name() const
|
||||
{
|
||||
return "LM Studio (Chat Completions)";
|
||||
}
|
||||
|
||||
QString LMStudioProvider::apiKey() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
QString LMStudioProvider::url() const
|
||||
{
|
||||
return "http://localhost:1234";
|
||||
}
|
||||
|
||||
QFuture<QList<QString>> LMStudioProvider::getInstalledModels(const QString &url)
|
||||
{
|
||||
m_client->setUrl(ensureOpenAIV1Base(url));
|
||||
m_client->setApiKey(apiKey());
|
||||
return m_client->listModels();
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID LMStudioProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::LMStudio;
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities LMStudioProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image
|
||||
| PluginLLMCore::ProviderCapability::ModelListing
|
||||
| PluginLLMCore::ProviderCapability::Thinking;
|
||||
}
|
||||
|
||||
void LMStudioProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
|
||||
if (settings.useTopP())
|
||||
request["top_p"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
request["top_k"] = settings.topK();
|
||||
if (settings.useFrequencyPenalty())
|
||||
request["frequency_penalty"] = settings.frequencyPenalty();
|
||||
if (settings.usePresencePenalty())
|
||||
request["presence_penalty"] = settings.presencePenalty();
|
||||
};
|
||||
|
||||
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||
applyModelParams(Settings::quickRefactorSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
|
||||
if (!toolsDefinitions.isEmpty()) {
|
||||
request["tools"] = toolsDefinitions;
|
||||
LOG_MESSAGE(QString("Added %1 tools to LMStudio request").arg(toolsDefinitions.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LLMQore::RequestID LMStudioProvider::sendRequest(
|
||||
const QUrl &url, const QJsonObject &payload, const QString &endpoint)
|
||||
{
|
||||
return PluginLLMCore::Provider::sendRequest(
|
||||
QUrl(ensureOpenAIV1Base(url.toString())), payload, endpoint);
|
||||
}
|
||||
|
||||
::LLMQore::BaseClient *LMStudioProvider::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LLMQore/OpenAIClient.hpp>
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class LMStudioProvider : public PluginLLMCore::Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LMStudioProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
|
||||
::LLMQore::BaseClient *client() const override;
|
||||
QString apiKey() const override;
|
||||
|
||||
LLMQore::RequestID sendRequest(
|
||||
const QUrl &url, const QJsonObject &payload, const QString &endpoint) override;
|
||||
|
||||
private:
|
||||
::LLMQore::OpenAIClient *m_client;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,144 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "LMStudioResponsesProvider.hpp"
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
#include "providers/ProviderUrlUtils.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
LMStudioResponsesProvider::LMStudioResponsesProvider(QObject *parent)
|
||||
: PluginLLMCore::Provider(parent)
|
||||
, m_client(new ::LLMQore::OpenAIResponsesClient(QString(), QString(), QString(), this))
|
||||
{
|
||||
Tools::registerQodeAssistTools(m_client->tools());
|
||||
}
|
||||
|
||||
QString LMStudioResponsesProvider::name() const
|
||||
{
|
||||
return "LM Studio (Responses API)";
|
||||
}
|
||||
|
||||
QString LMStudioResponsesProvider::apiKey() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
QString LMStudioResponsesProvider::url() const
|
||||
{
|
||||
return "http://localhost:1234";
|
||||
}
|
||||
|
||||
void LMStudioResponsesProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_output_tokens"] = settings.maxTokens();
|
||||
|
||||
if (settings.useTopP()) {
|
||||
request["top_p"] = settings.topP();
|
||||
}
|
||||
};
|
||||
|
||||
auto applyThinkingMode = [&request](const auto &settings) {
|
||||
QString effortStr = settings.openAIResponsesReasoningEffort.stringValue().toLower();
|
||||
if (effortStr.isEmpty()) {
|
||||
effortStr = "medium";
|
||||
}
|
||||
|
||||
QJsonObject reasoning;
|
||||
reasoning["effort"] = effortStr;
|
||||
request["reasoning"] = reasoning;
|
||||
request["max_output_tokens"] = settings.thinkingMaxTokens();
|
||||
request["store"] = true;
|
||||
|
||||
QJsonArray include;
|
||||
include.append("reasoning.encrypted_content");
|
||||
request["include"] = include;
|
||||
};
|
||||
|
||||
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||
const auto &qrSettings = Settings::quickRefactorSettings();
|
||||
applyModelParams(qrSettings);
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode(qrSettings);
|
||||
}
|
||||
} else {
|
||||
const auto &chatSettings = Settings::chatAssistantSettings();
|
||||
applyModelParams(chatSettings);
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode(chatSettings);
|
||||
}
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
const auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
|
||||
if (!toolsDefinitions.isEmpty()) {
|
||||
request["tools"] = toolsDefinitions;
|
||||
LOG_MESSAGE(QString("Added %1 tools to LM Studio Responses request")
|
||||
.arg(toolsDefinitions.size()));
|
||||
}
|
||||
}
|
||||
|
||||
request["stream"] = true;
|
||||
}
|
||||
|
||||
QFuture<QList<QString>> LMStudioResponsesProvider::getInstalledModels(const QString &baseUrl)
|
||||
{
|
||||
m_client->setUrl(ensureOpenAIV1Base(baseUrl));
|
||||
m_client->setApiKey(apiKey());
|
||||
return m_client->listModels();
|
||||
}
|
||||
|
||||
LLMQore::RequestID LMStudioResponsesProvider::sendRequest(
|
||||
const QUrl &url, const QJsonObject &payload, const QString &endpoint)
|
||||
{
|
||||
return PluginLLMCore::Provider::sendRequest(
|
||||
QUrl(ensureOpenAIV1Base(url.toString())), payload, endpoint);
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID LMStudioResponsesProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::OpenAIResponses;
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities LMStudioResponsesProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Thinking
|
||||
| PluginLLMCore::ProviderCapability::Image
|
||||
| PluginLLMCore::ProviderCapability::ModelListing;
|
||||
}
|
||||
|
||||
::LLMQore::BaseClient *LMStudioResponsesProvider::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LLMQore/OpenAIResponsesClient.hpp>
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class LMStudioResponsesProvider : public PluginLLMCore::Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LMStudioResponsesProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
|
||||
::LLMQore::BaseClient *client() const override;
|
||||
QString apiKey() const override;
|
||||
|
||||
LLMQore::RequestID sendRequest(
|
||||
const QUrl &url, const QJsonObject &payload, const QString &endpoint) override;
|
||||
|
||||
private:
|
||||
::LLMQore::OpenAIResponsesClient *m_client;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,125 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "LlamaCppProvider.hpp"
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
LlamaCppProvider::LlamaCppProvider(QObject *parent)
|
||||
: PluginLLMCore::Provider(parent)
|
||||
, m_client(new ::LLMQore::LlamaCppClient(QString(), QString(), QString(), this))
|
||||
{
|
||||
Tools::registerQodeAssistTools(m_client->tools());
|
||||
}
|
||||
|
||||
QString LlamaCppProvider::name() const
|
||||
{
|
||||
return "llama.cpp";
|
||||
}
|
||||
|
||||
QString LlamaCppProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().llamaCppApiKey();
|
||||
}
|
||||
|
||||
QString LlamaCppProvider::url() const
|
||||
{
|
||||
return "http://localhost:8080";
|
||||
}
|
||||
|
||||
void LlamaCppProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
|
||||
if (settings.useTopP())
|
||||
request["top_p"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
request["top_k"] = settings.topK();
|
||||
if (settings.useFrequencyPenalty())
|
||||
request["frequency_penalty"] = settings.frequencyPenalty();
|
||||
if (settings.usePresencePenalty())
|
||||
request["presence_penalty"] = settings.presencePenalty();
|
||||
};
|
||||
|
||||
auto applyThinkingMode = [&request]() {
|
||||
QJsonObject chatTemplateKwargs = request["chat_template_kwargs"].toObject();
|
||||
chatTemplateKwargs["enable_thinking"] = true;
|
||||
request["chat_template_kwargs"] = chatTemplateKwargs;
|
||||
};
|
||||
|
||||
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||
applyModelParams(Settings::quickRefactorSettings());
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode();
|
||||
LOG_MESSAGE(QString("LlamaCppProvider: Thinking mode enabled for QuickRefactoring"));
|
||||
}
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode();
|
||||
LOG_MESSAGE(QString("LlamaCppProvider: Thinking mode enabled for Chat"));
|
||||
}
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
|
||||
if (!toolsDefinitions.isEmpty()) {
|
||||
request["tools"] = toolsDefinitions;
|
||||
LOG_MESSAGE(QString("Added %1 tools to llama.cpp request").arg(toolsDefinitions.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QFuture<QList<QString>> LlamaCppProvider::getInstalledModels(const QString &baseUrl)
|
||||
{
|
||||
m_client->setUrl(baseUrl);
|
||||
m_client->setApiKey(Settings::providerSettings().llamaCppApiKey());
|
||||
return m_client->listModels();
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID LlamaCppProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::LlamaCpp;
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities LlamaCppProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Thinking
|
||||
| PluginLLMCore::ProviderCapability::Image
|
||||
| PluginLLMCore::ProviderCapability::ModelListing;
|
||||
}
|
||||
|
||||
::LLMQore::BaseClient *LlamaCppProvider::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
|
||||
#include <LLMQore/LlamaCppClient.hpp>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class LlamaCppProvider : public PluginLLMCore::Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LlamaCppProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
|
||||
::LLMQore::BaseClient *client() const override;
|
||||
QString apiKey() const override;
|
||||
|
||||
private:
|
||||
::LLMQore::LlamaCppClient *m_client;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,111 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "MistralAIProvider.hpp"
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
MistralAIProvider::MistralAIProvider(QObject *parent)
|
||||
: PluginLLMCore::Provider(parent)
|
||||
, m_client(new ::LLMQore::MistralClient(QString(), QString(), QString(), this))
|
||||
{
|
||||
Tools::registerQodeAssistTools(m_client->tools());
|
||||
}
|
||||
|
||||
QString MistralAIProvider::name() const
|
||||
{
|
||||
return "Mistral AI";
|
||||
}
|
||||
|
||||
QString MistralAIProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().mistralAiApiKey();
|
||||
}
|
||||
|
||||
QString MistralAIProvider::url() const
|
||||
{
|
||||
return "https://api.mistral.ai";
|
||||
}
|
||||
|
||||
QFuture<QList<QString>> MistralAIProvider::getInstalledModels(const QString &url)
|
||||
{
|
||||
m_client->setUrl(url);
|
||||
m_client->setApiKey(apiKey());
|
||||
return m_client->listModels();
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID MistralAIProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::MistralAI;
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities MistralAIProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image
|
||||
| PluginLLMCore::ProviderCapability::ModelListing
|
||||
| PluginLLMCore::ProviderCapability::Thinking;
|
||||
}
|
||||
|
||||
void MistralAIProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
|
||||
if (settings.useTopP())
|
||||
request["top_p"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
request["top_k"] = settings.topK();
|
||||
if (settings.useFrequencyPenalty())
|
||||
request["frequency_penalty"] = settings.frequencyPenalty();
|
||||
if (settings.usePresencePenalty())
|
||||
request["presence_penalty"] = settings.presencePenalty();
|
||||
};
|
||||
|
||||
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||
applyModelParams(Settings::quickRefactorSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
|
||||
if (!toolsDefinitions.isEmpty()) {
|
||||
request["tools"] = toolsDefinitions;
|
||||
LOG_MESSAGE(QString("Added %1 tools to Mistral request").arg(toolsDefinitions.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::LLMQore::BaseClient *MistralAIProvider::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LLMQore/MistralClient.hpp>
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class MistralAIProvider : public PluginLLMCore::Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MistralAIProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
|
||||
::LLMQore::BaseClient *client() const override;
|
||||
QString apiKey() const override;
|
||||
|
||||
private:
|
||||
::LLMQore::MistralClient *m_client;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,124 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "OllamaCompatProvider.hpp"
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OllamaCompatProvider::OllamaCompatProvider(QObject *parent)
|
||||
: PluginLLMCore::Provider(parent)
|
||||
, m_client(new ::LLMQore::OpenAIClient(QString(), QString(), QString(), this))
|
||||
{
|
||||
Tools::registerQodeAssistTools(m_client->tools());
|
||||
}
|
||||
|
||||
QString OllamaCompatProvider::name() const
|
||||
{
|
||||
return "Ollama (OpenAI-compatible)";
|
||||
}
|
||||
|
||||
QString OllamaCompatProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().ollamaBasicAuthApiKey();
|
||||
}
|
||||
|
||||
QString OllamaCompatProvider::url() const
|
||||
{
|
||||
return "http://localhost:11434";
|
||||
}
|
||||
|
||||
void OllamaCompatProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
|
||||
if (settings.useTopP())
|
||||
request["top_p"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
request["top_k"] = settings.topK();
|
||||
if (settings.useFrequencyPenalty())
|
||||
request["frequency_penalty"] = settings.frequencyPenalty();
|
||||
if (settings.usePresencePenalty())
|
||||
request["presence_penalty"] = settings.presencePenalty();
|
||||
};
|
||||
|
||||
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||
applyModelParams(Settings::quickRefactorSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
|
||||
if (!toolsDefinitions.isEmpty()) {
|
||||
request["tools"] = toolsDefinitions;
|
||||
LOG_MESSAGE(
|
||||
QString("Added %1 tools to OllamaCompat request").arg(toolsDefinitions.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QFuture<QList<QString>> OllamaCompatProvider::getInstalledModels(const QString &baseUrl)
|
||||
{
|
||||
QString url = baseUrl;
|
||||
if (!url.endsWith(QStringLiteral("/v1")))
|
||||
url += QStringLiteral("/v1");
|
||||
m_client->setUrl(url);
|
||||
m_client->setApiKey(apiKey());
|
||||
return m_client->listModels();
|
||||
}
|
||||
|
||||
LLMQore::RequestID OllamaCompatProvider::sendRequest(
|
||||
const QUrl &url, const QJsonObject &payload, const QString &endpoint)
|
||||
{
|
||||
const QString effectiveEndpoint
|
||||
= endpoint.isEmpty() ? QStringLiteral("/v1/chat/completions") : endpoint;
|
||||
return PluginLLMCore::Provider::sendRequest(url, payload, effectiveEndpoint);
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID OllamaCompatProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::OpenAICompatible;
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities OllamaCompatProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image
|
||||
| PluginLLMCore::ProviderCapability::ModelListing
|
||||
| PluginLLMCore::ProviderCapability::Thinking;
|
||||
}
|
||||
|
||||
::LLMQore::BaseClient *OllamaCompatProvider::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LLMQore/OpenAIClient.hpp>
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class OllamaCompatProvider : public PluginLLMCore::Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OllamaCompatProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
|
||||
::LLMQore::BaseClient *client() const override;
|
||||
QString apiKey() const override;
|
||||
|
||||
LLMQore::RequestID sendRequest(
|
||||
const QUrl &url, const QJsonObject &payload, const QString &endpoint) override;
|
||||
|
||||
private:
|
||||
::LLMQore::OpenAIClient *m_client;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,137 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "OllamaProvider.hpp"
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OllamaProvider::OllamaProvider(QObject *parent)
|
||||
: PluginLLMCore::Provider(parent)
|
||||
, m_client(new ::LLMQore::OllamaClient(QString(), QString(), QString(), this))
|
||||
{
|
||||
Tools::registerQodeAssistTools(m_client->tools());
|
||||
}
|
||||
|
||||
QString OllamaProvider::name() const
|
||||
{
|
||||
return "Ollama (Native)";
|
||||
}
|
||||
|
||||
QString OllamaProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().ollamaBasicAuthApiKey();
|
||||
}
|
||||
|
||||
QString OllamaProvider::url() const
|
||||
{
|
||||
return "http://localhost:11434";
|
||||
}
|
||||
|
||||
void OllamaProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applySettings = [&request](const auto &settings) {
|
||||
QJsonObject options;
|
||||
options["num_predict"] = settings.maxTokens();
|
||||
options["temperature"] = settings.temperature();
|
||||
options["stop"] = request.take("stop");
|
||||
|
||||
if (settings.useTopP())
|
||||
options["top_p"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
options["top_k"] = settings.topK();
|
||||
if (settings.useFrequencyPenalty())
|
||||
options["frequency_penalty"] = settings.frequencyPenalty();
|
||||
if (settings.usePresencePenalty())
|
||||
options["presence_penalty"] = settings.presencePenalty();
|
||||
|
||||
request["options"] = options;
|
||||
request["keep_alive"] = settings.ollamaLivetime();
|
||||
};
|
||||
|
||||
auto applyThinkingMode = [&request]() {
|
||||
request["enable_thinking"] = true;
|
||||
QJsonObject options = request["options"].toObject();
|
||||
options["temperature"] = 1.0;
|
||||
request["options"] = options;
|
||||
};
|
||||
|
||||
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||
const auto &qrSettings = Settings::quickRefactorSettings();
|
||||
applySettings(qrSettings);
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode();
|
||||
LOG_MESSAGE(QString("OllamaProvider: Thinking mode enabled for QuickRefactoring"));
|
||||
}
|
||||
} else {
|
||||
const auto &chatSettings = Settings::chatAssistantSettings();
|
||||
applySettings(chatSettings);
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode();
|
||||
LOG_MESSAGE(QString("OllamaProvider: Thinking mode enabled for Chat"));
|
||||
}
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
|
||||
if (!toolsDefinitions.isEmpty()) {
|
||||
request["tools"] = toolsDefinitions;
|
||||
LOG_MESSAGE(
|
||||
QString("OllamaProvider: Added %1 tools to request").arg(toolsDefinitions.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QFuture<QList<QString>> OllamaProvider::getInstalledModels(const QString &baseUrl)
|
||||
{
|
||||
m_client->setUrl(baseUrl);
|
||||
m_client->setApiKey(Settings::providerSettings().ollamaBasicAuthApiKey());
|
||||
return m_client->listModels();
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID OllamaProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::Ollama;
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities OllamaProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Thinking
|
||||
| PluginLLMCore::ProviderCapability::Image
|
||||
| PluginLLMCore::ProviderCapability::ModelListing;
|
||||
}
|
||||
|
||||
::LLMQore::BaseClient *OllamaProvider::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
|
||||
#include <LLMQore/OllamaClient.hpp>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class OllamaProvider : public PluginLLMCore::Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OllamaProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
|
||||
::LLMQore::BaseClient *client() const override;
|
||||
QString apiKey() const override;
|
||||
|
||||
private:
|
||||
::LLMQore::OllamaClient *m_client;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,109 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "OpenAICompatProvider.hpp"
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OpenAICompatProvider::OpenAICompatProvider(QObject *parent)
|
||||
: PluginLLMCore::Provider(parent)
|
||||
, m_client(new ::LLMQore::OpenAIClient(QString(), QString(), QString(), this))
|
||||
{
|
||||
Tools::registerQodeAssistTools(m_client->tools());
|
||||
}
|
||||
|
||||
QString OpenAICompatProvider::name() const
|
||||
{
|
||||
return "OpenAI Compatible";
|
||||
}
|
||||
|
||||
QString OpenAICompatProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().openAiCompatApiKey();
|
||||
}
|
||||
|
||||
QString OpenAICompatProvider::url() const
|
||||
{
|
||||
return "http://localhost:1234/v1";
|
||||
}
|
||||
|
||||
void OpenAICompatProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
|
||||
if (settings.useTopP())
|
||||
request["top_p"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
request["top_k"] = settings.topK();
|
||||
if (settings.useFrequencyPenalty())
|
||||
request["frequency_penalty"] = settings.frequencyPenalty();
|
||||
if (settings.usePresencePenalty())
|
||||
request["presence_penalty"] = settings.presencePenalty();
|
||||
};
|
||||
|
||||
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||
applyModelParams(Settings::quickRefactorSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
|
||||
if (!toolsDefinitions.isEmpty()) {
|
||||
request["tools"] = toolsDefinitions;
|
||||
LOG_MESSAGE(
|
||||
QString("Added %1 tools to OpenAICompat request").arg(toolsDefinitions.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QFuture<QList<QString>> OpenAICompatProvider::getInstalledModels(const QString &)
|
||||
{
|
||||
return QtFuture::makeReadyFuture(QList<QString>{});
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID OpenAICompatProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::OpenAICompatible;
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities OpenAICompatProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image
|
||||
| PluginLLMCore::ProviderCapability::Thinking;
|
||||
}
|
||||
|
||||
::LLMQore::BaseClient *OpenAICompatProvider::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LLMQore/OpenAIClient.hpp>
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class OpenAICompatProvider : public PluginLLMCore::Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OpenAICompatProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
|
||||
::LLMQore::BaseClient *client() const override;
|
||||
QString apiKey() const override;
|
||||
|
||||
private:
|
||||
::LLMQore::OpenAIClient *m_client;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,140 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "OpenAIProvider.hpp"
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OpenAIProvider::OpenAIProvider(QObject *parent)
|
||||
: PluginLLMCore::Provider(parent)
|
||||
, m_client(new ::LLMQore::OpenAIClient(QString(), QString(), QString(), this))
|
||||
{
|
||||
Tools::registerQodeAssistTools(m_client->tools());
|
||||
}
|
||||
|
||||
QString OpenAIProvider::name() const
|
||||
{
|
||||
return "OpenAI (Chat Completions)";
|
||||
}
|
||||
|
||||
QString OpenAIProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().openAiApiKey();
|
||||
}
|
||||
|
||||
QString OpenAIProvider::url() const
|
||||
{
|
||||
return "https://api.openai.com/v1";
|
||||
}
|
||||
|
||||
void OpenAIProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
QString model = request.value("model").toString().toLower();
|
||||
bool useNewParameter = model.contains("gpt-4o") || model.contains("gpt-4-turbo")
|
||||
|| model.contains("o1-") || model.contains("gpt-5")
|
||||
|| model.startsWith("o1") || model.contains("o3");
|
||||
|
||||
bool isReasoningModel = model.contains("o1-") || model.contains("gpt-5")
|
||||
|| model.startsWith("o1") || model.contains("o3");
|
||||
|
||||
if (useNewParameter) {
|
||||
request["max_completion_tokens"] = settings.maxTokens();
|
||||
} else {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
}
|
||||
|
||||
if (!isReasoningModel) {
|
||||
request["temperature"] = settings.temperature();
|
||||
|
||||
if (settings.useTopP())
|
||||
request["top_p"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
request["top_k"] = settings.topK();
|
||||
|
||||
} else {
|
||||
request["temperature"] = 1.0;
|
||||
}
|
||||
|
||||
if (settings.useFrequencyPenalty())
|
||||
request["frequency_penalty"] = settings.frequencyPenalty();
|
||||
if (settings.usePresencePenalty())
|
||||
request["presence_penalty"] = settings.presencePenalty();
|
||||
};
|
||||
|
||||
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||
applyModelParams(Settings::quickRefactorSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
|
||||
if (!toolsDefinitions.isEmpty()) {
|
||||
request["tools"] = toolsDefinitions;
|
||||
LOG_MESSAGE(QString("Added %1 tools to OpenAI request").arg(toolsDefinitions.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QFuture<QList<QString>> OpenAIProvider::getInstalledModels(const QString &baseUrl)
|
||||
{
|
||||
m_client->setUrl(baseUrl);
|
||||
m_client->setApiKey(apiKey());
|
||||
return m_client->listModels().then([](const QList<QString> &allModels) {
|
||||
QList<QString> filtered;
|
||||
for (const QString &modelId : allModels) {
|
||||
if (!modelId.contains("dall-e") && !modelId.contains("whisper")
|
||||
&& !modelId.contains("tts") && !modelId.contains("davinci")
|
||||
&& !modelId.contains("babbage") && !modelId.contains("omni")) {
|
||||
filtered.append(modelId);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
});
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID OpenAIProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::OpenAI;
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities OpenAIProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image
|
||||
| PluginLLMCore::ProviderCapability::ModelListing
|
||||
| PluginLLMCore::ProviderCapability::Thinking;
|
||||
}
|
||||
|
||||
::LLMQore::BaseClient *OpenAIProvider::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LLMQore/OpenAIClient.hpp>
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class OpenAIProvider : public PluginLLMCore::Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OpenAIProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
|
||||
::LLMQore::BaseClient *client() const override;
|
||||
QString apiKey() const override;
|
||||
|
||||
private:
|
||||
::LLMQore::OpenAIClient *m_client;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,147 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "OpenAIResponsesProvider.hpp"
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OpenAIResponsesProvider::OpenAIResponsesProvider(QObject *parent)
|
||||
: PluginLLMCore::Provider(parent)
|
||||
, m_client(new ::LLMQore::OpenAIResponsesClient(QString(), QString(), QString(), this))
|
||||
{
|
||||
Tools::registerQodeAssistTools(m_client->tools());
|
||||
}
|
||||
|
||||
QString OpenAIResponsesProvider::name() const
|
||||
{
|
||||
return "OpenAI (Responses API)";
|
||||
}
|
||||
|
||||
QString OpenAIResponsesProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().openAiApiKey();
|
||||
}
|
||||
|
||||
QString OpenAIResponsesProvider::url() const
|
||||
{
|
||||
return "https://api.openai.com/v1";
|
||||
}
|
||||
|
||||
void OpenAIResponsesProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_output_tokens"] = settings.maxTokens();
|
||||
|
||||
if (settings.useTopP()) {
|
||||
request["top_p"] = settings.topP();
|
||||
}
|
||||
};
|
||||
|
||||
auto applyThinkingMode = [&request](const auto &settings) {
|
||||
QString effortStr = settings.openAIResponsesReasoningEffort.stringValue().toLower();
|
||||
if (effortStr.isEmpty()) {
|
||||
effortStr = "medium";
|
||||
}
|
||||
|
||||
QJsonObject reasoning;
|
||||
reasoning["effort"] = effortStr;
|
||||
request["reasoning"] = reasoning;
|
||||
request["max_output_tokens"] = settings.thinkingMaxTokens();
|
||||
request["store"] = true;
|
||||
|
||||
QJsonArray include;
|
||||
include.append("reasoning.encrypted_content");
|
||||
request["include"] = include;
|
||||
};
|
||||
|
||||
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||
const auto &qrSettings = Settings::quickRefactorSettings();
|
||||
applyModelParams(qrSettings);
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode(qrSettings);
|
||||
}
|
||||
} else {
|
||||
const auto &chatSettings = Settings::chatAssistantSettings();
|
||||
applyModelParams(chatSettings);
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode(chatSettings);
|
||||
}
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
const auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
|
||||
if (!toolsDefinitions.isEmpty()) {
|
||||
request["tools"] = toolsDefinitions;
|
||||
LOG_MESSAGE(QString("Added %1 tools to OpenAI Responses request")
|
||||
.arg(toolsDefinitions.size()));
|
||||
}
|
||||
}
|
||||
|
||||
request["stream"] = true;
|
||||
}
|
||||
|
||||
QFuture<QList<QString>> OpenAIResponsesProvider::getInstalledModels(const QString &baseUrl)
|
||||
{
|
||||
m_client->setUrl(baseUrl);
|
||||
m_client->setApiKey(apiKey());
|
||||
return m_client->listModels().then([](const QList<QString> &models) {
|
||||
QList<QString> filtered;
|
||||
static const QStringList modelPrefixes = {"gpt-5", "o1", "o2", "o3", "o4"};
|
||||
for (const QString &modelId : models) {
|
||||
for (const QString &prefix : modelPrefixes) {
|
||||
if (modelId.contains(prefix)) {
|
||||
filtered.append(modelId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
});
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID OpenAIResponsesProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::OpenAIResponses;
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities OpenAIResponsesProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Thinking
|
||||
| PluginLLMCore::ProviderCapability::Image
|
||||
| PluginLLMCore::ProviderCapability::ModelListing;
|
||||
}
|
||||
|
||||
::LLMQore::BaseClient *OpenAIResponsesProvider::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LLMQore/OpenAIResponsesClient.hpp>
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class OpenAIResponsesProvider : public PluginLLMCore::Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OpenAIResponsesProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
|
||||
::LLMQore::BaseClient *client() const override;
|
||||
QString apiKey() const override;
|
||||
|
||||
private:
|
||||
::LLMQore::OpenAIResponsesClient *m_client;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "OpenRouterAIProvider.hpp"
|
||||
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
OpenRouterProvider::OpenRouterProvider(QObject *parent)
|
||||
: OpenAICompatProvider(parent)
|
||||
{}
|
||||
|
||||
QString OpenRouterProvider::name() const
|
||||
{
|
||||
return "OpenRouter";
|
||||
}
|
||||
|
||||
QString OpenRouterProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().openRouterApiKey();
|
||||
}
|
||||
|
||||
QString OpenRouterProvider::url() const
|
||||
{
|
||||
return "https://openrouter.ai/api";
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID OpenRouterProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::OpenRouter;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "providers/OpenAICompatProvider.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class OpenRouterProvider : public OpenAICompatProvider
|
||||
{
|
||||
public:
|
||||
explicit OpenRouterProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
QString apiKey() const override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
// LM Studio presents its OpenAI-compatible API as <host>/v1/..., while the
|
||||
// OpenAI-style clients expect the base URL to already include /v1. Accept the
|
||||
// configured URL either with or without the /v1 suffix and return it normalized.
|
||||
inline QString ensureOpenAIV1Base(const QString &url)
|
||||
{
|
||||
QString base = url.trimmed();
|
||||
while (base.endsWith(QLatin1Char('/')))
|
||||
base.chop(1);
|
||||
if (!base.endsWith(QStringLiteral("/v1")))
|
||||
base += QStringLiteral("/v1");
|
||||
return base;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pluginllmcore/ProvidersManager.hpp"
|
||||
#include "providers/ClaudeProvider.hpp"
|
||||
#include "providers/DeepSeekProvider.hpp"
|
||||
#include "providers/CodestralProvider.hpp"
|
||||
#include "providers/GoogleAIProvider.hpp"
|
||||
#include "providers/LMStudioProvider.hpp"
|
||||
#include "providers/LMStudioResponsesProvider.hpp"
|
||||
#include "providers/LlamaCppProvider.hpp"
|
||||
#include "providers/MistralAIProvider.hpp"
|
||||
#include "providers/OllamaCompatProvider.hpp"
|
||||
#include "providers/OllamaProvider.hpp"
|
||||
#include "providers/OpenAICompatProvider.hpp"
|
||||
#include "providers/OpenAIProvider.hpp"
|
||||
#include "providers/OpenAIResponsesProvider.hpp"
|
||||
#include "providers/OpenRouterAIProvider.hpp"
|
||||
#include "providers/QwenProvider.hpp"
|
||||
#include "providers/QwenResponsesProvider.hpp"
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
inline void registerProviders()
|
||||
{
|
||||
auto &providerManager = PluginLLMCore::ProvidersManager::instance();
|
||||
providerManager.registerProvider<OllamaProvider>();
|
||||
providerManager.registerProvider<OllamaCompatProvider>();
|
||||
providerManager.registerProvider<ClaudeProvider>();
|
||||
providerManager.registerProvider<OpenAIProvider>();
|
||||
providerManager.registerProvider<OpenAIResponsesProvider>();
|
||||
providerManager.registerProvider<OpenAICompatProvider>();
|
||||
providerManager.registerProvider<LMStudioProvider>();
|
||||
providerManager.registerProvider<LMStudioResponsesProvider>();
|
||||
providerManager.registerProvider<OpenRouterProvider>();
|
||||
providerManager.registerProvider<MistralAIProvider>();
|
||||
providerManager.registerProvider<GoogleAIProvider>();
|
||||
providerManager.registerProvider<LlamaCppProvider>();
|
||||
providerManager.registerProvider<CodestralProvider>();
|
||||
providerManager.registerProvider<QwenProvider>();
|
||||
providerManager.registerProvider<QwenResponsesProvider>();
|
||||
providerManager.registerProvider<DeepSeekProvider>();
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,111 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "QwenProvider.hpp"
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
QwenProvider::QwenProvider(QObject *parent)
|
||||
: PluginLLMCore::Provider(parent)
|
||||
, m_client(new ::LLMQore::OpenAIClient(QString(), QString(), QString(), this))
|
||||
{
|
||||
Tools::registerQodeAssistTools(m_client->tools());
|
||||
}
|
||||
|
||||
QString QwenProvider::name() const
|
||||
{
|
||||
return "Qwen (OpenAI)";
|
||||
}
|
||||
|
||||
QString QwenProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().qwenApiKey();
|
||||
}
|
||||
|
||||
QString QwenProvider::url() const
|
||||
{
|
||||
return "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
|
||||
}
|
||||
|
||||
QFuture<QList<QString>> QwenProvider::getInstalledModels(const QString &url)
|
||||
{
|
||||
m_client->setUrl(url);
|
||||
m_client->setApiKey(apiKey());
|
||||
return m_client->listModels();
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID QwenProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::Qwen;
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities QwenProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Image
|
||||
| PluginLLMCore::ProviderCapability::Thinking
|
||||
| PluginLLMCore::ProviderCapability::ModelListing;
|
||||
}
|
||||
|
||||
void QwenProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_tokens"] = settings.maxTokens();
|
||||
request["temperature"] = settings.temperature();
|
||||
|
||||
if (settings.useTopP())
|
||||
request["top_p"] = settings.topP();
|
||||
if (settings.useTopK())
|
||||
request["top_k"] = settings.topK();
|
||||
if (settings.useFrequencyPenalty())
|
||||
request["frequency_penalty"] = settings.frequencyPenalty();
|
||||
if (settings.usePresencePenalty())
|
||||
request["presence_penalty"] = settings.presencePenalty();
|
||||
};
|
||||
|
||||
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||
applyModelParams(Settings::quickRefactorSettings());
|
||||
} else {
|
||||
applyModelParams(Settings::chatAssistantSettings());
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
|
||||
if (!toolsDefinitions.isEmpty()) {
|
||||
request["tools"] = toolsDefinitions;
|
||||
LOG_MESSAGE(QString("Added %1 tools to Qwen request").arg(toolsDefinitions.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::LLMQore::BaseClient *QwenProvider::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LLMQore/OpenAIClient.hpp>
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class QwenProvider : public PluginLLMCore::Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit QwenProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
|
||||
::LLMQore::BaseClient *client() const override;
|
||||
QString apiKey() const override;
|
||||
|
||||
private:
|
||||
::LLMQore::OpenAIClient *m_client;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,134 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "QwenResponsesProvider.hpp"
|
||||
|
||||
#include <LLMQore/ToolsManager.hpp>
|
||||
#include "logger/Logger.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/CodeCompletionSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProviderSettings.hpp"
|
||||
#include "settings/QuickRefactorSettings.hpp"
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
QwenResponsesProvider::QwenResponsesProvider(QObject *parent)
|
||||
: PluginLLMCore::Provider(parent)
|
||||
, m_client(new ::LLMQore::OpenAIResponsesClient(QString(), QString(), QString(), this))
|
||||
{
|
||||
Tools::registerQodeAssistTools(m_client->tools());
|
||||
}
|
||||
|
||||
QString QwenResponsesProvider::name() const
|
||||
{
|
||||
return "Qwen (OpenAI Response)";
|
||||
}
|
||||
|
||||
QString QwenResponsesProvider::apiKey() const
|
||||
{
|
||||
return Settings::providerSettings().qwenApiKey();
|
||||
}
|
||||
|
||||
QString QwenResponsesProvider::url() const
|
||||
{
|
||||
return "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
|
||||
}
|
||||
|
||||
void QwenResponsesProvider::prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled)
|
||||
{
|
||||
if (!prompt->isSupportProvider(providerID())) {
|
||||
LOG_MESSAGE(QString("Template %1 doesn't support %2 provider").arg(name(), prompt->name()));
|
||||
}
|
||||
|
||||
prompt->prepareRequest(request, context);
|
||||
|
||||
auto applyModelParams = [&request](const auto &settings) {
|
||||
request["max_output_tokens"] = settings.maxTokens();
|
||||
|
||||
if (settings.useTopP()) {
|
||||
request["top_p"] = settings.topP();
|
||||
}
|
||||
};
|
||||
|
||||
auto applyThinkingMode = [&request](const auto &settings) {
|
||||
QString effortStr = settings.openAIResponsesReasoningEffort.stringValue().toLower();
|
||||
if (effortStr.isEmpty()) {
|
||||
effortStr = "medium";
|
||||
}
|
||||
|
||||
QJsonObject reasoning;
|
||||
reasoning["effort"] = effortStr;
|
||||
request["reasoning"] = reasoning;
|
||||
request["max_output_tokens"] = settings.thinkingMaxTokens();
|
||||
request["store"] = true;
|
||||
|
||||
QJsonArray include;
|
||||
include.append("reasoning.encrypted_content");
|
||||
request["include"] = include;
|
||||
};
|
||||
|
||||
if (type == PluginLLMCore::RequestType::QuickRefactoring) {
|
||||
const auto &qrSettings = Settings::quickRefactorSettings();
|
||||
applyModelParams(qrSettings);
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode(qrSettings);
|
||||
}
|
||||
} else {
|
||||
const auto &chatSettings = Settings::chatAssistantSettings();
|
||||
applyModelParams(chatSettings);
|
||||
|
||||
if (isThinkingEnabled) {
|
||||
applyThinkingMode(chatSettings);
|
||||
}
|
||||
}
|
||||
|
||||
if (isToolsEnabled) {
|
||||
const auto toolsDefinitions = m_client->tools()->getToolsDefinitions();
|
||||
if (!toolsDefinitions.isEmpty()) {
|
||||
request["tools"] = toolsDefinitions;
|
||||
LOG_MESSAGE(
|
||||
QString("Added %1 tools to Qwen Responses request").arg(toolsDefinitions.size()));
|
||||
}
|
||||
}
|
||||
|
||||
request["stream"] = true;
|
||||
}
|
||||
|
||||
QFuture<QList<QString>> QwenResponsesProvider::getInstalledModels(const QString &url)
|
||||
{
|
||||
m_client->setUrl(url);
|
||||
m_client->setApiKey(apiKey());
|
||||
return m_client->listModels();
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderID QwenResponsesProvider::providerID() const
|
||||
{
|
||||
return PluginLLMCore::ProviderID::OpenAIResponses;
|
||||
}
|
||||
|
||||
PluginLLMCore::ProviderCapabilities QwenResponsesProvider::capabilities() const
|
||||
{
|
||||
return PluginLLMCore::ProviderCapability::Tools | PluginLLMCore::ProviderCapability::Thinking
|
||||
| PluginLLMCore::ProviderCapability::Image | PluginLLMCore::ProviderCapability::ModelListing;
|
||||
}
|
||||
|
||||
::LLMQore::BaseClient *QwenResponsesProvider::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LLMQore/OpenAIResponsesClient.hpp>
|
||||
#include <pluginllmcore/Provider.hpp>
|
||||
|
||||
namespace QodeAssist::Providers {
|
||||
|
||||
class QwenResponsesProvider : public PluginLLMCore::Provider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit QwenResponsesProvider(QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
QString url() const override;
|
||||
void prepareRequest(
|
||||
QJsonObject &request,
|
||||
PluginLLMCore::PromptTemplate *prompt,
|
||||
PluginLLMCore::ContextData context,
|
||||
PluginLLMCore::RequestType type,
|
||||
bool isToolsEnabled,
|
||||
bool isThinkingEnabled) override;
|
||||
QFuture<QList<QString>> getInstalledModels(const QString &url) override;
|
||||
PluginLLMCore::ProviderID providerID() const override;
|
||||
PluginLLMCore::ProviderCapabilities capabilities() const override;
|
||||
|
||||
::LLMQore::BaseClient *client() const override;
|
||||
QString apiKey() const override;
|
||||
|
||||
private:
|
||||
::LLMQore::OpenAIResponsesClient *m_client;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Providers
|
||||
@@ -42,13 +42,12 @@
|
||||
#include "chat/ChatOutputPane.h"
|
||||
#include "chat/NavigationPanel.hpp"
|
||||
#include "context/DocumentReaderQtCreator.hpp"
|
||||
#include "pluginllmcore/ProvidersManager.hpp"
|
||||
#include "logger/RequestPerformanceLogger.hpp"
|
||||
#include "mcp/McpClientsManager.hpp"
|
||||
#include "mcp/McpServerManager.hpp"
|
||||
#include "sources/skills/SkillsManager.hpp"
|
||||
#include "tools/ToolsRegistration.hpp"
|
||||
#include "providers/Providers.hpp"
|
||||
#include "settings/AgentRole.hpp"
|
||||
#include "settings/ChatAssistantSettings.hpp"
|
||||
#include "settings/GeneralSettings.hpp"
|
||||
#include "settings/ProjectSettingsPanel.hpp"
|
||||
@@ -64,7 +63,6 @@
|
||||
#include <AgentFactory.hpp>
|
||||
#include <GenericProvider.hpp>
|
||||
#include <SessionManager.hpp>
|
||||
#include "templates/Templates.hpp"
|
||||
#include "widgets/CustomInstructionsManager.hpp"
|
||||
#include "widgets/QuickRefactorDialog.hpp"
|
||||
#include <ChatView/ChatView.hpp>
|
||||
@@ -141,9 +139,6 @@ public:
|
||||
|
||||
loadTranslations();
|
||||
|
||||
Providers::registerProviders();
|
||||
Templates::registerTemplates();
|
||||
|
||||
CustomInstructionsManager::instance().loadInstructions();
|
||||
|
||||
Utils::Icon QCODEASSIST_ICON(
|
||||
@@ -180,16 +175,6 @@ public:
|
||||
m_sessionFileRegistry = new Chat::SessionFileRegistry{this};
|
||||
m_skillsManager = new Skills::SkillsManager{this};
|
||||
|
||||
{
|
||||
auto &providers = PluginLLMCore::ProvidersManager::instance();
|
||||
for (const QString &providerName : providers.providersNames()) {
|
||||
if (auto *provider = providers.getProviderByName(providerName)) {
|
||||
if (auto *toolsManager = provider->toolsManager())
|
||||
Tools::registerSkillTool(toolsManager, m_skillsManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings::chatAssistantSettings().enableChatInBottomToolBar()) {
|
||||
m_chatOutputPane = new Chat::ChatOutputPane{
|
||||
m_engine, m_sessionFileRegistry, m_skillsManager};
|
||||
@@ -214,8 +199,26 @@ public:
|
||||
m_providerLauncher,
|
||||
m_providersPageNavigator);
|
||||
|
||||
// Ensure the default agent roles exist on disk before agents load, so a
|
||||
// chat agent's `role = "<id>"` resolves to a system prompt even on a fresh
|
||||
// install where the Roles settings page was never opened.
|
||||
Settings::AgentRolesManager::ensureDefaultRoles();
|
||||
|
||||
m_agentFactory = new AgentFactory(m_providerInstanceFactory, m_providerSecretsStore, this);
|
||||
m_sessionManager = new SessionManager(m_agentFactory, this);
|
||||
{
|
||||
auto &contributors = m_sessionManager->toolContributors();
|
||||
contributors.add([](::LLMQore::ToolsManager *tools) {
|
||||
Tools::registerQodeAssistTools(tools);
|
||||
});
|
||||
contributors.add([skills = m_skillsManager](::LLMQore::ToolsManager *tools) {
|
||||
if (skills)
|
||||
Tools::registerSkillTool(tools, skills);
|
||||
});
|
||||
contributors.add([](::LLMQore::ToolsManager *tools) {
|
||||
Mcp::McpClientsManager::instance().registerToolsOn(tools);
|
||||
});
|
||||
}
|
||||
m_engine->rootContext()->setContextProperty("agentFactory", m_agentFactory);
|
||||
m_engine->rootContext()->setContextProperty("sessionManager", m_sessionManager);
|
||||
m_agentsPageNavigator = new Settings::AgentsPageNavigator(this);
|
||||
|
||||
@@ -171,7 +171,10 @@ void AgentListPane::rebuildList()
|
||||
const QSet<QString> &activeTags = m_tagStrip->activeTags();
|
||||
auto addAgents = [&](const std::vector<const AgentConfig *> &agents) {
|
||||
for (const AgentConfig *cfg : agents) {
|
||||
auto *item = new AgentListItem(*cfg, content);
|
||||
AgentConfig display = *cfg;
|
||||
if (m_factory)
|
||||
display.model = m_factory->effectiveModel(cfg->name);
|
||||
auto *item = new AgentListItem(display, content);
|
||||
item->setSelected(cfg->name == m_currentName);
|
||||
item->setActiveTags(activeTags);
|
||||
connect(item, &AgentListItem::clicked, this, &AgentListPane::onRowClicked);
|
||||
|
||||
@@ -9,6 +9,7 @@ add_library(Session STATIC
|
||||
Session.hpp Session.cpp
|
||||
SessionManager.hpp SessionManager.cpp
|
||||
SystemPromptBuilder.hpp SystemPromptBuilder.cpp
|
||||
ToolContributorRegistry.hpp
|
||||
)
|
||||
|
||||
target_link_libraries(Session
|
||||
|
||||
@@ -210,7 +210,9 @@ LLMQore::RequestID Session::sendCompletion(Templates::ContextData ctx)
|
||||
if (!provider->prepareRequest(payload, tmpl, ctx, /*tools=*/false, /*thinking=*/false))
|
||||
return {};
|
||||
|
||||
const auto id = provider->sendRequest(QUrl(provider->url()), payload, cfg.endpoint);
|
||||
QString endpoint = cfg.endpoint;
|
||||
endpoint.replace(QStringLiteral("${MODEL}"), cfg.model);
|
||||
const auto id = provider->sendRequest(QUrl(provider->url()), payload, endpoint);
|
||||
if (id.isEmpty())
|
||||
return {};
|
||||
|
||||
@@ -242,7 +244,9 @@ LLMQore::RequestID Session::dispatch(
|
||||
if (!provider->prepareRequest(payload, tmpl, ctx, tools, thinking))
|
||||
return {};
|
||||
|
||||
const auto id = provider->sendRequest(QUrl(provider->url()), payload, cfg.endpoint);
|
||||
QString endpoint = cfg.endpoint;
|
||||
endpoint.replace(QStringLiteral("${MODEL}"), cfg.model);
|
||||
const auto id = provider->sendRequest(QUrl(provider->url()), payload, endpoint);
|
||||
if (id.isEmpty())
|
||||
return {};
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <QPointer>
|
||||
#include <QString>
|
||||
|
||||
#include "ToolContributorRegistry.hpp"
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class AgentFactory;
|
||||
@@ -41,6 +43,9 @@ public:
|
||||
|
||||
void cancelAll();
|
||||
|
||||
ToolContributorRegistry &toolContributors() noexcept { return m_toolContributors; }
|
||||
const ToolContributorRegistry &toolContributors() const noexcept { return m_toolContributors; }
|
||||
|
||||
signals:
|
||||
void sessionCreated(Session *session);
|
||||
void sessionRemoved(Session *session);
|
||||
@@ -48,6 +53,7 @@ signals:
|
||||
private:
|
||||
QPointer<AgentFactory> m_agentFactory;
|
||||
QList<QPointer<Session>> m_sessions;
|
||||
ToolContributorRegistry m_toolContributors;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
41
sources/Session/ToolContributorRegistry.hpp
Normal file
41
sources/Session/ToolContributorRegistry.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace LLMQore {
|
||||
class ToolsManager;
|
||||
}
|
||||
|
||||
namespace QodeAssist {
|
||||
|
||||
class ToolContributorRegistry
|
||||
{
|
||||
public:
|
||||
using Contributor = std::function<void(LLMQore::ToolsManager *)>;
|
||||
|
||||
void add(Contributor contributor)
|
||||
{
|
||||
if (contributor)
|
||||
m_contributors.push_back(std::move(contributor));
|
||||
}
|
||||
|
||||
void contribute(LLMQore::ToolsManager *tools) const
|
||||
{
|
||||
if (!tools)
|
||||
return;
|
||||
for (const auto &contributor : m_contributors)
|
||||
contributor(tools);
|
||||
}
|
||||
|
||||
void clear() { m_contributors.clear(); }
|
||||
|
||||
private:
|
||||
std::vector<Contributor> m_contributors;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
@@ -248,6 +248,15 @@ void AgentFactory::setModelOverride(const QString &agentName, const QString &mod
|
||||
saveModelOverrides();
|
||||
}
|
||||
|
||||
QString AgentFactory::effectiveModel(const QString &agentName) const
|
||||
{
|
||||
const QString ov = m_modelOverrides.value(agentName);
|
||||
if (!ov.isEmpty())
|
||||
return ov;
|
||||
const AgentConfig *cfg = configByName(agentName);
|
||||
return cfg ? cfg->model : QString();
|
||||
}
|
||||
|
||||
namespace {
|
||||
QString modelOverridesPath()
|
||||
{
|
||||
|
||||
@@ -59,6 +59,10 @@ public:
|
||||
[[nodiscard]] QString modelOverride(const QString &agentName) const;
|
||||
void setModelOverride(const QString &agentName, const QString &model);
|
||||
|
||||
// The model that will actually be sent for this agent: the settings
|
||||
// override if set, otherwise the agent TOML's default `model`.
|
||||
[[nodiscard]] QString effectiveModel(const QString &agentName) const;
|
||||
|
||||
[[nodiscard]] Providers::ProviderInstanceFactory *instanceFactory() const noexcept;
|
||||
[[nodiscard]] Providers::ProviderSecretsStore *secretsStore() const noexcept;
|
||||
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QStringList>
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <inja/inja.hpp>
|
||||
@@ -155,6 +159,45 @@ void registerStringHelpers(inja::Environment &env)
|
||||
});
|
||||
}
|
||||
|
||||
// Read a role's system prompt from the role JSON written by the settings Roles
|
||||
// UI (AgentRolesManager). Returns "" if the role doesn't exist.
|
||||
std::string roleSystemPrompt(const QString &id)
|
||||
{
|
||||
if (id.isEmpty())
|
||||
return {};
|
||||
const QString path
|
||||
= Core::ICore::userResourcePath(
|
||||
QStringLiteral("qodeassist/agent_roles/%1.json").arg(id))
|
||||
.toFSPathString();
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
qWarning("[QodeAssist] agent_role: role '%s' not found at %s",
|
||||
qUtf8Printable(id), qUtf8Printable(path));
|
||||
return {};
|
||||
}
|
||||
return QJsonDocument::fromJson(f.readAll())
|
||||
.object()
|
||||
.value("systemPrompt")
|
||||
.toString()
|
||||
.toStdString();
|
||||
}
|
||||
|
||||
// Building blocks for composing a profile's `system_prompt` (alongside
|
||||
// read_file/file_exists):
|
||||
// {{ agent_role() }} — the runtime-selected role (Bindings.roleId, which
|
||||
// the chat sets; falls back to "developer")
|
||||
// {{ agent_role("<id>") }} — a specific role by id
|
||||
void registerAgentRole(inja::Environment &env, const Bindings &b)
|
||||
{
|
||||
const QString runtimeRole = b.roleId.isEmpty() ? QStringLiteral("developer") : b.roleId;
|
||||
env.add_callback("agent_role", 0, [runtimeRole](inja::Arguments &) -> nlohmann::json {
|
||||
return roleSystemPrompt(runtimeRole);
|
||||
});
|
||||
env.add_callback("agent_role", 1, [](inja::Arguments &args) -> nlohmann::json {
|
||||
return roleSystemPrompt(QString::fromStdString(args.at(0)->get<std::string>()));
|
||||
});
|
||||
}
|
||||
|
||||
void registerSandbox(inja::Environment &env)
|
||||
{
|
||||
|
||||
@@ -183,6 +226,7 @@ QString render(const QString &templateSource, const Bindings &bindings, QString
|
||||
registerFileExists(env, bindings);
|
||||
registerReadDir(env, bindings);
|
||||
registerStringHelpers(env);
|
||||
registerAgentRole(env, bindings);
|
||||
|
||||
inja::Template tpl;
|
||||
try {
|
||||
|
||||
@@ -12,6 +12,9 @@ struct Bindings
|
||||
{
|
||||
QString projectDir;
|
||||
QString homeDir;
|
||||
// Role id selected at runtime (e.g. in the chat). Used by the no-arg
|
||||
// `{{ agent_role() }}` template callback; empty falls back to "developer".
|
||||
QString roleId;
|
||||
};
|
||||
|
||||
QString render(const QString &templateSource, const Bindings &bindings,
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
<file>partials/google_part.jinja</file>
|
||||
<file>partials/ollama_messages.jinja</file>
|
||||
|
||||
<file>chat_base.toml</file>
|
||||
<file>openai_base_chat.toml</file>
|
||||
<file>openai_responses_base.toml</file>
|
||||
<file>anthropic_base_chat.toml</file>
|
||||
|
||||
@@ -3,16 +3,18 @@ schema_version = 1
|
||||
name = "Anthropic Base Chat"
|
||||
description = "Anthropic Messages API request body (/v1/messages). Abstract — extend it and set model."
|
||||
abstract = true
|
||||
extends = "Chat Base"
|
||||
|
||||
provider_instance = "Claude"
|
||||
endpoint = "/v1/messages"
|
||||
enable_tools = true
|
||||
tags = ["chat", "claude", "anthropic", "cloud"]
|
||||
|
||||
system_prompt = """{{ agent_role() }}"""
|
||||
|
||||
[body]
|
||||
max_tokens = 8192
|
||||
temperature = 1
|
||||
stream = true
|
||||
system = """{% if existsIn(ctx, "system_prompt") %}{{ tojson(ctx.system_prompt) }}{% endif %}"""
|
||||
messages = """
|
||||
[ {% include "partials/anthropic_messages.jinja" %} ]
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
schema_version = 1
|
||||
|
||||
name = "Chat Base"
|
||||
description = "Shared system prompt for coding-chat agents. Abstract — not selectable."
|
||||
abstract = true
|
||||
|
||||
system_prompt = """
|
||||
You are a helpful coding assistant integrated into Qt Creator.
|
||||
Answer concisely. Prefer concrete diffs or minimal patches over rewriting
|
||||
whole files. Use markdown code blocks with language tags.
|
||||
|
||||
{% if file_exists("${PROJECT_DIR}/README.md") %}
|
||||
## Project README.md
|
||||
{{ read_file("${PROJECT_DIR}/README.md") }}
|
||||
{% endif %}
|
||||
"""
|
||||
@@ -3,12 +3,13 @@ schema_version = 1
|
||||
name = "Google Base Chat"
|
||||
description = "Google Gemini generateContent request body. Abstract — extend it and set model/endpoint."
|
||||
abstract = true
|
||||
extends = "Chat Base"
|
||||
|
||||
provider_instance = "Google AI"
|
||||
enable_tools = true
|
||||
tags = ["chat", "gemini", "google", "cloud"]
|
||||
|
||||
system_prompt = """{{ agent_role() }}"""
|
||||
|
||||
[body]
|
||||
system_instruction = """{% if existsIn(ctx, "system_prompt") %}{ "parts": [ { "text": {{ tojson(ctx.system_prompt) }} } ] }{% endif %}"""
|
||||
contents = """
|
||||
|
||||
@@ -4,7 +4,7 @@ extends = "Google Base Chat"
|
||||
name = "Gemini Chat"
|
||||
description = "Google Gemini 2.5 Flash (generateContent API) — coding chat with thinking."
|
||||
|
||||
endpoint = "/models/gemini-2.5-flash:streamGenerateContent?alt=sse"
|
||||
endpoint = "/models/${MODEL}:streamGenerateContent?alt=sse"
|
||||
model = "gemini-2.5-flash"
|
||||
|
||||
enable_thinking = true
|
||||
|
||||
@@ -3,12 +3,13 @@ schema_version = 1
|
||||
name = "Ollama Base Chat"
|
||||
description = "Ollama native /api/chat request body. Abstract — extend it and set model/options."
|
||||
abstract = true
|
||||
extends = "Chat Base"
|
||||
|
||||
provider_instance = "Ollama (Native)"
|
||||
endpoint = "/api/chat"
|
||||
tags = ["ollama", "local"]
|
||||
|
||||
system_prompt = """{{ agent_role() }}"""
|
||||
|
||||
[body]
|
||||
stream = true
|
||||
messages = """
|
||||
|
||||
@@ -3,16 +3,18 @@ schema_version = 1
|
||||
name = "OpenAI Base Chat"
|
||||
description = "OpenAI Chat Completions request body. Abstract — extend it and set provider/endpoint/model."
|
||||
abstract = true
|
||||
extends = "Chat Base"
|
||||
|
||||
provider_instance = "OpenAI (Chat Completions)"
|
||||
endpoint = "/chat/completions"
|
||||
enable_tools = true
|
||||
tags = ["chat", "openai", "cloud"]
|
||||
|
||||
system_prompt = """{{ agent_role() }}"""
|
||||
|
||||
[body]
|
||||
max_tokens = 8192
|
||||
temperature = 0.7
|
||||
stream = true
|
||||
messages = """
|
||||
[ {% include "partials/openai_messages.jinja" %} ]
|
||||
"""
|
||||
|
||||
@@ -3,16 +3,18 @@ schema_version = 1
|
||||
name = "OpenAI Responses Base"
|
||||
description = "OpenAI Responses API request body (/responses). Abstract — extend it and set provider/endpoint/model."
|
||||
abstract = true
|
||||
extends = "Chat Base"
|
||||
|
||||
provider_instance = "OpenAI (Responses API)"
|
||||
endpoint = "/responses"
|
||||
enable_tools = true
|
||||
tags = ["chat", "openai", "responses", "cloud"]
|
||||
|
||||
system_prompt = """{{ agent_role() }}"""
|
||||
|
||||
[body]
|
||||
max_output_tokens = 8192
|
||||
temperature = 0.7
|
||||
stream = true
|
||||
instructions = """{% if existsIn(ctx, "system_prompt") %}{{ tojson(ctx.system_prompt) }}{% endif %}"""
|
||||
input = """
|
||||
[ {% include "partials/openai_responses_input.jinja" %} ]
|
||||
|
||||
@@ -2,6 +2,7 @@ add_library(Common INTERFACE)
|
||||
|
||||
target_sources(Common INTERFACE
|
||||
ContextData.hpp
|
||||
ResponseCleaner.hpp
|
||||
)
|
||||
|
||||
target_include_directories(Common INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <QStringList>
|
||||
#include <QRegularExpression>
|
||||
|
||||
namespace QodeAssist::PluginLLMCore {
|
||||
namespace QodeAssist {
|
||||
|
||||
class ResponseCleaner
|
||||
{
|
||||
@@ -100,5 +100,4 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::PluginLLMCore
|
||||
|
||||
} // namespace QodeAssist
|
||||
@@ -199,6 +199,7 @@ public:
|
||||
AgentRosterRow(int index,
|
||||
const QString &name,
|
||||
const AgentConfig *cfg,
|
||||
const QString &effectiveModel,
|
||||
bool active,
|
||||
bool first,
|
||||
bool last,
|
||||
@@ -226,6 +227,7 @@ private:
|
||||
AgentRosterRow::AgentRosterRow(int index,
|
||||
const QString &name,
|
||||
const AgentConfig *cfg,
|
||||
const QString &effectiveModel,
|
||||
bool active,
|
||||
bool first,
|
||||
bool last,
|
||||
@@ -282,7 +284,7 @@ AgentRosterRow::AgentRosterRow(int index,
|
||||
body->setSpacing(2);
|
||||
|
||||
const QString displayName = cfg ? cfg->name : tr("%1 (missing)").arg(name);
|
||||
const QString model = cfg ? cfg->model : QString();
|
||||
const QString model = effectiveModel;
|
||||
const bool isUser = cfg && cfg->isUserSource();
|
||||
|
||||
body->addWidget(buildIdentityLine(displayName, model, active, isUser, theme));
|
||||
@@ -562,9 +564,11 @@ void AgentRosterWidget::rebuildRows()
|
||||
for (int i = 0; i < m_names.size(); ++i) {
|
||||
const QString &name = m_names.at(i);
|
||||
const AgentConfig *cfg = m_factory ? m_factory->configByName(name) : nullptr;
|
||||
const QString effModel = m_factory ? m_factory->effectiveModel(name) : QString();
|
||||
auto *row = new AgentRosterRow(i,
|
||||
name,
|
||||
cfg,
|
||||
effModel,
|
||||
i == m_activeIndex,
|
||||
/*first*/ i == 0,
|
||||
/*last*/ i == m_names.size() - 1,
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pluginllmcore/PromptTemplate.hpp"
|
||||
#include <QJsonArray>
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class Alpaca : public PluginLLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
QString name() const override { return "Alpaca"; }
|
||||
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::Chat; }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "### Instruction:" << "### Response:";
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
|
||||
QString fullContent;
|
||||
|
||||
if (context.systemPrompt) {
|
||||
fullContent += context.systemPrompt.value() + "\n\n";
|
||||
}
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
if (msg.role == "user") {
|
||||
fullContent += QString("### Instruction:\n%1\n\n").arg(msg.content);
|
||||
} else if (msg.role == "assistant") {
|
||||
fullContent += QString("### Response:\n%1\n\n").arg(msg.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messages.append(QJsonObject{{"role", "user"}, {"content", fullContent}});
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for models using Alpaca instruction format:\n\n"
|
||||
"{\n"
|
||||
" \"messages\": [\n"
|
||||
" {\n"
|
||||
" \"role\": \"user\",\n"
|
||||
" \"content\": \"<system prompt>\\n\\n"
|
||||
"### Instruction:\\n<user message>\\n\\n"
|
||||
"### Response:\\n<assistant response>\\n\\n\"\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Combines all messages into a single formatted prompt.";
|
||||
}
|
||||
bool isSupportProvider(PluginLLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case PluginLLMCore::ProviderID::Ollama:
|
||||
case PluginLLMCore::ProviderID::LMStudio:
|
||||
case PluginLLMCore::ProviderID::OpenRouter:
|
||||
case PluginLLMCore::ProviderID::OpenAICompatible:
|
||||
case PluginLLMCore::ProviderID::LlamaCpp:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@@ -1,76 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "pluginllmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class ChatML : public PluginLLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
QString name() const override { return "ChatML"; }
|
||||
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::Chat; }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "<|im_start|>" << "<|im_end|>";
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
|
||||
if (context.systemPrompt) {
|
||||
messages.append(QJsonObject{
|
||||
{"role", "system"},
|
||||
{"content",
|
||||
QString("<|im_start|>system\n%2\n<|im_end|>").arg(context.systemPrompt.value())}});
|
||||
}
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
messages.append(QJsonObject{
|
||||
{"role", msg.role},
|
||||
{"content",
|
||||
QString("<|im_start|>%1\n%2\n<|im_end|>").arg(msg.role, msg.content)}});
|
||||
}
|
||||
}
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for models supporting ChatML format:\n\n"
|
||||
"{\n"
|
||||
" \"messages\": [\n"
|
||||
" {\n"
|
||||
" \"role\": \"system\",\n"
|
||||
" \"content\": \"<|im_start|>system\\n<system prompt>\\n<|im_end|>\"\n"
|
||||
" },\n"
|
||||
" {\n"
|
||||
" \"role\": \"user\",\n"
|
||||
" \"content\": \"<|im_start|>user\\n<user message>\\n<|im_end|>\"\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Compatible with multiple providers supporting the ChatML token format.";
|
||||
}
|
||||
bool isSupportProvider(PluginLLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case PluginLLMCore::ProviderID::Ollama:
|
||||
case PluginLLMCore::ProviderID::LMStudio:
|
||||
case PluginLLMCore::ProviderID::OpenRouter:
|
||||
case PluginLLMCore::ProviderID::OpenAICompatible:
|
||||
case PluginLLMCore::ProviderID::LlamaCpp:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@@ -1,150 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "pluginllmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class Claude : public PluginLLMCore::PromptTemplate
|
||||
{
|
||||
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;
|
||||
|
||||
if (context.systemPrompt) {
|
||||
request["system"] = context.systemPrompt.value();
|
||||
}
|
||||
|
||||
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()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonArray content;
|
||||
QJsonObject thinkingBlock;
|
||||
thinkingBlock["type"] = msg.isRedacted ? "redacted_thinking" : "thinking";
|
||||
|
||||
QString thinkingText = msg.content;
|
||||
int signaturePos = thinkingText.indexOf("\n[Signature: ");
|
||||
if (signaturePos != -1) {
|
||||
thinkingText = thinkingText.left(signaturePos);
|
||||
}
|
||||
|
||||
if (!msg.isRedacted) {
|
||||
thinkingBlock["thinking"] = thinkingText;
|
||||
}
|
||||
thinkingBlock["signature"] = msg.signature;
|
||||
content.append(thinkingBlock);
|
||||
|
||||
messages.append(QJsonObject{{"role", "assistant"}, {"content", content}});
|
||||
} else if (msg.images && !msg.images->isEmpty()) {
|
||||
QJsonArray content;
|
||||
|
||||
if (!msg.content.isEmpty()) {
|
||||
content.append(QJsonObject{{"type", "text"}, {"text", msg.content}});
|
||||
}
|
||||
|
||||
for (const auto &image : msg.images.value()) {
|
||||
QJsonObject imageBlock;
|
||||
imageBlock["type"] = "image";
|
||||
|
||||
QJsonObject source;
|
||||
if (image.isUrl) {
|
||||
source["type"] = "url";
|
||||
source["url"] = image.data;
|
||||
} else {
|
||||
source["type"] = "base64";
|
||||
source["media_type"] = image.mediaType;
|
||||
source["data"] = image.data;
|
||||
}
|
||||
imageBlock["source"] = source;
|
||||
content.append(imageBlock);
|
||||
}
|
||||
|
||||
messages.append(QJsonObject{{"role", msg.role}, {"content", content}});
|
||||
} else {
|
||||
messages.append(QJsonObject{{"role", msg.role}, {"content", msg.content}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for Anthropic's Claude models:\n\n"
|
||||
"{\n"
|
||||
" \"system\": \"<system prompt>\",\n"
|
||||
" \"messages\": [\n"
|
||||
" {\"role\": \"user\", \"content\": \"<user message>\"},\n"
|
||||
" {\"role\": \"assistant\", \"content\": \"<assistant response>\"}\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Formats content according to Claude API specifications.";
|
||||
}
|
||||
bool isSupportProvider(PluginLLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::PluginLLMCore::ProviderID::Claude:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pluginllmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class CodeLlamaFim : public PluginLLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; }
|
||||
QString name() const override { return "CodeLlama FIM"; }
|
||||
QString endpoint() const override { return QStringLiteral("/api/generate"); }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "<EOT>" << "<PRE>" << "<SUF" << "<MID>";
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
request["prompt"] = QString("<PRE> %1 <SUF>%2 <MID>")
|
||||
.arg(context.prefix.value_or(""), context.suffix.value_or(""));
|
||||
request["system"] = context.systemPrompt.value_or("");
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Specialized template for CodeLlama FIM:\n\n"
|
||||
"{\n"
|
||||
" \"prompt\": \"<PRE> <code prefix> <SUF><code suffix> <MID>\",\n"
|
||||
" \"system\": \"<system prompt>\"\n"
|
||||
"}\n\n"
|
||||
"Optimized for code completion with CodeLlama models.";
|
||||
}
|
||||
bool isSupportProvider(PluginLLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::PluginLLMCore::ProviderID::Ollama:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pluginllmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class CodeLlamaQMLFim : public PluginLLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; }
|
||||
QString name() const override { return "CodeLlama QML FIM"; }
|
||||
QString endpoint() const override { return QStringLiteral("/api/generate"); }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "<SUF>" << "<PRE>" << "</PRE>" << "</SUF>" << "< EOT >" << "\\end"
|
||||
<< "<MID>" << "</MID>" << "##";
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
request["prompt"] = QString("<SUF>%1<PRE>%2<MID>")
|
||||
.arg(context.suffix.value_or(""), context.prefix.value_or(""));
|
||||
request["system"] = context.systemPrompt.value_or("");
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Specialized template for QML code completion with CodeLlama:\n\n"
|
||||
"{\n"
|
||||
" \"prompt\": \"<SUF><code suffix><PRE><code prefix><MID>\",\n"
|
||||
" \"system\": \"<system prompt>\"\n"
|
||||
"}\n\n"
|
||||
"Specifically optimized for QML/JavaScript code completion.";
|
||||
}
|
||||
bool isSupportProvider(PluginLLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::PluginLLMCore::ProviderID::Ollama:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "llmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class DeepSeekCoderFim : public LLMQore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
LLMQore::TemplateType type() const override { return LLMQore::TemplateType::Fim; }
|
||||
QString name() const override { return "DeepSeekCoder FIM"; }
|
||||
QString promptTemplate() const override
|
||||
{
|
||||
return "<|fim▁begin|>%1<|fim▁hole|>%2<|fim▁end|>";
|
||||
}
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
void prepareRequest(QJsonObject &request, const LLMQore::ContextData &context) const override
|
||||
{
|
||||
QString formattedPrompt = promptTemplate().arg(context.prefix, context.suffix);
|
||||
request["prompt"] = formattedPrompt;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "The message will contain the following tokens: "
|
||||
"<|fim▁begin|>%1<|fim▁hole|>%2<|fim▁end|>";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@@ -1,161 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "pluginllmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class GoogleAI : public PluginLLMCore::PromptTemplate
|
||||
{
|
||||
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
|
||||
{
|
||||
QJsonArray contents;
|
||||
|
||||
if (context.systemPrompt && !context.systemPrompt->isEmpty()) {
|
||||
request["system_instruction"] = QJsonObject{
|
||||
{"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;
|
||||
|
||||
if (msg.isThinking) {
|
||||
if (!msg.content.isEmpty()) {
|
||||
QJsonObject thinkingPart;
|
||||
thinkingPart["text"] = msg.content;
|
||||
thinkingPart["thought"] = true;
|
||||
parts.append(thinkingPart);
|
||||
}
|
||||
|
||||
if (!msg.signature.isEmpty()) {
|
||||
QJsonObject signaturePart;
|
||||
signaturePart["thoughtSignature"] = msg.signature;
|
||||
parts.append(signaturePart);
|
||||
}
|
||||
|
||||
if (parts.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
content["role"] = "model";
|
||||
} else {
|
||||
if (!msg.content.isEmpty()) {
|
||||
parts.append(QJsonObject{{"text", msg.content}});
|
||||
}
|
||||
|
||||
if (msg.images && !msg.images->isEmpty()) {
|
||||
for (const auto &image : msg.images.value()) {
|
||||
QJsonObject imagePart;
|
||||
|
||||
if (image.isUrl) {
|
||||
QJsonObject fileData;
|
||||
fileData["mime_type"] = image.mediaType;
|
||||
fileData["file_uri"] = image.data;
|
||||
imagePart["file_data"] = fileData;
|
||||
} else {
|
||||
QJsonObject inlineData;
|
||||
inlineData["mime_type"] = image.mediaType;
|
||||
inlineData["data"] = image.data;
|
||||
imagePart["inline_data"] = inlineData;
|
||||
}
|
||||
|
||||
parts.append(imagePart);
|
||||
}
|
||||
}
|
||||
|
||||
QString role = msg.role;
|
||||
if (role == "assistant") {
|
||||
role = "model";
|
||||
}
|
||||
|
||||
content["role"] = role;
|
||||
}
|
||||
|
||||
content["parts"] = parts;
|
||||
contents.append(content);
|
||||
}
|
||||
|
||||
request["contents"] = contents;
|
||||
}
|
||||
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for Google AI models (Gemini):\n\n"
|
||||
"{\n"
|
||||
" \"system_instruction\": {\"parts\": {\"text\": \"<system prompt>\"}},\n"
|
||||
" \"contents\": [\n"
|
||||
" {\n"
|
||||
" \"role\": \"user\",\n"
|
||||
" \"parts\": [{\"text\": \"<user message>\"}]\n"
|
||||
" },\n"
|
||||
" {\n"
|
||||
" \"role\": \"model\",\n"
|
||||
" \"parts\": [\n"
|
||||
" {\"text\": \"<thinking>\", \"thought\": true},\n"
|
||||
" {\"thoughtSignature\": \"<signature>\"},\n"
|
||||
" {\"text\": \"<assistant response>\"}\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Supports proper role mapping (model/user roles), images, and thinking blocks.";
|
||||
}
|
||||
|
||||
bool isSupportProvider(PluginLLMCore::ProviderID id) const override
|
||||
{
|
||||
return id == QodeAssist::PluginLLMCore::ProviderID::GoogleAI;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pluginllmcore/PromptTemplate.hpp"
|
||||
#include <QJsonArray>
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class Llama2 : public PluginLLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
QString name() const override { return "Llama 2"; }
|
||||
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::Chat; }
|
||||
QStringList stopWords() const override { return QStringList() << "[INST]"; }
|
||||
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
|
||||
QString fullContent;
|
||||
|
||||
if (context.systemPrompt) {
|
||||
fullContent
|
||||
+= QString("[INST]<<SYS>>\n%1\n<</SYS>>[/INST]\n").arg(context.systemPrompt.value());
|
||||
}
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
if (msg.role == "user") {
|
||||
fullContent += QString("[INST]%1[/INST]\n").arg(msg.content);
|
||||
} else if (msg.role == "assistant") {
|
||||
fullContent += msg.content + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messages.append(QJsonObject{{"role", "user"}, {"content", fullContent}});
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for Llama 2 models:\n\n"
|
||||
"{\n"
|
||||
" \"messages\": [\n"
|
||||
" {\n"
|
||||
" \"role\": \"user\",\n"
|
||||
" \"content\": \"[INST]<<SYS>>\\n<system prompt>\\n<</SYS>>[/INST]\\n"
|
||||
"<assistant response>\\n"
|
||||
"[INST]<user message>[/INST]\\n\"\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Compatible with Ollama, LM Studio, and other services for Llama 2.";
|
||||
}
|
||||
bool isSupportProvider(PluginLLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case PluginLLMCore::ProviderID::Ollama:
|
||||
case PluginLLMCore::ProviderID::LMStudio:
|
||||
case PluginLLMCore::ProviderID::OpenRouter:
|
||||
case PluginLLMCore::ProviderID::OpenAICompatible:
|
||||
case PluginLLMCore::ProviderID::LlamaCpp:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@@ -1,80 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "pluginllmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class Llama3 : public PluginLLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
QString name() const override { return "Llama 3"; }
|
||||
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::Chat; }
|
||||
QStringList stopWords() const override
|
||||
{
|
||||
return QStringList() << "<|start_header_id|>" << "<|end_header_id|>" << "<|eot_id|>";
|
||||
}
|
||||
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
QJsonArray messages;
|
||||
|
||||
if (context.systemPrompt) {
|
||||
messages.append(QJsonObject{
|
||||
{"role", "system"},
|
||||
{"content",
|
||||
QString("<|start_header_id|>system<|end_header_id|>%2<|eot_id|>")
|
||||
.arg(context.systemPrompt.value())}});
|
||||
}
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
messages.append(QJsonObject{
|
||||
{"role", msg.role},
|
||||
{"content",
|
||||
QString("<|start_header_id|>%1<|end_header_id|>%2<|eot_id|>")
|
||||
.arg(msg.role, msg.content)}});
|
||||
}
|
||||
}
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for Llama 3 models:\n\n"
|
||||
"{\n"
|
||||
" \"messages\": [\n"
|
||||
" {\n"
|
||||
" \"role\": \"system\",\n"
|
||||
" \"content\": \"<|start_header_id|>system<|end_header_id|><system "
|
||||
"prompt><|eot_id|>\"\n"
|
||||
" },\n"
|
||||
" {\n"
|
||||
" \"role\": \"user\",\n"
|
||||
" \"content\": \"<|start_header_id|>user<|end_header_id|><user "
|
||||
"message><|eot_id|>\"\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Compatible with Ollama, LM Studio, and OpenAI-compatible services for Llama 3.";
|
||||
}
|
||||
bool isSupportProvider(PluginLLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case PluginLLMCore::ProviderID::Ollama:
|
||||
case PluginLLMCore::ProviderID::LMStudio:
|
||||
case PluginLLMCore::ProviderID::OpenRouter:
|
||||
case PluginLLMCore::ProviderID::OpenAICompatible:
|
||||
case PluginLLMCore::ProviderID::LlamaCpp:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "pluginllmcore/PromptTemplate.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class LlamaCppFim : public PluginLLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; }
|
||||
QString name() const override { return "llama.cpp FIM"; }
|
||||
QString endpoint() const override { return QStringLiteral("/infill"); }
|
||||
QStringList stopWords() const override { return {}; }
|
||||
|
||||
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
request["input_prefix"] = context.prefix.value_or("");
|
||||
request["input_suffix"] = context.suffix.value_or("");
|
||||
|
||||
if (context.filesMetadata && !context.filesMetadata->isEmpty()) {
|
||||
QJsonArray filesArray;
|
||||
for (const auto &file : *context.filesMetadata) {
|
||||
QJsonObject fileObj;
|
||||
fileObj["filename"] = file.filePath;
|
||||
fileObj["text"] = file.content;
|
||||
filesArray.append(fileObj);
|
||||
}
|
||||
request["input_extra"] = filesArray;
|
||||
}
|
||||
}
|
||||
|
||||
QString description() const override
|
||||
{
|
||||
return "Default llama.cpp FIM (Fill-in-Middle) /infill template with native format:\n\n"
|
||||
"{\n"
|
||||
" \"input_prefix\": \"<code prefix>\",\n"
|
||||
" \"input_suffix\": \"<code suffix>\",\n"
|
||||
" \"input_extra\": \"<system prompt>\"\n"
|
||||
"}\n\n"
|
||||
"Recommended for models with FIM capability.";
|
||||
}
|
||||
|
||||
bool isSupportProvider(PluginLLMCore::ProviderID id) const override
|
||||
{
|
||||
return id == QodeAssist::PluginLLMCore::ProviderID::LlamaCpp;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
@@ -1,121 +0,0 @@
|
||||
// Copyright (C) 2024-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "pluginllmcore/PromptTemplate.hpp"
|
||||
#include "templates/ToolMessages.hpp"
|
||||
|
||||
namespace QodeAssist::Templates {
|
||||
|
||||
class MistralAIFim : public PluginLLMCore::PromptTemplate
|
||||
{
|
||||
public:
|
||||
PluginLLMCore::TemplateType type() const override { return PluginLLMCore::TemplateType::FIM; }
|
||||
QString name() const override { return "Mistral AI FIM"; }
|
||||
QString endpoint() const override { return QStringLiteral("/v1/fim/completions"); }
|
||||
QStringList stopWords() const override { return QStringList(); }
|
||||
void prepareRequest(QJsonObject &request, const PluginLLMCore::ContextData &context) const override
|
||||
{
|
||||
request["prompt"] = context.prefix.value_or("");
|
||||
request["suffix"] = context.suffix.value_or("");
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for MistralAI models with FIM support:\n\n"
|
||||
"{\n"
|
||||
" \"prompt\": \"<code prefix>\",\n"
|
||||
" \"suffix\": \"<code suffix>\"\n"
|
||||
"}\n\n"
|
||||
"Optimized for code completion with MistralAI models.";
|
||||
}
|
||||
bool isSupportProvider(PluginLLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::PluginLLMCore::ProviderID::MistralAI:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class MistralAIChat : public PluginLLMCore::PromptTemplate
|
||||
{
|
||||
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
|
||||
{
|
||||
QJsonArray messages;
|
||||
|
||||
if (context.systemPrompt) {
|
||||
messages.append(
|
||||
QJsonObject{{"role", "system"}, {"content", context.systemPrompt.value()}});
|
||||
}
|
||||
|
||||
if (context.history) {
|
||||
for (const auto &msg : context.history.value()) {
|
||||
if (appendOpenAIToolMessage(messages, msg)) {
|
||||
continue;
|
||||
}
|
||||
if (msg.images && !msg.images->isEmpty()) {
|
||||
QJsonArray content;
|
||||
|
||||
if (!msg.content.isEmpty()) {
|
||||
content.append(QJsonObject{{"type", "text"}, {"text", msg.content}});
|
||||
}
|
||||
|
||||
for (const auto &image : msg.images.value()) {
|
||||
QJsonObject imageBlock;
|
||||
imageBlock["type"] = "image_url";
|
||||
|
||||
QJsonObject imageUrl;
|
||||
if (image.isUrl) {
|
||||
imageUrl["url"] = image.data;
|
||||
} else {
|
||||
imageUrl["url"] = QString("data:%1;base64,%2").arg(image.mediaType, image.data);
|
||||
}
|
||||
imageBlock["image_url"] = imageUrl;
|
||||
content.append(imageBlock);
|
||||
}
|
||||
|
||||
messages.append(QJsonObject{{"role", msg.role}, {"content", content}});
|
||||
} else {
|
||||
messages.append(QJsonObject{{"role", msg.role}, {"content", msg.content}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
request["messages"] = messages;
|
||||
}
|
||||
QString description() const override
|
||||
{
|
||||
return "Template for MistralAI chat-capable models:\n\n"
|
||||
"{\n"
|
||||
" \"messages\": [\n"
|
||||
" {\"role\": \"system\", \"content\": \"<system prompt>\"},\n"
|
||||
" {\"role\": \"user\", \"content\": \"<user message>\"},\n"
|
||||
" {\"role\": \"assistant\", \"content\": \"<assistant response>\"}\n"
|
||||
" ]\n"
|
||||
"}\n\n"
|
||||
"Supports system messages, conversation history, and images.";
|
||||
}
|
||||
bool isSupportProvider(PluginLLMCore::ProviderID id) const override
|
||||
{
|
||||
switch (id) {
|
||||
case QodeAssist::PluginLLMCore::ProviderID::MistralAI:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::Templates
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user