Skip to content

Commit

Permalink
feat: added yandexgpt support
Browse files Browse the repository at this point in the history
  • Loading branch information
ilyhalight committed Feb 22, 2025
1 parent 4295e13 commit 1b17706
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 3 deletions.
3 changes: 3 additions & 0 deletions README-RU.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ const langs = await client.getLangs();
|| YandexBrowser | Translate<br>Detect<br>GetLangs | 10k chars/req<br>10k chars/req |
|| YandexCloud | Translate<br>Detect<br>GetLangs | 2k chars/req<br>1k chars/req |
|| YandexTranslate | Translate<br>Detect<br>GetLangs | 10k chars/req<br>10k chars/req |
|| YandexGPT\*¹ | Translate<br>Detect<br>GetLangs | 10k chars/req<br>10k chars/req |
|| MSEdge | Translate<br>Detect<br>GetLangs | 50k chars/req<br>50k chars/req |

\*¹ - перевод с помощью YandexGPT работает только для пары en-ru. Для всех остальных случаев используется перевод аналогичный YandexTranslate

## Сборка

Для сборки необходимо наличие:
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ You can see more code examples [here](https://github.com/FOSWLY/translate/tree/m
|| YandexBrowser | Translate<br>Detect<br>GetLangs | 10k chars/req<br>10k chars/req |
|| YandexCloud | Translate<br>Detect<br>GetLangs | 2k chars/req<br>1k chars/req |
|| YandexTranslate | Translate<br>Detect<br>GetLangs | 10k chars/req<br>10k chars/req |
|| YandexGPT\*¹ | Translate<br>Detect<br>GetLangs | 10k chars/req<br>10k chars/req |
|| MSEdge | Translate<br>Detect<br>GetLangs | 50k chars/req<br>50k chars/req |

\*¹ - translation using YandexGPT only works for the en-ru pair, For all other cases, a translation similar to YandexTranslate is used.

## Build

To build, you must have:
Expand Down
8 changes: 7 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
# 1.x (unreleased)
# 1.0.5

## Lib

- Added support [new YandexGPT translation model](https://habr.com/ru/companies/yandex/articles/884416/)
- Removed sonarjs rule comments
- Rewritted typebox generation logic with `@toil/typebox-genx`

## Workspace

Expand Down
3 changes: 3 additions & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ import { TranslationService } from "@/types/client";
import YandexBrowserProvider from "./yandexbrowser";
import YandexCloudProvider from "./yandexcloud";
import YandexTranslateProvider from "./yandextranslate";
import YandexGPTProvider from "./yandexgpt";
import MSEdgeTranslateProvider from "./msedge";

export { default as BaseProvider } from "./base";
export { default as YandexBrowserProvider } from "./yandexbrowser";
export { default as YandexCloudProvider } from "./yandexcloud";
export { default as YandexTranslateProvider } from "./yandextranslate";
export { default as YandexGPTProvider } from "./yandexgpt";
export { default as MSEdgeTranslateProvider } from "./msedge";

export const availableProviders = {
[TranslationService.yandexbrowser]: YandexBrowserProvider,
[TranslationService.yandexcloud]: YandexCloudProvider,
[TranslationService.yandextranslate]: YandexTranslateProvider,
[TranslationService.yandexgpt]: YandexGPTProvider,
[TranslationService.msedge]: MSEdgeTranslateProvider,
};

Expand Down
111 changes: 111 additions & 0 deletions src/providers/yandexgpt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Lang } from "@/types/client";

import {
TranslateDetailSuccessResponse,
TranslateOptions,
} from "@/types/providers/yandextranslate";
import { TranslateError } from "@/errors";
import YandexTranslateProvider from "./yandextranslate";

/**
* The total limit of characters per request is 10k chars
*/
export default class YandexGPTProvider extends YandexTranslateProvider {
YAGPT_ORIGIN = "https://neuro.translate.yandex.ru";

getSid() {
const timestamp = Date.now().toString(16);

return (
timestamp +
Array.from({ length: 16 - timestamp.length }, () =>
Math.floor(Math.random() * 16).toString(16),
).join("")
);
}

isSupportedLLMOptions(options: TranslateOptions) {
return ![
TranslateOptions.detailAlign,
TranslateOptions.detailAlignAndDetectedLang,
].includes(options);
}

async llmRawRequest(
text: string | string[],
lang: Lang = "en-ru",
options: TranslateOptions = TranslateOptions.default,
format: "text" | "html" = "text",
) {
if (lang !== "en-ru") {
throw new TranslateError("LLM service support only en-ru lang");
}

if (!this.isSupportedLLMOptions(options)) {
throw new TranslateError(
"Detail align options not supported by LLM service",
);
}

if (!Array.isArray(text)) {
text = [text];
}

const sid = this.getSid();
const params = new URLSearchParams({
lang,
id: `${sid}-0-0`,
srv: "yabrowser-tr-text-app",
tr_model: "llm_text",
format,
}).toString();

const textArray: readonly [string, string][] = text.map((val) => [
"text",
val,
]);
const body = new URLSearchParams([
["options", options.toString()],
["lang", lang],
...textArray,
]);

const res = await this.request<TranslateDetailSuccessResponse>(
`/translate?${params}`,
body,
{
Origin: this.YAGPT_ORIGIN,
Referer: `${this.YAGPT_ORIGIN}/`,
},
);

if (!this.isSuccessProviderRes<TranslateDetailSuccessResponse>(res)) {
throw new TranslateError(res.data);
}

const { lang: resLang, text: resText = [""], align, detected } = res.data;

return {
lang: resLang,
translations: resText,
align,
detected,
};
}

/**
* You can use this method if you need also get a detected language or align
*/
async rawTranslate(
text: string | string[],
lang: Lang = "en-ru",
options: TranslateOptions = TranslateOptions.default,
format: "text" | "html" = "text",
) {
if (lang === "en-ru" && this.isSupportedLLMOptions(options)) {
return await this.llmRawRequest(text, lang, options, format);
}

return await super.rawTranslate(text, lang, options, format);
}
}
5 changes: 4 additions & 1 deletion src/types/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ export type FetchFunction = (
init?: any,
) => Promise<Response>;

// eslint-disable-next-line sonarjs/redundant-type-aliases
export type Lang = string;
export type LangPair = `${string}-${string}`;

Expand All @@ -22,6 +21,10 @@ export enum TranslationService {
* The total limit of characters per request is 10k chars
*/
yandextranslate = "yandextranslate",
/**
* The total limit of characters per request is 10k chars
*/
yandexgpt = "yandexgpt",
/**
* The total limit of characters per request is 50k chars
*/
Expand Down
3 changes: 2 additions & 1 deletion tests/data.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const phrase = "The quick brown fox jumps over the lazy dog";
export const phrase =
"His intemperate youthful idealism, for which he'd done five years of hard time in Siberia, provided the impetus for Crime and Punishment and The Devils; his sensualism and compulsive nature and caustic rationality were the personally destabilizing forces against which he subsequently erected the fortress of The Brother Karamazov and lesser redoubts like The Gambler.";
export const secondPhrase = "Hello world!";
33 changes: 33 additions & 0 deletions tests/yandexgpt.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, test } from "bun:test";
import TranslationClient from "../src";
import { TranslationService } from "../src/types/client";
import { phrase, secondPhrase } from "./data";

const translationClient = new TranslationClient({
service: TranslationService.yandexgpt,
});

describe("translate", () => {
test("translate phrase", async () => {
const res = await translationClient.translate(phrase);
expect(res.lang).toEqual("en-ru");
expect(res.translations.length).toEqual(1);
expect(res.translations[0]).toInclude("Бесов");
});
test("translate multi phrases", async () => {
const res = await translationClient.translate([phrase, secondPhrase]);
expect(res.lang).toEqual("en-ru");
expect(res.translations.length).toEqual(2);
});
});

test("detect language", async () => {
const res = await translationClient.detect(phrase);
expect(res.lang).toEqual("en");
expect(res.score).not.toEqual(null);
});

test("get languages", async () => {
const res = await translationClient.getLangs();
expect(res.length).toBeGreaterThan(0);
});

0 comments on commit 1b17706

Please sign in to comment.