БэкендData и ИИPython

Как создать ИИ-агента на Python: даём ему калькулятор и поиск по заметкам

Собираем ИИ-агента на Python и Ollama: модель выбирает калькулятор или поиск по заметкам, Python выполняет tool call, а цикл возвращает итоговый ответ.

Кодик

Автор

7 мин чтения

Минимальный ИИ-агент состоит из модели, списка разрешённых функций и цикла, который возвращает модели результаты этих функций. Дадим локальной модели калькулятор и поиск по файлу. Агент выберет tool, а Python проверит имя, аргументы и число шагов.

Обычный чат отвечает из контекста модели. Агент может запросить действие и продолжить с фактическим результатом. Свободного доступа к компьютеру у него нет: границу задаёт список tools. Здесь разрешены калькулятор и чтение notes.txt. Shell, удаление файлов и браузер отсутствуют.

1Модель выбирает

По описанию вопроса она запрашивает calculate или search_notes.

2Python выполняет

Программа находит функцию в белом списке и проверяет аргументы.

3Модель объясняет

Результат tool call добавляется в историю, после чего строится финальный ответ.

Чем агент отличается от обычного чат-бота

Вопрос пользователя сначала получает локальная модель Ollama. Вместе с сообщениями она видит описания двух Python-функций. Если вопрос можно решить без действия, модель отвечает сразу. Если нужен факт из notes.txt или точное вычисление, ответ содержит tool_calls. Программа добавляет сообщение ассистента в историю, выполняет каждую разрешённую функцию и добавляет отдельное сообщение role=tool. Следующий запрос к модели содержит и вопрос, и её решение вызвать tool, и фактический результат. Максимум три итерации не даёт циклу продолжаться бесконечно.

КомпонентДанные на входеРезультат
МодельИстория и схемы toolsТекст или список tool_calls
calculateСтрока вроде 18 * 7Точное число 126
search_notesКлючевые словаПодходящие строки файла
Цикл агентаЗапросы и результатыСледующий шаг или финальный ответ

Агентом проект делает не длинный системный промпт, а замкнутый цикл «решение, действие, наблюдение, новый ответ». При этом именно программа, а не модель, определяет разрешённые действия и момент остановки.

Один цикл ИИ-агента
Модель не исполняет действие сама. Результат функции возвращается модели как наблюдение

Белый список функций обязателен. Нельзя брать имя из tool call и исполнять его через eval, import или shell. Сопоставьте допустимые строки с заранее созданными Python-функциями. Неизвестное имя должно вернуть ошибку как данные, а не запускать произвольный код.

Пишем два инструмента и цикл агента

Установите Ollama, загрузите модель с tool calling и создайте проект: uv init notes-agent, затем uv add ollama. В примере используется llama3.1, как в официальном примере Ollama Python. Создайте рядом notes.txt. Калькулятор принимает два числа и знак отдельными аргументами. Это безопаснее строки для eval и проще проверить.

import operator
from pathlib import Path
from ollama import chat

OPS = {"+": operator.add, "-": operator.sub,
       "*": operator.mul, "/": operator.truediv}

def calculate(a: float, operation: str, b: float) -> str:
    """Calculate two numbers with +, -, * or /."""
    if operation not in OPS:
        raise ValueError("Неизвестная операция")
    return str(OPS[operation](a, b))

def search_notes(query: str) -> str:
    """Find matching lines in notes.txt."""
    words = query.lower().split()
    lines = Path("notes.txt").read_text(encoding="utf-8").splitlines()
    found = [line for line in lines
             if all(word in line.lower() for word in words)]
    return "\n".join(found[:5]) or "Ничего не найдено"

TOOLS = {"calculate": calculate, "search_notes": search_notes}
messages = [{"role": "user", "content": input("Вопрос: ")}]

for _ in range(3):
    answer = chat(model="llama3.1", messages=messages,
                  tools=list(TOOLS.values()))
    messages.append(answer.message)
    if not answer.message.tool_calls:
        print(answer.message.content)
        break
    for call in answer.message.tool_calls:
        function = TOOLS.get(call.function.name)
        try:
            result = function(**call.function.arguments) if function else "Запрещено"
        except Exception as error:
            result = f"Ошибка: {error}"
        messages.append({"role": "tool", "content": str(result),
                         "tool_name": call.function.name})
else:
    print("Стоп: слишком много шагов")
Что должно произойти после запуска
  • Вопрос «Сколько будет 18 * 7?» вызывает calculate и заканчивается ответом 126.
  • Вопрос «Что записано про словари?» вызывает search_notes и использует найденные строки.
  • Составной вопрос может вызвать оба инструмента в одной итерации.
  • Неизвестная функция и неверное выражение возвращаются модели как контролируемая ошибка.

Чат-бот и агент
Разница находится в проверяемом действии. Автономность задаёт код цикла, а не название проекта

Как работает tool calling по шагам

Ollama получает функции в tools и превращает сигнатуры и docstring в описание для модели. Модель возвращает имя и arguments, но не запускает Python. Программа ищет имя в TOOLS, вызывает функцию и добавляет role=tool с результатом. Перед ним нужно сохранить assistant с tool_calls, иначе история потеряет причину действия. Новый chat либо запросит следующий tool, либо вернёт финальный content.

СостояниеЧто лежит в messagesСледующее действие
СтартОдин вопрос пользователяМодель выбирает ответ или tool
Запрос действияСообщение assistant с tool_callsPython выполняет белый список
НаблюдениеОдно или несколько сообщений role=toolМодель читает результаты
ФинишAssistant без tool_callsПечатаем content
ЛимитТри итерации исчерпаныОстанавливаем цикл

Docstring влияет на выбор инструмента, поэтому описание должно совпадать с реальными возможностями. search_notes возвращает максимум пять строк, где есть все слова запроса. Это локальный поиск, не векторная база. Калькулятор не принимает код: только два числа и один знак из OPERATORS. В рабочем агенте добавьте журнал вызовов, таймауты, строгую проверку аргументов и подтверждение перед изменяющим действием.

Tool call является предложением модели. Не доверяйте имени, типам и значениям только потому, что их сгенерировала модель. Проверьте белый список, диапазоны, длину строки, доступ к файлам и количество шагов на стороне Python.

Проверяем выбор инструмента и границы доступа

Подготовьте notes.txt из шести строк. Сначала вызовите calculate и search_notes напрямую без модели. Затем задайте арифметику, поиск, составной запрос и обычный разговор. Временно печатайте имя и arguments перед вызовом, чтобы увидеть решение модели. После проверки уберите вывод. Переименуйте search_notes, уточните docstring и сравните выбор.

Проверка своими руками
  1. Создайте notes.txt и убедитесь, что файл читается из текущего каталога.
  2. Проверьте calculate(18, "*", 7) и неизвестный знак. Второй вызов должен быть отклонён.
  3. Проверьте поиск по одному слову, по двум словам и по отсутствующему слову.
  4. Спросите модель только об арифметике и запишите имя выбранного инструмента.
  5. Задайте составной вопрос, которому нужны и заметки, и вычисление.
  6. Попросите выполнить неизвестное действие. Агент не должен запускать shell или придумывать новый tool.
Готово, если выполняются все пункты
  • Обе функции проходят прямую проверку без участия модели.
  • История содержит assistant tool_calls перед сообщениями role=tool.
  • Неизвестные имена не исполняются.
  • Цикл завершается ответом или жёстким лимитом шагов.

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

Развиваем агента без опасной автономности

Следующим можно добавить сохранение заметки, но это действие с побочным эффектом. Покажите точный текст и путь, запросите подтверждение и разрешите один файл. Поиск улучшайте постепенно: нормализация, заголовки, затем embeddings при необходимости. Один ответ на промпт ещё не агент, а бесконтрольное исполнение команд уже опасно. Полезная середина состоит из узких tools, наблюдаемого цикла и стоп-условий.

Как изучить тему в Кодике

Сначала закрепите функции Python, словари, файлы, исключения и цикл for. Tool calling станет понятным диспетчером: имя ищется в TOOLS, аргументы распаковываются через **, результат добавляется в messages.

ШагЧто изучитьКонтрольная задача
1Функции, словари и **kwargsВызвать функцию из белого списка
2Файлы и кодировка UTF-8Найти строки в notes.txt
3Исключения и проверкиБезопасно отклонить плохое выражение
4История сообщенийНарисовать роли user, assistant, tool
5Локальная модель OllamaПолучить один корректный tool call

В тренажёре Кодика перепишите отдельно search_notes и диспетчер TOOLS. Затем соберите цикл из псевдокода, не глядя в статью. Если вы можете объяснить каждое добавление в messages, следующий агент с погодой, календарём или базой уже не будет казаться новой магией.

Калькулятор принимает строку для eval

Передавайте числа и знак отдельно, затем выбирайте функцию из OPERATORS.

Имя функции исполняется динамически

Разрешайте только ключи словаря TOOLS, созданного разработчиком.

Результат tool не добавлен в историю

Модель не увидит наблюдение и может повторять тот же вызов.

У цикла нет лимита

Даже хороший промпт не заменяет максимальное число шагов и таймаут.

Сверьтесь с первичным источником. Команды, API и ограничения примера проверяйте по официальному примеру Ollama Python для tool calling. Если интерфейс или версия изменились, первичная документация важнее скриншота из старой инструкции.

Что получится в итоге

Готовый результат: Как создать ИИ-агента на Python: даём ему калькулятор и поиск по заметкам
Локальный агент выбирает один из двух разрешённых tools, получает точный результат и превращает его в понятный ответ с ограниченной историей шагов.

Короткие ответы
Может ли модель сама выполнить Python-функцию?

Нет. Она формирует tool call, а функцию вызывает ваша программа после проверки.

Зачем добавлять сообщение assistant перед role=tool?

Оно хранит решение модели и связывает следующий результат с конкретным вызовом.

Почему нужен лимит итераций?

Модель может повторить действие или зациклиться на ошибке. Лимит гарантирует остановку.

Как понять, что проект действительно работает?

Проверьте основной сценарий, ошибочный ввод, повторный запуск и один граничный случай. Затем объясните вслух, где появляются данные, кто принимает решение и где хранится состояние.

Можно ли начать с готового кода из статьи?

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

Сначала сделайте агента наблюдаемым, потом добавляйте возможности

Базу закрепите в курсе Python, а общую идею сравните со статьёй про AI-агентов. Для локальной модели сначала настройте Ollama на компьютере.

Если словарь TOOLS или список messages пока выглядят непривычно, разберите словари Python. В Кодике полезно пройти путь от двух обычных функций к одному tool call и только затем добавлять второй инструмент.