9#include <QtCore/qjsonarray.h>
10#include <QtCore/qjsonobject.h>
12using namespace Qt::Literals::StringLiterals;
16OpenAICompatible::OpenAICompatible()
17 : m_payloadBase(std::make_unique<QJsonObject>()),
18 m_formatTryCounter(TranslationSettings::maxJsonFormatTries())
20 m_payloadBase->insert(
"stream"_L1,
false);
21 m_payloadBase->insert(
"temperature"_L1, TranslationSettings::temperature());
27 const QString &userContext)
const
29 QHash<QString, QList<
const TranslatorMessage *>> nonPluralGroups;
30 QHash<QString, QList<
const TranslatorMessage *>> pluralGroups;
32 for (
const auto &item : messages.items) {
33 const QString key = item->context() + item->label();
35 pluralGroups[key].append(item);
37 nonPluralGroups[key].append(item);
42 out.reserve(nonPluralGroups.size() + pluralGroups.size());
44 auto createBatches = [&](
const QHash<QString, QList<
const TranslatorMessage *>> &groups,
45 int pluralFormsCount) {
46 for (
auto it = groups.cbegin(); it != groups.cend(); ++it) {
47 auto msgIt = it.value().cbegin();
48 while (msgIt != it.value().cend()) {
50 b.srcLang = messages.srcLang;
51 b.tgtLang = messages.tgtLang;
53 b.userContext = userContext;
54 b.pluralFormsCount = pluralFormsCount;
55 b.items.reserve(it.value().size());
56 while (msgIt != it.value().cend() && b.items.size() < maxBatchSize) {
59 item.translation = item
.msg->translation();
60 b.items.append(std::move(item));
63 out.append(std::move(b));
68 createBatches(nonPluralGroups, 1);
78 QJsonDocument doc = QJsonDocument::fromJson(response, &err);
79 if (err.error != QJsonParseError::NoError) {
80 decrementFormatCounter();
85 const QJsonObject root = doc.object();
86 const QJsonArray choices = root.value(
"choices"_L1).toArray();
87 if (choices.isEmpty()) {
88 decrementFormatCounter();
92 const QJsonObject firstChoice = choices.first().toObject();
93 const QJsonObject message = firstChoice.value(
"message"_L1).toObject();
94 const QString content = message.value(
"content"_L1).toString();
97 QJsonDocument contentDoc = QJsonDocument::fromJson(content.toUtf8(), &err);
98 QJsonValue contentValue;
99 if (err.error == QJsonParseError::NoError) {
100 contentValue = contentDoc.object();
103 contentValue = content;
106 QHash<QString, QStringList> translations;
108 translations = extractPluralTranslations(contentValue,
"Plurals"_L1);
110 auto singleTranslations = extractKeyValuePairs(contentValue,
"Translations"_L1);
111 for (
auto it = singleTranslations.cbegin(); it != singleTranslations.cend(); ++it)
112 translations[it.key()] << it.value();
115 if (translations.isEmpty()) {
116 decrementFormatCounter();
122 m_formatLocked =
true;
130 QJsonDocument doc = QJsonDocument::fromJson(response, &err);
131 if (err.error != QJsonParseError::NoError)
135 const QJsonObject obj = doc.object();
136 const QJsonArray arr = obj.value(
"data"_L1).toArray();
138 for (
const QJsonValue &v : arr)
139 models.append(v.toObject().value(
"id"_L1).toString());
145 QJsonObject systemMessage;
146 systemMessage.insert(
"role"_L1,
"system"_L1);
148 systemMessage.insert(
"content"_L1,
149 plural ? pluralTranslationSystemPrompt() : translationSystemPrompt());
151 QJsonObject userMessage;
152 userMessage.insert(
"role"_L1,
"user"_L1);
153 userMessage.insert(
"content"_L1, makePrompt(b));
156 messages.append(systemMessage);
157 messages.append(userMessage);
159 QJsonObject req = *m_payloadBase;
160 req.insert(
"messages"_L1, messages);
162 switch (m_formatStage) {
163 case JsonFormatStage::JsonObject: {
165 QJsonObject responseFormat;
166 responseFormat.insert(
"type"_L1,
"json_object"_L1);
167 req.insert(
"response_format"_L1, responseFormat);
170 case JsonFormatStage::JsonSchema: {
173 schema.insert(
"type"_L1,
"object"_L1);
174 QJsonObject properties;
175 QJsonObject translationsArray;
176 translationsArray.insert(
"type"_L1,
"array"_L1);
177 properties.insert(
"Translations"_L1, translationsArray);
178 schema.insert(
"properties"_L1, properties);
180 required.append(
"Translations"_L1);
181 schema.insert(
"required"_L1, required);
183 QJsonObject jsonSchema;
184 jsonSchema.insert(
"name"_L1,
"translations"_L1);
185 jsonSchema.insert(
"schema"_L1, schema);
187 QJsonObject responseFormat;
188 responseFormat.insert(
"type"_L1,
"json_schema"_L1);
189 responseFormat.insert(
"json_schema"_L1, jsonSchema);
190 req.insert(
"response_format"_L1, responseFormat);
193 case JsonFormatStage::None:
198 return QJsonDocument(req).toJson();
203 if (
auto m = m_payloadBase->constFind(
"model"_L1);
204 m == m_payloadBase->constEnd() || *m != modelName) {
206 m_formatStage = JsonFormatStage::JsonObject;
207 m_formatTryCounter = TranslationSettings::maxJsonFormatTries();
208 m_formatLocked =
false;
209 m_payloadBase->insert(
"model"_L1, modelName);
224 QString base = m_url;
225 if (!base.endsWith(u'/'))
227 return QUrl(base +
"v1/chat/completions"_L1);
232 QString base = m_url;
233 if (!base.endsWith(u'/'))
235 return QUrl(base +
"v1/models"_L1);
240 decrementFormatCounter();
248 if (--m_formatTryCounter <= 0) {
251 switch (m_formatStage) {
252 case JsonFormatStage::JsonObject:
253 m_formatStage = JsonFormatStage::JsonSchema;
254 m_formatTryCounter = maxTries;
256 case JsonFormatStage::JsonSchema:
257 m_formatStage = JsonFormatStage::None;
258 m_formatTryCounter = maxTries;
260 case JsonFormatStage::None:
270 lines.reserve(b.items.size() + 32);
272 if (!b.userContext.isEmpty())
273 lines <<
"Application Context: "_L1 + b.userContext;
275 lines <<
"Context: "_L1 + b.context;
276 lines <<
"Target: "_L1 + b.tgtLang;
277 if (b.pluralFormsCount > 1)
278 lines <<
"Plural forms: "_L1 + QString::number(b.pluralFormsCount);
279 lines <<
"Items:"_L1;
280 for (
const Item &it : b.items) {
281 QString line =
"- source: '%1'"_L1.arg(it.msg->sourceText());
282 if (
const QString comment = it.msg->comment(); !comment.isEmpty())
283 line +=
", comment: '%1'"_L1.arg(comment);
287 return lines.join(QLatin1Char(
'\n'));
const TranslatorMessage * msg
std::optional< QByteArray > stageModel(const QString &modelName) override
QList< Batch > makeBatches(const Messages &messages, const QString &userContext) const override
~OpenAICompatible() override
QStringList extractModels(const QByteArray &data) const override
QHash< QString, QStringList > extractTranslations(const QByteArray &response, bool plural) override
void onRequestRejected() override
QUrl discoveryEndpoint() const override
QByteArray payload(const Batch &b) const override
void setUrl(const QString &url) override
QUrl translationEndpoint() const override
static int maxBatchSize()
static int maxJsonFormatTries()