Закажи экспресс-аудит своего дела онлайн всего за 199 ₽
и получи рекомендации по улучшению - Жми сюда !

Простой вызов инструмента Agentic с помощью Gemma 4

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

Простой вызов инструмента Agentic с помощью Gemma 4

# Введение

В недавней статье на сайте Machine Learning Mastery мы создали агента, который обращается к внешним ресурсам , то есть получает информацию о погоде, новостях, курсах валют и времени из общедоступных API. В той статье хорошо описана синтезирующая часть паттерна, но более интересная часть осталась нераскрытой: агент, который рассуждает об окружающей среде, анализирует собственную машину и передает логику, которую он не считает своей собственной, на выполнение которой он не способен. Можно утверждать, что это ближе к истинно «агентичному» подходу.

Эта статья продолжает тему предыдущей. Мы добавим в Gemma 4 два новых инструмента — изолированный локальный файловый менеджер и ограниченный интерпретатор Python — и будем наблюдать, как модель самостоятельно решает, когда искать информацию, а когда выполнять вычисления.

Среди тем, которые мы рассмотрим, следующие:

  • Почему для того, чтобы «агентный» вызов инструментов был интересным, недостаточно одних лишь веб-API.
  • Как создать инструмент для проверки файловой системы с жесткими ограничениями на обход путей.
  • Как подключить инструмент интерпретатора Python к модели, не передавая ему ключи от вашего компьютера.
  • Как тот же самый цикл оркестровки, что и раньше, адаптируется к этим новым возможностям.

Настоятельно рекомендую сначала прочитать эту статью, прежде чем продолжить.

# От разговора к инициативе

Когда единственными инструментами, которые вы предоставляете языковой модели, являются веб-API только для чтения, по сути, у вас всё ещё есть чат-бот, хотя и с потенциально более глубоким доступом к информации. Модель получает запрос, решает, к какому API обратиться, и объединяет JSON-ответ в абзац. Нет реального понятия окружения , нет состояния для анализа, нет последствий для рассуждений; это сценарий, больше похожий на генерацию с расширенными возможностями поиска, чем на истинную субъектность.

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

Семейство Gemma 4, и в частности вариант gemma4:e2b edge, который мы используем, достаточно компактно для локального запуска на ноутбуке, и в то же время достаточно эффективно обрабатывает структурированный вывод, чтобы надежно управлять подобными циклами. Именно это сочетание делает паттерн локального агента интересным. Полный код для этого руководства можно найти здесь.

# Архитектурное повторное использование

Цикл оркестровки из предыдущего урока не меняется. Мы определяем функции Python, предоставляем к ним доступ через JSON-схему, передаем реестр в Ollama вместе с запросом пользователя, перехватываем любой блок tool_calls в ответе, выполняем запрошенную функцию локально, добавляем результат в качестве сообщения роли инструмента и повторно запрашиваем модель, чтобы она могла синтезировать окончательный ответ. Тот же вспомогательный метод call_ollama, тот же словарь TOOL_FUNCTIONS, тот же массив схемы available_tools из предыдущего урока — все это присутствует.

Изменения коснулись самой природы инструментов. Если предыдущая партия представляла собой тонкие клиенты, работающие через удаленные API, то те, которые мы будем создавать сейчас, будут запускать код непосредственно на машине. Это смещает задачу проектирования с «как мне обработать этот ответ» на «как мне убедиться, что модель не сможет, даже случайно, сделать то, что ей не должно быть разрешено».

# Инструмент 1: Проводник файловой системы в изолированной среде

Первый инструмент, list_directory_contents, позволяет модели видеть, какие файлы находятся в заданной папке. Это звучит тривиально, пока вы не вспомните, что os.listdir принимает любую строку, включая /, ~, и ../../и т. д. Простая реализация могла бы без проблем направить «любопытство» модели прямо к вашим API-ключам.

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

# Безопасность: ограничить list_directory_contents этим базовым каталогом и его потомками # Устанавливается в текущий рабочий каталог при запуске скрипта SAFE_BASE_DIR = os.path.abspath(os.getcwd()) def list_directory_contents(path: str = «.»») -> str: «»»Перечисляет файлы и каталоги внутри пути, ограниченного безопасным базовым каталогом.»»» try: # Преобразует в абсолютный путь и проверяет, находится ли он внутри SAFE_BASE_DIR # Это блокирует попытки обхода, такие как '../../etc' или абсолютные пути, такие как '/' requested = os.path.abspath(os.path.join(SAFE_BASE_DIR, path)) if not (requested == SAFE_BASE_DIR or requested.startswith(SAFE_BASE_DIR + os.sep)): return ( f»Ошибка: Отказано в доступе. Путь '{path}' разрешается за пределами » f»разрешенное рабочее пространство ({SAFE_BASE_DIR}).» ) …

Схема проста, но заслуживает дальнейшего рассмотрения. Мы никогда не доверяем строке, сгенерированной моделью. Мы объединяем её с базовым каталогом, разрешаем её абсолютно (так что «..» нормализуется), а затем проверяем, что разрешенный путь по-прежнему начинается с базового каталога. И /etc/passwd, и ../../somewhere сводятся к путям, которые не проходят проверку префикса и отклоняются ещё до вызова os.listdir.

Остальная часть функции выполняет вспомогательные функции: подтверждает существование пути и его принадлежность к каталогу, выводит список его содержимого и форматирует каждую запись как [DIR] или [FILE] с указанием размера в байтах. Возвращаемая строка представляет собой простой английский текст со структурой, которую модель может обработать на втором проходе:

entries = sorted(os.listdir(requested)) if not entries: return f»Каталог '{path}' пуст.» lines = [f»Содержимое '{path}' ({len(entries)} элементов):»] for name in entries: full = os.path.join(requested, name) if os.path.isdir(full): lines.append(f» [DIR] {name}/») else: try: size = os.path.getsize(full) lines.append(f» [FILE] {name} ({size} байт)») except OSError: lines.append(f» [FILE] {name}») return «n».join(lines)

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

{ «type»: «function», «function»: { «name»: «list_directory_contents», «description»: ( «Перечисляет файлы и подкаталоги внутри пути в рабочей области пользователя. » «Используйте это для проверки среды перед ответами на вопросы о локальных файлах.» ), «parameters»: { «type»: «object», «properties»: { «path»: { «type»: «string», «description»: ( «Относительный путь внутри рабочей области, например, '.', 'data' или 'src/utils'. » «По умолчанию — корень рабочей области.» ) } }, «required»: [] } } }

Обратите внимание, что в описании содержится небольшой элемент подсказки для разработчиков: «Используйте это для проверки окружения, прежде чем отвечать на вопросы о локальных файлах». Это предложение подталкивает Gemma 4 к тому, чтобы вызывать инструмент, когда пользователь задает расплывчатый вопрос о «моих файлах», а не гадать, что там может находиться.

# Инструмент 2: Ограниченный интерпретатор Python

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

В данной реализации используется функция exec() с намеренно упрощенным пространством имен builtins:

def execute_python_code(code: str) -> str: «»»Выполняет фрагмент кода Python и возвращает то, что было выведено в стандартный вывод. Это песочница только для обучения. Функция exec() принципиально небезопасна; не предоставляйте доступ к этому инструменту ненадежным пользователям или сетям. Приведенные ниже ограничения предотвращают случайные инциденты, а не целенаправленных злоумышленников. «»» try: # Минимальная ограниченная среда. Мы сокращаем __builtins__ до небольшого # белого списка, чтобы, например, open(), eval() и __import__ не были напрямую # доступны из глобальной области видимости фрагмента кода. safe_builtins = { «abs»: abs, «all»: all, «any»: any, «bool»: bool, «dict»: dict, «divmod»: divmod, «enumerate»: enumerate, «filter»: filter, «float»: float, «int»: int, «len»: len, «list»: list, «map»: map, «max»: max, «min»: min, «pow»: pow, «print»: print, «range»: range, «repr»: repr, «reversed»: reversed, «round»: round, «set»: set, «sorted»: sorted, «str»: str, «sum»: sum, «tuple»: tuple, «zip»: zip, } # Предварительно импортируем пару безопасных и полезных модулей, чтобы модели не приходилось этого делать. import math, statistics restricted_globals = { «__builtins__»: safe_builtins, «math»: math, «statistics»: statistics, }

Несколько важных решений. Мы полностью заменяем встроенные функции (__builtins__), вместо того чтобы вносить отдельные функции в черный список, что означает, что функции open, eval, exec, compile, __import__, input и все остальное, не включенное в наш белый список, просто отсутствуют в фрагменте кода. Мы предварительно импортируем математические и статистические данные в глобальные переменные фрагмента кода, потому что модель будет постоянно к ним обращаться, и мы не хотим заставлять ее бороться с ограничениями __import__. Мы перехватываем стандартный вывод с помощью contextlib.redirect_stdout, чтобы модель получала обратно именно то, что вывел ее фрагмент кода:

# Захватываем стандартный вывод, чтобы передать вывод обратно в буфер модели. buffer = io.StringIO() with contextlib.redirect_stdout(buffer): exec(code, restricted_globals, {}) output = buffer.getvalue().strip() if not output: return «Код успешно выполнен, но не вывел никакого результата. Используйте print() для возврата значения.» return f»Вывод:n{output}»

Ветвь с пустым выходным значением важнее, чем кажется. Небольшие модели обычно пишут выражения типа x = sum(range(101)) и забывают использовать print(x). Возврат конкретной ошибки, указывающей на необходимость использования print(), дает циклу управления возможность повторить попытку; без этого модель синтезировала бы окончательный ответ на основе пустой строки и уверенно выдумала бы значение.

В заключение несколько слов о безопасности, поскольку в документации к скрипту об этом прямо говорится: это песочница для обучения, а не для защиты. Целеустремленный противник может выйти из песочницы выполнения Python десятком способов, большинство из которых связаны с интроспекцией объектов через ().__class__.__mro__. Для однопользовательского агента, работающего на вашем собственном ноутбуке с вашими собственными командами, белого списка вполне достаточно. Для всего остального вам понадобится настоящий уровень изоляции — дочерний процесс с seccomp, контейнер или RestrictedPython.

# Цикл оркестровки

Основной цикл по структуре остался неизменным по сравнению с предыдущим уроком. Модель запрашивается с помощью запроса пользователя и реестра инструментов, и если она отвечает значением tool_calls, каждый вызов направляется в TOOL_FUNCTIONS:

if «tool_calls» in message and message[«tool_calls»]: print(«[TOOL EXECUTION]») messages.append(message) num_tools = len(message[«tool_calls»]) for i, tool_call in enumerate(message[«tool_calls»]): function_name = tool_call[«function»][«name»] arguments = tool_call[«function»][«arguments»] … if function_name in TOOL_FUNCTIONS: func = TOOL_FUNCTIONS[function_name] try: result = func(**arguments) … messages.append({ «role»: «tool», «content»: str(result), «name»: function_name })

Для этого скрипта стоит немного подкорректировать форматирование командной строки. Аргумент code инструмента execute_python_code может быть многострочной строкой с переносами строк, что приведет к повреждению ASCII-дерева при наивном выводе. Мы преобразуем и обрезаем строковые аргументы только для отображения; модель по-прежнему получает полную строку при запуске функции:

def _short(v): if isinstance(v, str): flat = v.replace(«n», «\n») if len(flat) > 60: flat = flat[:57] + «…» return f»'{flat}'» return str(v) args_str = «, «.join(f»{k}={_short(v)}» for k, v in arguments.items())

После того как результаты работы каждого инструмента добавлены обратно в историю сообщений в виде записи «роль»: «инструмент», мы повторно вызываем Ollama с расширенными данными, и модель выдает свой обоснованный окончательный ответ. Тот же двухэтапный алгоритм, та же логика.

# Тестирование инструментов

А теперь протестируем вызов нашего инструмента. Если вы еще этого не сделали, загрузите gemma4:e2b с помощью команды `ollama pull gemma4:e2b`, затем запустите скрипт из папки, в которую вам не жалко заглянуть, используя модель.

Начнём с инструмента для работы с файловой системой. Из каталога проекта:

Какие скрипты находятся в моей текущей папке, и какой из них, по-видимому, следует использовать для обработки CSV-файлов?

Результат:

[СИСТЕМА] ○ Инструмент: execute_python_code………………….[ЗАГРУЖЕНО] ○ Инструмент: list_directory_contents………………[ЗАГРУЖЕНО] ○ Рабочая область: /Users/matt/projects/gemma_agent…..[ПЕСОЧНИЦА] [ПОДСКАЗКА] Какие скрипты находятся в моей текущей папке, и какой из них, по-видимому, следует использовать для обработки CSV-файлов? [ВЫПОЛНЕНИЕ] ● Запрос к модели… [ВЫПОЛНЕНИЕ ИНСТРУМЕНТА] └── Вызов: list_directory_contents ├─ Аргументы: path='.' └─ Результат: Содержимое '.' (5 элементов): [ФАЙЛ] README.md (412 байт) [ФАЙЛ] csv_cleaner.py (1834 байта) [ФАЙЛ] main.py (10786 байт) [ФАЙЛ] notes.txt (88 байт) [ФАЙЛ] sales_report.py (2210 байт) [ВЫПОЛНЕНИЕ] ● Синтез результатов… [ОТВЕТ] В вашей текущей папке находится пять файлов. Файл, предназначенный для обработки CSV-данных, — это csv_cleaner.py — его название явно указывает на то, что он обрабатывает CSV-входные данные. sales_report.py также может обрабатывать CSV-данные, но его название больше относится к выводу, чем к приему данных.

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

Далее, интерпретатор Python. Небольшая задача, с которой небольшие модели неизменно ошибаются, если их попросить сделать это в уме:

Каково стандартное отклонение чисел 12, 18, 23, 24, 29, 31, 35, 41, 44, 47, округленных до четырех знаков после запятой?

Результат:

[СИСТЕМА] ○ Инструмент: execute_python_code………………….[ЗАГРУЖЕНО] ○ Инструмент: list_directory_contents………………[ЗАГРУЖЕНО] ○ Рабочая область: /Users/matt/projects/gemma_agent…..[ПЕСОЧНИЦА] [ПОДСКАЗКА] Каково стандартное отклонение чисел 12, 18, 23, 24, 29, 31, 35, 41, 44, 47, округленных до четырех знаков после запятой? [ВЫПОЛНЕНИЕ] ● Запрос к модели… [ВЫПОЛНЕНИЕ ИНСТРУМЕНТА] └── Вызов: execute_python_code ├─ Аргументы: code='nums = [12, 18, 23, 24, 29, 31, 35, 41, 44, 47]nprint(rou…' └─ Результат: Вывод: 11.4659 [ВЫПОЛНЕНИЕ] ● Синтез результатов… [ОТВЕТ] Стандартное отклонение этих чисел, округленное до четырех знаков после запятой, составляет 11.4659.

Модель полностью переложила вычисления на плечи пользователя; она написала фрагмент кода, названный statistics.stdev, округлила результат и сообщила то, что сказал интерпретатор. Никаких вычислений в уме, никаких приближений, никаких искусственно созданных значащих цифр.

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

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

Выход:

[СИСТЕМА] ○ Инструмент: execute_python_code………………….[ЗАГРУЖЕНО] ○ Инструмент: list_directory_contents………………[ЗАГРУЖЕНО] ○ Рабочая область: /Users/matt/projects/gemma_agent…..[ПЕСОЧНИЦА] [ПОДСКАЗКА] Посмотрите на файлы в текущей папке и сообщите мне их общий размер в килобайтах, округленный до двух знаков после запятой. [ВЫПОЛНЕНИЕ] ● Запрос к модели… [ВЫПОЛНЕНИЕ ИНСТРУМЕНТА] ┌── Вызов: list_directory_contents │ ├─ Аргументы: path='.' │ └─ Результат: Содержимое '.' (5 элементов): │ [ФАЙЛ] README.md (412 байт) │ [ФАЙЛ] csv_cleaner.py (1834 байта) │ [ФАЙЛ] main.py (10786 байт) │ [ФАЙЛ] notes.txt (88 байт) │ [ФАЙЛ] sales_report.py (2210 байт) │ └── Вызов: execute_python_code ├─ Аргументы: code='sizes = [412, 1834, 10786, 88, 2210]nprint(round(sum(siz…' └─ Результат: Вывод: 15.33 [ВЫПОЛНЕНИЕ] ● Синтез результатов… [ОТВЕТ] Всего пять файлов в текущей папке 15,33 КБ.

Два инструмента, в правильном порядке, причем результат работы одного передает аргументы другому — результат работы модели с 2 миллиардами параметров, запущенной на ноутбуке без графического процессора. Инструмент файловой системы обосновывает модель на том, что существует на самом деле; инструмент интерпретатора обосновывает ответ на том, что является истиной на самом деле. Модель вносит свой вклад в ту часть, в которой она действительно хороша, а именно в определение того, какой вопрос задать какому инструменту.

Стоит также проверить работу защитных механизмов, просто чтобы убедиться, что они работают. Запрос к модели «вывести содержимое /etc» приводит к ожидаемому сообщению об отказе в результате выполнения инструмента, которое модель затем корректно возвращает, вместо того чтобы создавать поддельный список содержимого каталога. Запрос к выполнению open('/etc/passwd').read() внутри интерпретатора приводит к ошибке NameError, поскольку open не входит в список разрешенных встроенных функций. Обе ошибки преобразуются в полезные строки ошибок вместо скрытых компромиссов, что именно и нужно на этом уровне.

# Заключение

В предыдущем уроке было показано, что Gemma 4 может осуществлять доступ к интернету от вашего имени. В этом уроке показано, что она может, при условии соблюдения осторожности, получить доступ к компьютеру, за которым вы сидите. Как только у вас появится работающий цикл вызова инструментов, интересный вопрос перестанет быть «может ли модель вызывать функцию» и начнется с вопроса «к чему я должен позволить ей прикасаться».

Совместное использование инструмента, учитывающего файловую систему, и инструмента для выполнения кода позволяет приблизиться к тому, что действительно заслуживает термина «агент» : он может наблюдать за окружающей средой, определять, какие вычисления важны, и выполнять эти вычисления детерминированно, а не гадать. Далее схема обобщается. Запросы к базе данных, команды оболочки, операции Git, анализ документов — каждый из этих процессов использует одну и ту же JSON-схему, одну и ту же таблицу диспетчеризации, один и тот же двухэтапный синтез с любым уровнем безопасности, соответствующим радиусу поражения базового вызова.

Сначала постройте периметр. Затем передайте модели ключи от того, что находится внутри неё.

Мэтью Мэйо ( @mattmayo13 ) имеет степень магистра компьютерных наук и диплом специалиста по анализу данных. Будучи главным редактором KDnuggets & Statology и внештатным редактором Machine Learning Mastery, Мэтью стремится сделать сложные концепции науки о данных доступными для всех. В сферу его профессиональных интересов входят обработка естественного языка, языковые модели, алгоритмы машинного обучения и изучение новых технологий искусственного интеллекта. Его движет стремление демократизировать знания в сообществе специалистов по науке о данных. Мэтью занимается программированием с 6 лет.

Источник: www.kdnuggets.com

✅ Найденные теги: Agentic, Вызов, Инструмента, новости, Помощью, Простой

Добавить комментарий

Нет других записей в этой рубрике.

Новости других рубрик

Архив рубрики ~Лента новостей~: Осталось 5 дней: сэкономьте до 410 долларов на билетах на TechCrunch Disrupt 2026 до повышения цен. Архив рубрики ~Лента новостей~: Исследование Google 2025: Более смелые прорывы, большее влияние Архив рубрики ~Лента новостей~: Умер Крейг Вентер, пионер и провидец в области геномики. Архив рубрики ~Лента новостей~: Выяснились детали мега-IPO SpaceX, а также первый прибыльный квартал Anthropic Архив рубрики ~Лента новостей~: Препятствия и планы развития корпоративного ИИ, безопасность и физический ИИ: второй день на TechEx. Архив рубрики ~Лента новостей~: Компания WiseTech начала сокращение штата, но, по словам сотрудников, не включила упоминание «искусственного интеллекта» в электронные письма, рассылаемые китайским работникам. Архив рубрики ~Лента новостей~: Искусственный интеллект не уничтожил единообразие бренда — он сделал его критически важным. Архив рубрики ~Лента новостей~: Фолдинг белка на ноутбуке. De novo дизайн KRAS G12D (Switch II) ингибитора. Докинг, валидация в AlfaFold Server и PyMOL