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

410 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/*`
Быстрая проверка локально:
```bash
cd backend
pytest -s --capture=no tests/test_ml_openapi_contract.py
```
Дополнительная валидация OpenAPI (если установлен валидатор):
```bash
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
```bash
curl -X POST "http://localhost:8000/api/v1/actions/ingest" \
-H "Accept: application/json" \
-F "file=@/app/examples/travel.yaml;type=application/yaml"
```
#### Пример ответа ingest
```json
{
"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` для одной цепочки сообщений, чтобы сохранялся контекст.
```bash
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
}'
```
#### Пример ответа чата
```json
{
"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`
```bash
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 попытки), передавая ей текст ошибки валидации.