Инструменты

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

Предопределенные инструменты

В дополнение входят предопределенные инструменты, которые ассистент может вызывать от имени пользователя. Они сгруппированы по назначению, и каждая группа описана в отдельном разделе ниже. На данный момент дополнение предоставляет одну группу: инструменты Data Load.

Инструменты Data Load

Инструменты Data Load позволяют ассистенту отвечать на вопросы о данных приложения.

Процесс загрузки данных

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

  1. Discover — получить список сущностей, доступных пользователю, и проанализировать нужные из них, включая атрибуты, связи и перечисления.

  2. Generate — сформировать JPQL-запрос только для чтения с именованными параметрами.

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

  4. Repair — если валидация не прошла, попросить модель исправить запрос и повторно его провалидировать, не более заданного числа попыток.

  5. Execute — выполнить запрос через DataManager, применить права доступа к сущностям и атрибутам и вернуть страницу строк.

Все шаги выполняются на сервере. Запрос всегда представляет собой SELECT только для чтения. Операции записи и обходы через native SQL отклоняются на этапе валидации, а при выполнении дополнительно применяются права текущего пользователя на чтение. Запрос к сущности, которую пользователь не может читать, будет отклонен, а атрибуты, недоступные для чтения, будут исключены из возвращаемых строк, так же как dataGrid скрывает колонки, привязанные к нечитаемым атрибутам. В обоих случаях пользователь не получает данные, к которым у него нет доступа.

Доступные инструменты

Этот процесс предоставляется модели в виде трех инструментов. Имя инструмента — это идентификатор, который модель использует для его вызова. Это же имя используется при переопределении инструмента.

Имя инструмента Назначение

aitls_getAvailableEntities

Возвращает краткие метаданные для каждой сущности, доступной пользователю в текущий момент: имя сущности, локализованные названия и имена свойств. Сущности, скрытые фильтрацией приложения или подсистемой безопасности, не возвращаются. Ассистент использует этот инструмент для анализа модели данных и получения корректных имен сущностей для последующих вызовов.

aitls_getDomainModelForEntities

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

aitls_executeQuery

Валидирует, при необходимости исправляет и выполняет JPQL-запрос только для чтения, возвращая результирующие строки. Параметры передаются как структурированный запрос, содержащий текст запроса, именованные параметры, имена результирующих колонок и информацию о пагинации. Результат содержит строки, признак наличия дополнительных строк, а также ошибки валидации или выполнения.

Эти три инструмента объединены маркерным интерфейсом DataLoadAiTool, поэтому реестр может собирать их как одну группу. См. AiToolRegistry.

Управление доступной моделью данных

По умолчанию ассистент видит все JPA-сущности приложения, кроме сущностей фреймворка, так как пакеты io.jmix исключены, и сущностей системного уровня. Этот набор можно сузить или расширить с помощью свойств jmix.aitools.dataload.*.

Например, чтобы скрыть определенную сущность от ассистента и ограничить число строк по умолчанию для одного запроса:

# Hide the User entity from the AI
jmix.aitools.dataload.exclude-entities[0]=User
# Default number of rows returned when a generated query does not specify one
jmix.aitools.dataload.jpql-execution-max-result=50

Правила включения и исключения оцениваются для каждой сущности отдельно. Свойства exclude- скрывают сущности. Свойства include- включают сущности в набор по умолчанию, в том числе сущности, скрытые по умолчанию, например сущности фреймворка или системного уровня. При этом exclude-entities по-прежнему имеет приоритет над любым включением. Полный список параметров, включая ограничение числа строк, возвращаемых одним запросом, см. в свойствах Data Load.

Фильтрация через конфигурацию меняет только набор сущностей, предоставляемых модели. Независимо от конфигурации каждый запрос все равно выполняется с учетом прав текущего пользователя на data access. Запрос к сущности, которую пользователь не может читать, будет отклонен, а атрибуты, недоступные для чтения, будут удалены из результата. Пользователь никогда не сможет прочитать данные, к которым у него нет доступа.

Кастомизация доступности

Набор сущностей, предоставляемых модели, определяется через бин AvailableEntityFilter. Реализация по умолчанию скрывает сущности, на чтение которых у текущего пользователя нет прав. Чтобы изменить это поведение, например применить собственные правила видимости, зарегистрируйте свой бин, реализующий io.jmix.aitools.dataload.introspection.AvailableEntityFilter.

Настроенные сущности и их метаданные предоставляет бин AvailableEntityService, который можно использовать и в собственных инструментах. См. пример переопределения.

Пользовательские инструменты и реестр

Помимо предопределенных инструментов, можно добавлять ассистенту собственные инструменты и переопределять инструменты, поставляемые дополнением.

Создание инструмента

Инструмент — это Spring-бин, который:

  • реализует маркерный интерфейс io.jmix.aitools.tool.JmixAiTool

  • объявляет один или несколько методов с аннотацией Spring AI @Tool

При запуске дополнение находит все такие бины, собирает их методы @Tool и делает их доступными ассистенту. Следующий бин добавляет инструмент, который возвращает каталог этапов онбординга:

@Component
public class OnboardingTools implements JmixAiTool { (1)

    @Autowired
    private DataManager dataManager;
    @Autowired
    private AiToolStatusPublisher statusPublisher;

    @Tool(name = "getStepCatalog", (2)
            description = "Returns the catalog of onboarding steps with their duration in days.")
    public String getStepCatalog(ToolContext toolContext) { (3)
        String message = "Loading the onboarding step catalog";
        statusPublisher.update(message, toolContext); (4)

        List<Step> steps = dataManager.load(Step.class).all().list(); (5)

        statusPublisher.complete(message, steps.size() + " steps", toolContext);

        return steps.stream()
                .sorted(Comparator.comparing(Step::getSortValue))
                .map(step -> step.getName() + " — " + step.getDuration() + " day(s)")
                .collect(Collectors.joining("\n"));
    }
}
1 Реализация JmixAiTool помечает бин как источник инструментов.
2 Атрибуты name и description аннотации @Tool — это то, что видит модель. Описание должно объяснять, когда и как использовать инструмент.
3 Параметр ToolContext предоставляется фреймворком и не раскрывается модели. Он нужен только для публикации обновлений статуса. См. Публикация обновлений статуса.
4 Публикует обновление статуса о начале выполнения перед запуском длительной операции.
5 Загружает сущности Step через DataManager с учетом прав текущего пользователя на доступ к данным.

Параметры метода становятся входной схемой инструмента. Чтобы добавить описания, аннотируйте их @ToolParam. Возвращаемое значение отправляется модели как результат работы инструмента.

Для объявления методов @Tool в classpath приложения должен присутствовать Model API из Spring AI. Стартер модели Spring AI, который вы добавляете в разделе Подключение модели, уже его предоставляет.

AiToolRegistry

io.jmix.aitools.tool.AiToolRegistry — центральный реестр всех инструментов, доступных в приложении. Он создается один раз при запуске на основе всех бинов JmixAiTool, после применения разрешения переопределений. Его методы:

Метод Описание

getAll()

Возвращает все итоговые инструменты в порядке регистрации.

findByName(String name)

Возвращает инструмент, зарегистрированный под заданным именем, либо пустой результат.

findByMarker(Class<? extends JmixAiTool> marker)

Возвращает инструменты, исходный бин которых реализует указанный маркерный подинтерфейс JmixAiTool, например DataLoadAiTool.class.

getAllCallbacks()

Возвращает объекты Spring AI ToolCallback для всех итоговых инструментов, готовые для передачи в ChatClient.

Ассистент передает модели getAllCallbacks(), поэтому любой добавленный вами инструмент становится доступным автоматически. Больше ничего регистрировать не нужно.

Переопределение существующего инструмента

Чтобы заменить существующий инструмент, включая предопределенный, аннотируйте метод @Tool аннотацией @ToolOverride и передайте имя инструмента, который нужно заменить. Метод по-прежнему должен быть аннотирован @Tool и объявлен в бине JmixAiTool.

Следующий бин переопределяет aitls_getAvailableEntities и возвращает доступные сущности, отсортированные по их локализованному имени:

@Component
public class SortedEntitiesTool implements DataLoadAiTool { (1)

    @Autowired
    private AvailableEntityService availableEntityService;

    @Tool(description = "Returns entities available to the user, ordered by localized name.")
    @ToolOverride("aitls_getAvailableEntities") (2)
    public List<EntitySummary> getAvailableEntities() {
        return availableEntityService.getEntitySummaries().stream() (3)
                .sorted(Comparator.comparing(this::firstLocalizedName))
                .toList();
    }

    private String firstLocalizedName(EntitySummary summary) {
        return summary.getLocalizedNames().isEmpty()
                ? summary.getEntityName()
                : summary.getLocalizedNames().get(0);
    }
}
1 Реализация DataLoadAiTool оставляет переопределение в группе инструментов Data Load. Для инструмента, не связанного с загрузкой данных, реализуйте JmixAiTool напрямую.
2 @ToolOverride указывает имя заменяемого инструмента. Переопределение публикуется под этим именем, поэтому собственное имя @Tool у метода не имеет значения.
3 Повторно используется стандартный AvailableEntityService, поэтому переопределение по-прежнему учитывает фильтрацию сущностей и безопасность, меняя только порядок сортировки.

Как работает разрешение переопределений:

  • Если несколько методов @Tool создают инструмент с одним и тем же именем, выигрывает метод, помеченный @ToolOverride, а исходный инструмент исключается из реестра.

  • Переопределение наследует маркерные интерфейсы заменяемого инструмента, поэтому поиск в реестре по маркеру продолжает работать.

  • Если указанного инструмента не существует, в лог пишется предупреждение, а переопределение регистрируется как обычный новый инструмент под собственным именем @Tool. Если это имя уже используется другим инструментом, будет выброшено исключение, чтобы избежать неявной замены несвязанного инструмента.

Публикация обновлений статуса

Длительно выполняющиеся инструменты могут передавать прогресс в UI через бин io.jmix.aitools.tool.AiToolStatusPublisher, показанный в примере выше. Он реализует двухфазный контракт:

  • update(message, toolContext) — шаг начался, и UI показывает индикатор выполнения.

  • complete(message, snippet, toolContext) — шаг завершился, и UI добавляет фрагмент результата в ту же запись. Значение message должно совпадать с тем, которое было передано в update.

Оба метода принимают ToolContext метода инструмента. Когда инструмент вызывается вне UI чата, например через программный API, callback статуса отсутствует, и методы просто ничего не делают, поэтому вызывать их всегда безопасно.