# Интеграция из Rust / TypeScript Два пути. **А (HTTP)** — самый простой: Python-процесс один раз загружает модель, любой язык ходит по REST. **Б (ONNX)** — без Python в рантайме: модель запускается локально в вашем процессе. | | Путь А: HTTP-сервис | Путь Б: ONNX | |---|---|---| | Зависимости рантайма | запущенный Python-процесс | onnxruntime (+ токенизатор) в вашем языке | | Сегментация HTML | да, на стороне сервиса | нет — в примерах обрабатывается одна дорожка; сегментацию нужно вызвать отдельно или портировать (`html_extract.py` ~60 строк) | | Канонизация имён | да, на стороне сервиса | по `aliases.json` (простой словарь) | | Скорость | сеть + инференс (~мс) | локальный инференс (~мс) | | Кому подходит | сервис/бэкенд | CLI/десктоп/edge, где Python нельзя | ## Путь А: HTTP-сервис Запуск (из корня проекта): ```bash python -m src.service --model models/best --port 8000 # или открыто наружу: python -m src.service --host 0.0.0.0 --port 8000 ``` API: * `GET /health` → `{"status": "ok", "model": "...", "device": "...", "classes": [...]}` * `POST /predict` — ровно одно из полей: * `{"text": "Перевод: Дублированный | MovieDalen"}` → одна дорожка; * `{"html": "…вся страница…"}` → все дорожки релиза. Ответ: `{"tracks": [ …контракт из docs/inference.md… ]}`. curl: ```bash curl -X POST http://127.0.0.1:8000/predict -H "Content-Type: application/json" \ -d '{"text": "Перевод: Дублированный | MovieDalen"}' ``` > **Windows/Git Bash:** кириллица в аргументе `-d` может искажаться кодировкой > консоли — используйте `--data-binary @req.json` с файлом в UTF-8. ### Rust (examples/rust/http_client) ```bash cd examples/rust/http_client cargo run -- --text "Перевод: Дублированный | MovieDalen" cargo run -- --html page.html DUB_URL=http://host:8000 cargo run -- --text "..." ``` Ядро клиента: ```rust let resp: PredictResponse = client .post("http://127.0.0.1:8000/predict") .json(&serde_json::json!({ "html": page_html })) .send().await? .json().await?; for t in &resp.tracks { println!("{} — {}{}", t.translation_type, t.studio.clone().unwrap_or_else(|| "—".into()), if t.is_18plus { " 18+" } else { "" }); } ``` ### TypeScript / Node.js (examples/typescript) Без зависимостей (Node >= 18, глобальный fetch): ```bash node examples/typescript/predict.mjs --text "Одноголосый закадровый — Дмитрий Есарев" node examples/typescript/predict.mjs --html page.html ``` ```ts const res = await fetch("http://127.0.0.1:8000/predict", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: "Одноголосый закадровый — Дмитрий Есарев" }), }); const { tracks } = await res.json(); ``` Типизированная версия — `examples/typescript/predict.ts` (запуск: `npx tsx predict.ts --text "..."`). ### PowerShell (examples/powershell/predict.ps1) Скрипт (работает и в Windows PowerShell 5.1, и в PowerShell 7): ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File examples\powershell\predict.ps1 -Text "Перевод: Дублированный | MovieDalen" pwsh -File examples\powershell\predict.ps1 -HtmlFile page.html -Url http://host:8000 ``` Однострочник (POST с кириллицей; UTF-8 байтами — важно для PS 5.1, где строковый `-Body` уходит в ISO-8859-1): ```powershell $b = [Text.Encoding]::UTF8.GetBytes((@{ text = "Перевод: Дублированный | MovieDalen" } | ConvertTo-Json)) (Invoke-RestMethod -Uri http://127.0.0.1:8000/predict -Method Post -ContentType "application/json" -Body $b).tracks ``` Проверка живости: `Invoke-RestMethod http://127.0.0.1:8000/health`. Ответ сервис отдаёт с `charset=utf-8`, поэтому кириллица в ответе корректно декодируется и в PS 5.1. ## Путь Б: ONNX без Python Экспорт (из корня проекта, после обучения): ```bash python -m src.export_onnx # -> models/best-onnx/ + reports/onnx_parity.md ``` Состав `models/best-onnx/`: `model.onnx` (входы `input_ids`, `attention_mask`, `token_type_ids` int64 [B, seq]; выходы `type_logits` [B, 14], `bio_logits` [B, seq, 3], `age_logits` [B, 2]), `tokenizer.json` (HF fast-токенизатор), `label_maps.json` (классы), `aliases.json` (нормализация имён). Эквивалентность PyTorch проверяется на всём held-out test (совпадение типа/спана/18+ ≥ 99%) — отчёт в `reports/onnx_parity.md`. Клиентская постобработка (одинакова во всех языках): 1. токенизировать дорожку (max_len 128, truncation); 2. прогнать ONNX, взять `argmax`; 3. спан: первый токен с меткой B, затем цепочка I; текст спана — по offsets токенизатора (или `decode` токенов спана); 4. `is_18plus = argmax(age_logits) == 1`; 5. канонизация автора по `aliases.json`. ### Rust (examples/rust/onnx_local) ```bash cd examples/rust/onnx_local && cargo build ORT_DYLIB_PATH="$(find ../../.venv -name onnxruntime.dll | head -1)" \ ./target/debug/dub-ai-onnx-local.exe ../../models/best-onnx \ "Перевод: Дублированный | MovieDalen" ``` Крейты: `ort 2.0.0-rc.15` + `tokenizers` (читает `tokenizer.json` как есть). Библиотека onnxruntime грузится динамически — DLL можно взять из pip-пакета `onnxruntime` (уже есть в `.venv` проекта) или из официального релиза; проверено: тип, спан автора и 18+ совпадают с Python-инференсом. ### TypeScript (examples/typescript/onnx_local.mjs) ```bash cd examples/typescript npm init -y && npm i onnxruntime-node @huggingface/transformers node onnx_local.mjs "../../models/best-onnx" "Перевод: Дублированный | MovieDalen" ``` `@huggingface/transformers` используется только как токенизатор; инференс — `onnxruntime-node`. ## Замечание про сегментацию страниц в пути Б Сервис (путь А) сам находит все дорожки в HTML. В пути Б примеры принимают одну дорожку; для полной обработки страницы нужно сначала получить сегменты (например, один вызов лёгкого HTTP-эндпоинта или порт `html_extract.py` — это регулярки без зависимостей, кроме BeautifulSoup для HTML).