Files
2026-03-17 18:32:44 +03:00

26 KiB
Raw Permalink Blame History

ML Core Swagger (Сдача)

Актуальный сдаваемый Swagger для ML core backend лежит в файле:

  • docs/ml_core_backend_openapi.yaml

Что входит в документ:

  • POST /api/v1/pipelines/generate
  • GET /api/v1/pipelines/dialogs
  • GET /api/v1/pipelines/dialogs/{dialog_id}/history
  • POST /api/v1/pipelines/dialog/reset
  • POST /api/v1/pipelines/{pipeline_id}/run
  • GET /api/v1/executions
  • GET /api/v1/executions/{run_id}

Что не входит в этот документ:

  • auth, actions, capabilities ручки
  • demo /ml/* ручки из demo-backend/openapi/*

Быстрая проверка локально:

cd backend
pytest -s --capture=no tests/test_ml_openapi_contract.py

Дополнительная валидация OpenAPI (если установлен валидатор):

python3 -m openapi_spec_validator docs/ml_core_backend_openapi.yaml

AI Copilot

Core Concept

Главная задача проекта: Трансформация статической документации API в автономную интеллектуальную систему. Пользователь предоставляет набор OpenAPI файлов и бизнес-логику, которую он хочет получить, чтобы протестировать, а система незамедлительно проектирует и исполняет готовые Pipelines для решения задачи.

The Value Chain

Action → Capability → Pipeline

Система работает по принципу восходящей абстракции:

  • Actions (Технический слой): Набор разрозненных эндпоинтов из OpenAPI.
  • Capability (Логический слой): Группа из одного или нескольких Actions, объединенных общей бизнес-целью. Это абстракция для AI, который знает, как выполнить конкретную функцию.
  • Pipeline (Сценарный слой): Последовательность из Capabilities, решающая задачу пользователя.задачу. | Итоговый план действий. |

ЦА — продакт менеджеры

Проблема: у продакт менеджеров есть гипотеза (например: «Если отправлять пуш тем, кто бросил корзину через 15 минут, конверсия вырастет»), но чтобы её проверить, нужно ставить задачу в спринт, ждать 2 недели и отвлекать бэкенд.

Решение: Наш AI-copilot позволяет проджект менеджеру предоставить системе API маркетинговой платформы и CRM, а затем автоматически создать и запустить Pipeline, решающий бизнес-задачу.

User stories

Роль Я хочу (Действие) Чтобы (Ценность) Definition of Done (Критерии приемки) Модуль
PM Загрузить файл OpenAPI (Swagger) Система получила сырую базу технических методов (Actions). Файл парсится, эндпоинты сохранены в БД. Ingestion
PM Объединить несколько Actions в одну Capability Скрыть техническую сложность и создать «навык» (напр. «Обновить профиль»). В базе создана сущность Capability, связанная с 1+ Actions. Capability
PM Получить авто-описание для Capability от AI Не тратить время на ручное заполнение названий и смыслов для каждого навыка. Каждой Capability присвоено человекочитаемое имя и описание. Semantic
PM Описать бизнес-задачу в чате (напр. «Найди новичков и поздоровайся») AI-копилот сам подобрал нужные Capabilities и построил из них Pipeline. AI выдает валидный JSON-граф с ID навыков и логическими связями. Synthesis
PM Видеть сгенерированный сценарий в виде графа Визуально подтвердить, что данные (напр. user_id) передаются верно. На канвасе отрисованы ноды и стрелки (React Flow). Synthesis
PM Вручную отредактировать параметры внутри ноды Финальный сценарий на 100% соответствовал конкретной задаче. Форма редактирования корректно сохраняет данные в объект ноды. Execution
PM Подтвердить запуск Pipeline нажатием кнопки Контролировать процесс и избежать случайных ошибок в реальных системах. Запуск происходит только после клика; первая нода переходит в статус active. Execution
PM Наблюдать за выполнением шагов в реальном времени Видеть прогресс и понимать, на каком этапе сейчас находится выполнение. Активная нода подсвечивается; статусы (Success/Fail) обновляются в UI. Execution
PM Получить лог ответов от всех API по завершении Убедиться, что гипотеза проверена и данные ушли/пришли корректно. По завершении выводится панель с результатами (JSON-логи). Execution

Domain layer

Все сущности, покрывающие user-stories:

Сущность Что это Зачем нужна
Action Инструмент в ящике. Хранит технические детали одного эндпоинта (URL, метод, JSON-схема).

Что в модели Action:

Поле Тип Назначение
id UUID PK Первичный ключ
operation_id String operationId из OpenAPI
method HttpMethod enum GET / POST / PUT / PATCH / DELETE …
path String URL-путь, напр. /users/{id}
base_url String Базовый URL из servers[] спецификации
summary String Краткое описание из OpenAPI
description Text Подробное описание из OpenAPI
tags JSON Теги для группировки
parameters_schema JSON JSON Schema query/path/header параметров
request_body_schema JSON JSON Schema тела запроса
response_schema JSON JSON Schema успешного ответа (2xx)
source_filename String Имя загруженного Swagger-файла
raw_spec JSON Оригинальный фрагмент операции из спецификации
created_at / updated_at DateTime Через TimestampMixin

Capability|Навык мастера.|Описывает бизнес-логику (связку 1+ Actions) и семантику для AI. Node|Шаг в инструкции.|Конкретный блок в графе. Ссылается на Capability, но хранит индивидуальные настройки (например, текст письма именно для этого шага). Pipeline|Инструкция (чертеж).|Коллекция нод и связей между ними. Хранит общую структуру графа.

Что в модели Pipeline:

Поле Тип Назначение
id UUID PK Первичный ключ
name String Название пайплайна
description Text Описание сценария
user_prompt Text Оригинальный промпт PM из чата
nodes JSON Список нод графа с параметрами и позициями
edges JSON Список рёбер графа и порядка выполнения
status PipelineStatus enum Статус пайплайна: DRAFT / READY / ARCHIVED
created_by UUID FK Ссылка на пользователя-автора
created_at / updated_at DateTime Через TimestampMixin

Execution (Run)|Процесс сборки.|Хранит статус конкретного запуска (ID, время старта, текущий статус всего процесса). Context|Рабочая память.|Временный объект внутри Execution, где лежат результаты выполненных нод для подстановки в следующие.

Infrastructure layer

Компонент Технология Роль и описание
Database PostgreSQL Хранение структурированных данных (Actions, Capabilities, Pipelines).
LLM Inference vLLM / TGI (Text Generation Inference) Хостинг твоей модели (Llama-3, Mistral и др.). Предоставляет высокопроизводительный OpenAI-совместимый API.
ORM SQLAlchemy + Alembic Асинхронный маппинг доменных моделей на таблицы БД и управление миграциями без даунтайма.
Async Runtime FastAPI + Uvicorn Ядро монолита. Обработка запросов, управление жизненным циклом приложения и фоновыми задачами.
HTTP Client HTTPX (Async) фНеблокирующие вызовы к твоей локальной модели и внешним API сервисов (Slack, CRM и т.д.) внутри ExecutionCore.
Cache / State Redis Хранение промежуточного состояния (Context) активных пайплайнов и кэширование результатов инференса.
Containerization Docker & Compose Изоляция сервисов (App, DB, Model Server) и их оркестрация одной командой.

Service layer

Каждый сервис отвечает за свой этап жизненного цикла — от загрузки API до получения результата.

  1. IngestionService

    Задача: Обработка Swagger/OpenAPI.

    Инструменты: Библиотеки prance или openapi-spec-validator.

    Логика: Принимает файл через UploadFile, парсит его и сохраняет в БД (PostgreSQL) список Actions.

    Метод: async def ingest_openapi(file: UploadFile) -> List[ActionDomain]: ...

  2. CapabilityService

    Задача: Группировка и описание навыков.

    Инструменты: LangChain или просто прямой вызов OpenAI SDK.

    Логика: Получает ID Actions, делает запрос к LLM для генерации описания Capability, сохраняет результат.

    Метод: async def create_capability(action_ids: list[UUID], name: str) -> CapabilityDomain: ...

  3. SynthesisService

    Задача: Сборка Pipeline через LLM.

    Логика:

    • Получает промпт от PM.
    • Выбирает подходящие Capability из библиотеки в БД.
    • Формирует промпт для QWEN-2.5, чтобы она вернула JSON-структуру графа.

    Метод: async def synthesize_pipeline(user_query: str) -> PipelineDomain: ...

  4. ExecutionService

    Задача: Асинхронное выполнение графа.

    Инструменты: httpx (асинхронный клиент для запросов).

    Логика:

    • Инициализирует Context (Pydantic модель).
    • Проходит циклом по нодам Pipeline.
    • Заменяет переменные (Variable Injection).
    • Делает await client.request(...).

    Метод: async def run_execution(pipeline_id: UUID) -> ExecutionResult: ...

Api layer

Actions

Загрузка и валидация Swagger/OpenAPI.

Метод Путь Описание
POST /api/v1/actions/ingest Загрузка файла OpenAPI (Multipart form-data).
GET /api/v1/actions Получение списка всех импортированных методов с фильтрацией.
GET /api/v1/actions/{id} Детальная схема конкретного экшена.
DELETE /api/v1/actions/{id} Удаление экшена (если API обновилось или метод больше не нужен).

Пример запроса ingest

curl -X POST "http://localhost:8000/api/v1/actions/ingest" \
  -H "Accept: application/json" \
  -F "file=@/app/examples/travel.yaml;type=application/yaml"

Пример ответа ingest

{
  "created_actions_count": 5,
  "created_capabilities_count": 5,
  "capabilities": [
    {
      "id": "7c1d5c9b-2c9d-4f1c-9d2e-4a8f3e1b7a11",
      "action_id": "e4b0bcb6-6a8c-4b0a-8e18-3f44b5f7d1c2",
      "name": "get_recent_users",
      "description": "Get recent users for travel campaigns",
      "input_schema": null,
      "output_schema": {
        "type": "object"
      },
      "data_format": {
        "parameter_locations": ["query"],
        "request_content_types": [],
        "request_schema_type": null,
        "response_content_types": ["application/json"],
        "response_schema_types": ["object"]
      },
      "created_at": "2026-03-14T12:00:00Z",
      "updated_at": "2026-03-14T12:00:00Z"
    }
  ]
}

Capabilities

Превращаем API в способности

Метод Путь Описание
POST /api/v1/capabilities/suggest AI-powered: Система анализирует Actions и предлагает логические связки.
POST /api/v1/capabilities Создание навыка: связка 1+ Action ID, маппинг данных и описание.
GET /api/v1/capabilities Список всех навыков для отображения в библиотеке на фронте.
GET /api/v1/capabilities/{id} Посмотреть, из каких Actions состоит навык и как внутри ходят данные.
DELETE /api/v1/capabilities/{id} Удаление навыка.

Pipelines

Основная точка входа для чата и канваса (React Flow).

Метод Путь Описание
POST /api/v1/pipelines/generate AI-powered: Промпт из чата -> AI подбирает Capabilities -> Возвращает JSON-граф.
GET /api/v1/pipelines Список всех сохраненных или сгенерированных сценариев.
GET /api/v1/pipelines/{id} Загрузка конкретного графа на канвас.
PUT /api/v1/pipelines/{id} Сохранение ручных правок: если PM подвигал ноды или изменил параметры.
DELETE /api/v1/pipelines/{id} Удаление сценария.

Вызов чата: POST /api/v1/pipelines/generate

Используйте один и тот же dialog_id для одной цепочки сообщений, чтобы сохранялся контекст.

curl -X POST "http://localhost:8000/api/v1/pipelines/generate" \
  -H "Content-Type: application/json" \
  -d '{
    "dialog_id": "11111111-1111-1111-1111-111111111111",
    "message": "Нужно взять 30 последних пользователей, распределить по 5 отелям и отправить email-офферы",
    "user_id": null,
    "capability_ids": null
  }'

Пример ответа чата

{
  "status": "ready",
  "message_ru": "Пайплайн собран. Можно запускать.",
  "chat_reply_ru": "Пайплайн собран. Можно запускать. План шагов: get_users_recent -> get_hotels_top -> post_segments_hotel.",
  "pipeline_id": "7b17ac70-3f39-4e70-8f8a-4a2f1fd4ff7e",
  "nodes": [
    {
      "step": 1,
      "name": "get_users_recent",
      "description": "Отбирает недавних пользователей для travel campaign.",
      "input_connected_from": [],
      "output_connected_to": [3],
      "input_data_type_from_previous": [],
      "external_inputs": [],
      "endpoints": [
        {
          "name": "get_users_recent",
          "capability_id": "c4be1e66-2e04-4c6f-8d8f-6f39f1f46087",
          "action_id": "e4b0bcb6-6a8c-4b0a-8e18-3f44b5f7d1c2",
          "output_type": "users[]"
        }
      ]
    },
    {
      "step": 2,
      "name": "get_hotels_top",
      "description": "Получает список топовых отелей для офферов.",
      "input_connected_from": [],
      "output_connected_to": [3],
      "input_data_type_from_previous": [],
      "external_inputs": [],
      "endpoints": [
        {
          "name": "get_hotels_top",
          "capability_id": "470ae37e-029e-4c67-a293-acb848675d0b",
          "action_id": "96f0bc8f-e294-46a9-9a0e-48e1b8f2a941",
          "output_type": "hotels[]"
        }
      ]
    }
  ],
  "edges": [
    {
      "from_step": 1,
      "to_step": 3,
      "type": "users"
    },
    {
      "from_step": 2,
      "to_step": 3,
      "type": "hotels"
    }
  ],
  "missing_requirements": [],
  "context_summary": "Пользователь хочет собрать travel-рассылку из доступных capability."
}

status может быть:

  • ready — граф построен, pipeline_id/nodes/edges заполнены.
  • needs_input — нужно уточнение или добавить Swagger/OpenAPI.
  • cannot_build — с текущими данными сценарий не собирается.

Сброс диалога: POST /api/v1/pipelines/dialog/reset

curl -X POST "http://localhost:8000/api/v1/pipelines/dialog/reset" \
  -H "Content-Type: application/json" \
  -d '{
    "dialog_id": "11111111-1111-1111-1111-111111111111"
  }'

Execution

Запуск пайплайна.

Метод Путь Описание
POST /api/v1/pipelines/{id}/run Запуск пайплайна. Создает объект Execution и запускает цикл выполнения.
GET /api/v1/executions История всех запусков (кто, когда и с каким результатом запускал).
GET /api/v1/executions/{run_id} Статус в реальном времени: Поллинг для фронта (какая нода сейчас горит зеленым).
POST /api/v1/executions/{run_id}/approve Подтверждение «опасного» шага (если нода требует Approval).

CapabilityService

Задача: Инкапсулировать один или несколько технических Actions (API-эндпоинтов) в единый, понятный для LLM и пользователя бизнес-навык (Capability).

Этап 1: Формирование связки (Action Binding)

На вход сервис получает массив ID Actions и их порядковые номера. Сервис валидирует, что эти эндпоинты существуют в БД.

Внутренний маппинг: Сервис принимает правила передачи данных между экшенами. Например, ответ от GET /users (поле user.id) должен лечь в тело запроса POST /emails (в поле recipient_id).

Абстракция входа/выхода: Сервис вычисляет публичную схему навыка (Input/Output Schema). Он берет все обязательные параметры всех внутренних экшенов, вычитает из них те, которые закрыты внутренним маппингом, и формирует итоговый JSON Schema. Для внешнего мира этот навык теперь выглядит как одна функция.

Этап 2: Семантическое обогащение (LLM Summarization)

Чтобы ИИ в будущем понимал, зачем нужен этот инструмент, сервис обращается к локальной LLM (vLLM/TGI).

Промпт к LLM: Сервис отправляет системный промпт, содержащий URL-адреса, методы и JSON-схемы объединенных экшенов.

Задача LLM: Сгенерировать:

  • Короткое название (например, create_refund_ticket).
  • Подробное описание (например, «Используется для создания заявки на возврат средств в Zendesk и проверки статуса транзакции в Stripe»).

Этап 3: Сохранение навыка в библиотеку

Текстовое описание и название навыка сохраняются в PostgreSQL вместе с input_schema и output_schema.

Этот шаг делает Capability доступной для последующей сборки пайплайнов без дополнительного индексационного слоя.

SynthesisService

Задача: Принять текстовый запрос пользователя, найти подходящие инструменты (Capabilities) и собрать из них валидный направленный ациклический граф (DAG), готовый к исполнению.

Этот сервис вызывается каждый раз, когда пользователь пишет промпт в чат. Время его работы напрямую влияет на UX, поэтому он должен быть оптимизирован под максимальную скорость.

Этап 1: Отбор доступных навыков (Capability Selection)

Запрос пользователя (например, "Найди последние 5 оплаченных заказов и отправь их в канал #sales") сопоставляется с доступной библиотекой Capability.

Сервис собирает релевантный список навыков из PostgreSQL и готовит их для контекста LLM.

Результат: Вместо сотен API-методов, в prompt передаются только подходящие "строительные блоки" (например, search_orders и send_slack_message).

Этап 2: Сборка контекста для LLM (Prompt Engineering)

Сервис формирует динамический промпт для генерации графа. В него вшиваются:

Инструкция (System Message): Жесткие правила работы («Ты парсер. Возвращай только JSON. Не выдумывай ID навыков»).

Библиотека инструментов: JSON-схемы найденных на предыдущем шаге Capabilities (их id, name, description и input_schema).

Промпт пользователя: Оригинальный текст запроса.

Этап 3: Генерация графа (LLM Inference)

Локальная LLM обрабатывает контекст и возвращает структуру пайплайна в строгом формате.

LLM определяет узлы (Nodes) — какие навыки использовать.

LLM определяет ребра (Edges) — в каком порядке их вызывать.

LLM прописывает переменные (Variable Injection) — как данные перетекают между узлами, используя синтаксис шаблонизатора (например, {{node_1.output.orders_list}}).

Этап 4: Строгая валидация (Sanitization & DAG Check)

LLM склонны к галлюцинациям, поэтому перед сохранением в базу SynthesisService проводит жесткую проверку полученного JSON:

Schema Validation (Pydantic): Проверка, что структура ответа строго соответствует модели Pipeline.

Capability Existence: Проверка, что все capability_id в узлах реально существуют в базе (LLM не выдумала несуществующий навык).

DAG Validation (Топологическая сортировка): Граф проверяется на отсутствие циклов (A -> B -> C -> A), чтобы предотвратить бесконечное выполнение.

Parameter Validation: Проверка, что все обязательные поля из input_schema каждого навыка либо заполнены статичными значениями, либо имеют ссылку-шаблон на предыдущий узел.

Если граф проходит валидацию, он сохраняется в таблицу pipelines и отдается на фронтенд для визуализации на канвасе. Если валидация провалена — сервис просит LLM исправить ошибку (Retry Logic, максимум 2 попытки), передавая ей текст ошибки валидации.