Минимальный ИИ-агент состоит из модели, списка разрешённых функций и цикла, который возвращает модели результаты этих функций. Дадим локальной модели калькулятор и поиск по файлу. Агент выберет tool, а Python проверит имя, аргументы и число шагов.
Обычный чат отвечает из контекста модели. Агент может запросить действие и продолжить с фактическим результатом. Свободного доступа к компьютеру у него нет: границу задаёт список tools. Здесь разрешены калькулятор и чтение notes.txt. Shell, удаление файлов и браузер отсутствуют.
По описанию вопроса она запрашивает calculate или search_notes.
Программа находит функцию в белом списке и проверяет аргументы.
Результат tool call добавляется в историю, после чего строится финальный ответ.
Чем агент отличается от обычного чат-бота
Вопрос пользователя сначала получает локальная модель Ollama. Вместе с сообщениями она видит описания двух Python-функций. Если вопрос можно решить без действия, модель отвечает сразу. Если нужен факт из notes.txt или точное вычисление, ответ содержит tool_calls. Программа добавляет сообщение ассистента в историю, выполняет каждую разрешённую функцию и добавляет отдельное сообщение role=tool. Следующий запрос к модели содержит и вопрос, и её решение вызвать tool, и фактический результат. Максимум три итерации не даёт циклу продолжаться бесконечно.
| Компонент | Данные на входе | Результат |
|---|---|---|
| Модель | История и схемы tools | Текст или список tool_calls |
| calculate | Строка вроде 18 * 7 | Точное число 126 |
| search_notes | Ключевые слова | Подходящие строки файла |
| Цикл агента | Запросы и результаты | Следующий шаг или финальный ответ |
Агентом проект делает не длинный системный промпт, а замкнутый цикл «решение, действие, наблюдение, новый ответ». При этом именно программа, а не модель, определяет разрешённые действия и момент остановки.

Модель не исполняет действие сама. Результат функции возвращается модели как наблюдение
Пишем два инструмента и цикл агента
Установите 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_calls | Python выполняет белый список |
| Наблюдение | Одно или несколько сообщений role=tool | Модель читает результаты |
| Финиш | Assistant без tool_calls | Печатаем content |
| Лимит | Три итерации исчерпаны | Останавливаем цикл |
Docstring влияет на выбор инструмента, поэтому описание должно совпадать с реальными возможностями. search_notes возвращает максимум пять строк, где есть все слова запроса. Это локальный поиск, не векторная база. Калькулятор не принимает код: только два числа и один знак из OPERATORS. В рабочем агенте добавьте журнал вызовов, таймауты, строгую проверку аргументов и подтверждение перед изменяющим действием.
Проверяем выбор инструмента и границы доступа
Подготовьте notes.txt из шести строк. Сначала вызовите calculate и search_notes напрямую без модели. Затем задайте арифметику, поиск, составной запрос и обычный разговор. Временно печатайте имя и arguments перед вызовом, чтобы увидеть решение модели. После проверки уберите вывод. Переименуйте search_notes, уточните docstring и сравните выбор.
- Создайте notes.txt и убедитесь, что файл читается из текущего каталога.
- Проверьте
calculate(18, "*", 7)и неизвестный знак. Второй вызов должен быть отклонён. - Проверьте поиск по одному слову, по двум словам и по отсутствующему слову.
- Спросите модель только об арифметике и запишите имя выбранного инструмента.
- Задайте составной вопрос, которому нужны и заметки, и вычисление.
- Попросите выполнить неизвестное действие. Агент не должен запускать 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, следующий агент с погодой, календарём или базой уже не будет казаться новой магией.
Передавайте числа и знак отдельно, затем выбирайте функцию из OPERATORS.
Разрешайте только ключи словаря TOOLS, созданного разработчиком.
Модель не увидит наблюдение и может повторять тот же вызов.
Даже хороший промпт не заменяет максимальное число шагов и таймаут.
Что получится в итоге

Локальный агент выбирает один из двух разрешённых tools, получает точный результат и превращает его в понятный ответ с ограниченной историей шагов.
Может ли модель сама выполнить Python-функцию?
Нет. Она формирует tool call, а функцию вызывает ваша программа после проверки.
Зачем добавлять сообщение assistant перед role=tool?
Оно хранит решение модели и связывает следующий результат с конкретным вызовом.
Почему нужен лимит итераций?
Модель может повторить действие или зациклиться на ошибке. Лимит гарантирует остановку.
Как понять, что проект действительно работает?
Проверьте основной сценарий, ошибочный ввод, повторный запуск и один граничный случай. Затем объясните вслух, где появляются данные, кто принимает решение и где хранится состояние.
Можно ли начать с готового кода из статьи?
Да. После первого запуска измените один параметр, добавьте одну проверку и повторите проект без подсказки. Так пример превращается в собственный навык.
Базу закрепите в курсе Python, а общую идею сравните со статьёй про AI-агентов. Для локальной модели сначала настройте Ollama на компьютере.
Если словарь TOOLS или список messages пока выглядят непривычно, разберите словари Python. В Кодике полезно пройти путь от двух обычных функций к одному tool call и только затем добавлять второй инструмент.