От установки Python до работающего агента с тремя инструментами. Полный код, пояснения каждой строчки и запуск за 10 минут. Для начинающих.
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)
Нам нужен только 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 с любым текстовым содержанием в той же папке — он понадобится для демонстрации инструмента чтения файлов. Например, напиши туда короткую заметку или стихотворение.
Инструменты (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, }
Теперь самое интересное — цикл, в котором агент живёт. Это бесконечный (с ограничениями) цикл: отправляем запрос в 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-промпт важен: он задаёт тон и поведение агента. Для русского языка явно указываем отвечать на русском — иначе некоторые модели по умолчанию используют английский.
Теперь собираем всё в единый файл 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}")
Запусти 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-промпт инструкцию: «Используй инструменты, только если не можешь ответить из своих знаний».
Поздравляю — у тебя есть работающий 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 — лежит в основе всех этих продвинутых сценариев.