Система Расширений
Система Расширений Ordinaut предоставляет мощную и гибкую платформу для расширения функциональности основного планировщика задач. Расширения позволяют добавлять новые возможности, такие как веб-интерфейсы, инструменты мониторинга, интеграции протоколов и пользовательские реализации инструментов, сохраняя при этом четкое разделение от основной системы.
Обзор Архитектуры
Система расширений следует архитектуре плагинов с безопасностью на основе возможностей:
graph TB
Core[Ordinaut Core] --> Loader[Загрузчик Расширений]
Loader --> |Обнаруживает| Built[Встроенные Расширения]
Loader --> |Обнаруживает| External[Внешние Расширения]
Loader --> |Управляет| Lifecycle[Жизненный Цикл]
Built --> Obs[observability]
Built --> WebUI[webui]
Built --> MCP[mcp_http]
Built --> Events[events_demo]
Lifecycle --> Setup[Фаза Setup]
Lifecycle --> Runtime[Фаза Runtime]
Lifecycle --> Shutdown[Фаза Shutdown]
Setup --> |Предоставляет| Capabilities[Возможности]
Capabilities --> Routes[HTTP Маршруты]
Capabilities --> Tools[Реестр Инструментов]
Capabilities --> EventsPub[Публикация Событий]
Capabilities --> EventsSub[Подписка на События]
Capabilities --> Background[Фоновые Задачи]
Capabilities --> Static[Статичные Файлы]
Ключевые Особенности
🔐 Безопасность на Основе Возможностей
Расширения запрашивают специфические возможности и получают доступ только к одобренной функциональности:
- ROUTES
- Создание HTTP конечных точек
- TOOLS
- Доступ к реестру инструментов
- EVENTS_PUB
- Публикация событий
- EVENTS_SUB
- Подписка на события
- BACKGROUND_TASKS
- Долгоработающие процессы
- STATIC
- Обслуживание статичных файлов
🚀 Ленивая Загрузка
Расширения загружаются по требованию при первом обращении, оптимизируя время запуска и использование ресурсов.
📡 Система Событий
Система pub/sub на основе Redis Streams позволяет расширениям общаться друг с другом.
🔧 Реестр Инструментов
Регистрация инструментов с пространством имен позволяет расширениям предоставлять пользовательские инструменты и действия.
📊 Фоновые Задачи
Система супервизора управляет долгоработающими фоновыми процессами для расширений.
Встроенные Расширения
observability
Назначение: Сбор метрик Prometheus и мониторинг
Конечная Точка: /ext/observability/metrics
Возможности: ROUTES
Предоставляет комплексные системные метрики включая: - Метрики HTTP запросов/ответов - Статистику выполнения задач - Использование системных ресурсов - Пользовательские бизнес-метрики
webui
Назначение: Веб-интерфейс управления задачами
Конечная Точка: /ext/webui/
Возможности: ROUTES
, STATIC
Возможности: - Создание и управление задачами - Мониторинг выполнения pipeline - Статус системы в реальном времени - Интерфейс управления расширениями
mcp_http
Назначение: Model Context Protocol через HTTP
Конечная Точка: /ext/mcp_http/
Возможности: ROUTES
Предоставляет HTTP конечные точки совместимые с MCP для: - Обнаружения и вызова инструментов - Управления сессиями - Потоковых ответов - Интеграции с AI ассистентами
events_demo
Назначение: Демонстрация системы событий Redis Streams
Конечная Точка: /ext/events_demo/
Возможности: ROUTES
, EVENTS_PUB
, EVENTS_SUB
Демонстрирует: - Публикацию и подписку на события - Межрасширенческое общение - Потоковую передачу событий в реальном времени
Создание Расширений
Структура Расширения
Каждое расширение требует два файла:
extension.json - Манифест расширения:
{
"id": "мое_расширение",
"name": "Мое Пользовательское Расширение",
"version": "1.0.0",
"description": "Описание функциональности расширения",
"module": "extension.py",
"enabled": true,
"eager": false,
"grants": ["ROUTES", "TOOLS"]
}
extension.py - Реализация расширения:
from typing import Any, Optional
from fastapi import APIRouter, FastAPI
from ordinaut.plugins.base import Extension, ExtensionInfo, Capability
class МоеРасширение(Extension):
def info(self) -> ExtensionInfo:
return ExtensionInfo(
id="мое_расширение",
name="Мое Пользовательское Расширение",
version="1.0.0",
description="Пользовательская функциональность для Ordinaut"
)
def requested_capabilities(self) -> set[Capability]:
return {Capability.ROUTES, Capability.TOOLS}
def setup(
self,
*,
app: FastAPI,
mount_path: str,
tool_registry: Any,
grants: set[Capability],
context: dict[str, Any] | None = None,
) -> Optional[APIRouter]:
router = APIRouter()
@router.get("/привет")
def привет():
return {"сообщение": "Привет от моего расширения!"}
# Регистрирует пользовательские инструменты если предоставлена возможность TOOLS
if Capability.TOOLS in grants:
tool_registry.register_tool("мой_инструмент", функция_инструмента)
return router
async def on_startup(self, app: FastAPI) -> None:
print("Мое расширение запускается...")
async def on_shutdown(self, app: FastAPI) -> None:
print("Мое расширение завершается...")
def get_extension():
return МоеРасширение()
Обнаружение Расширений
Расширения обнаруживаются из нескольких источников:
- Встроенные Расширения: директория
ordinaut/extensions/
- Переменные Окружения: переменная окружения
ORDINAUT_EXT_PATHS
- Python Entry Points: группа entry point
ordinaut.plugins
Структура Директории
мое_расширение/
├── extension.json # Манифест расширения
├── extension.py # Основной код расширения
├── static/ # Статичные файлы (при использовании возможности STATIC)
│ ├── index.html
│ └── style.css
└── templates/ # Файлы шаблонов
└── dashboard.html
Возможности Расширений
Возможность ROUTES
Позволяет расширениям регистрировать HTTP конечные точки:
def setup(self, *, app: FastAPI, mount_path: str, **kwargs) -> APIRouter:
router = APIRouter()
@router.get("/статус")
def get_status():
return {"статус": "здоровый"}
@router.post("/действие")
async def выполнить_действие(request: ЗапросДействия):
результат = await обработать_действие(request)
return {"результат": результат}
return router
Возможность TOOLS
Обеспечивает доступ к реестру инструментов для регистрации пользовательских инструментов:
def setup(self, *, tool_registry: Any, grants: set[Capability], **kwargs):
if Capability.TOOLS in grants:
# Регистрирует пользовательский инструмент
def мой_пользовательский_инструмент(входные_данные: dict) -> dict:
return {"обработано": True, "данные": входные_данные}
tool_registry.register_tool(
name="ext.мое_расширение.пользовательский_инструмент",
func=мой_пользовательский_инструмент,
schema={
"input": {"type": "object"},
"output": {"type": "object"}
}
)
Возможности EVENTS_PUB/EVENTS_SUB
Включают публикацию и подписку на события:
def setup(self, *, context: dict[str, Any], grants: set[Capability], **kwargs):
if Capability.EVENTS_PUB in grants or Capability.EVENTS_SUB in grants:
events = context.get("events")
if Capability.EVENTS_PUB in grants:
# Публикует события
await events.publish("задача.завершена", {
"task_id": "123",
"статус": "успех"
})
if Capability.EVENTS_SUB in grants:
# Подписывается на события
async def обработать_событие_задачи(данные_события):
print(f"Получено событие задачи: {данные_события}")
await events.subscribe("task.*", обработать_событие_задачи)
Возможность BACKGROUND_TASKS
Управляет долгоработающими фоновыми процессами:
def setup(self, *, context: dict[str, Any], grants: set[Capability], **kwargs):
if Capability.BACKGROUND_TASKS in grants:
background = context.get("background")
async def фоновый_работник():
while True:
await обработать_фоновую_работу()
await asyncio.sleep(60)
# Запускает фоновую задачу
await background.start_task("мой_работник", фоновый_работник)
Конфигурация Расширений
Переменные Окружения
ORDINAUT_EXT_PATHS
- Пути к директориям расширений разделенные двоеточиемORDINAUT_EXT_ENTRY_GRANTS
- JSON конфигурация предоставлений для entry point расширенийORDINAUT_EXT_ENTRY_EAGER
- JSON конфигурация активной загрузкиORDINAUT_REQUIRE_SCOPES
- Включает авторизацию на основе областей
Авторизация на Основе Областей
Расширения могут требовать специфические области для доступа:
# Требует область 'ext:мое_расширение:routes'
curl -H "X-Scopes: ext:мое_расширение:routes" \\
http://localhost:8080/ext/мое_расширение/защищенный
Жизненный Цикл Расширения
Фаза Обнаружения
- Сканирует директорию
ordinaut/extensions/
для встроенных расширений - Проверяет переменную окружения
ORDINAUT_EXT_PATHS
- Загружает Python entry points из группы
ordinaut.plugins
- Валидирует манифесты расширений против JSON схемы
Фаза Загрузки
- Импортирует модуль расширения
- Вызывает фабричную функцию
get_extension()
- Валидирует информацию расширения соответствующую манифесту
- Предоставляет запрошенные возможности на основе конфигурации
Фаза Настройки
- Инициализирует контекст расширения (инструменты, события, фоновые задачи)
- Вызывает метод
setup()
расширения - Монтирует возвращаемый маршрутизатор с соответствующим префиксом
- Регистрирует обработчики запуска/завершения
Фаза Выполнения
- Обрабатывает HTTP запросы к конечным точкам расширения
- Обрабатывает вызовы инструментов
- Управляет публикацией/подпиской на события
- Контролирует фоновые задачи
Фаза Завершения
- Вызывает метод
on_shutdown()
расширения - Останавливает фоновые задачи
- Очищает ресурсы
- Отмонтирует маршруты
Лучшие Практики Разработки Расширений
Безопасность
- Валидируйте все входные данные используя модели Pydantic или JSON Schema
- Используйте области разрешений соответственно
- Санитизируйте выходные данные для предотвращения инъекционных атак
- Записывайте события связанные с безопасностью для аудиторских следов
Производительность
- Реализуйте async методы где возможно
- Используйте ленивую загрузку для дорогих ресурсов
- Кешируйте часто используемые данные
- Мониторьте использование ресурсов и реализуйте лимиты
Надежность
- Обрабатывайте ошибки грациозно с соответствующими HTTP статус кодами
- Реализуйте проверки здоровья для фоновых сервисов
- Используйте экспоненциальный откат для повторов
- Предоставляйте осмысленные сообщения об ошибках
Сопровождаемость
- Следуйте семантическому версионированию для релизов расширений
- Документируйте все публичные API с OpenAPI схемами
- Пишите комплексные тесты для функциональности расширений
- Используйте согласованное логирование со структурированными форматами
Тестирование Расширений
Модульное Тестирование
import pytest
from fastapi.testclient import TestClient
from мое_расширение import get_extension
@pytest.fixture
def extension():
return get_extension()
@pytest.fixture
def client(extension):
app = FastAPI()
router = extension.setup(
app=app,
mount_path="/test",
tool_registry=MockToolRegistry(),
grants={Capability.ROUTES}
)
app.include_router(router, prefix="/test")
return TestClient(app)
def test_extension_endpoint(client):
response = client.get("/test/привет")
assert response.status_code == 200
assert response.json() == {"сообщение": "Привет от моего расширения!"}
Интеграционное Тестирование
def test_extension_with_ordinaut():
# Запускает Ordinaut с расширением
with OrdianautTestServer(extensions=["мое_расширение"]) as server:
response = server.get("/ext/мое_расширение/привет")
assert response.status_code == 200
Примеры Расширений
Простое HTTP Расширение
class ПростоеHTTPРасширение(Extension):
def info(self) -> ExtensionInfo:
return ExtensionInfo(
id="простое_http",
name="Простое HTTP Расширение",
version="1.0.0"
)
def requested_capabilities(self) -> set[Capability]:
return {Capability.ROUTES}
def setup(self, **kwargs) -> APIRouter:
router = APIRouter()
@router.get("/пинг")
def пинг():
return {"понг": True}
return router
Расширение Реестра Инструментов
class РасширениеИнструмента(Extension):
def requested_capabilities(self) -> set[Capability]:
return {Capability.TOOLS}
def setup(self, *, tool_registry, **kwargs):
def вычислить_хеш(данные: str) -> str:
import hashlib
return hashlib.sha256(данные.encode()).hexdigest()
tool_registry.register_tool("hash.sha256", вычислить_хеш)
Расширение Обработки Событий
class РасширениеСобытия(Extension):
def requested_capabilities(self) -> set[Capability]:
return {Capability.EVENTS_SUB, Capability.EVENTS_PUB}
def setup(self, *, context, **kwargs):
events = context["events"]
async def обработать_завершение_задачи(данные_события):
# Обрабатывает завершенную задачу
результат = await обработать_результат_задачи(данные_события)
# Публикует обработанный результат
await events.publish("задача.обработана", результат)
# Подписывается на события завершения задач
events.subscribe("задача.завершена", обработать_завершение_задачи)
Устранение Неполадок
Общие Проблемы
Расширение не загружается
- Проверьте JSON синтаксис манифеста расширения
- Убедитесь что функция get_extension()
существует
- Убедитесь что директория расширения в пути обнаружения
Доступ запрещен - Проверьте предоставления возможностей в манифесте расширения - Убедитесь что требования областей выполнены - Проверьте конфигурацию разрешений расширения
Расширение падает - Проверьте логи расширения для деталей ошибки - Убедитесь что все зависимости установлены - Протестируйте расширение в изоляции
Инструменты Отладки
# Список обнаруженных расширений
curl http://localhost:8080/ext/status
# Проверить здоровье расширения
curl http://localhost:8080/ext/мое_расширение/health
# Посмотреть метрики расширения
curl http://localhost:8080/ext/observability/metrics | grep extension
Система расширений предоставляет надежную основу для расширения возможностей Ordinaut, сохраняя при этом стандарты безопасности, производительности и надежности.