
Эта документация разработки ИИ сервисов на платформе ТРОПАСС и подготовки необходимых файлов. Если вам нужно только вызвать готовый сервис из своего кода, сразу переходите к разделу «Где взять API-ключ и model_id».
Готовый пример проекта лежит в репозитории tropass-ai/hello-tropass на GitHub: в нём есть описание примера, metadata.json, готовые файлы для archive.zip и AGENTS.md с правилами для ИИ-агента. Репозиторий можно использовать как шаблон (кнопка Use this template).
Основы
Сервисы ТРОПАСС описываются входными и выходными контрактами и определённой структурой файлов. Структура типового Python-проекта:
hello-tropass/
├── main.py # главная функция и приложение сервера
├── utils.py # вспомогательные функции (можно свои модули и пакеты в modules/)
├── modules/
│ └── __init__.py
├── metadata.json # входы и выходы (грузится отдельным файлом, в архив не входит)
├── requirements.txt
├── input_examples/
│ └── input.json # тестовый вход
├── output_examples/ # файлы для панелей image/video (если нужны)
├── tests/ # тесты pytest
├── run_test.py # запуск main() без сервера
├── build.sh # сборка deploy/archive.zip и копия metadata.json в deploy/
└── deploy/ # то, что грузится на платформу (в .gitignore)
Обязательно подготовить два файла: metadata.json и main.py. В файле metadata.json содержится структура входных и выходных данных сервиса, а в файле main.py - главная исполняемая функция.
Папка modules - для ваших скриптов; таких папок может быть сколько угодно. Библиотека tropass_sdk подключается как обычный Python-пакет: tropass-sdk на PyPI. Достаточно указать tropass-sdk[server] в requirements.txt (в примере tropass-sdk[server]==2.1), копировать её в проект не нужно.
В папке input_examples лежит тестовый вход. Он нужен для автотеста модели после сборки; рекомендуем его добавлять, но это не обязательно.
В requirements.txt перечисляются библиотеки сервиса. В архив archive.zip для загрузки на платформу идут main.py, utils.py, requirements.txt, modules/, input_examples/ и output_examples/; тесты, run_test.py и metadata.json не входят. Состав архива не ограничен: добавляйте свои файлы и пакеты.
Требования к версии Python
Минимальная поддерживаемая версия: Python >= 3.10
Описание полей и файл metadata.json
Для нового сервиса следует описать входные и выходные поля в отдельном файле metadata.json (пример: hello-tropass/metadata.json). Он состоит из набора ключей, назначение которых - описать в максимально краткой форме, какие входные и выходные поля нужны вашему сервису.
Файл metadata.json имеет следующий вид:
{
"model_name": "work",
"model_service": "Демо модель для разработчиков",
"inputs": [
{
"indicator_name": "text_input",
"display_field_name": "Тут вводится текст",
"indicator_type": "text",
"is_required": true,
"allowed_file_extensions": ["png", "jpg"],
"default_value": "Тут вводится дефолтное значение"
}
]
"outputs": [
{
"indicator_name": "text_output",
"display_result_name": "Тут будет текстовая панель",
"indicator_type": "text"
}
]
}
Поле is_required позволяет пометить поле как обязательное или необязательное для заполнения пользователем.
Поле default_value позволяет задать дефолтное значение для инпута. Пример:
- text, dropdown - "любая строка"
- number - строка, которую можно перевести в число, разделитель только точка. Пример валидного значения - "0.42"
- checkbox - "True" или "False"
- multi-select - строка, содержащая json массива строк. Пример валидного значения - "["Выбор +100500","Выбор 1"]" (в json требуются только двойные кавычки, одинарные - невалидный синтаксис)
- selector-slider - строка, содержащая json из двух элементов, строки и числа. Пример валидного значения - "["Выбор +100500",1]" .
- voice, file, multi-file - нельзя установить дефолтное значение
Необязательное поле верхнего уровня python_version ("3.10", "3.11", "3.12", "3.13" или "3.14") задает версию Python для сборки; допустимые версии зависят от окружения. Поле system_dependencies ({"extra_apt_packages": [...]}) задает apt-пакеты (см. раздел про системные зависимости). Типы панелей в outputs берутся из списка: text, series, diagram, donut, dumbbell, bd, polarLine, polarColumn, circles, image, video, file.
Поле model_name в файле задает название модели на латинице. Оно нужно, чтобы найти модель на платформе.
Поле allowed_file_extensions позволяет указать расширения файлов, которые можно положить в этот инпут.
Массив inputs задает перечень входных полей модели. Каждый элемент массива - это отдельное поле ввода с названием на естественном языке (display_field_name), типом (indicator_type), ярлыком для скрипта (indicator_name)
Варианты инпутов и их описание ниже.
Поле outputs задает характеристики панелей, которые выводятся в Ядре. Это поле позволяет спланировать, какие панели с данными будут выводиться в результате операций внутри сервиса. Элементы поля состоят из ярлыка-метки (indicator_name), названия панели на естественном языке (display_result_name) и типа панели (indicator_type).
Входные поля
Инпуты моделей для сервиса собираются в файле metadata.json. Поле инпута состоит из частей:
indicator_name - задает название ярлыка для входного поля. Именно это название будет приходить в исполняемый скрипт сервиса. По нему вы сможете найти данные, передаваемые от пользователей в своем сервисе. Постарайтесь сделать его уникальным, чтобы ничего не упало.
display_field_name - отображаемое название входного поля. Это название будет видно пользователю в Ядре. Тут следует написать, что вы хотите, чтобы он заполнил.
indicator_type - тип индикатора. Отвечает за вид входного поля, который будет отображаться пользователю, подробно в таблице ниже.
possible_values - список возможных значений входного поля (нужно для разного рода селекторов)
Обратите внимание, что для некоторых полей (например, для слайдера) требуется привести дополнительную информацию в специальных ключах. Детали приводятся в таблице
Типы входных полей
| № | Тип | Как выглядит элемент | Как будет отображаться поле |
|---|---|---|---|
| 1 | Селектор | {"indicator_name": "region", "display_field_name": "Регион проживания", "indicator_type": "dropdown", "possible_values": ["Архангельская область", "Владимирская область", "Московская область"]} | ![]() |
| 2 | Числовое значение | {"indicator_name": "dohod", "display_field_name": "Ваш возраст", "indicator_type": "number"} | ![]() |
| 3 | Логическое значение | {"indicator_name": "place", "display_field_name": "Являетесь ли вы государственным служащим?", "indicator_type": "checkbox"} | ![]() |
| 4 | Слайдер | {"indicator_name": "proc", "display_field_name": "Сколько лет вы хотели бы прожить на пенсии?", "indicator_type": "number", "min_value": 1, "max_value": 35, "ui_elem_type": "slider"} | ![]() |
| 5 | Текст | {"indicator_name": "text1", "display_field_name": "inputs", "indicator_type": "text"} | ![]() |
| 6 | Дата | {"indicator_name": "date1", "display_field_name": "Тут мог быть ваша дата", "indicator_type": "date"} | Похоже, его нет |
| 7 | Файл | {"indicator_name": "file1", "display_field_name": "Тут мог быть ваш файл", "indicator_type": "file"} | ![]() |
| 8 | Диктофон | {"indicator_name": "voice1", "display_field_name": "Запишите голосовое сообщение", "indicator_type": "voice"} | Кнопка записи голосового сообщения (диктофон) |
| 9 | JSON | {"indicator_name": "json1", "display_field_name": "Введите валидный json", "indicator_type": "json"} | Поле для ввода json значения |
Выходные поля и панели
Отображение панелей в ТРОПАСС зависит от их типа, который указывается в файле metadata.json и данных, отправляемых моделью. Сначала рассмотрим данные, которые нужно заполнить в metadata.json.
indicator_name - ярлык панели на латинице, по которому затем в скрипте можно определить панель.
display_result_name - название панели русском, будет отображаться в ТРОПАСС
indicator_type - тип панели.
Типы панелей являются жесткими шаблонами ТРОПАСС. Вся информация, которая поступает из вашей модели ИИ должна выводиться в каком-то из используемых типов панелей. Такой "жесткий слой" позволяет избавиться разработчику от необходимости самому программировать графики и картинки и отдать эту нудную работу платформе.
Число панелей на 1 сервис не ограничено, поэтому структурировать и вывести можно любой объем информации с любым содержанием. Более того, число типов панелей постоянно растет, и мы можем добавить новые по запросам разработчика. Перечень типов с привязкой к выводу приводятся в таблице ниже.
Типы выходных панелей
| № | Тип | Как добавить | Вид в Ядре |
|---|---|---|---|
| 1 | Ниточка | {"indicator_name": "name", "display_result_name": "Хорошая ли идея - купить недвижимость", "indicator_type": "series"} | ![]() |
| 2 | Ромашка | {"indicator_name": "name", "display_result_name": "Оценка регионов", "indicator_type": "polarColumn"} | ![]() |
| 3 | Линии | {"indicator_name": "name", "display_result_name": "1. Физическая культура и спорт. Рекомендации по ресурсу", "indicator_type": "dumbbell"} | ![]() |
| 4 | Розочка | {"indicator_name": "name", "display_result_name": "Изменения по сравнению с прошлым", "indicator_type": "polarLine"} | ![]() |
| 5 | Слайды | {"indicator_name": "name", "display_result_name": "Какое-то название", "indicator_type": "bd"} | ![]() |
| 6 | Колончатая | {"indicator_name": "name", "display_result_name": "Состав платежа", "indicator_type": "diagram"} | ![]() |
| 7 | Текстовая | {"indicator_name": "name", "display_result_name": "Тут могло быть ваше название панели", "indicator_type": "text"} | ![]() |
| 8 | Видео | {"indicator_name": "name", "display_result_name": "Тут могло быть ваше название панели", "indicator_type": "video"} | ![]() |
| 9 | Картиночная | {"indicator_name": "name", "display_result_name": "Тут могло быть ваше название панели", "indicator_type": "image"} | ![]() |
| 10 | Файловая | {"indicator_name": "name", "display_result_name": "Тут могло быть ваше название панели", "indicator_type": "file"} | ![]() |
Остальная настройка вывода производится в исполняемом файле main.py
Где взять API-ключ и model_id
Для вызова сервиса снаружи нужны три вещи: адрес шлюза, API-ключ и model_id.
- Адрес шлюза -
https://апи.тропасс.рф/apiПолный адрес вызова:https://апи.тропасс.рф/api/rpc/call-model. - API-ключ выпускается в личном кабинете (ядра), а не в админке:
- откройте раздел Api ключи;
- нажмите «Новый ключ», введите название и отметьте сервисы, к которым ключ должен давать доступ (в списке только те модели, которые добавлены вам в Маркете);
- скопируйте токен кнопкой «копировать». Токен передаётся в заголовке
Authorization: Bearer <токен>. Не храните его в репозитории.
model_id- это UUID сервиса. Аналитик берёт его в админке: раздел «Модели аналитика», откройте нужный сервис на редактирование. Адрес страницы имеет вид/analytics-models/edit/<model_id>, UUID в конце адреса и естьmodel_id.
Python-примеры для вызова сервиса снаружи (через API платформы)
Базовый шаблон запроса (общий для всех типов):
import json
import httpx
API_URL = "https://апи.тропасс.рф/api"
MODEL_ID = "uuid-вашей-модели"
USER_API_TOKEN = "ваш-api-токен"
payload = {
"model_id": MODEL_ID,
"model_request_data": {
# indicator_name: значение (см. примеры ниже по номерам)
}
}
response = httpx.post(
f"{API_URL}/rpc/call-model",
data={"model_payload": json.dumps(payload)},
headers={"Authorization": f"Bearer {USER_API_TOKEN}"},
)
result = response.json()
1. Селектор (dropdown) — одна строка из possible_values:
"model_request_data": {"region": ["Московская область"]}
2. Числовое значение (number) — int или float:
"model_request_data": {"dohod": [35]}
3. Логическое значение (checkbox) — bool:
"model_request_data": {"place": [True]} # или False
4. Слайдер (number + ui_elem_type: slider) — число в пределах min..max:
"model_request_data": {"proc": [20]}
5. Текст (text) — строка до 100 000 символов:
"model_request_data": {"text1": ["Ваш текст здесь"]}
6. Дата (date) — строка в формате ДД.ММ.ГГГГ:
"model_request_data": {"date1": ["24.03.2026"]}
7. Файл (file) — файл загружается как multipart, в model_request_data список с именем файла:
payload = {
"model_id": MODEL_ID,
"model_request_data": {"file1": ["document.pdf"]},
}
response = httpx.post(
f"{API_URL}/rpc/call-model",
data={"model_payload": json.dumps(payload)},
files=[("files", ("document.pdf", open("document.pdf", "rb"), "application/pdf"))],
headers={"Authorization": f"Bearer {USER_API_TOKEN}"},
)
Вместо загрузки файла можно передать прямую S3-ссылку:
"file1": ["https://<хранилище>/путь/к/файлу.pdf"]
8. Диктофон (voice) — аналогично файлу, но аудио:
payload = {
"model_id": MODEL_ID,
"model_request_data": {"voice1": ["recording.ogg"]},
}
response = httpx.post(
f"{API_URL}/rpc/call-model",
data={"model_payload": json.dumps(payload)},
files=[("files", ("recording.ogg", open("recording.ogg", "rb"), "audio/ogg"))],
headers={"Authorization": f"Bearer {USER_API_TOKEN}"},
)
9. JSON (json) — строка с валидным JSON-массивом объектов:
"model_request_data": {
"json1": json.dumps([{"ключ": "значение", "число": 42}])
}
# Итоговое значение: '[{"ключ": "значение", "число": 42}]'
Файл main.py и контракт вывода данных
Несмотря на то, что классы контракта в пакете tropass_sdk.schemas могут выглядеть сложно, сам вывод достаточно простой и в меру удобный. В своем максимально развернутом виде контракт выглядит так
from tropass_sdk import schemas
custom_panel = schemas.ModelPanelOutputSchema(
panel_output_name="panel",
panel_type="series",
panel_show_order=1,
# Кастомизируем вложенные структуры
primary_data=schemas.ModelPrimaryDataSchema(
plot_data=schemas.ModelPlotDataSchema(
x_axis_values=["Сегодня", "Вчера", "На прошлой неделе", "На позапрошлой неделе"],
series_data=[
schemas.ModelSeriesDataSchema(
legend_name="Продажи",
plot_values=[100.5, 200.3, 150.0, 300.2]
),
schemas.ModelSeriesDataSchema(
legend_name="Другие продажи",
plot_values=[10.5, 20.3, 15.0, 30.2]
)
]
),
media=[
schemas.ModelMediaItemSchema(
file_name="Отчет",
local_abs_path="/tmp/report.png"
),
schemas.ModelMediaItemSchema(
file_name="Другой отчет",
local_abs_path="/tmp/report.png"
)
]
),
# Добавляем описание (опционально)
descriptions=[
schemas.ModelDescriptionSchema(
content="# Тестовое описание\n*Пример*",
description_type=schemas.DescriptionTypeEnum.MD
)
],
# Прикрепляем файлы для скачивания (опционально)
attachments=[
schemas.ModelMediaItemSchema(
file_name=out_name,
local_abs_path=str(out_path)
)
]
)
Давайте рассмотрим ключи подробно.
panel_output_name - ярлык-метка панели. Он соответствует indicator_name из файла metadata.json и нужен, чтобы мы смогли найти вашу панель
panel_type - тип панели (соответствует indicator_type из файла metadata.json)
panel_show_order - порядок вывода панели. Вы можете управлять порядком вывода панелей в своем сервисе. Но если не хотите, мы определим его за вас и это поле можно не заполнять.
primary_data - блок основных данных панели. Если вы выводите данные для графиков, то в нем следует заполнять блок plot_data. Если медийную информацию (картинки, видео и пр.) - следует заполнять блок media.
plot_data - блок данных для графиков
x_axis_values - набор подписей по оси абсцисс (она же ось Х). Обычно это массив строк.
series_data - массив с данными для графика, в него складываются данные для вывода.
legend_name - название ряда на графике
plot_values - массив числовых данных для вывода на график. Одному массиву соответствует одна ряд на графике. Как видно из структуры контракта, таких рядов может быть много.
media - начало блока медийной информации
file_name - название файла, которое будет отображаться в Ядре
local_abs_path - адрес пути до файла
descriptions - список подписей для панели. В каждой панели может выводиться текстовая подпись, ее можно вставить в этот блок
content - сам текст подписи
description_type - тип вывода. Сейчас поддерживается 3 типа, которые можно выбирать: простой текст (тип PLAIN), текст в html (тип HTML), текст в разметке markdown (тип MD). Как видно, в примере выбран тип MD.
attachments - список прикрепленных файлов (для панели типа file именно сюда кладутся файлы для скачивания). К каждой панеле можно прикрепить файлик, если захотите. Все они указываются в этом блоке
file_name - отображаемое название файла
local_abs_path - адрес прикрепленного файла
Ненужные блоки можно не заполнять. Примеры вывода разных панелей есть в hello-tropass. Если контракт заполнен неверно, ТРОПАСС не обработает данные и вы их не увидите; проверить его можно локально: все классы типизированы и лежат в пакете tropass_sdk.schemas, а при неверных полях ModelPrimaryDataSchema не соберётся.
Применительно к конкретной панели, описанный контракт выглядит следующим образом.
Типовой шаблон скрипта main.py
Шаблон - hello-tropass/main.py. Ниже - описание его частей. Общее описание примера (панели, тестирование, сборка) - в README примера.
Файл main.py начинается с импорта библиотек Python. Любые импортируемые библиотеки должны быть в requirements.txt, иначе автосборщик их не подгрузит.
В блоке # Загрузка классов ТРОПАСС подключаются schemas и ModelServer из tropass_sdk, которые необходимы для работы сервиса на платформе. Этот блок не удаляйте. Ниже подключаются ваши модули: в примере это utils.py, лежащий рядом с main.py; свои пакеты удобно складывать в папку modules/.
Тестовый вход лежит в input_examples/input.json: в нём приведены примеры для всех типов инпутов, имеющихся на платформе. Именно такой объект model_input приходит от пользователя в функцию main(). Скопируйте model_input, с которым отлаживаетесь, в input.json: это позволит запускать сервис командой python3 run_test.py, а платформе - проводить автотест при сборке.
Код примера состоит из трёх частей:
- Входы. Класс
Inputsи функцияread_inputs()разбираютmodel_inputи показывают, как читать поле каждого типа. Ошибки пользователя (поле не заполнено, не число, некорректный JSON) выбрасываются какutils.InputErrorи возвращаются текстовой панелью с причиной. - Панели. На каждый тип панели своя функция
panel_*(panel_text,panel_series,panel_diagram,panel_imagesи т.д.). Ненужные функции удаляются вместе со строкой в списке панелей и записью вoutputsизmetadata.json. - Главная функция
main(). Она разбирает входы, содержит место для вашей логики, собирает список панелей и расставляет порядок вывода (порядок в списке = порядок на экране). Функция может быть синхронной или асинхронной, для нас это не имеет значения. ЗавершаетсяMLModelResponseSchema(...).model_dump(), этот вызов мы советуем не трогать.
В конце файла создаётся сервер: ModelServer(model_func=main, model_name="hello_tropass", model_version="1.0.0") и application = server.build_application(). Переменная application - это приложение, которое запускает платформа, ее имя менять не нужно. В model_name и model_version укажите название и версию вашего сервиса.
Особенности SDK 2.1
Эти правила проверены валидацией tropass-sdk и часто становятся причиной «молчаливых» ошибок:
ModelPrimaryDataSchemaсодержит только два поля: plot_data и media. Поляtext_contentв схеме нет: если его передать, оно будет молча проигнорировано и текст до пользователя не доедет.- Текст панели передавайте в
descriptions[].content. - У text-панели
primary_dataможно не передавать вовсе (primary_data=None). Если передаёте, тоplot_dataв нём обязателен: поставьте заглушку (x_axis_values=["1"], одна серия сplot_values=[0.0]). В примере для этого естьutils.empty_primary_data(). plot_dataпо-настоящему нужен для графических панелей: diagram, series, dumbbell, polarLine, polarColumn, donut.- Значения
description_typeв ответе - строчные:"md","html","plain". ЧерезDescriptionTypeEnum.MDи т.п. это получается само; если собираете словарь вручную, заглавные"MD"/"HTML"валидация отклонит. - Файлы результата (картинки, видео, вложения) должны лежать внутри
files_directory_path. Платформа сама подхватит их поlocal_abs_pathи загрузит в хранилище. - Значения
plot_valuesдолжны быть числами (float). Пропуски (NaN) из pandas замените нулями или уберите до сборки ответа. - Если считаете таблицы в pandas, для
DataFrame.to_markdown()добавьте вrequirements.txtпакетtabulate: он не входит в зависимости pandas. panel_output_nameкаждой панели обязан совпадать сindicator_nameизoutputsвmetadata.json, иначе панель не отобразится.
Доступ к входным данным
Значения в model_input всегда приходят списками, ключи - это indicator_name из metadata.json:
# Текстовое поле и выпадающий список
text_value = model_input.get("text_input", [""])[0]
dropdown_value = model_input.get("dropdown_input", [""])[0]
# Число
number_value = model_input.get("number_input", ["0"])[0]
number = float(number_value) if number_value else 0
# Чекбокс (в разных клиентах приходит как true/false, "1"/"0" или "True"/"False")
checkbox_value = model_input.get("checkbox_input", ["0"])[0]
is_checked = str(checkbox_value).lower() in ("1", "true")
# Файл: приходит АБСОЛЮТНЫЙ ПУТЬ к файлу внутри контейнера
# (платформа сама кладёт туда загруженный файл; имя может не совпадать с исходным)
file_path = model_input.get("file_input", [""])[0]
# Мультиселект и multi-file: список значений / список путей
multi_select = model_input.get("multi_select_input", [])
Необязательные поля (is_required: false) могут отсутствовать в model_input, поэтому читайте их через .get(...). Рабочий пример чтения всех 12 типов - функция read_inputs() в main.py примера.
Ресурсы платформы: common_resources и request_metadata
common_resources
Второй аргумент main() - словарь с системными ресурсами. Платформа заполняет его сама, из конфигурации вашего сервиса:
| Ключ | Тип | Описание |
|---|---|---|
files_directory_path | string | Каталог для файлов ответа (всегда есть) |
model_api_gateway_params | dict | URL и токен шлюза для вызова других сервисов (см. Вызов других сервисов) |
big_llm_api_params, medium_llm_api_params, small_llm_api_params | dict | Параметры LLM трёх размеров (см. LLM-вызовы) |
web_search_params | dict | Сервис веб-поиска (опционально) |
coredev_database_params, hot_database_params | dict | Подключения к БД (опционально) |
Состав зависит от настроек сервиса: читайте нужные ключи через .get(...) и не рассчитывайте, что они есть всегда. Пример (секреты заменены):
common_resources = {
"files_directory_path": "/srv/www/temp/<request-id>/response",
"model_api_gateway_params": {
"url": "https://апи.тропасс.рф/api/rpc/call-model",
"api_token": "<api_token>"
},
"big_llm_api_params": {
"api": "https://tropass-m3-mxfp8.tropass.me",
"model": "tropass-m3-mxfp8",
"user": "<user>",
"password": "<password>",
"verify": False
}
}
Секреты (пароли БД, токены, пароли LLM) никогда не кладите в код,
input.jsonи репозиторий: их выдаёт платформа вcommon_resources.
request_metadata и токен пользователя
SDK-сервер добавляет в model_input["request_metadata"] данные из заголовков запроса:
| Заголовок | Ключ в request_metadata |
|---|---|
X-User-ID | user_id |
X-User-Locale | locale |
X-User-Api-Token | user_api_token |
Токен пользователя имеет приоритет над сервисным: подставляйте его в model_api_gateway_params.api_token перед вызовами других сервисов, чтобы вызов списывался на аккаунт пользователя:
request_metadata = model_input.get("request_metadata") or {}
user_api_token = request_metadata.get("user_api_token")
if user_api_token:
gateway_params = dict(common_resources.get("model_api_gateway_params") or {})
gateway_params["api_token"] = user_api_token
common_resources = dict(common_resources)
common_resources["model_api_gateway_params"] = gateway_params
Паттерн app_config_scope: не таскать common_resources через все функции
Если в сервисе много модулей, передавать common_resources в каждую функцию неудобно. Положите его в ContextVar и читайте из любого места:
# modules/context.py
import contextlib
import contextvars
_CONFIG: contextvars.ContextVar[dict] = contextvars.ContextVar("_service_config", default=None)
@contextlib.contextmanager
def app_config_scope(common_resources: dict):
token = _CONFIG.set(common_resources or {})
try:
yield
finally:
_CONFIG.reset(token)
def get_config(key: str, default=None):
cfg = _CONFIG.get()
return cfg.get(key, default) if cfg else default
def get_files_dir(default: str = "/tmp") -> str:
return get_config("files_directory_path", default)
# main.py
with context.app_config_scope(common_resources):
return _run(model_input) # все модули внутри читают get_config() / get_files_dir()
Параллельность.
asyncio.to_threadиasyncio.gatherкопируют ContextVar в поток сами, а вотThreadPoolExecutorне прокидывает ContextVar в рабочие потоки (в отличие от asyncio). Оборачивайте каждую задачу в копию контекста, причём отдельную на каждыйsubmit(один Context нельзя входить параллельно из нескольких потоков):pool.submit(contextvars.copy_context().run, functools.partial(fn, ...)).
Вызов других сервисов через model gateway
Сервис может вызывать другие сервисы платформы (LLM-шлюзы, генераторы картинок, конвертеры документов, веб-поиск и т.д.) через единый RPC-шлюз.
POST https://апи.тропасс.рф/api/rpc/call-model
Content-Type: multipart/form-data
Authorization: Bearer <api_token>
Idempotency-Key: <uuid> # уникален на каждый вызов
Адрес и формат авторизации берите из
common_resources["model_api_gateway_params"](url,api_token). Блок «Python-примеры для вызова сервиса снаружи» выше показывает вызов сервиса снаружи (например, из своего приложения, с личным API-ключом); изнутри сервиса действуют правила этого раздела.
Тело - multipart из двух полей:
| Поле | Тип | Содержимое |
|---|---|---|
model_payload | text | JSON-строка {"model_id": "<uuid сервиса>", "model_request_data": {...}} |
files | file | файлы для загрузки (если у вызываемого сервиса есть file-инпуты) |
Критичные правила формата:
- Все значения инпутов - массивы:
"user_prompt": ["текст"],"width_px": ["1600"], даже если значение одно. - Файловые инпуты: значение - ИМЯ загружаемого файла, а сам файл прикладывается в
filesпод тем же именем:"data_file": ["data.json"]+files=(("data.json", fh),). Платформа положит файл в контейнер вызываемого сервиса и подставит путь. Расширение имени определяет парсер на той стороне. common_resourcesвызываемого сервиса передавать не нужно: платформа подставит их из его конфигурации.model_id- это UUID модели, а неmodel_name. Берите его из таблицы ниже или из URL карточки сервиса на платформе.
Авторизация
Authorization: Bearer <api_token>, где токен берётся так:
user_api_tokenизrequest_metadata- приоритет, вызов спишется с пользователя;- иначе сервисный
api_tokenизmodel_api_gateway_params.
Плюс обязательный заголовок Idempotency-Key: <uuid4> - уникальный на каждый вызов (защита от дублей при ретраях на стороне шлюза).
Синхронный и асинхронный режимы
- Без заголовка
Tropass-Model-Call-Version(по умолчанию) вызов синхронный: ответ приходит в теле POST (HTTP 200). Так вызывается большинство сервисов. - С заголовком
Tropass-Model-Call-Version: 2вызов асинхронный: POST вернёт202 {"task_id": "...", "status": "distributed"}, а результат забирайте поллингомGET https://апи.тропасс.рф/api/rest/model-tasks/{task_id}(тот же Bearer) до статуса, отличного отdistributed/processing. Ошибка исполнения:status=processing_error, текст вerror_message.
Разбор ответа
{
"panel_items": [
{
"panel_output_name": "chart_image",
"panel_type": "image",
"primary_data": {
"media": [
{"file_name": "график.png", "mime_type": "image/png",
"s3_url": "https://<хранилище>/...?X-Amz-Expires=3600"}
]
},
"descriptions": [{"content": "### Подпись", "description_type": "md"}],
"panel_show_order": 1
}
]
}
| Что искать | Где |
|---|---|
| Панели | panel_items[], опознавать по panel_output_name, порядок - panel_show_order |
| Картинки и файлы | primary_data.media[]: local_abs_path (путь к файлу на файловой системе платформы) или s3_url (presigned-ссылка, живёт около часа - скачивайте сразу) |
| Текст | descriptions[].content (поля primary_data.text_content не существует) |
Ошибки и таймауты
| HTTP | Причина | Что делать |
|---|---|---|
| 422 | files не приложены или имя не совпадает с file-инпутом | проверить multipart |
| 503 | сервис или контейнер упал | один ретрай через ~30 с, дальше обратитесь в поддержку платформы |
| таймаут клиента | тяжёлый сервис (LLM, рендер) | клиентский таймаут 300-600 с |
Ретраить имеет смысл только transient-ошибки (сеть, 5xx, битый JSON) и с экспоненциальной задержкой. Ошибки исполнения внутри вызываемого сервиса (например, упавший рендер приходит как HTTP 200 и текстовая панель с ошибкой) ретраем без изменения запроса не лечатся.
Известные сервисы
| Сервис | model_id | Назначение |
|---|---|---|
| MD-конвертер | см. карточку сервиса (начинается с 019f5b06-) | docx/pdf/xlsx в Markdown |
| Image-gen | 019e2248-902a-77c1-8a48-335d175f866b | генерация изображений по промпту |
| Upscaler | 019e224a-33b1-7b42-a654-2b5b3ab6c3c6 | повышение разрешения изображений |
| Web-search | 019fd153-2b4e-77fc-bae0-855bd71161bd | поиск по открытым источникам |
| Инфографер (base) | 01a06309-f4b5-7a16-a827-aca0e70fccde | PNG-инфографика (R+ggplot2) по табличным данным и промпту |
| Инфографер (R-part) | 01a06bb2-6c2f-753f-be5e-4942d1875f45 | низкоуровневый исполнитель R-кода |
Пример клиента внутри сервиса
Не пишите клиент с нуля: возьмите за основу минимальный рабочий вариант:
import json
import uuid
import requests
def call_tropass_model(common_resources: dict, model_id: str, payload_data: dict,
files: dict[str, str] | None = None, timeout: int = 600) -> dict:
"""Синхронный вызов сервиса через шлюз. files: {имя_в_payload: локальный_путь}"""
params = common_resources["model_api_gateway_params"]
body = {"model_id": model_id, "model_request_data": payload_data}
opened = [(name, open(path, "rb")) for name, path in (files or {}).items()]
try:
resp = requests.post(
params["url"],
headers={"Authorization": f"Bearer {params['api_token']}",
"Idempotency-Key": str(uuid.uuid4())}, # на каждый вызов - новый
data={"model_payload": json.dumps(body, ensure_ascii=False)},
files=[("files", (name, fh)) for name, fh in opened],
timeout=timeout,
verify=False, # внутри контура используются самоподписанные сертификаты
)
finally:
for _, fh in opened:
fh.close()
resp.raise_for_status()
return resp.json()
def panel_text(panel: dict) -> str:
return "\n\n".join(d.get("content", "") for d in panel.get("descriptions") or [])
def panel_media(panel: dict) -> list[dict]:
return (panel.get("primary_data") or {}).get("media") or []
# Пример: сгенерировать картинку
result = call_tropass_model(common_resources, "019e2248-902a-77c1-8a48-335d175f866b",
{"prompt": ["Нарисуй кота"]})
for panel in result.get("panel_items", []):
for media in panel_media(panel):
url = media.get("s3_url") # presigned-ссылка живёт ~1 час - скачивайте сразу
Что стоит добавить в боевой версии: ретраи на transient-ошибки с экспоненциальной задержкой и jitter; для файлов - seek(0) перед повторной отправкой; для результата - сначала пробовать local_abs_path, затем s3_url; Idempotency-Key генерировать заново на каждую попытку, если нужно реальное повторное исполнение.
Пример вызова с файлом через curl:
curl -s 'https://апи.тропасс.рф/api/rpc/call-model' \
-H 'accept: application/json' \
-H "Authorization: Bearer $API_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-F 'model_payload={
"model_id": "<uuid целевого сервиса>",
"model_request_data": {
"data_file": ["data.json"],
"user_prompt": ["Построй график по данным"]
}
}' \
-F 'files=@data.json;type=application/json'
LLM-вызовы
Для работы с LLM используйте big_llm_api_params, medium_llm_api_params и small_llm_api_params из common_resources: это OpenAI-совместимые эндпоинты /v1/chat/completions.
{
"big_llm_api_params": {
"api": "https://tropass-m3-mxfp8.tropass.me",
"model": "tropass-m3-mxfp8",
"user": "<user>",
"password": "<password>", # вариант 1: basic-auth
"verify": False # самоподписанные сертификаты внутри контура
},
"medium_llm_api_params": {
"api": "https://tropass36-35b-a3b-fp8.tropass.me",
"model": "tropass36-35B-A3B-FP8",
"api_key": "<api_key>" # вариант 2: Bearer-токен
}
}
Авторизация (приоритет сверху вниз): 1) api_key - заголовок Authorization: Bearer <api_key> (basic при этом не шлётся); 2) user + password - HTTPBasicAuth; 3) иначе без авторизации.
import requests
def ask_llm(params: dict, system_prompt: str, user_text: str,
temperature: float = 0.2, max_tokens: int = 2000, timeout: int = 300) -> str:
api_key = params.get("api_key")
headers, auth = None, None
if api_key:
headers = {"Authorization": f"Bearer {api_key}"}
elif params.get("user") and params.get("password"):
auth = (params["user"], params["password"])
resp = requests.post(
f"{params['api'].rstrip('/')}/v1/chat/completions",
json={
"model": params["model"],
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_text},
],
"temperature": temperature,
"max_tokens": max_tokens,
},
auth=auth, headers=headers, verify=params.get("verify", True), timeout=timeout,
)
resp.raise_for_status()
return resp.json()["choices"][0]["message"]["content"]
Практика, проверенная в боевых сервисах:
- Цепочка запасных моделей: big, затем medium, затем small. Если большая недоступна после всех ретраев, пробуйте следующий размер, а не падайте.
- Ретраи только на transient-ошибки (сеть, 5xx): 3-5 попыток с экспоненциальной задержкой.
- Thinking-модели: ответ может лежать в
message.reasoning_content/message.reasoning, а вcontentпопадают блоки<think>...</think>- вырезайте их регулярным выражением. Полезно передавать"chat_template_kwargs": {"enable_thinking": False}. - JSON-ответы: просите «строгий JSON без markdown» и парсите по шагам: прямой
json.loads, затем починка типовых ошибок ("k"="v",=вместо:, лишние запятые,strict=Falseдля управляющих символов), затем fenced-блок ```json, затем первый{...}или[...]. - VL (картинки): картинка передаётся как
image_urlс data-URL base64:{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}}. Перед отправкой уменьшайте ширину до ~1600 px.
Системные зависимости и requirements.txt
requirements.txt
В requirements.txt обязательно укажите SDK с суффиксом [server]: без него при деплое поставятся не те зависимости.
tropass-sdk[server]==2.1
pandas==2.3.3
requests==2.32.5
Рекомендации: фиксируйте точные версии, следите за конфликтами между пакетами, проверяйте совместимость с Python 3.12+. Если используете psycopg2, берите psycopg2-binary.
Системные пакеты (apt)
Если сервису нужны системные утилиты (ffmpeg, pandoc и т.п.), перечислите их в metadata.json, в секции system_dependencies:
{
"model_name": "my_service",
"model_service": "Мой сервис",
"inputs": [],
"outputs": [],
"system_dependencies": {
"extra_apt_packages": ["ffmpeg", "pandoc", "poppler-utils", "libgomp1"]
}
}
| Пакет | Назначение |
|---|---|
ffmpeg | обработка аудио и видео |
pandoc | конвертация документов |
poppler-utils | работа с PDF |
libgomp1 | OpenMP для параллельных вычислений |
Тестирование сервиса
Данные для тестов
Положите в input_examples/ один или несколько файлов в формате {"model_input": {...}, "common_resources": {...}}. Платформа использует input.json для автотеста после сборки, а вам такие файлы заодно документируют контракт сервиса. Для отладки в самом main.py удобно держать закомментированный блок с тестовыми model_input и common_resources (как в примере).
Прогон без сервера: run_test.py (рекомендуемый способ)
Лёгкий раннер вызывает main() напрямую, без uvicorn. Положите run_test.py рядом с main.py (готовый файл есть в примере) и запустите python3 run_test.py или python3 run_test.py test_input_2.json. Он читает файл из input_examples/, подставляет локальный каталог для результатов, вызывает main() и сохраняет ответ в response.json:
result = asyncio.run(main(model_input, common_resources))
with open(os.path.join(files_dir, "response.json"), "w", encoding="utf-8") as f:
json.dump(result, f, ensure_ascii=False, indent=2, default=str)
Проверка через сервер (uvicorn + curl)
Локальное тестирование делается с библиотекой tropass-sdk. Из корня проекта запустите сервер:
uvicorn main:application --host 0.0.0.0 --port 8000 --workers 4
Через несколько секунд (до 10) отправьте запрос:
curl -X 'POST' 'http://localhost:8000/prediction' \
-H 'accept: application/json' \
-H 'X-User-Locale: ru' \
-H 'Content-Type: application/json' \
-d '{
"model_input": {
"text_input": ["Тут ваш текст"],
"selector_input": ["Выбор +100500"],
"number_input": [123.0],
"checkbox_input": [true],
"request_metadata": {"user_id": "<user_uuid>", "locale": "ru", "user_api_token": "<api_token>"}
},
"common_resources": {
"model_api_gateway_params": {"url": "https://апи.тропасс.рф/api/rpc/call-model", "api_token": "<api_token>"},
"files_directory_path": "/tmp/test_response"
}
}'
Если сервис вызывает другие сервисы или LLM, подставьте в common_resources реальные параметры из вашего окружения (токены и пароли - только из своих секретов, не из репозитория). Каталог из files_directory_path должен существовать.
Сборка и деплой
-
Убедитесь, что на месте
metadata.json,main.py,requirements.txtи, если используются,utils.py,modules/,input_examples/,output_examples/. -
Проверьте
requirements.txt(tropass-sdk[server]==2.1) и протестируйте сервис локально. -
Соберите архив скриптом
./build.shиз примера: он создаётdeploy/archive.zipи копирует вdeploy/файлmetadata.json. Вручную то же делается так:zip -r deploy/archive.zip main.py utils.py requirements.txt modules input_examples output_examplesВнутри архива
main.pyиrequirements.txtдолжны лежать в корне:archive.zip ├── main.py ├── utils.py ├── requirements.txt ├── modules/ │ └── __init__.py ├── input_examples/ │ └── input.json └── output_examples/ -
Войдите в админку платформы под ролью аналитика и откройте раздел «Модели аналитика», затем нажмите создание новой модели.
-
Заполните форму:
- Название модели и Техническое описание - произвольный понятный текст;
- Язык модели - Python; Версия Python и Менеджер пакетов - выберите из списка (должны подходить вашему
requirements.txt); - Архив модели (ZIP) - загрузите
archive.zipили вставьте прямую HTTP(S)-ссылку на него; - Metadata (JSON) - загрузите ваш
metadata.json.
-
Нажмите «Сохранить». Платформа соберёт сервис; после сборки она прогонит
input.jsonкак автотест. Статус сборки и логи видны в карточке модели. -
Когда сборка прошла, сервис можно запускать: из интерфейса платформы или из своего кода, см. раздел «Где взять API-ключ и
model_id».
Сколько занимает сборка и какие ресурсы выбирать
Сборка Python-сервиса с pandas и несколькими пакетами занимает около 10-12 минут, после неё сервис запускается за несколько секунд. Не переименовывайте сервис за это время (см. «Известные проблемы»).
Ресурсы: начинайте с небольших запросов (порядка 0,1 CPU и 0,5 ГБ памяти) и лимитов 1-2 CPU и 1-2 ГБ памяти; тяжёлым сервисам (OCR, обработка больших файлов) нужно больше. Завышенные запросы увеличивают время ожидания места под сервис и расходуют общие ресурсы платформы. Если сервис перезапускается из-за нехватки памяти, поднимайте лимит памяти, а не запрос.
Чек-лист перед деплоем
-
metadata.jsonсодержитmodel_name,model_service,inputs,outputs -
model_nameуникален, на латинице - В
requirements.txtуказанаtropass-sdk[server]==2.1 -
main()возвращаетMLModelResponseSchema(...).model_dump() - Все
panel_output_nameсовпадают сindicator_nameизoutputs -
plot_dataзаполнен для графических панелей; для text-панелейprimary_data=Noneлибо заглушкаplot_data - Текст панелей лежит в
descriptions[].content,description_type-md/html/plain - Файлы результата лежат в
files_directory_pathи указаны вmedia[].local_abs_pathилиattachments[].local_abs_path - При вызовах шлюза:
model_id- UUID, значения инпутов списками,Idempotency-Keyуникален, пользовательский токен приоритетнее сервисного -
s3_urlиз ответов скачивается сразу (ссылка живёт около часа) - Клиентские таймауты для тяжёлых сервисов 300-600 с, ретраи только на transient-ошибки
- Локальное тестирование пройдено (
run_test.pyи/или uvicorn + curl) - В коде и
input.jsonнет секретов - Создан
archive.zip, в нём есть все нужные файлы
Частые вопросы
Как передать несколько значений в одном поле? Используйте тип multi-select (или multi-file для файлов).
Как сохранить файл, созданный в сервисе? Скопируйте или создайте его в files_directory_path и укажите в media (панели image/video) или attachments (любая панель, в том числе file).
Как вызвать другой сервис ТРОПАСС? Через model gateway: параметры берутся из model_api_gateway_params.
Как вызвать LLM? Через LLM-вызовы: параметры из big/medium/small_llm_api_params.
Почему не отображается панель или график? Проверьте, что panel_output_name совпадает с indicator_name в metadata.json, а plot_data содержит числовые значения.
Почему не доехал текст панели? Текст нужно передавать в descriptions[].content: поля text_content в primary_data нет, оно молча игнорируется.
Известные проблемы
Список того, о чём нужно знать до загрузки сервиса. Обновляется по мере исправления.
| Тема | Что делать |
|---|---|
| Health check | Параметр «Включить health check» при загрузке сервиса пока лучше не включать. |
| Переименование | Сервис пока не переименовываем во время сборки: название модели не меняйте, пока сборка не закончилась (статус VALID или ошибка). |
Нашли что-то, чего здесь нет? Сообщите нам (см. ниже), и мы добавим это в список.
Обратная связь и предложения
- Форма: оставить предложение или сообщить об ошибке. Подходит для идей по функционалу, ошибок в документации, вопросов, которые остались без ответа, и замечаний к сборке и деплою. Форма открыта без входа в систему.
- Почта: dev@tropass.me. Если удобнее написать письмом, укажите в нём то же самое, что просит форма: что хотите изменить или что не получилось, в каком разделе документации и название сервиса с версией.
Опишите задачу конкретно, а при ошибке приложите версию tropass-sdk и текст ошибки из карточки модели.
Код примера
Полный рабочий сервис, он же шаблон, лежит в репозитории tropass-ai/hello-tropass: main.py, utils.py, run_test.py, тесты и build.sh.















