Твой первый AI-агент за выходные: пошаговый туториал

От установки Python до работающего агента с тремя инструментами. Полный код, пояснения каждой строчки и запуск за 10 минут. Для начинающих.

📊 Начинающий⏱ 20 мин

# 1. ЧТО ТАКОЕ AI-АГЕНТ И ЗАЧЕМ ОН НУЖЕН

AI-агент — это программа, которая использует большую языковую модель (LLM) как «мозг», а инструменты — как «руки». В отличие от обычного чат-бота, который просто генерирует текст, агент может выполнять действия: искать информацию в интернете, читать файлы, запускать код, отправлять email, управлять календарём. Он не просто отвечает — он действует.

Ключевая идея: LLM сама решает, какой инструмент вызвать и с какими параметрами. Ты описываешь доступные инструменты на естественном языке, модель анализирует запрос пользователя и генерирует «вызов инструмента» (tool call / function call). Твой код этот вызов исполняет и возвращает результат модели — она продолжает рассуждение. Цикл повторяется, пока задача не решена. Это называется «agentic loop».

В этом туториале мы соберём агента, который умеет: (1) узнавать текущее время, (2) выполнять математические расчёты через Python, (3) читать текстовые файлы. Весь код — чистый Python, без тяжёлых фреймворков. Тебе понадобится Python 3.10+, API-ключ к любой LLM (OpenAI, DeepSeek, OpenRouter — любая с поддержкой function calling), и 20 минут времени.

По окончании туториала у тебя будет рабочий агент, которого можно расширить своими инструментами. Ты поймёшь, как устроен agentic loop изнутри и почему фреймворки типа LangChain и CrewAI делают то же самое — только с большим количеством абстракций.

# Принцип работы AI-агента (agentic loop):
# 1. Пользователь отправляет запрос: "Сколько будет 42 * 365?"
# 2. LLM видит список инструментов: [get_time, calculate, read_file]
# 3. LLM решает: нужно вызвать calculate(expression="42 * 365")
# 4. Твой код исполняет calculate → результат: 15330
# 5. Результат отправляется обратно в LLM
# 6. LLM формирует финальный ответ: "42 × 365 = 15 330"
# 7. Если нужно — цикл продолжается (agentic loop)

# 2. УСТАНОВКА И ПОДГОТОВКА

Нам нужен только Python и один пакет: openai (клиент для OpenAI-совместимых API). Если ты используешь другую LLM (DeepSeek, Groq, Together AI, OpenRouter), синтаксис идентичен — просто меняешь base_url. Для минимализма мы не тянем LangChain, CrewAI и прочие тяжеловесные библиотеки. Весь агент уместится в одном файле на 100 строк.

Проверь, что Python установлен: открой терминал и выполни python --version. Должно быть 3.10 или выше. Если нет — скачай с python.org. Создай новую папку для проекта (например, my_first_agent) и перейди в неё.

# Шаг 1: Создаём папку и виртуальное окружение
mkdir my_first_agent && cd my_first_agent
python -m venv venv
source venv/bin/activate  # Linux/Mac
# или: venv\Scripts\activate  (Windows)

# Шаг 2: Устанавливаем единственную зависимость
pip install openai python-dotenv

# Шаг 3: Создаём файл .env с API-ключом
# echo 'OPENAI_API_KEY=sk-...' > .env
# ИЛИ для DeepSeek:
# echo 'DEEPSEEK_API_KEY=sk-...' > .env

# Шаг 4: Создаём основной файл
touch agent.py

Создай также тестовый файл sample.txt с любым текстовым содержанием в той же папке — он понадобится для демонстрации инструмента чтения файлов. Например, напиши туда короткую заметку или стихотворение.

# 3. ИНСТРУМЕНТЫ АГЕНТА

Инструменты (tools) — это Python-функции, которые агент может вызывать. Каждый инструмент должен иметь: имя, описание на естественном языке (его читает LLM, чтобы понять, когда применять), и схему параметров в формате JSON Schema. LLM не исполняет код — она только генерирует JSON с именем инструмента и аргументами. Твой код этот JSON парсит и вызывает реальную функцию.

Давай создадим три инструмента. Важно: описание должно быть максимально конкретным. LLM «читает» описание и решает, подходит ли инструмент для задачи. Плохое описание = модель не поймёт, когда вызывать инструмент.

# ======= ИНСТРУМЕНТЫ АГЕНТА =======

import datetime
import json
import os

def get_current_time(timezone: str = "UTC") -> str:
    """Возвращает текущее время и дату."""
    now = datetime.datetime.now(datetime.timezone.utc)
    return f"Сейчас {now.strftime('%Y-%m-%d %H:%M:%S')} UTC"

def calculate(expression: str) -> str:
    """Вычисляет математическое выражение на Python. Безопасно."""
    # Безопасное вычисление: только числа, операторы, пробелы и скобки
    allowed = set("0123456789+-*/().% ")
    if not all(c in allowed for c in expression):
        return "Ошибка: выражение содержит недопустимые символы"
    try:
        result = eval(expression, {"__builtins__": {}}, {})
        return f"Результат: {expression} = {result}"
    except Exception as e:
        return f"Ошибка вычисления: {e}"

def read_file(filename: str) -> str:
    """Читает содержимое текстового файла."""
    safe_path = os.path.join(".", os.path.basename(filename))
    if not os.path.exists(safe_path):
        return f"Файл '{filename}' не найден"
    if os.path.getsize(safe_path) > 100_000:
        return "Файл слишком большой (>100KB)"
    with open(safe_path, 'r', encoding='utf-8') as f:
        return f.read()

# ======= ОПИСАНИЕ ИНСТРУМЕНТОВ ДЛЯ LLM (JSON Schema) =======
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "Узнать текущее время и дату. Вызывай, когда пользователь спрашивает 'который час', 'какое сегодня число' или 'сколько времени'.",
            "parameters": {
                "type": "object",
                "properties": {},
                "required": []
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "Вычислить математическое выражение. Поддерживает: +, -, *, /, **, %, скобки. Примеры: '2+2', '15*7', '(42+8)/2', '2**10'.",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "Математическое выражение в виде строки, например '42*365' или '(100+50)/3'"
                    }
                },
                "required": ["expression"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "read_file",
            "description": "Прочитать содержимое текстового файла. Используй, когда пользователь просит показать, что внутри файла, или прочитать документ.",
            "parameters": {
                "type": "object",
                "properties": {
                    "filename": {
                        "type": "string",
                        "description": "Имя файла, который нужно прочитать (например, 'sample.txt')"
                    }
                },
                "required": ["filename"]
            }
        }
    }
]

# Маппинг: имя инструмента → Python-функция
TOOL_FUNCTIONS = {
    "get_current_time": get_current_time,
    "calculate": calculate,
    "read_file": read_file,
}

# 4. AGENTIC LOOP — СЕРДЦЕ АГЕНТА

Теперь самое интересное — цикл, в котором агент живёт. Это бесконечный (с ограничениями) цикл: отправляем запрос в LLM → получаем ответ → если это tool call → исполняем и отправляем результат обратно → если это текст → показываем пользователю. Важно ограничить количество итераций (max_steps), чтобы агент не зациклился.

Обрати внимание: мы храним историю сообщений (messages). Каждый новый запрос к LLM включает всю историю — и запросы пользователя, и ответы ассистента, и результаты инструментов. Это позволяет агенту «помнить» контекст.

# ======= AGENTIC LOOP =======

from openai import OpenAI
from dotenv import load_dotenv
import os

load_dotenv()

# Настройка клиента. Для DeepSeek:
# client = OpenAI(api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com")
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

class SimpleAgent:
    """Минимальный AI-агент с agentic loop."""

    def __init__(self, model="gpt-4o-mini", max_steps=10):
        self.model = model
        self.max_steps = max_steps
        self.messages = [
            {
                "role": "system",
                "content": (
                    "Ты — полезный AI-ассистент с доступом к инструментам. "
                    "Отвечай на русском языке. Если вопрос требует вычислений "
                    "или актуальной информации, используй инструменты. "
                    "Не придумывай данные — всегда проверяй через инструменты."
                )
            }
        ]

    def run(self, user_input: str) -> str:
        """Запустить агента с запросом пользователя."""
        self.messages.append({"role": "user", "content": user_input})

        for step in range(self.max_steps):
            print(f"\n--- Agent Loop: step {step + 1} ---")

            # 1. Отправляем историю в LLM
            response = client.chat.completions.create(
                model=self.model,
                messages=self.messages,
                tools=TOOLS,
                tool_choice="auto",  # LLM сама решает, вызывать ли инструмент
            )

            msg = response.choices[0].message

            # 2. Если LLM хочет вызвать инструмент
            if msg.tool_calls:
                # Сохраняем запрос инструмента в историю
                self.messages.append(msg)

                for tool_call in msg.tool_calls:
                    tool_name = tool_call.function.name
                    tool_args = json.loads(tool_call.function.arguments)

                    print(f"  → Вызываю: {tool_name}({tool_args})")

                    # 3. Исполняем инструмент
                    func = TOOL_FUNCTIONS.get(tool_name)
                    if func:
                        result = func(**tool_args)
                    else:
                        result = f"Инструмент '{tool_name}' не найден"

                    print(f"  ← Результат: {result[:200]}")

                    # 4. Сохраняем результат в историю
                    self.messages.append({
                        "role": "tool",
                        "tool_call_id": tool_call.id,
                        "content": result,
                    })

                # Продолжаем цикл — LLM получит результат и решит, что делать дальше
                continue

            # 5. Если LLM вернула текст (финальный ответ)
            if msg.content:
                self.messages.append({"role": "assistant", "content": msg.content})
                return msg.content

        # Если закончились шаги — просим LLM подвести итог
        return "⚠️ Агент достиг лимита шагов. Попробуйте упростить запрос."

Разберём ключевые моменты. tool_choice="auto" — режим, в котором LLM сама решает, нужен ли инструмент. Можно форсировать: tool_choice="required" (обязательно вызвать инструмент) или tool_choice="none" (никогда не вызывать). Сообщения с role="tool" содержат tool_call_id — LLM использует этот ID, чтобы сопоставить вызов с результатом. system-промпт важен: он задаёт тон и поведение агента. Для русского языка явно указываем отвечать на русском — иначе некоторые модели по умолчанию используют английский.

# 5. ФИНАЛЬНАЯ СБОРКА И ЗАПУСК

Теперь собираем всё в единый файл agent.py, добавляем интерактивный цикл и запускаем. Полный код ниже — скопируй его целиком, создай .env с API-ключом, и запусти python agent.py.

# ======= ПОЛНЫЙ КОД: agent.py =======
import datetime, json, os
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()

# ---------- ИНСТРУМЕНТЫ ----------
def get_current_time(**kwargs) -> str:
    now = datetime.datetime.now(datetime.timezone.utc)
    return f"Текущее время (UTC): {now.strftime('%Y-%m-%d %H:%M:%S')}. " \
           f"День недели: {['Пн','Вт','Ср','Чт','Пт','Сб','Вс'][now.weekday()]}."

def calculate(expression: str, **kwargs) -> str:
    allowed = set("0123456789+-*/().% eE")
    if not all(c in allowed for c in expression):
        return "Ошибка: недопустимые символы в выражении"
    try:
        result = eval(expression, {"__builtins__": {}}, {})
        return f"{expression} = {result}"
    except Exception as e:
        return f"Ошибка: {e}"

def read_file(filename: str, **kwargs) -> str:
    safe_path = os.path.join(".", os.path.basename(filename))
    if not os.path.exists(safe_path):
        return f"Файл '{filename}' не найден в текущей папке"
    with open(safe_path, 'r', encoding='utf-8') as f:
        return f.read()

TOOLS = [
    {"type": "function", "function": {"name": "get_current_time", "description": "Узнать текущее время и дату", "parameters": {"type": "object", "properties": {}, "required": []}}},
    {"type": "function", "function": {"name": "calculate", "description": "Вычислить математическое выражение", "parameters": {"type": "object", "properties": {"expression": {"type": "string", "description": "Математическое выражение"}}, "required": ["expression"]}}},
    {"type": "function", "function": {"name": "read_file", "description": "Прочитать текстовый файл", "parameters": {"type": "object", "properties": {"filename": {"type": "string", "description": "Имя файла"}}, "required": ["filename"]}}},
]

TOOL_FUNCTIONS = {
    "get_current_time": get_current_time,
    "calculate": calculate,
    "read_file": read_file,
}

# ---------- АГЕНТ ----------
class SimpleAgent:
    def __init__(self, model="gpt-4o-mini", max_steps=10):
        self.model = model
        self.max_steps = max_steps
        self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
        self.messages = [
            {"role": "system", "content": "Ты полезный ассистент. Отвечай на русском. Используй инструменты когда нужно."}
        ]

    def run(self, user_input):
        self.messages.append({"role": "user", "content": user_input})
        for step in range(self.max_steps):
            resp = self.client.chat.completions.create(
                model=self.model, messages=self.messages, tools=TOOLS, tool_choice="auto"
            )
            msg = resp.choices[0].message
            if msg.tool_calls:
                self.messages.append(msg)
                for tc in msg.tool_calls:
                    fn = TOOL_FUNCTIONS[tc.function.name]
                    args = json.loads(tc.function.arguments)
                    result = fn(**args)
                    self.messages.append({"role": "tool", "tool_call_id": tc.id, "content": result})
                continue
            if msg.content:
                self.messages.append({"role": "assistant", "content": msg.content})
                return msg.content
        return "⚠️ Достигнут лимит шагов."

# ---------- ИНТЕРАКТИВНЫЙ ЗАПУСК ----------
if __name__ == "__main__":
    agent = SimpleAgent()
    print("🤖 Твой первый AI-агент готов! Введи запрос (exit для выхода):")
    while True:
        user_input = input("\n👤 Ты: ")
        if user_input.lower() in ["exit", "quit", "выход"]:
            break
        response = agent.run(user_input)
        print(f"\n🤖 Агент: {response}")

# 6. ТЕСТИРУЕМ АГЕНТА

Запусти python agent.py и попробуй следующие запросы. Каждый из них демонстрирует разный сценарий использования инструментов. Обрати внимание на вывод в консоли — ты увидишь, как агент шаг за шагом вызывает инструменты.

# Тест 1: Вычисление
👤 Ты: Сколько будет (156 + 244) * 3 / 6?
# Агент вызовет calculate(expression="(156+244)*3/6")
🤖 Агент: Результат вычисления: (156 + 244) × 3 ÷ 6 = 200

# Тест 2: Текущее время
👤 Ты: Который час? И какой сегодня день недели?
# Агент вызовет get_current_time()
🤖 Агент: Сейчас 2026-06-06 11:42:15 UTC. Сегодня суббота.

# Тест 3: Чтение файла
👤 Ты: Прочитай файл sample.txt и перескажи кратко
# Агент вызовет read_file(filename="sample.txt")
# Затем LLM обработает содержимое и сделает краткий пересказ

# Тест 4: Комбинированный запрос
👤 Ты: Прочитай sample.txt, посчитай количество слов в нём,
   и скажи, сколько времени занял бы анализ при скорости
   200 слов в минуту
# Агент сделает несколько вызовов последовательно!

# Тест 5: Обычный разговор (без инструментов)
👤 Ты: Привет! Расскажи коротко, что такое Python?
# Агент ответит без вызова инструментов — LLM знает ответ

Если агент не вызывает инструмент, когда должен — проверь описание инструмента в TOOLS. Оно должно быть максимально конкретным и содержать примеры. Если агент вызывает инструмент слишком часто — добавь в system-промпт инструкцию: «Используй инструменты, только если не можешь ответить из своих знаний».

# 7. КУДА ДАЛЬШЕ: РАСШИРЯЕМ АГЕНТА

Поздравляю — у тебя есть работающий AI-агент! Теперь его можно расширять. Вот идеи для следующих шагов, от простых к продвинутым:

Добавь поиск в интернете. Инструмент web_search(query) с использованием Serper API (google search) или бесплатного DuckDuckGo. Агент сможет отвечать на вопросы о текущих событиях, курсах валют, погоде. Код: pip install duckduckgo-search, затем from duckduckgo_search import DDGS и оберни в функцию.

Добавь память между сессиями. Сейчас агент забывает всё после перезапуска. Сохраняй messages в JSON-файл или SQLite. При старте загружай историю. Можно добавить векторную память: храни эмбеддинги всех разговоров в ChromaDB и при новом запросе ищи релевантные прошлые диалоги.

Используй локальную модель. Вместо OpenAI API можно запустить модель локально через Ollama: ollama pull qwen2.5:7b, затем замени client на OpenAI(base_url="http://localhost:11434/v1", api_key="ollama"). Это бесплатно и приватно!

# Пример: инструмент поиска в интернете (DuckDuckGo)
pip install duckduckgo-search

from duckduckgo_search import DDGS

def web_search(query: str) -> str:
    """Ищет информацию в интернете через DuckDuckGo."""
    with DDGS() as ddgs:
        results = list(ddgs.text(query, max_results=3))
    if not results:
        return "Ничего не найдено"
    return "\n\n".join(
        f"{i+1}. {r['title']}\n{r['body']}"
        for i, r in enumerate(results)
    )

# Добавь в TOOLS и TOOL_FUNCTIONS — и агент научится гуглить!

# Пример: локальная модель через Ollama
# 1. Установи Ollama: curl -fsSL https://ollama.com/install.sh | sh
# 2. Скачай модель: ollama pull qwen2.5:7b
# 3. Измени клиент:
client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama"  # Ollama не требует реального ключа
)
agent = SimpleAgent(model="qwen2.5:7b")  # Бесплатно и локально!

Другие направления для развития: подключи агента к Telegram-боту (python-telegram-bot), добавь голосовой ввод через Whisper API, научи агента генерировать изображения (DALL-E / Stable Diffusion), или собери multi-agent систему, где несколько агентов с разными ролями обсуждают задачу и приходят к консенсусу. Фундамент, который ты построил — чистый agentic loop — лежит в основе всех этих продвинутых сценариев.

🔗 Полезные ссылки

📖 OpenAI Function Calling Docs📖 Ollama (локальные модели)📖 Скачать Python📖 OpenRouter (доступ к 200+ моделям)