Skip to content
ierokirykasPublic

About

No description or website provided.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Loginom LLM Bridge — интеллектуальная среда интеграции low-code платформы с LLM

Веб-приложение, которое встраивает локальные языковые модели в low-code ETL-платформу Loginom: веб-чат с подмешиванием таблиц в контекст, пул GGUF-моделей с ленивой загрузкой, двусторонний файловый обмен с Loginom через программную генерацию .lgp-пакета и перевод ответов на русский.

Выпускная квалификационная работа бакалавра, 2026.

О публикации. В репозиторий попадает только код. Текст работы, титульные листы, задание и база с реальными чатами — локально и в .gitignore.


Проблема

Аналитик в Loginom получил от заказчика CSV на 3 000 строк. Нужно разобрать свободный текст, нормализовать и вернуть результат обратно в пайплайн. Ручная разметка не масштабируется, облачный LLM нарушает требования по персональным данным.

Три барьера, и все три снимает этот проект:

  1. Формат. LLM отвечает текстом, Loginom ждёт таблицу. Нужен разбор markdown-таблиц из ответа и сборка таблицы с выбранными столбцами и строками.
  2. Обратный ход. Результат надо вернуть в Loginom. У Loginom нет REST-приёмника «из веба» — сценарий собирается только как файл-пакет .lgp.
  3. Ресурсы. Локально на одной машине десятки гигабайт VRAM. Одновременно несколько моделей в памяти не помещаются.

Как это решено

1. Мост через генерацию .lgp-пакета

Ядро проекта. Loginom исполняет сценарий только из файла .lgp (zip с манифестом PackageIndex.xml и графом в Unit_0/Unit.xml), и внутри пакета состояние дублируется ещё и в бинарных .bin. Публичного API для сборки пакета нет.

Отсюда — реверс-инжиниринг формата в loginom_integration.py:

Шаг Что делает
make_unique_filename() генерирует имя вида Files/ABC1234.csv ровно из 17 символов
save_csv() пишет CSV в utf-8-sig (Loginom с кодовой страницей 65001)
modify_xml() re.sub по FileName="..." в Unit.xml, зачистка <ColumnDefs> внутри TBGImportTextFile
patch_bin() бинарный патч: замена FileName с сохранением длины, замена DisplayName узла, обнуление блока ColumnDefs после маркера Engine\x11TBGImportTextFile
build_lgp() copytree шаблона в временную папку → патчи → zipfile (DEFLATE) → .lgp
run_loginom() subprocess.run([loginom.exe, lgp])

Обратный путь — скрипт Loginom.js (36 строк) на движке TBGJavaScriptEngine: читает InputTable из builtIn/Data, формирует JSON.stringify({table: {headers, rows}}) и пишет в файл. Python читает файл и отдаёт результат клиенту.

Полный цикл:

Клиент --POST /api/loginom/export {headers, rows}--> FastAPI
FastAPI: build_lgp → run_loginom (поток daemon) → {"status":"started","task_id"}
Клиент --GET /api/loginom/result/{task_id} каждые 2 с--> FastAPI
FastAPI читает loginom_output.json → {"status":"ready", headers, rows}

2. Управление пулом моделей

Несколько версий архитектуры за 10 дней. Изначально на каждую модель поднимался отдельный процесс llama-server:

subprocess.Popen([LLAMA_SERVER_PATH, "-m", path, "--port", str(PORT), "-ngl", "0", "-c", "8192"])

Дальше — один процесс-роутер с OpenAI-совместимым API и горячей перезагрузкой моделей:

POST /models/load
{
  "model":    "Ternary-Bonsai-8B-Q2_0",
  "-ngl":     99,                    # всё на GPU
  "-c":       ctx,                   # контекст под конкретную модель
  "-np":      1,                     # один параллельный слот
  "--cont-batching": True
}

Модели грузятся лениво — при старте не загружается ничего. Перед загрузкой новой выгружаются все (_unload_all_models()), в роутере одновременно живёт только одна модель. Клиент спрашивает GET /api/model/status раз в 3 секунды.

Контекст подбирается по имени файла:

bonsai   → 16384      nanbeige → 32768
hy-mt2   →  8192      minicpm  → 131072
иначе    →  8192

KV-кэш квантизован и ограничен 4 ГБ: --cache-type-k q8_0 --cache-type-v q8_0 --cache-ram 4096 --no-kv-offload.

3. Перевод отдельной моделью

POST /api/translate поднимает Hy-MT2-1.8B с {"jinja": True} (GGUF от Tencent требует chat-шаблон Jinja) и temperature=0.3.

На фронтенде перевод двухуровневый: автоматический — если в ответе модели есть не-ASCII, после завершения стрима перевод уходит в фон и результат подставляется с флагом showTranslation; ручной — кнопка Languages на каждом сообщении переключает оригинал ↔ перевод.


Архитектура

┌──────────────────────────────────────────────────────────────┐
│  React SPA (Vite + Tailwind 4)                              │
│  Prompt Builder · роли · конструктор таблиц · загрузка файлов│
│  SSE-ридер · экспорт CSV · отправка в Loginom                │
└───────────────────────┬──────────────────────────────────────┘
                        │ HTTP /api/*  +  SSE
┌───────────────────────▼──────────────────────────────────────┐
│  FastAPI (main.py, 632 строки)                               │
│  13 эндпоинтов · статика · автостарт Vite-дева · free_port   │
└──┬──────────────┬────────────────────┬───────────────────────┘
   │              │                    │
┌──▼───────────┐ ┌▼─────────────────┐ ┌▼──────────────────────┐
│ SQLite       │ │ loginom_         │ │ llama.cpp роутер :8080│
│ database.py  │ │ integration.py   │ │ (форк Prism)          │
│ 7 таблиц     │ │ сборка .lgp      │ │ /v1/chat/completions  │
│ 406 строк    │ │ 229 строк        │ │ /models/load|unload   │
└──────────────┘ └──────────────────┘ └───────────────────────┘

Хранилище

7 таблиц SQLite (PRAGMA journal_mode=WAL, foreign_keys=ON):

Таблица Содержимое
chats заголовок, модель, суммарные токены, даты
messages роль, контент, reasoning, usage_json, tokens_per_second, context_window, tables_json, files_json
branches ветки диалога при edit-send: parent_message_id + messages_json
roles именованные системные промпты
tables_data заголовки и строки пользовательских таблиц
text_files абзацы загруженных текстовых файлов
model_contexts n_ctx, зафиксированный за каждой моделью

Поверх — generic-слой STORAGE_MAP, отображающий ключи фронтенда (diplom_chats, diplom_roles, diplom_tables, diplom_text_files, diplom_model_contexts) на CRUD, чтобы не плодить эндпоинты.

API

Метод Путь Назначение
POST /api/chat чат, синхронный или SSE
GET /api/models список GGUF в MODELS_DIR + загруженные
POST /api/model/load загрузка модели в роутер
POST /api/model/unload выгрузка
GET /api/model/status статус текущей модели
GET /api/context-window размер контекста
POST /api/upload CSV / XLSX / TXT / DOCX
POST /api/translate перевод через Hy-MT2
POST /api/loginom/export сборка .lgp и запуск Loginom
GET /api/loginom/result/{task_id} результат (polling)
GET/POST/DELETE /api/data/{key} generic CRUD по хранилищу

SSE проксирует POST /v1/chat/completions со stream=True и дописывает служебный кадр с метриками после завершения. В потоковом режиме токены оцениваются как len(text) // 4; точные значения берутся из json.timings (prompt_n, predicted_n, predicted_per_second, predicted_ms).

Возможности интерфейса

  • edit-send с ветвлением — при правке сообщения «хвост» истории сохраняется в branches, сама история обрезается, запрос уходит с усечённым контекстом. Ветку можно восстановить.
  • Конструктор таблиц — добавление и удаление строк и столбцов, выбор строк и столбцов для включения в запрос.
  • Загрузка файлов drag-and-drop: CSV, XLSX, TXT, DOCX (парсинг openpyxl и python-docx с ленивым импортом).
  • Скачивание CSV (с корректным экранированием кавычек) и кода из markdown-блоков.
  • Markdown-таблицы в ответе модели: отрисовка, отправка в Loginom, скачивание.
  • Блокировка автоскролла при ручном скролле, авторесайз textarea, поллинг списка моделей раз в 15 секунд.
  • Тёмная тема, ⌘↵ / Ctrl+Enter для отправки, отмена активного запроса.

Стек

Backend (Python 3.12): FastAPI 0.136, uvicorn 0.46, Pydantic 2.13, requests 2.32, python-multipart, openpyxl 3.1.5, python-docx 1.2.0, sqlite3.

LLM-рантайм: внешний llama-server из форка Prism (prism-ml/Ternary-Bonsai) — нужны нестандартные эндпоинты /models/load и /models/unload, которых нет в апстриме. CUDA-сборка, -ngl 99.

Frontend: React 18.3.1, Vite 6.3.5, Tailwind CSS 4.1.12 (CSS-first, @theme / @custom-variant), TypeScript, lucide-react, next-themes. Исходный UI сгенерирован в Figma Make, сборка — Vite (не Next.js).

Модели: Ternary-Bonsai-8B-Q2_0 (основная), Hy-MT2-1.8B-Q4_K_M (перевод), MiniCPM5-1B-F16 (быстрая).

Кода: 1 267 строк Python в 3 файлах бэкенда + 2 610 строк в одном App.tsx фронтенда.


Запуск

Требования: Windows, GPU с CUDA (RXT 3050 достаточно), собранный llama.cpp с роутером (форк Prism), GGUF-модели в MODELS_DIR, loginom.exe для экспорта.

# зависимости (нужен именно 3.12 — в 3.14 нет fastapi/uvicorn/pydantic)
py -3.12 -m pip install fastapi uvicorn pydantic requests python-multipart \
                          openpyxl python-docx

# фронтенд
npm.cmd --prefix ".\Chatbot interface design1" install
npm.cmd --prefix ".\Chatbot interface design1" run build

# сервер (отдаёт собранный dist + API)
py -3.12 main.py

Открыть http://localhost:8000, Swagger — http://127.0.0.1:8000/docs.

Режим разработки: если папки dist/ нет, а порт 5173 свободен, lifespan() сам запускает npm run dev — Vite поднимается на 5173 с прокси /api → localhost:8000.

Переменные окружения:

MODELS_DIR        # папка с *.gguf
LLAMA_SERVER_PATH # путь к llama-server.exe
FRONTEND_DIR      # папка фронтенда

Дефолтный LLAMA_SERVER_PATH указывает на C:\llama.cpp\prism-worktree\build\bin\Release\llama-server.exe — путь из-под форка Prism, на чистой машине его не существует. Задайте переменную явно.


Известные ограничения

Бэкенд:

  • CORS открыт полностью: allow_origins=["*"], allow_credentials=True. uvicorn слушает 0.0.0.0 — не выставляйте наружу.
  • Абсолютные пути (C:\llama.cpp\..., C:\Users\<имя>\..., C:\Program Files\Loginom\...) зашиты как дефолты os.environ.get. FRONTEND_DIR и путь к loginom_output.json указывают на конкретную машину.
  • main.py при старте принудительно убивает процесс на порту 8000 (netstat -ano → taskkill /F), и аналогично на 8080.
  • POST /api/loginom/receive присутствует в Unit.xml шаблона как REST-узел на 127.0.0.1:8005 — это наследие отброшенного REST-варианта. В коде такого эндпоинта нет, сервер слушает 8000.
  • _ensure_loaded в переводе не выгружает текущую модель (в отличие от load_model) — перевод может конфликтовать с активной моделью.
  • Промпт пользователя вставляется в документ без экранирования.

Фронтенд:

  • App.tsx — монолит на 2 610 строк: 60+ useState, вся бизнес-логика и разметка в одном компоненте.
  • tsconfig.json отсутствует — типы в коде есть, проверка типов в сборке не выполняется.
  • 49 shadcn-компонентов (~5 400 строк) не используются ни разу — сгенерированы Figma Make, импортов из ./components в App.tsx и main.tsx нет. Весь фактический UI написан на «голом» Tailwind.
  • package.json раздут: 24 Radix-пакета, MUI, emotion, react-slick, canvas-confetti, react-router — из всего этого реально используются lucide-react и next-themes.
  • Мок-данные (INITIAL_MESSAGES, демо-таблицы выручки) зашиты в production-код.
  • Переводы не сохраняются в БД — живут только в состоянии React.
  • saveJson пишет и в localStorage, и в БД без версионирования схемы.

Нет requirements.txt, Dockerfile, тестов, CI.


Атрибуция

  • shadcn/ui и Radix UI — MIT, атрибуция в Chatbot interface design1/ATTRIBUTIONS.md.
  • Unsplash — кредиты указаны в том же файле, сами изображения не используются.
  • llama.cpp — MIT. Используется форк prism-ml/Ternary-Bonsai.
  • Loginom — проприетарный продукт; используется локально, код Loginom в репозитории не распространяется. Формат .lgp разобран для совместимости с собственным сценарием.
  • Исходный UI создан в Figma Make.

About

No description or website provided.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages