Как научить нейросеть работать руками: создание полноценного ИИ-агента с MCP и LangGraph за час

от автора

Друзья, приветствую! Надеюсь, успели соскучиться.

Последние пару месяцев я с головой ушёл в исследование интеграции ИИ-агентов в собственные Python-проекты. В процессе накопилось немало практических знаний и наблюдений, которыми просто грех не поделиться. Поэтому сегодня я возвращаюсь на Хабр — с новой темой, свежим взглядом и с намерением писать чаще.

На повестке дня — LangGraph и MCP: инструменты, с помощью которых можно создавать действительно полезных ИИ-агентов.

Если раньше мы спорили о том, какая нейросеть лучше отвечает на русском языке, то сегодня поле битвы сместилось в сторону более прикладных задач: кто лучше справляется с ролью ИИ-агента? Какие фреймворки действительно упрощают разработку? И как интегрировать всё это добро в реальный проект?

Но прежде чем нырнуть в практику и код, давайте разберёмся с базовыми понятиями. Особенно с двумя ключевыми: ИИ-агенты и MCP. Без них разговор про LangGraph будет неполным.

ИИ-агенты простыми словами

ИИ-агенты — это не просто «прокачанные» чат-боты. Они представляют собой более сложные, автономные сущности, которые обладают двумя важнейшими особенностями:

  1. Умение взаимодействовать и координироваться.
    Современные агенты способны делить задачи на подзадачи, вызывать других агентов, запрашивать внешние данные, работать в команде. Это уже не одиночный ассистент, а распределённая система, где каждый компонент может вносить свой вклад.

  2. Доступ к внешним ресурсам.
    ИИ-агент больше не ограничен рамками диалога. Он может обращаться к базам данных, выполнять вызовы к API, взаимодействовать с локальными файлами, векторными хранилищами знаний и даже запускать команды в терминале. Всё это стало возможным благодаря появлению MCP — нового уровня интеграции между моделью и средой.

Что такое MCP (Model Context Protocol)

Если говорить просто: MCP — это мост между нейросетью и её окружением. Он позволяет модели «понимать» контекст задачи, получать доступ к данным, выполнять вызовы и формировать обоснованные действия, а не просто выдавать текстовые ответы.

Представим аналогию:

  • У вас есть нейросеть — она умеет рассуждать и генерировать тексты.

  • Есть данные и инструменты — документы, API, базы знаний, терминал, код.

  • И есть MCP — это интерфейс, который позволяет модели взаимодействовать с этими внешними источниками так, как если бы они были частью её внутреннего мира.

Без MCP:

Модель — это изолированный диалоговый движок. Вы подаёте ей текст — она отвечает. И всё.

С MCP:

Модель становится полноценным исполнителем задач:

  • получает доступ к структурам данных и API;

  • вызывает внешние функции;

  • ориентируется в текущем состоянии проекта или приложения;

  • может запоминать, отслеживать и изменять контекст по мере диалога;

  • использует расширения, такие как инструменты поиска, код-раннеры, базу векторных эмбеддингов и пр.

В техническом смысле MCP — это протокол взаимодействия между LLM и её окружением, где контекст подаётся в виде структурированных объектов (вместо «сырого» текста), а вызовы оформляются как интерактивные операции (например, function calling, tool usage или agent actions). Именно это и превращает обычную модель в настоящего ИИ-агента, способного делать больше, чем просто «поговорить».

А теперь — к делу!

Теперь, когда мы разобрались с базовыми понятиями, логично задаться вопросом: «Как всё это реализовать на практике в Python?»

Вот здесь и вступает в игру LangGraph — мощный фреймворк для построения графов состояний, поведения агентов и цепочек мышления. Он позволяет «прошивать» логику взаимодействия между агентами, инструментами и пользователем, создавая живую архитектуру ИИ, адаптирующуюся к задачам.

В следующих разделах мы посмотрим, как:

  • строится агент с нуля;

  • создаются состояния, переходы и события;

  • интегрируются функции и инструменты;

  • и как вся эта экосистема работает в реальном проекте.

Немного теории: что такое LangGraph

Прежде чем приступить к практике, нужно сказать пару слов о самом фреймворке.

LangGraph — это проект от команды LangChain, тех самых, кто первыми предложили концепцию «цепочек» (chains) взаимодействия с LLM. Если раньше основной упор делался на линейные или условно-ветвящиеся пайплайны (langchain.chains), то теперь разработчики делают ставку на графовую модель, и именно LangGraph они рекомендуют как новое «ядро» для построения сложных ИИ-сценариев.

LangGraph — это фреймворк для построения конечных автоматов и графов состояний, в которых каждый нода (или узел) представляет собой часть логики агента: вызов модели, внешний инструмент, условие, пользовательский ввод и т.д.

Как это работает: графы и узлы

Концептуально, LangGraph строится на следующих идеях:

  • Граф — это структура, которая описывает возможные пути выполнения логики. Можно думать о нём как о карте: из одной точки можно перейти в другую в зависимости от условий или результата выполнения.

  • Узлы (ноды) — это конкретные шаги внутри графа. Каждый узел выполняет какую-то функцию: вызывает модель, вызывает внешний API, проверяет условие или просто обновляет внутреннее состояние.

  • Переходы между узлами — это логика маршрутизации: если результат предыдущего шага такой-то, то идём туда-то.

  • Состояние (state) — передаётся между узлами и накапливает всё, что нужно: историю, промежуточные выводы, пользовательский ввод, результат работы инструментов и т.д.

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

Почему это удобно?

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

  • легко отлаживать;

  • легко визуализировать;

  • легко масштабировать под новые задачи;

  • легко интегрировать внешние инструменты и MCP-протоколы.

По сути, LangGraph — это «мозг» агента, где каждый шаг задокументирован, контролируем и может быть изменён без хаоса и «магии».

Ну а теперь — хватит теории!

Можно ещё долго рассказывать о графах, состояний, композиции логики и преимуществах LangGraph над классическими пайплайнами. Но, как показывает практика, лучше один раз увидеть в коде.

Пора перейти к практике. Дальше — пример на Python: создадим простого, но полезного ИИ-агента на базе LangGraph, который будет использовать внешние инструменты, память и принимать решения сам.

Подготовка: облачные и локальные нейросети

Для того чтобы приступить к созданию ИИ-агентов, нам в первую очередь нужен мозг — языковая модель. Здесь есть два подхода:

  • использовать облачные решения, где всё готово «из коробки»;

  • или поднять модель локально — для полной автономии и конфиденциальности.

Рассмотрим оба варианта.

Облачные сервисы: быстро и удобно

Самый простой путь — воспользоваться мощностями крупных провайдеров: OpenAI, Anthropic, DeepSeek, Groq, Mistral и других. Вам достаточно приобрести API-ключ и начать использовать модели через стандартные HTTP-запросы.

Где взять ключи и токены:

  • OpenAI — ChatGPT и другие продукты;

  • Anthropic — Claude;

  • OpenRouter.ai — десятки моделей (один токен — множество моделей через OpenAI-совместимый API);

  • Amvera Cloud — возможность подключить LLaMA с оплатой рублями и встроенным проксированием до OpenAI и Anthropic.

Этот путь удобен, особенно если вы:

  • не хотите настраивать инфраструктуру;

  • разрабатываете с упором на скорость;

  • работаете с ограниченными ресурсами.

Локальные модели: полный контроль

Если вам важна приватность, работа без интернета или вы хотите строить полностью автономные агенты, то имеет смысл развернуть нейросеть локально.

Основные преимущества:

  • Конфиденциальность — данные остаются у вас;

  • Работа без интернета — полезно в изолированных сетях;

  • Отсутствие подписок и токенов — бесплатно после настройки.

Недостатки очевидны:

  • Требования к ресурсам (особенно к видеопамяти);

  • Настройка может занять время;

  • Некоторые модели сложно развернуть без опыта.

Тем не менее, есть инструменты, которые делают локальный запуск проще. Один из лучших на сегодня — это Ollama.

Развёртывание локальной LLM через Ollama + Docker

Мы подготовим локальный запуск модели Qwen 2.5 (qwen2.5:32b) с использованием Docker-контейнера и системы Ollama. Это позволит интегрировать нейросеть с MCP и использовать её в собственных агентах на базе LangGraph.

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

📦 Быстрая установка (сводка шагов)

  1. Установите Docker + Docker Compose

  2. Создайте структуру проекта:

    mkdir qwen-local && cd qwen-local
  3. Создайте docker-compose.yml
    (универсальный вариант, GPU определяется автоматически)

    services:   ollama:     image: ollama/ollama:latest     container_name: ollama_qwen     ports:       - '11434:11434'     volumes:       - ./ollama_data:/root/.ollama       - /tmp:/tmp     environment:       - OLLAMA_HOST=0.0.0.0       - OLLAMA_ORIGINS=*     restart: unless-stopped 
  4. Запустите контейнер:

    docker compose up -d
  5. Загрузите модель:

    docker exec -it ollama_qwen ollama pull qwen2.5:32b
  6. Проверьте работу через API:

    curl http://localhost:11434/api/generate \   -H "Content-Type: application/json" \   -d '{"model": "qwen2.5:32b", "prompt": "Привет!", "stream": false}' 
  1. Интеграция с Python:

    import requests  def query(prompt):     res = requests.post("http://localhost:11434/api/generate", json={         "model": "qwen2.5:32b",         "prompt": prompt,         "stream": False     })     return res.json()['response']  print(query("Объясни квантовую запутанность")) 

Теперь у вас полноценная локальная LLM, готовая к работе с MCP и LangGraph.

Что дальше?

У нас есть выбор между облачными и локальными моделями, и мы научились подключать обе. Самое интересное впереди — создание ИИ-агентов на LangGraph, которые используют выбранную модель, память, инструменты и собственную логику.

Переходим к самому вкусному — коду и практике!

Подготовка к написанию кода

Перед тем как перейти к практике, важно подготовить рабочее окружение. Я предполагаю, что вы уже знакомы с основами Python, знаете, что такое библиотеки и зависимости, и понимаете, зачем использовать виртуальное окружение.

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

Шаг 1: Создание виртуального окружения

Создайте новое виртуальное окружение в папке проекта:

python -m venv venv source venv/bin/activate  # для Linux/macOS venv\Scripts\activate     # для Windows 

Шаг 2: Установка зависимостей

Создайте файл requirements.txt и добавьте в него следующие строки:

langchain==0.3.26 langchain-core==0.3.69 langchain-deepseek==0.1.3 langchain-mcp-adapters==0.1.9 langchain-ollama==0.3.5 langchain-openai==0.3.28 langgraph==0.5.3 langgraph-checkpoint==2.1.1 langgraph-prebuilt==0.5.2 langgraph-sdk==0.1.73 langsmith==0.4.8 mcp==1.12.0 ollama==0.5.1 openai==1.97.0

⚠️ Актуальные версии указаны на 21 июля 2025 года. С момента публикации они могли измениться — проверяйте актуальность перед установкой.

Затем установите зависимости:

pip install -r requirements.txt

Шаг 3: Конфигурация переменных окружения

Создайте в корне проекта файл .env и добавьте в него нужные API-ключи:

OPENAI_API_KEY=sk-proj-1234 DEEPSEEK_API_KEY=sk-123 OPENROUTER_API_KEY=sk-or-v1-123 BRAVE_API_KEY=BSAj123KlbvBGpH1344tLwc

Назначение переменных:

  • OPENAI_API_KEY — ключ для доступа к GPT-моделям от OpenAI;

  • DEEPSEEK_API_KEY — ключ для использования моделей Deepseek;

  • OPENROUTER_API_KEY — единый ключ для доступа к множеству моделей через OpenRouter (можно получить бесплатно);

  • BRAVE_API_KEY — API-ключ для MCP-модуля web-поиска через Brave Search (можно получить бесплатно).

Некоторые MCP-инструменты (например, brave-web-search) требуют ключ для работы. Без него они просто не активируются.

А если у вас нет API-ключей?

Не проблема. Вы можете начать разработку с локальной моделью (например, через Ollama), не подключая ни одного внешнего сервиса. В этом случае файл .env можно не создавать вовсе.

Готово! Теперь у нас есть всё необходимое для начала — изолированное окружение, зависимости, и, при необходимости, доступ к облачным нейросетям и MCP-интеграциям.

Далее — запустим нашего LLM-агента разными способами.

Простой запуск LLM-агентов через LangGraph: базовая интеграция

Начнём с самого простого: как «подключить мозг» к будущему агенту. Мы разберём базовые способы запуска языковых моделей (LLM) с помощью LangChain, чтобы в следующем шаге перейти к интеграции с LangGraph и построению полноценного ИИ-агента.

Импорты

import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI from langchain_ollama import ChatOllama from langchain_deepseek import ChatDeepSeek 
  • os и load_dotenv() — для загрузки переменных из .env-файла.

  • ChatOpenAI, ChatOllama, ChatDeepSeek — обёртки для подключения языковых моделей через LangChain.

💡 Если вы используете альтернативные подходы к работе с конфигурациями (например, Pydantic Settings), можете заменить load_dotenv() на свой привычный способ.

Загрузка переменных окружения

load_dotenv()

Это подгрузит все переменные из .env, включая ключи для доступа к API OpenAI, DeepSeek, OpenRouter и другим.

Простые функции для получения LLM

OpenAI

def get_openai_llm():     return ChatOpenAI(model="gpt-4o-mini", api_key=os.getenv("OPENAI_API_KEY")) 

Если переменная OPENAI_API_KEY корректно задана, LangChain подставит её автоматически — явное указание api_key=... здесь опционально.

DeepSeek

def get_deepseek_llm():     return ChatDeepSeek(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY")) 

Аналогично, но используем обёртку ChatDeepSeek.

OpenRouter (и другие совместимые API)

def get_openrouter_llm(model="moonshotai/kimi-k2:free"):     return ChatOpenAI(         model=model,         api_key=os.getenv("OPENROUTER_API_KEY"),         base_url="https://openrouter.ai/api/v1",         temperature=0     ) 

Особенности:

  • ChatOpenAI используется, несмотря на то что модель не от OpenAI — потому что OpenRouter использует тот же протокол.

  • base_url обязателен: он указывает на OpenRouter API.

  • Модель moonshotai/kimi-k2:free выбрана как один из наиболее сбалансированных вариантов по качеству и скорости на момент написания статьи.

  • API-ключ OpenRouter нужно передавать явно — автоматическая подстановка здесь не работает.

Мини-тест: проверка работы модели

if __name__ == "__main__":     llm = get_openrouter_llm(model="moonshotai/kimi-k2:free")     response = llm.invoke("Кто ты?")     print(response.content) 

Если всё настроено правильно, вы получите осмысленный ответ от модели. Поздравляю — первый шаг сделан!

Но это ещё не агент

На текущем этапе мы подключили LLM и сделали простой вызов. Это больше похоже на консольного чат-бота, чем на ИИ-агента.

Почему?

  • Мы пишем синхронный, линейный код без логики состояния или целей.

  • Агент не принимает решений, не запоминает контекст и не использует инструменты.

  • MCP и LangGraph пока не задействованы.

Что дальше?

Далее мы реализуем полноценного ИИ-агента с использованием LangGraph — сначала без MCP, чтобы сфокусироваться на архитектуре, состояниях и логике самого агента.

Погружаемся в настоящую агентную механику. Поехали!

Агент классификации вакансий: от теории к практике

Создадим реального ИИ-агента, который будет решать конкретную бизнес-задачу — автоматическую классификацию описаний вакансий и услуг. Этот пример покажет, как применить концепции LangGraph на практике и создать полезный инструмент для HR-платформ и бирж фриланса.

Задача агента

Наш агент принимает на вход текстовое описание вакансии или услуги и выполняет трёхуровневую классификацию:

  1. Тип работы: проектная работа или постоянная вакансия

  2. Категория профессии: из 45+ предопределённых специальностей

  3. Тип поиска: ищет ли человек работу или ищет исполнителя

Результат возвращается в структурированном JSON-формате с оценкой уверенности для каждой классификации.

🏗️ Архитектура агента на LangGraph

Следуя принципам LangGraph, создаём граф состояний из четырёх узлов:

📝 Входное описание       ↓ 🔍 Узел классификации типа работы       ↓ 📂 Узел классификации категории       ↓ 🎯 Узел определения типа поиска       ↓ 📊 Узел расчёта уверенности       ↓ ✅ JSON-результат 

Каждый узел — это специализированная функция, которая:

  • Получает текущее состояние агента

  • Выполняет свою часть анализа

  • Обновляет состояние и передаёт его дальше

Управление состоянием

Определяем структуру памяти агента через TypedDict:

class State(TypedDict):     """Состояние агента для хранения информации о процессе классификации"""     description: str     job_type: str     category: str     search_type: str     confidence_scores: Dict[str, float]     processed: bool 

Это рабочая память агента — всё, что он помнит и накапливает в процессе анализа. Подобно тому, как человек-эксперт держит в уме контекст задачи при анализе документа.

Давайте рассмотрим полный код, а после сконцентрируемся на основных моментах.

import asyncio import json from typing import TypedDict, Dict, Any from enum import Enum from langgraph.graph import StateGraph, END from langchain.prompts import PromptTemplate from langchain_openai import ChatOpenAI from langchain.schema import HumanMessage   # Категории профессий CATEGORIES = [     "2D-аниматор", "3D-аниматор", "3D-моделлер",      "Бизнес-аналитик", "Блокчейн-разработчик", ... ]   class JobType(Enum):     PROJECT = "проектная работа"     PERMANENT = "постоянная работа"   class SearchType(Enum):     LOOKING_FOR_WORK = "поиск работы"     LOOKING_FOR_PERFORMER = "поиск исполнителя"   class State(TypedDict):     """Состояние агента для хранения информации о процессе классификации"""     description: str     job_type: str     category: str     search_type: str     confidence_scores: Dict[str, float]     processed: bool   class VacancyClassificationAgent:     """Асинхронный агент для классификации вакансий и услуг"""      def __init__(self, model_name: str = "gpt-4o-mini", temperature: float = 0.1):         """Инициализация агента"""         self.llm = ChatOpenAI(model=model_name, temperature=temperature)         self.workflow = self._create_workflow()      def _create_workflow(self) -> StateGraph:         """Создает рабочий процесс агента на основе LangGraph"""         workflow = StateGraph(State)          # Добавляем узлы в граф         workflow.add_node("job_type_classification", self._classify_job_type)         workflow.add_node("category_classification", self._classify_category)         workflow.add_node("search_type_classification", self._classify_search_type)         workflow.add_node("confidence_calculation", self._calculate_confidence)          # Определяем последовательность выполнения узлов         workflow.set_entry_point("job_type_classification")         workflow.add_edge("job_type_classification", "category_classification")         workflow.add_edge("category_classification", "search_type_classification")         workflow.add_edge("search_type_classification", "confidence_calculation")         workflow.add_edge("confidence_calculation", END)          return workflow.compile()      async def _classify_job_type(self, state: State) -> Dict[str, Any]:         """Узел для определения типа работы: проектная или постоянная"""         prompt = PromptTemplate(             input_variables=["description"],             template="""             Проанализируй следующее описание и определи тип работы.              Описание: {description}              Ответь только одним из двух вариантов:             - "проектная работа" - если это временная задача, проект, фриланс, разовая работа             - "постоянная работа" - если это постоянная должность, штатная позиция, долгосрочное трудоустройство              Тип работы:             """         )          message = HumanMessage(content=prompt.format(description=state["description"]))         response = await self.llm.ainvoke([message])         job_type = response.content.strip().lower()          # Нормализуем ответ         if "проектная" in job_type or "проект" in job_type or "фриланс" in job_type:             job_type = JobType.PROJECT.value         else:             job_type = JobType.PERMANENT.value          return {"job_type": job_type}      async def _classify_category(self, state: State) -> Dict[str, Any]:         """Узел для определения категории профессии"""         categories_str = "\n".join([f"- {cat}" for cat in CATEGORIES])          prompt = PromptTemplate(             input_variables=["description", "categories"],             template="""             Проанализируй описание вакансии/услуги и определи наиболее подходящую категорию из списка.              Описание: {description}              Доступные категории:             {categories}              Выбери ТОЧНО одну категорию из списка выше, которая лучше всего соответствует описанию.             Ответь только названием категории без дополнительных пояснений.              Категория:             """         )          message = HumanMessage(content=prompt.format(             description=state["description"],             categories=categories_str         ))         response = await self.llm.ainvoke([message])         category = response.content.strip()          # Проверяем, есть ли категория в списке доступных         if category not in CATEGORIES:             # Ищем наиболее похожую категорию             category = self._find_closest_category(category)          return {"category": category}      async def _classify_search_type(self, state: State) -> Dict[str, Any]:         """Узел для определения типа поиска"""         prompt = PromptTemplate(             input_variables=["description"],             template="""             Проанализируй описание и определи, кто и что ищет.              Описание: {description}              Ответь только одним из двух вариантов:             - "поиск работы" - если соискатель ищет работу/заказы             - "поиск исполнителя" - если работодатель/заказчик ищет исполнителя              Обрати внимание на ключевые слова:             - "ищу работу", "резюме", "хочу работать" = поиск работы             - "требуется", "ищем", "вакансия", "нужен специалист" = поиск исполнителя              Тип поиска:             """         )          message = HumanMessage(content=prompt.format(description=state["description"]))         response = await self.llm.ainvoke([message])         search_type = response.content.strip().lower()          # Нормализуем ответ         if "поиск работы" in search_type or "ищу работу" in search_type:             search_type = SearchType.LOOKING_FOR_WORK.value         else:             search_type = SearchType.LOOKING_FOR_PERFORMER.value          return {"search_type": search_type}      async def _calculate_confidence(self, state: State) -> Dict[str, Any]:         """Узел для расчета уровня уверенности в классификации"""         prompt = PromptTemplate(             input_variables=["description", "job_type", "category", "search_type"],             template="""             Оцени уверенность классификации по шкале от 0.0 до 1.0 для каждого параметра:              Описание: {description}             Тип работы: {job_type}             Категория: {category}             Тип поиска: {search_type}              Ответь в формате JSON:             {{                 "job_type_confidence": 0.0-1.0,                 "category_confidence": 0.0-1.0,                 "search_type_confidence": 0.0-1.0             }}             """         )          message = HumanMessage(content=prompt.format(             description=state["description"],             job_type=state["job_type"],             category=state["category"],             search_type=state["search_type"]         ))         response = await self.llm.ainvoke([message])          try:             confidence_scores = json.loads(response.content.strip())         except:             # Fallback значения если парсинг не удался             confidence_scores = {                 "job_type_confidence": 0.7,                 "category_confidence": 0.7,                 "search_type_confidence": 0.7             }          return {             "confidence_scores": confidence_scores,             "processed": True         }      def _find_closest_category(self, predicted_category: str) -> str:         """Находит наиболее похожую категорию из списка доступных"""         # Простая эвристика поиска по вхождению ключевых слов         predicted_lower = predicted_category.lower()          for category in CATEGORIES:             category_lower = category.lower()             if predicted_lower in category_lower or category_lower in predicted_lower:                 return category          # Если ничего не найдено, возвращаем первую категорию как fallback         return CATEGORIES[0]      async def classify(self, description: str) -> Dict[str, Any]:         """Основной метод для классификации вакансии/услуги"""         initial_state = {             "description": description,             "job_type": "",             "category": "",             "search_type": "",             "confidence_scores": {},             "processed": False         }          # Запускаем рабочий процесс         result = await self.workflow.ainvoke(initial_state)          # Формируем итоговый ответ в формате JSON         classification_result = {             "job_type": result["job_type"],             "category": result["category"],             "search_type": result["search_type"],             "confidence_scores": result["confidence_scores"],             "success": result["processed"]         }          return classification_result   async def main():     """Демонстрация работы агента"""     agent = VacancyClassificationAgent()      # Тестовые примеры     test_cases = [         "Требуется Python разработчик для создания веб-приложения на Django. Постоянная работа, полный рабочий день.",         "Ищу заказы на создание логотипов и фирменного стиля. Работаю в Adobe Illustrator.",         "Нужен 3D-аниматор для краткосрочного проекта создания рекламного ролика.",         "Резюме: опытный маркетолог, ищу удаленную работу в сфере digital-маркетинга",         "Ищем фронтенд-разработчика React в нашу команду на постоянную основе"     ]      test_cases = []     print("🤖 Демонстрация работы агента классификации вакансий\n")      for i, description in enumerate(test_cases, 1):         print(f"📋 Тест {i}:")         print(f"Описание: {description}")          try:             result = await agent.classify(description)             print("Результат классификации:")             print(json.dumps(result, ensure_ascii=False, indent=2))          except Exception as e:             print(f"❌ Ошибка: {e}")          print("-" * 80)   if __name__ == "__main__":     asyncio.run(main()) 

Асинхронная архитектура

В отличие от простых примеров, наш агент работает полностью асинхронно:

async def _classify_job_type(self, state: State) -> Dict[str, Any]:     """Узел для определения типа работы: проектная или постоянная"""     response = await self.llm.ainvoke([message])     # Обработка ответа...     return {"job_type": job_type} 

Это позволяет:

  • Обрабатывать множественные запросы параллельно

  • Интегрироваться с асинхронными веб-фреймворками

  • Эффективно использовать ресурсы при масштабировании

Специализированные промпты для каждого узла

Каждый узел использует целевой промпт, оптимизированный под свою задачу:

For job type classification:

template=""" Проанализируй следующее описание и определи тип работы.  Описание: {description}  Ответь только одним из двух вариантов: - "проектная работа" - если это временная задача, проект, фриланс - "постоянная работа" - если это постоянная должность, штатная позиция  Тип работы: """ 

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

Обработка ошибок и fallback-механизмы

Агент включает умную обработку неожиданных ситуаций:

def _find_closest_category(self, predicted_category: str) -> str:     """Находит наиболее похожую категорию из списка доступных"""     predicted_lower = predicted_category.lower()      for category in CATEGORIES:         category_lower = category.lower()         if predicted_lower in category_lower or category_lower in predicted_lower:             return category      # Fallback: возвращаем первую категорию     return CATEGORIES[0] 

Это делает агента устойчивым к ошибкам — даже если модель вернёт неточный результат, система найдёт наиболее подходящий вариант из допустимых.

Оценка уверенности

Финальный узел рассчитывает метрики качества классификации:

async def _calculate_confidence(self, state: State) -> Dict[str, Any]:     """Узел для расчета уровня уверенности в классификации"""     # Запрос к LLM для оценки уверенности по шкале 0.0-1.0     # Возврат структурированного JSON с метриками 

Это позволяет:

  • Отслеживать качество работы агента

  • Выделять случаи, требующие ручной проверки

  • Оптимизировать промпты на основе статистики

Практическое применение

async def main():     agent = VacancyClassificationAgent()      description = "Требуется Python разработчик для создания веб-приложения"     result = await agent.classify(description)      print(json.dumps(result, ensure_ascii=False, indent=2)) 

Результат:

{   "job_type": "постоянная работа",   "category": "Бэкенд-разработчик (Node.js, Python, PHP, Ruby)",   "search_type": "поиск исполнителя",   "confidence_scores": {     "job_type_confidence": 0.95,     "category_confidence": 0.88,     "search_type_confidence": 0.92   },   "success": true } 

Ключевые преимущества архитектуры

  1. Модульность — каждый узел решает одну задачу, легко тестировать и улучшать отдельно

  2. Расширяемость — можно добавлять новые узлы анализа без изменения существующих

  3. Прозрачность — весь процесс принятия решений документирован и отслеживаем

  4. Производительность — асинхронная обработка множественных запросов

  5. Надёжность — fallback-механизмы и обработка ошибок

Реальная польза

Такой агент может использоваться в:

  • HR-платформах для автоматической категоризации резюме и вакансий

  • Биржах фриланса для улучшения поиска и рекомендаций

  • Внутренних системах компаний для обработки заявок и проектов

  • Аналитических решениях для исследования рынка труда

MCP в действии: создаём агента с файловой системой и веб-поиском

После того как мы разобрались с базовыми принципами LangGraph и создали простого классификатора, пора перейти к настоящей магии — интеграции агента с внешним миром через MCP (Model Context Protocol).

Сейчас мы создадим полноценного ИИ-помощника, который сможет:

  • Работать с файловой системой (читать, создавать, изменять файлы)

  • Искать актуальную информацию в интернете

  • Запоминать контекст диалога

  • Обрабатывать ошибки и восстанавливаться после сбоев

От теории к реальным инструментам

Помните, как в начале статьи мы говорили о том, что MCP — это мост между нейросетью и её окружением? Сейчас вы увидите это на практике. Наш агент получит доступ к реальным инструментам:

# Инструменты файловой системы - read_file — чтение файлов - write_file — запись и создание файлов - list_directory — просмотр содержимого папок - create_directory — создание папок  # Инструменты веб-поиска - brave_web_search — поиск в интернете - get_web_content — получение содержимого страниц 

Это уже не «игрушечный» агент — это рабочий инструмент, который может решать реальные задачи.

Кода и текста уже накопилось достаточно много, поэтому далее я приведу лишь общее описание принципов и концепции разработки подобных ИИ-агентов с интеграцией MCP. Полный пример кода интеграции с MCP — как для одного сервера, так и для нескольких — вы найдете в моём бесплатном телеграм-канале «Лёгкий путь в Python». Сообщество уже насчитывает около 4000 участников, присоединяйтесь.

🏗️ Архитектура: от простого к сложному

1. Конфигурация как основа стабильности

@dataclass class AgentConfig:     """Упрощенная конфигурация AI-агента"""     filesystem_path: str = "/path/to/work/directory"     model_provider: ModelProvider = ModelProvider.OLLAMA     use_memory: bool = True     enable_web_search: bool = True      def validate(self) -> None:         """Валидация конфигурации"""         if not os.path.exists(self.filesystem_path):             raise ValueError(f"Путь не существует: {self.filesystem_path}") 

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

2. Фабрика моделей: гибкость выбора

class ModelFactory:     """Упрощенная фабрика моделей"""      @staticmethod     def create_model(config: AgentConfig):         """Создает модель согласно конфигурации"""         provider = config.model_provider.value          if provider == "ollama":             return ChatOllama(model="qwen2.5:32b", base_url="http://localhost:11434")         elif provider == "openai":             return ChatOpenAI(model="gpt-4o-mini", api_key=os.getenv("OPENAI_API_KEY"))         # ... другие провайдеры 

Один код — множество моделей. Хотите бесплатную локальную модель? Используйте Ollama. Нужна максимальная точность? Переключитесь на GPT-4. Важна скорость? Попробуйте DeepSeek. Код остаётся тем же.

3. MCP-интеграция: подключение к реальному миру

async def _init_mcp_client(self):     """Инициализация MCP клиента"""     mcp_config = {         "filesystem": {             "command": "npx",             "args": ["-y", "@modelcontextprotocol/server-filesystem", self.filesystem_path],             "transport": "stdio"         },         "brave-search": {             "command": "npx",             "args": ["-y", "@modelcontextprotocol/server-brave-search@latest"],             "transport": "stdio",             "env": {"BRAVE_API_KEY": os.getenv("BRAVE_API_KEY")}         }     }      self.mcp_client = MultiServerMCPClient(mcp_config)     self.tools = await self.mcp_client.get_tools() 

Здесь происходит ключевая работа MCP: мы подключаем к агенту внешние MCP-серверы, которые предоставляют набор инструментов и функций. Агент при этом получает не просто отдельные функции, а полноценное контекстное понимание того, как работать с файловой системой и интернетом.

Для корректной работы указанных MCP-серверов потребуется установить Node.js и npm последней версии. После этого глобально установите необходимые MCP-серверы с помощью команд:

npm install -g @modelcontextprotocol/server-filesystem npm install -g @modelcontextprotocol/server-brave-search@latest

Если вы планируете использовать сервер server-brave-search, обязательно получите бесплатный API-ключ на сайте Brave и установите его в переменную окружения BRAVE_API_KEY.

Таким образом, MCP-сервера предоставляют вашему ИИ-агенту расширенные возможности через стандартизированный протокол, позволяющий интегрировать разные внешние сервисы и инструменты для работы с реальным миром.

Устойчивость к ошибкам

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

@retry_on_failure(max_retries=2, delay=1.0) async def process_message(self, user_input: str, thread_id: str = "default") -> str:     """Обработка сообщения пользователя с повторными попытками"""     try:         config = {"configurable": {"thread_id": thread_id}}         message_input = {"messages": [HumanMessage(content=user_input)]}          response = await self.agent.ainvoke(message_input, config)         return response["messages"][-1].content      except Exception as e:         error_msg = f"❌ Ошибка обработки: {e}"         logger.error(error_msg)         return error_msg 

Декоратор @retry_on_failure автоматически повторяет операции при временных сбоях. Пользователь даже не заметит, что что-то пошло не так.

Контекстная память

if self.config.use_memory:     self.checkpointer = InMemorySaver()     logger.info("Память агента включена") 

Агент помнит всю историю разговора. Вы можете сказать «создай файл config.py«, затем «добавь в него настройки базы данных», и агент поймёт, что речь идёт о том же файле. Это кардинально меняет пользовательский опыт.

Умный системный промпт

def _get_system_prompt(self) -> str:     base_prompt = (         "Ты — умный AI-ассистент, который помогает пользователю работать с файлами "         "и искать информацию в интернете. Всегда внимательно анализируй запрос "         "и выбирай наиболее подходящий инструмент."     )      if self.config.enable_web_search:         base_prompt += (             " Если требуется найти актуальные данные (погоду, новости, цены), "             "обязательно используй веб-поиск и предоставляй самую свежую информацию."         )      return base_prompt 

Промпт адаптируется к возможностям агента. Если веб-поиск отключён, агент не будет предлагать найти что-то в интернете. Если включён — будет активно использовать эту возможность.

🚀 Практические сценарии использования

Работа с кодом:

> Создай структуру проекта Flask с папками templates, static и models ✅ Создание папок... 📁 Создал директорию /project/templates 📁 Создал директорию /project/static 📁 Создал директорию /project/models 📄 Создал базовый app.py с настройками Flask 

Актуальная информация:

> Какая сейчас погода в Москве? 🔍 Ищу актуальную информацию о погоде... 🌤️ В Москве сейчас +25°C, переменная облачность, ветер 3 м/с 

Анализ и обработка файлов:

> Прочитай все .py файлы в папке и создай документацию 📖 Читаю Python файлы... 📄 Найдено 5 файлов: app.py, models.py, views.py, utils.py, config.py 📝 Создаю документацию на основе анализа кода... ✅ Документация сохранена в README.md 

Ключевые отличия от простого чат-бота

  1. Реальные действия — агент не просто говорит, что делать, а делает сам

  2. Контекстная память — помнит всю историю и может ссылаться на предыдущие действия

  3. Актуальные данные — может получать свежую информацию из интернета

  4. Обработка ошибок — graceful degradation при проблемах с инструментами

  5. Адаптивность — поведение зависит от доступных инструментов

От примера к продакшену

Этот код демонстрирует архитектурные паттерны для создания продакшн-готовых агентов:

  • Модульная конфигурация — легко менять поведение без изменения кода

  • Абстракция провайдеров — поддержка множества LLM из коробки

  • Graceful error handling — система не падает при проблемах

  • Расширяемость — новые инструменты добавляются декларативно

  • Наблюдаемость — подробное логирование для отладки

Итоги: от теории к практике ИИ-агентов

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

Что мы освоили

1. Фундаментальные концепции

  • Разобрались с различием между чат-ботами и настоящими ИИ-агентами

  • Поняли роль MCP (Model Context Protocol) как моста между моделью и внешним миром

  • Изучили архитектуру LangGraph для построения сложной логики агентов

2. Практические навыки

  • Настроили рабочее окружение с поддержкой облачных и локальных моделей

  • Создали агента-классификатора с асинхронной архитектурой и управлением состояниями

  • Построили MCP-агента с доступом к файловой системе и веб-поиску

3. Архитектурные паттерны

  • Научились проектировать графы состояний для сложной логики

  • Освоили модульную конфигурацию и фабрики моделей

  • Внедрили обработку ошибок и retry-механизмы для продакшн-готовых решений

Ключевые преимущества подхода

LangGraph + MCP дают нам:

  • Прозрачность — каждый шаг агента документирован и отслеживаем

  • Расширяемость — новые возможности добавляются декларативно

  • Надёжность — встроенная обработка ошибок и восстановление

  • Гибкость — поддержка множества моделей и провайдеров из коробки

Практическая ценность

Созданные примеры — это не просто демонстрация технологий. Это готовые решения для реальных задач:

  • Агент-классификатор можно внедрить в HR-платформы и биржи фриланса

  • MCP-агент подходит для автоматизации рабочих процессов и анализа данных

  • Архитектурные паттерны масштабируются на проекты любой сложности

Уровень подготовки

Сегодня я намеренно не усложнял материал глубокими техническими деталями и сложной терминологией. Цель была простая — преодолеть базовый порог входа в тему интеграции ИИ-агентов с собственными проектами.

Этой информации достаточно, чтобы:

  • Понять принципы работы современных ИИ-агентов

  • Начать экспериментировать с собственными решениями

  • Оценить потенциал технологий для ваших задач

  • Сделать осознанный выбор инструментов для проекта

Что дальше?

Мы затронули лишь верхушку айсберга. LangGraph и MCP предлагают гораздо более широкие возможности:

  • Мультиагентные системы — координация команд специализированных агентов

  • Продвинутые MCP-серверы — интеграция с базами данных, CRM, API сервисов

  • Сложные графы состояний — циклы, условные переходы, параллельное выполнение

  • Production deployment — масштабирование, мониторинг, A/B тестирование

Где найти больше материалов

Полный исходный код всех примеров, а также эксклюзивные материалы, которые я не публикую на Хабре, вы найдёте в моём телеграм-канале «Легкий путь в Python».

Обратная связь важна

Если вам интересны более глубокие темы:

  • LLM и ИИ-агенты в интеграции с собственными проектами

  • MCP-серверы и создание кастомных инструментов

  • Детальное рассмотрение LangGraph — продвинутые паттерны и оптимизации

Дайте знать своим:

  • 👍 Лайком этой статьи

  • 📱 Подпиской на телеграм-канал

  • 💬 Комментарием с вопросами и предложениями тем

Ваша активность показывает, что тема востребована, и мотивирует создавать ещё больше качественного контента.

Заключение

ИИ-агенты — это не футуристическая фантастика, а реальная технология сегодняшнего дня. С помощью LangGraph и MCP мы можем создавать системы, которые решают конкретные бизнес-задачи, автоматизируют рутину и открывают новые возможности.

Главное — начать. Возьмите код из примеров, адаптируйте под свои задачи, экспериментируйте. Каждый проект — это новый опыт и шаг к мастерству в области ИИ-разработки.

Удачи в ваших проектах!


Amvera Cloud – облако для простого запуска проектов со встроенным CI/CD (деплой идёт через Git или загрузку файлов в интерфейсе), встроенным проксированием до ведущих LLM и собственным инференсом LLaMA. Вам не нужно думать о настройке NGINX, виртуальных машин и другой инфраструктуры. Зарегистрируйтесь и получите 111 рублей на тест.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Показать как собрать собственный MCP-сервер?

88.89% Конечно!8
11.11% Возможно…1
0% Нет0

Проголосовали 9 пользователей. Воздержавшихся нет.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Хотите более детальный разбор LangChain | LangGraph?

90% Конечно!9
10% Возможно…1
0% Нет0

Проголосовали 10 пользователей. Воздержавшихся нет.

ссылка на оригинал статьи https://habr.com/ru/articles/929568/