Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
ollama.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "ollama.h"
8
9#include <QtCore/qjsonarray.h>
10#include <QtCore/qjsonobject.h>
11
12using namespace Qt::Literals::StringLiterals;
13
14QT_BEGIN_NAMESPACE
15
16Ollama::Ollama()
17 : m_payloadBase(std::make_unique<QJsonObject>()),
18 m_useJsonFormat(TranslationSettings::maxJsonFormatTries())
19{
20 m_payloadBase->insert("stream"_L1, false);
21 m_payloadBase->insert("think"_L1, false);
22
23 QJsonObject opts;
24 opts.insert("temperature"_L1, TranslationSettings::temperature());
25 m_payloadBase->insert("options"_L1, opts);
26}
27
28Ollama::~Ollama() = default;
29
30QList<Batch> Ollama::makeBatches(const Messages &messages, const QString &userContext) const
31{
32 QHash<QString, QList<const TranslatorMessage *>> nonPluralGroups;
33 QHash<QString, QList<const TranslatorMessage *>> pluralGroups;
34
35 for (const auto &item : messages.items) {
36 const QString key = item->context() + item->label();
37 if (item->isPlural())
38 pluralGroups[key].append(item);
39 else
40 nonPluralGroups[key].append(item);
41 }
42
43 const int maxBatchSize = TranslationSettings::maxBatchSize();
44 QList<Batch> out;
45 out.reserve(nonPluralGroups.size() + pluralGroups.size());
46
47 auto createBatches = [&](const QHash<QString, QList<const TranslatorMessage *>> &groups,
48 int pluralFormsCount) {
49 for (auto it = groups.cbegin(); it != groups.cend(); ++it) {
50 auto msgIt = it.value().cbegin();
51 while (msgIt != it.value().cend()) {
52 Batch b;
53 b.srcLang = messages.srcLang;
54 b.tgtLang = messages.tgtLang;
55 b.context = it.key();
56 b.userContext = userContext;
57 b.pluralFormsCount = pluralFormsCount;
58 b.items.reserve(it.value().size());
59 while (msgIt != it.value().cend() && b.items.size() < maxBatchSize) {
60 Item item;
61 item.msg = *msgIt;
62 item.translation = item.msg->translation();
63 b.items.append(std::move(item));
64 msgIt++;
65 }
66 out.append(std::move(b));
67 }
68 }
69 };
70
71 createBatches(nonPluralGroups, 1);
72 createBatches(pluralGroups, messages.pluralFormsCount);
73
74 return out;
75}
76
77QHash<QString, QStringList> Ollama::extractTranslations(const QByteArray &response, bool plural)
78{
79 QJsonParseError err;
80 QJsonDocument doc = QJsonDocument::fromJson(response, &err);
81 if (err.error != QJsonParseError::NoError) {
82 m_useJsonFormat--;
83 return {};
84 }
85
86 QHash<QString, QStringList> translations;
87 if (plural) {
88 translations = extractPluralTranslations(doc.object(), "Plurals"_L1);
89 } else {
90 auto singleTranslations = extractKeyValuePairs(doc.object(), "Translations"_L1);
91 for (auto it = singleTranslations.cbegin(); it != singleTranslations.cend(); ++it)
92 translations[it.key()] << it.value();
93 }
94
95 if (translations.isEmpty()) {
96 m_useJsonFormat--;
97 return translations;
98 }
99
100 // If we get a successful response by using json format, the model
101 // is a formatted model. So we want to prevent falling back to
102 // non formatted model (harmony) if there are occasional empty
103 // responses later.
104 if (m_useJsonFormat > 0)
105 m_useJsonFormat = std::numeric_limits<int>::max();
106
107 return translations;
108}
109
110QStringList Ollama::extractModels(const QByteArray &response) const
111{
112 QJsonParseError err;
113 QJsonDocument doc = QJsonDocument::fromJson(response, &err);
114 if (err.error != QJsonParseError::NoError)
115 return {};
116 const QJsonObject obj = doc.object();
117 const QJsonArray arr = obj.value("models"_L1).toArray();
118 QStringList models;
119 for (const QJsonValue &v : arr)
120 models.append(v.toObject().value("name"_L1).toString());
121 return models;
122}
123
125{
126 QJsonObject systemMessage;
127 systemMessage.insert("role"_L1, "system"_L1);
128 const bool plural = b.pluralFormsCount > 1;
129 systemMessage.insert("content"_L1,
130 plural ? pluralTranslationSystemPrompt() : translationSystemPrompt());
131
132 QJsonObject userMessage;
133 userMessage.insert("role"_L1, "user"_L1);
134 userMessage.insert("content"_L1, makePrompt(b));
135
136 QJsonArray messages;
137 messages.append(systemMessage);
138 messages.append(userMessage);
139
140 QJsonObject req = *m_payloadBase;
141 req.insert("messages"_L1, messages);
142
143 if (m_useJsonFormat > 0)
144 req.insert("format"_L1, "json"_L1);
145
146 return QJsonDocument(req).toJson();
147}
148
149std::optional<QByteArray> Ollama::stageModel(const QString &modelName)
150{
151 if (auto m = m_payloadBase->constFind("model"_L1);
152 m == m_payloadBase->constEnd() || *m != modelName) {
153 m_useJsonFormat = TranslationSettings::maxJsonFormatTries();
154 m_payloadBase->insert("model"_L1, modelName);
155 }
156
157 std::optional<QByteArray> res;
158 if (!m_lastWakeupTimer.isValid()
159 || m_lastWakeupTimer.hasExpired(TranslationSettings::ollamaWakeUpTimeoutMs())) {
160 m_lastWakeupTimer.start();
161 QJsonObject wakeup;
162 wakeup.insert("model"_L1, modelName);
163 res.emplace(QJsonDocument(wakeup).toJson());
164 }
165
166 return res;
167}
168
169void Ollama::setUrl(const QString &url)
170{
171 m_url = url;
172}
173
175{
176 return QUrl(m_url).resolved(QUrl("/api/chat"_L1));
177}
178
180{
181 return QUrl(m_url).resolved(QUrl("/api/tags"_L1));
182}
183
185{
186 m_useJsonFormat--;
187}
188
189QString Ollama::makePrompt(const Batch &b) const
190{
191 QStringList lines;
192 lines.reserve(b.items.size() + 32);
193
194 if (!b.userContext.isEmpty())
195 lines << "Application Context: "_L1 + b.userContext;
196
197 lines << "Context: "_L1 + b.context;
198 lines << "Target: "_L1 + b.tgtLang;
199 if (b.pluralFormsCount > 1)
200 lines << "Plural forms: "_L1 + QString::number(b.pluralFormsCount);
201 lines << "Items:"_L1;
202 for (const Item &it : b.items) {
203 QString line = "- source: '%1'"_L1.arg(it.msg->sourceText());
204 if (const QString comment = it.msg->comment(); !comment.isEmpty())
205 line += ", comment: '%1'"_L1.arg(comment);
206 lines << line;
207 }
208
209 return lines.join(QLatin1Char('\n'));
210}
211
212QT_END_NAMESPACE
[0]
Definition lalr.h:84
const TranslatorMessage * msg
QList< Batch > makeBatches(const Messages &messages, const QString &userContext) const override
Definition ollama.cpp:30
QUrl translationEndpoint() const override
Definition ollama.cpp:174
void onRequestRejected() override
Definition ollama.cpp:184
QUrl discoveryEndpoint() const override
Definition ollama.cpp:179
QStringList extractModels(const QByteArray &data) const override
Definition ollama.cpp:110
QByteArray payload(const Batch &b) const override
Definition ollama.cpp:124
~Ollama() override
std::optional< QByteArray > stageModel(const QString &modelName) override
Definition ollama.cpp:149
void setUrl(const QString &url) override
Definition ollama.cpp:169
QHash< QString, QStringList > extractTranslations(const QByteArray &response, bool plural) override
Definition ollama.cpp:77