> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Провайдеры AI и локальные модели

> Регистрация провайдера моделей, подключение локальной модели, маршрутизация каждой функции к своей модели и бюджету и чтение состояния на экране

Всё, что связано с AI в Omniflow, начинается с провайдера, которого владелец
зарегистрировал и одобрил. Эта страница о том, как устроен реестр: три адаптера,
поля на экране, как направить его на модель, работающую на вашем железе, как
функция маршрутизируется к модели и получает потолок и что означает каждое
состояние на экране.

Решения, стоящие за конструкцией, — почему никто не обращается к провайдеру
напрямую, почему одобрение является строкой в базе и почему модель никогда не
действует сама, — описаны в
[Приватности AI и шлюзе](https://omniflow-vpn.mintlify.site/ru/operations/ai-privacy.md) и
[записи 0006](https://omniflow-vpn.mintlify.site/ru/architecture/decision-0006-ai-is-optional.md). Здесь — механика.

> [!WARNING]
>
> **В этой сборке провайдера не вызывает ни одна функция.** Реестр, запечатывание
> учётных данных, маршрутизация по функциям, бюджеты и отчёт об использовании
> настоящие — именно через них провайдер и будет использоваться. Шлюз и пакеты
> функций реализованы и покрыты модульными тестами, но ни один процесс их не
> подключает: на `/v1/panel` нет ни одной точки входа AI, и ни один экран вне
> этих настроек её не вызывает. Зарегистрированный сегодня провайдер — это
> конфигурация, ожидающая поверхностей, и его регистрация никуда ничего не
> отправляет.

## Где это находится

`/admin/settings/ai`. Для просмотра нужно `settings.read`, для изменений —
`settings.write`, что на практике означает владельца. Каждое изменение
записывается в журнал аудита вместе с исполнителем и причиной, которую собрала
панель.

## Три адаптера

У строки провайдера есть **вид** (kind), и вид решает, какую форму запроса
говорит Omniflow. Их три, и они намеренно тонкие: каждый сопоставляет запрос с
точкой входа этого провайдера и разбирает ответ обратно. Редактирование
чувствительных данных, бюджеты, повторы и ограничения принадлежат шлюзу, а не
адаптеру.

| Вид | Что вызывает | Базовый URL, если поле оставить пустым |
| - | - | - |
| `openai_compatible` | `POST {base}/chat/completions` | `https://api.openai.com/v1` |
| `anthropic` | `POST {base}/messages` | `https://api.anthropic.com/v1` |
| `gemini` | `POST {base}/models/{model}:generateContent` | `https://generativelanguage.googleapis.com/v1beta` |

Три свойства общие для всех трёх, и их стоит знать до того, как что-то
диагностировать:

* **Сообщение об ошибке от провайдера никогда не передаётся дальше.** Ответ не из
  диапазона 2xx превращается в `HTTP <статус>` и ничего больше, потому что текст
  ошибки провайдера может вернуть промпт, а в промпте содержимое клиента, которому
  нечего делать в браузере оператора. Подробности уходят в лог.
* **Тело ответа ограничено 4 МиБ, а запрос истекает через 60 секунд.** Точка
  входа, которая передаёт данные без конца, должна упасть, а не исчерпать память.
* **Задача без лимита токенов получает 1024.** Функция, которой не задали
  потолок, должна отвечать коротко, а не дорого; повышение потолка — осознанное
  действие на уровне функции.

Адаптер Anthropic фиксирует `anthropic-version: 2023-06-01` вместо слежения за
последней версией, потому что провайдер, меняющий форму ответа под работающей
установкой, — это отказ, который никто не разворачивал.

> [!NOTE]
>
> Точка входа Gemini принимает ключ API в параметре запроса; это устройство того
> API, а не выбор, сделанный здесь. Поэтому ключ попадает в логи запросов на
> стороне провайдера. Если для вашей установки это важно, выбирайте один из двух
> других видов.

## Регистрация провайдера

**1. Откройте реестр**

Перейдите на `/admin/settings/ai`. Карточка **Провайдеры** перечисляет
зарегистрированное; на свежей установке она сообщает, что провайдер не
настроен и поэтому каждая функция AI недоступна.

**2. Заполните идентичность**

**Идентификатор** — это стабильный слаг, который будет называть функция:
строчные буквы, цифры, `-` и `_`, от 2 до 41 символа. Он никогда не
показывается клиенту и не меняется, поэтому выбирайте долговечный
(`openai-main`, `local-llama`). **Название** — то, что читают операторы.

**3. Выберите вид и базовый URL**

Выберите `openai_compatible`, `anthropic` или `gemini`. Оставьте **Базовый
URL** пустым, чтобы использовать значение по умолчанию для этого вида из
таблицы выше, или задайте его, чтобы обратиться к другой точке входа:
региональному хосту, собственному шлюзу или локальной модели.

**4. Запишите, что провайдер делает с вашими данными**

**Нулевое хранение** и **Может обучаться на ваших данных** — это ваши ответы,
взятые из условий самого провайдера. Omniflow не может их проверить и не
пытается. Они показаны на карточке над переключателем, который отправил бы
данные провайдеру, и порождают предупреждения у каждой функции, направленной
на него.

**5. Добавьте ключ и одобрите**

Новый провайдер создаётся **выключенным**, что бы вы ещё ни задали. Вставьте
ключ API, сохраните, затем включите **Одобрен к использованию** на карточке
провайдера. Задача, направленная на провайдера без включённой строки,
отклоняется, а не переключается на другого.

Ключ API доступен только на запись, от начала и до конца. Он запечатывается
`APP_DATA_ENCRYPTION_KEY` до того, как попадёт в базу, привязывается к слагу
провайдера, поэтому шифротекст, перенесённый в другую строку, не откроется, и
никогда не возвращается через API. Поэтому поле всегда пустое при загрузке
экрана, а пустое поле сохраняет текущий ключ — заменяют его кнопкой **Заменить
ключ**.

Пустое поле сохраняет ключ, **только пока провайдер указывает туда, для чего
ключ вводили**. Сохранение, меняющее **вид** или **базовый URL** без нового
ключа, отклоняется с `422 secret_required`, а слой хранения сам удаляет
сохранённый ключ у перенаправленного провайдера. Иначе любой, кому можно править
адрес, направил бы его на свой сервер и получил ключ при следующем запросе, так и
не имея возможности его прочитать.

## Подключение локальной модели

Отдельного адаптера для локальной модели нет, и в этом смысл: всё, что реализует
форму OpenAI `/chat/completions`, регистрируется как `openai_compatible` со своим
базовым URL. Ollama, vLLM, LM Studio, сервер llama.cpp и большинство
самостоятельно размещённых шлюзов это умеют.

* Задайте **Базовый URL** как корень совместимой с OpenAI точки входа сервера,
  включая префикс пути, если он есть. Адаптер добавляет `/chat/completions`
  ровно к тому, что вы указали, а завершающий слеш отбрасывается.
* **Поле ключа API обязательно**, даже если ваш сервер его игнорирует, потому что
  провайдер без ключа не может быть построен. Для сервера без аутентификации
  укажите любое непустое значение-заглушку.
* Обращайтесь к серверу по имени, которое разрешает контейнер API. `localhost`
  внутри контейнера — это сам контейнер, а не хост: используйте имя сервиса
  Compose для модели в том же стеке или адрес хоста в локальной сети.
* **Задайте API `APP_AI_ALLOW_PRIVATE_NETWORK=true`.** По умолчанию базовый URL
  обязан быть HTTPS к публичному адресу: запрос несёт ключ API провайдера, а
  введённый оператором адрес внутри сети развёртывания превратил бы адаптер в
  способ туда достучаться. Имя сервиса Compose или адрес в локальной сети
  разрешается в частный адрес и отклоняется — при сохранении, если это IP-адрес
  или обычный HTTP, и при подключении, после разрешения имени, если это имя
  хоста. Разрешение задаётся переменной окружения, а не переключателем в
  панели, чтобы решал тот, кто запускает процесс, а не тот, у кого есть
  `settings.write`.
* **Модель** называется у функции, а не у провайдера, и это должно быть имя,
  которое публикует ваш сервер, а не имя у облачного вендора.

> [!WARNING]
>
> При включённом `APP_AI_ALLOW_PRIVATE_NETWORK` **любой** AI-провайдер может
> обращаться к частной сети API, а не только локальный: любой, у кого есть
> `settings.write`, сможет направить провайдера на внутренний адрес. Включайте
> его только там, где это приемлемо. Без него каждое соединение адаптера,
> включая открытое перенаправлением, проверяется после разрешения имени и
> отклоняется, если адрес не публичный.

## Маршрутизация функции к модели

Карточка **Функции** перечисляет каждую функцию, которая может быть у установки.
Каждая выключена, пока вы её не включите и не выберете провайдера и модель, а
функцию, включённую без обоих, нельзя сохранить: включённая функция, которая
упала бы при первом же вызове, выглядит как ошибка, а не как незаполненная
настройка.

| Функция | Для чего |
| - | - |
| `support_summary` | Сводки тредов обращений |
| `support_reply` | Предлагаемые ответы для правки оператором |
| `support_rewrite` | Короче, яснее, дружелюбнее, официальнее, техничнее |
| `support_translate` | Русский и английский с сохранением названий и переменных |
| `support_classify` | Предлагаемые категория, приоритет, теги и тональность |
| `marketing_draft` | Черновики новостей, объявлений и кампаний |
| `risk_analysis` | Объяснимая оценка злоупотреблений и мошенничества |
| `copilot` | Ассистент администратора, учитывающий права доступа |
| `mcp_tools` | Вызовы инструментов через зарегистрированные серверы MCP |

У каждой функции свои настройки, потому что сводка обращения и суждение о том,
совершает ли кто-то мошенничество, — это не один и тот же риск и они не должны
делить конфигурацию:

| Настройка | Допустимый диапазон | Что делает |
| - | - | - |
| Провайдер и модель | Одобренный провайдер; любая строка модели | Куда уходят запросы этой функции |
| Температура | от 0 до 2 | Передаётся провайдеру |
| Максимум токенов | от 1 до 200000 | Потолок ответа; 1024, если не задан |
| Таймаут | от 1000 до 600000 мс | Сколько может длиться один запрос |
| Бюджет: токены / окно / стоимость | Любое неотрицательное значение | Проверяется **до** вызова, а не измеряется после |
| Хранить промпты / Хранить ответы | Вкл. или выкл. | Хранится ли материал в Omniflow |
| Хранение | от 0 до 3650 дней | Сколько живёт сохранённый материал |

Хранение задаётся по функциям, потому что материал разный: черновик маркетинга —
это собственный текст оператора, а сводка обращения — чья-то жалоба. Промпты по
умолчанию не хранятся, ответы хранятся.

## Потолки и кого они останавливают

Бюджеты у функции ограничивают эту функцию. Карточка **Лимиты использования**
ограничивает людей, а это другой вопрос: «установка израсходовала бюджет» и «один
оператор зациклил копилот» требуют разных ответов, и останавливать нужно только
во втором случае.

У лимита есть область, окно в секундах и любая комбинация числа запросов, числа
токенов и потолка стоимости в минорных единицах:

| Область | Что ограничивает |
| - | - |
| `installation` | Всё и везде |
| `role` | Каждого оператора с этой ролью |
| `operator` | Одного названного оператора |
| `feature` | Одну функцию, кто бы её ни вызвал |

Когда подходит несколько лимитов, оператору сообщают о самом узком, потому что
средство различается: ожидание не поможет с лимитом, который назвал вас лично.

Неизмеримый счётчик отказывает в закрытую сторону. Тратить в счёт бюджета,
который никто не может измерить, — это способ узнать свой лимит из счёта.

## Чтение состояния на экране

Карточка **Расходы и производительность** показывает запросы, токены, среднюю и
p95 задержку, отказы и оценочную стоимость по функциям и моделям за окно, которое
печатается рядом с числами. Содержимое промптов и ответов в этом отчёте не
хранится, поэтому в нём его и нет.

У каждой функции показаны применимые к ней предупреждения, и каждое из них — факт,
а не мнение:

| Предупреждение | Что означает |
| - | - |
| Этот провайдер может обучаться на ваших данных | В строке провайдера отмечено обучение |
| Этот провайдер не с нулевым хранением | У провайдера не отмечено нулевое хранение |
| Регион данных не указан | В строке провайдера нет региона |
| Проверка соединения не выполнялась | Результат проверки соединения никогда не записывался. Нажмите **Проверить подключение**, чтобы записать его |
| Последняя проверка соединения не удалась | Сохранён отрицательный результат вместе с подробностями |
| Промпты хранятся в Omniflow | Эта функция сохраняет промпты |
| Провайдер и модель не выбраны | Функция не может работать в такой конфигурации |

## Проверка провайдера

**Проверить подключение** выполняет один настоящий запрос. Кнопка распечатывает
сохранённый ключ, отправляет собственный промпт Omniflow — *Reply with the single
word OK* — с потолком в шестнадцать токенов вывода и записывает результат в
строку провайдера. Именно этот результат экран покажет при следующей загрузке,
поэтому вердикт не может разойтись сам с собой после обновления страницы.

Это настоящее завершение, а не проверка доступности, потому что отказывают они
по-разному и на вопрос отвечает только первое. Адрес, который резолвится, и
ключ, который провайдер принимает, — разные факты, и проверка, доказывающая
только первое, сообщила бы об отозванном ключе как об исправном.

Оставьте **Модель для проверки** пустой — и проверка возьмёт ту модель, которую
на этого провайдера уже указывает какая-нибудь функция: именно эту конфигурацию
и стоит доказывать. У провайдера, на которого пока никто не указывает, выводить
модель неоткуда, и запрос отклоняется, а не угадывается.

> [!NOTE]
>
> Проверять провайдера, которого вы ещё не включили, можно — и работа идёт
> именно в таком порядке: вставить ключ, проверить его, затем одобрить. Всё, что
> способно отправить клиентские данные, по-прежнему требует **Одобрен к
> использованию**.

Три детали сделаны намеренно:

* **Нужно `settings.write`, а не `settings.read`.** Проверка распечатывает
  запечатанный ключ и тратит ваши деньги. Ни того, ни другого не должно мочь
  чтение экрана настроек.
* **Есть ограничение частоты** — тридцать проверок в час на оператора: щедро для
  цикла «вставил — проверил — исправил» и мало настолько, что зациклившийся
  повтор не потратит бюджет за вечер.
* **Проверка, которую не удалось выполнить, ничего не сохраняет.** Нет ключа,
  нет модели, незарегистрированный слаг — каждый случай отклоняется, а
  сохранённый результат остаётся прежним, потому что запись отказа утверждала бы,
  что попытка была. Проверка, которая *выполнилась* и не прошла, сохраняется — с
  категорией, а не словами провайдера: сообщение провайдера может вернуть
  промпт, а транспортная ошибка цитирует адрес, по которому шёл запрос. Ни один
  адаптер не кладёт ключ в URL — ключ Gemini передаётся в заголовке
  `x-goog-api-key`, — а из транспортных ошибок URL вырезается до того, как их
  что-либо запишет в лог.

Каждая проверка попадает в журнал аудита как `ai.provider.tested`, рядом с
записью `ai.provider.saved` о предшествовавшей замене ключа. В строке хранится
только последний результат, а спрашивают обычно так: «не работало, кто-то
поменял ключ, потом заработало».

## Разбор неполадок

| Симптом | Причина |
| - | - |
| Карточка «Провайдеры» сообщает, что провайдер не настроен | Ничего не зарегистрировано или ничего из зарегистрированного не одобрено. Строка есть, но **Одобрен к использованию** выключен. |
| Провайдер сохранён, но показывает **Нет ключа** | При сохранении поле ключа было пустым. Оно доступно только на запись, поэтому пустое поле означает «сохранить то, что хранится», а у новой строки не хранится ничего. |
| Функция не сохраняется как включённая | Для включения нужны и провайдер, и модель. Выберите оба, затем включайте. |
| Провайдер не удаляется | На него всё ещё указывает функция. Его удаление выключило бы эту функцию, ничего не сказав, поэтому внешний ключ отказывает. Сначала переключите или выключите функцию. |
| Замена ключа как будто ничего не делает | Кнопка **Заменить ключ** неактивна, пока в поле нет значения, потому что сохранение пустого поля — это способ *сохранить* текущий ключ. |
| **Проверить подключение** сообщает, что проверку не удалось выполнить | Ничего не отправлено, сохранённый результат не изменился. В сообщении сказано, что именно: не сохранён ключ, не указана модель и вывести её неоткуда, либо для этого вида провайдера в сборке нет адаптера. |
| Проверка сообщает, что до провайдера не удалось достучаться, без подробностей | Отказ произошёл на транспорте, а не в ответе провайдера. Адрес никогда не цитируется обратно — ни на экране, ни в логе. Проверьте базовый URL и сетевой путь из контейнера API. |
| Экрана нет в навигации вовсе | У вошедшего оператора нет `settings.read`, либо к этому процессу не подключён сервис операций — в этом случае маршрут отсутствует, а не запрещён, поэтому вызывающий ничего не узнаёт о настроенном. |

## Смежное

> **[Приватность AI и шлюз](https://omniflow-vpn.mintlify.site/ru/operations/ai-privacy.md)**
>
> Что удаляется до отправки и что AI никогда не позволено делать.

> **[Управление AI и оценка качества](https://omniflow-vpn.mintlify.site/ru/operations/ai-governance.md)**
>
> Включение по функциям, инвентаризация потоков данных, хранение и пороги оценки.

> **[Подключения MCP](https://omniflow-vpn.mintlify.site/ru/operations/mcp.md)**
>
> Регистрация сервера MCP, его списки разрешений и ограничения исходящих соединений.

> **[Настройки установки](https://omniflow-vpn.mintlify.site/ru/operations/installation-settings.md)**
>
> Остальные экраны конфигурации и то, как запечатываются секреты.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.