На фоне ажиотажа вокруг нейросетей все чаще встает вполне приземленный вопрос — сколько стоит содержать собственную LLM.
Современные ИИ-агенты уровня Claude, ChatGPT и DeepSeek уже давно перестали быть «чатами для развлечения». Это сложные системы, которые перед тем как выдать ответ, тратят десятки тысяч токенов на внутренние рассуждения, вызывают внешние функции, взаимодействуют с MCP-серверами и даже работают напрямую с интерфейсом ОС.
В продакшене — особенно при использовании нескольких агентов, собственных инструментов и фоновых задач — потребление токенов растет лавинообразно. При плотной нагрузке счет за API легко превращается в постоянную и плохо прогнозируемую статью расходов, от которой уже сложно отмахнуться.
Статья подготовлена автором Telegram-канала «Легкий путь в Python» Алексеем Яковенко.
Как сократить расходы на использование LLM, не потеряв в качестве
Самый очевидный ответ — развернуть собственную нейросеть на сервере и использовать ее как облачный API.
Однако здесь нас ждет стоп-фактор. Большинство мощных open-source языковых моделей требуют десятки гигабайт видеопамяти. 24, 48, а иногда и 80 ГБ VRAM — а это уже совсем другой бюджет на инфраструктуру, который подходит далеко не всем. Вся архитектура будет максимально прагматичной и ориентированной на продакшен.
Из каких компонентов состоит решение
Прежде чем писать команды и поднимать сервер, нужно зафиксировать, что именно мы строим. Важно понимать: речь идет не о запуске «локального чата», а о сборке небольшой, но полноценной инфраструктуры.
В итоговом варианте решение представляет собой несколько логических компонентов. В основе лежит LLM-сервер, отдельный сервис, отвечающий исключительно за инференс модели: прием запросов, генерацию ответов, а также работу с инструментами и MCP.
MCP (Model Context Protocol) — открытый протокол, позволяющий подключать к агенту внешние сервисы как инструменты. Вместо того чтобы писать обертки для каждого API вручную, можно использовать готовые MCP-серверы.
Между моделью и внешним миром расположен инференс-движок — слой, отвечающий за производительность, управление видеопамятью и API. Именно на этом уровне используется vLLM, подробнее о нем чуть ниже.
Для корректного доступа к сервису извне применяется reverse proxy. Он отвечает за SSL, доменное имя и создает задел для будущего масштабирования.
Поверх этого располагается FastAPI-приложение — отдельный бэкенд-слой с прикладной логикой. Здесь сосредоточено управление входящими запросами, обработка вызовов инструментов, интеграция MCP-серверов, а также контроль доступа и ограничений.
Такое разделение позволяет воспринимать LLM как обычный бэкенд-сервис, а не как монолит, в который положили все подряд.
Какой стек будем использовать
Возьмем приземленный и знакомый большинству Python-разработчиков стек:
- Linux-сервер с GPU;
- инференс-движок для LLM, vLLM;
- Nginx, FastAPI как основной бэкенд;
- MCP-серверы и инструменты — в качестве внешних или встроенных компонентов.
Никаких специфичных ML-фреймворков или редких зависимостей — все легко поддерживается и масштабируется.
Почему именно vLLM, а не классический запуск через transformers?
Если вы когда-либо поднимали LLM локально через transformers, то знаете, что такой подход хорошо подходит для экспериментов, но плохо масштабируется в сервисном режиме.
Если к модели обратятся сразу несколько человек, видеопамять начнет тормозить и вам придется вручную прикручивать API и придумать, как не «уронить» сервер при высокой нагрузке.
vLLM создан как раз для таких задач. Он хорошо держит несколько одновременных запросов, а также API, совместимый с OpenAI-стилем. Он справляется со сложными функциями и позволяет засунуть серьезные модели даже в обычную видеокарту на 16 ГБ VRAM. Проще говоря, vLLM — это инференс-движок, заточенный под продакшен.
Подготовка инфраструктуры
Переходим к практике. На этом этапе арендуем VPS с GPU и сразу привязываем к нему домен. Это позволит дальше не тратить время на настройку DNS.
Даже если вы никогда раньше не работали с VPS и доменами — ничего страшного. Повторяя шаги ниже, вы без проблем справитесь.
Домен
Чтобы наша система могла работать как полноценный «персональный ИИ-инстанс», нам необходимо открыть возможность отправки внешних запросов к нашему Qwen3.
При этом обращаться напрямую к IP-адресу с портом — не лучшая идея. Вместо этого мы будем использовать доменное имя с HTTPS. Без шифрования ваши промты и API-ключи будут лежать в сети в открытом виде, а современные браузеры и сервисы просто откажутся работать с «небезопасным» IP. Плюс, если сервер переедет, вам не придется переписывать конфиги во всех приложениях.
Шаги по регистрации домена:
Регистрируйте аккаунт в панели управления, если у вас его еще нет. Открывайте вкладку Продукты, далее Домены. Переходите в раздел регистрации доменных имен и нажимайте кнопку Зарегистрировать домен.

Проверьте доступность доменного имени, если имя свободно — нажимайте Оформить.

Заполните контактные данные администратора домена и завершите регистрацию. После этого доменное имя переходит в ваше распоряжение.

Далее переходите в раздел Доменные зоны и нажимайте Добавить зону.

Ожидайте, пока домен получит статус «Делегирован». Изменения вносятся в реестр за один рабочий час, но для полной работы DNS-серверов может потребоваться до 24 часов.

Этот статус означает, что мы можем полноценно управлять DNS-записями домена.
На этом этапе этих шагов более чем достаточно. К доменному имени мы еще вернемся позже — когда будем настраивать Nginx на VPS-сервере и привязывать домен к нашему vLLM-проекту, превращая его в полноценную точку входа для удаленных запросов.
VPS сервер с GPU 16 ГБ
В рамках статьи я сознательно буду показывать запуск проекта на бюджетной конфигурации с GPU 16 ГБ. Этого более чем достаточно для демонстрации, тестирования и личного использования.
Если же вы планируете использовать модель в продакшене с высокой или средней нагрузкой, большим количеством одновременных запросов и длинным контекстом — в этом случае стоит сразу смотреть на более мощное железо. Но логика настройки при этом останется той же.
Сейчас нам нужно арендовать VPS-сервер с GPU, получить к нему доступ и подготовить его к дальнейшей настройке.
Я остановился на следующем варианте конфигурации облачного сервера:
- процессор — четыре виртуальных ядра;
- оперативная память — 32 ГБ;
- накопитель — 100 ГБ SSD;
- графический ускоритель — одна видеокарта NVIDIA Tesla T4 с 16 ГБ видеопамяти.
Это сбалансированная и доступная конфигурация, которая отлично подходит для запуска Qwen3-14B в квантованном виде и работы через vLLM.
Как по шагам создать свой VPS
Переходим в в панель управления, открываем раздел Инфраструктура → Облачные серверы.

Кликаем на Создать сервер.

В качестве операционной системы выбираем Ubuntu 24.04. Она лучше всего адаптирована под работу с GPU, драйверами NVIDIA и современным ML-стеком (и идет с установленными драйверами на видеокарту).

Выбираем конфигурацию. Я возьму для тестов GPU с Tesla T4 (16 ГБ).

Добавим к стандартным 20 ГБ SSD диска еще 80 ГБ, чтобы хватило места для модели, кэша Hugging Face, а также для тяжелых зависимостей vLLM в виртуальном окружении.

Добавим SSH-ключ для входа на сервер без ввода пароля.

Создание SSH-ключа — инструкция для всех ОС
Инструкция для Windows:
Для современных Windows 10/11 лучше всего использовать встроенный клиент:
# Встроенный OpenSSH (Win 10/11)
ssh-keygen -t ed25519 -C "ваш@email.com"
# Или через PowerShell
New-Item -ItemType Directory -Force ~/.ssh
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""
Альтернатива: PuTTYgen (скачать с putty.org) → Generate → Save public/private key.
Инструкция для macOS / Linux:
Здесь все стандартно, используем терминал:
# Самый безопасный (рекомендую)
ssh-keygen -t ed25519 -C "ваш@email.com"
# Или RSA (совместимость со старыми системами)
ssh-keygen -t rsa -b 4096 -C "ваш@email.com"
Где лежат ключи:
- публичный (его мы копируем в панель облака):
~/.ssh/id_ed25519.pub; - приватный (это ваш пароль, его нельзя никому показывать):
~/.ssh/id_ed25519.
Как быстро скопировать ключ в буфер обмена:
# macOS
pbcopy < ~/.ssh/id_ed25519.pub
# Linux (xclip)
xclip -sel clip < ~/.ssh/id_ed25519.pub
# Windows PowerShell
Get-Content ~/.ssh/id_ed25519.pub | Set-Clipboard
Готово. Теперь вставляем публичный ключ в панель управления облака.


На этом этапе сервер готов к работе.
Связываем доменное имя с VPS сервером
Если на данном этапе у вас есть доменное имя и активирована доменная зона, то процесс связки доменного имени с сервером займет пару минут.
Переходим в Доменные зоны.

Проваливаемся внутрь записи и привязываем публичный IP-адрес сервера к домену, как указано ниже.

Оставляем имя группы пустым, если хотите, чтоб вызов нейросети шел по домену второго уровня.
Настройка VPS-сервера
Перед запуском LLM-модели необходимо подготовить VPS-сервер: установить базовые зависимости, проверить драйверы NVIDIA и поставить окружение для запуска vLLM. Приведем сервер в рабочее состояние.
Подключение к серверу
Ввод пароля не требуется, так как мы на предыдущем этапе уже связали свою локальную машину с VPS сервером через публичный ключ.

Обновление системы и установка базовых зависимостей
Для начала обновим пакеты и установим необходимый минимум:
sudo apt update && sudo apt install -y \
nginx \
certbot \
python3 \
python3-pip \
python3-venv \
python3-certbot-nginx

Что здесь важно:
- Python 3 + venv — основа для запуска vLLM;
- Nginx и certbot понадобятся позже для HTTPS и проксирования.
Мы закладываем инфраструктуру сразу, без необходимости возвращаться к этому этапу.
Драйверы NVIDIA
Если вы выбирали выделенный сервер с Ubuntu 24 в панели управления Selectel, то необходимости в отдельной установке драйверов на видеокарту у вас не будет.
Убедимся, что драйверы установлены, командой: nvidia-smi.
Если команда выводит информацию о Tesla T4 — значит, драйвер установлен и видеокарта готова к работе.

Дополнительно установим CUDA Toolkit из репозитория Ubuntu/Debian. Нам это нужно для компиляции CUDA-кода, работы с GPU и выполнения операций на видеокарте.
apt install -y nvidia-cuda-toolkit
Подготовка проекта и виртуального окружения
Создадим рабочую директорию под наш проект и разместим ее в домашнем каталоге:
cd ../home
mkdir vllm_qwen3
cd vllm_qwen3
Дальше создаем виртуальное окружение Python и активируем его:
python3 -m venv .venv
source .venv/bin/activate
Использование виртуального окружения позволяет:
- изолировать зависимости проекта;
- избежать конфликтов с системными пакетами;
- упростить дальнейшее сопровождение сервиса.
Устанавливаем инференс-движок vLLM: pip install vllm.

Запуск Qwen3 через vLLM на Tesla T4
Мы запускаем Qwen3-14B-AWQ на доступной Tesla T4 (16 ГБ VRAM) с 4-битной квантовкой AWQ и аккуратно подобранными лимитами памяти и контекста.
Цель — получить продакшн-совместимый OpenAI-API сервер и не прибегнуть к производительным A100/H100.
Генерация API-ключа
Для защиты API потребуется токен, получим его командой: openssl rand -hex 32.
Токен будет выглядеть примерно так: c8a9f7d2e4b1a6c3f9d8e7b2a4c6f1d3e5b7a9c2f4d6e8b1a3c5f7d9e2b4a6c8
Этот ключ потребуется передавать в заголовке:
Authorization: Bearer ВАШ_КЛЮЧ
Команда запуска
vllm serve Qwen/Qwen3-14B-AWQ \
--host 0.0.0.0 \
--port 8002 \
--gpu-memory-utilization 0.96 \
--max-model-len 8000 \
--max-num-seqs 1 \
--dtype half \
--enforce-eager \
--trust-remote-code \
--enable-prefix-caching \
--enable-chunked-prefill \
--quantization awq \
--served-model-name qwen3-14b \
--api-key "ВАШ_КЛЮЧ" \
--disable-log-requests \
--enable-auto-tool-choice \
--tool-call-parser hermes \
--chat-template-content-format string

Разбор параметров — что и за что отвечает
Модель Qwen/Qwen3-14B-AWQ
Как я уже говорил, используется уже квантованная версия модели в формате AWQ. На T4 это позволяет:
- уместить 14B модель в 16 ГБ VRAM;
- сохранить приемлемое качество;
- получить хорошую скорость инференса.
Сетевые параметры
--host 0.0.0.0— сервис доступен извне (не только localhost);--port 8002— порт OpenAI-совместимого API.
Память и производительность
--gpu-memory-utilization 0.96
vLLM может использовать до 96% VRAM. Позволяет максимально эффективно задействовать T4, не доводя до OOM.
--max-model-len 8000
Максимальный контекст — 8 000 токенов.
На T4 это практически верхняя безопасная граница при 4-битной модели. Больше — риск нехватки памяти при длинных диалогах.
--max-num-seqs 1
Одновременно обрабатывается один активный запрос. Это стабилизирует потребление памяти, исключает резкие скачки VRAM и подходит для личного сервера или low-load продакшена.
Если планируется многопользовательская нагрузка — параметр, как и мощность железа, нужно будет пересматривать.
--dtype half
Используется FP16 для вычислений (где это применимо). Оптимальный баланс скорости и стабильности для T4.
--enforce-eager
Принудительный eager-режим. На T4 это часто работает стабильнее, чем полностью компилируемый граф.
Оптимизации скорости
--enable-prefix-caching
Кэширует общий префикс запроса и сильно ускоряет диалоги, повторяющиеся системные промты, агентные сценарии.
--enable-chunked-prefill
Разбивает длинный ввод на части при чтении запроса. Позволяет избежать пикового потребления памяти при больших промтах.
Безопасность и интеграция
--api-key "..."
Ограничивает доступ к серверу. Без ключа запросы будут отклоняться.
--disable-log-requests
Не логирует тексты запросов в stdout. Важно для приватности и продакшн-развертываний.
Инструменты (Tool Calling)
--enable-auto-tool-choice
Позволяет модели автоматически выбирать инструмент.
--tool-call-parser hermes
Используется Hermes-парсер для корректной обработки tool-calls.
Это важно, если планируется:
- LangChain;
- агентные сценарии;
- вызов внешних функций.
Прочее
--trust-remote-code
Разрешает загрузку кастомного кода модели с HuggingFace. Необходим для корректной работы Qwen.
--served-model-name qwen3-14b
Имя, которое будет отображаться в API. Именно его будут видеть клиенты OpenAI-формата.
--chat-template-content-format string
Формат передачи контента в шаблон чата. Обеспечивает совместимость с OpenAI-стилем сообщений.
Что происходит после запуска
Скачивание модели происходит автоматически (8–9 ГБ, один раз), после чего ее загрузка в VRAM занимает около 20–40 секунд. Первый запрос может выполняться до 1–2 минут из-за нормального процесса компиляции графа. Однако все последующие ответы будут приходить практически мгновенно.
Не пугайтесь долгого первого старта, это стандартное поведение.

Что мы получили:
- 14B модель;
- 4-битная квантовка;
- 8 000 токенов контекста;
- OpenAI-совместимый API;
- Полная работа на одной Tesla T4.
Без A100, без 80 ГБ VRAM и без космического бюджета.
Теперь модель доступна по адресу:
http://SERVER_IP:8002
Быстрый тест API
Когда сервер поднялся, проверим, что все работает. Для этого необходимо открыть новый терминал и там выполнить вход на VPS сервер.
Если мы введем команду nvidia-smi, то увидим, что наша модель заняла практически весь размер видеопамяти:

Выполним простой запрос через curl:
curl http://localhost:8002/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer z64bf3f7f93601wZez6a621381axqfagzaaa0fd79c589aX2eaaadsr1a0cdd0491a2ea" \
-d '{
"model": "qwen3-14b",
"messages": [
{
"role": "user",
"content": "Привет! Кто тебя создал такого?"
}
],
"max_tokens": 256
}'

Сразу видим, что модель из коробки поддерживает режим Thinking. То есть кроме обычного ответа и доп данных, мы можем видеть процесс размышления модели. Бывает очень полезно в некоторых сценариях.
Если вы не хотите тратить время на получение размышлений, то можно легко эти самые размышления отключить.
Пример запроса:
curl http://localhost:8002/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer z64bf3f7f93601wZez6a621381axqfagzaaa0fd79c589aX2eaaadsr1a0cdd0491a2ea" \
-d '{
"model": "qwen3-14b",
"messages": [
{
"role": "user",
"content": "Привет! Кто ты?"
}
],
"max_tokens": 256,
"chat_template_kwargs": {
"enable_thinking": false
}
}'

Запуск через systemd
Для постоянной работы сервиса запускать его вручную — плохая идея. Поэтому сразу оформим все через systemd.
Создаем сервисный файл командой: sudo nano /etc/systemd/system/vllm-qwen3.service.
Пример конфигурации:
[Unit]
Description=vLLM Qwen3-14B Server
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
User=root
WorkingDirectory=/home/vllm_qwen3
Environment="PATH=/home/vllm_qwen3/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Environment="CUDA_VISIBLE_DEVICES=0"
ExecStart=/home/vllm_qwen3/.venv/bin/python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen3-14B-AWQ \
--host 0.0.0.0 \
--port 8002 \
--gpu-memory-utilization 0.96 \
--max-model-len 8000 \
--max-num-seqs 1 \
--dtype half \
--enforce-eager \
--trust-remote-code \
--enable-prefix-caching \
--enable-chunked-prefill \
--quantization awq \
--served-model-name qwen3-14b \
--api-key ВАШ_ТОКЕН\
--disable-log-requests \
--enable-auto-tool-choice \
--tool-call-parser hermes \
--chat-template-content-format string
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=vllm-qwen3
[Install]
WantedBy=multi-user.target
Активируем сервис:
# Перезагрузить systemd
sudo systemctl daemon-reload
# Включить автозапуск
sudo systemctl enable vllm-qwen3.service
# Запустить сервис
sudo systemctl start vllm-qwen3.service
# Проверить статус
sudo systemctl status vllm-qwen3.service
# Посмотреть логи
sudo journalctl -u vllm-qwen3.service -f

Теперь LLM будет автоматически подниматься вместе с сервером и переживать перезапуски.
Настраиваем Nginx и HTTPS
Превратим наш локальный LLM-сервис в настоящую облачную нейросеть, к которой можно подключаться с любого места — точно так же, как к ChatGPT или DeepSeek.
На данный момент у нас уже должно быть доменное имя, к которому привязана А-запись на домен второго уровня с привязкой к IP-адресу нашего сервера.

Конфигурация Nginx
Создадим конфигурационный файл:
nano /etc/nginx/sites-available/qwen3-vllm.ru
Содержимое:
server {
listen 80;
server_name ВАШ ДОМЕН (в моем случае qwen3-vllm.ru);
# Важно для долгих ответов и стриминга
proxy_read_timeout 600s;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
# Для больших промтов
client_max_body_size 50M;
location / {
proxy_pass http://127.0.0.1:8002;
proxy_http_version 1.1;
# WebSocket для стриминга
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Отключаем буферизацию для SSE
proxy_buffering off;
proxy_cache off;
}
}
Активация конфига и получение SSL сертификата
# Активируем сайт
ln -s /etc/nginx/sites-available/qwen3-vllm.ru /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
# Проверяем конфиг
nginx -t
# Перезапускаем Nginx
systemctl restart nginx

Подключаем HTTPS
Устанавливаем certbot (если еще не установлен):
sudo apt install -y certbot python3-certbot-nginx
Получаем SSL-сертификат
certbot --nginx -d ВАШ ДОМЕН --non-interactive --agree-tos --email your-email@example.com
Например:
certbot --nginx -d qwen3-vllm.ru --non-interactive --agree-tos --email ivanov_invan@gmail.com
Certbot автоматически включит HTTPS, настроит редирект и добавит автообновление сертификата.

Проверяем, что все работает
Открываем в браузере https://ВАШ_ДОМЕН/docs. В моем случае это: https://qwen3-vllm.ru/docs.

Так как vLLM использует FastAPI под капотом, вы сразу увидите Swagger-документацию. Это отличный знак — значит сервис полностью доступен извне.
На этом этапе у нас есть полностью рабочая облачная LLM с HTTPS-доступом и API, совместимым с OpenAI. Прежде чем двигаться дальше и добавлять прикладную бизнес-логику, имеет смысл проверить, как эта модель ведет себя в реальных сценариях.
Практика: тестируем LLM как API-сервис
Все примеры ниже выполняются на локальном компьютере. GPU, Docker и сложная инфраструктура не требуются — мы работаем с LLM как с обычным внешним API.
Подготовка локального окружения
Создадим отдельное виртуальное окружение для клиентского кода на своей локальной машине:
mkdir llm-client
cd llm-client
python3 -m venv .venv
source .venv/bin/activate
Установим минимальный набор зависимостей:
pip install langchain langchain-openai langgraph langchain-mcp-adapters pydantic-settings httpx
Или, если вы клонировали репозиторий: pip install -r requirements.txt.
Далее начинается достаточно много примеров кода на Python. Для того, чтобы было проще ориентироваться по тексту далее, можете клонировать репозиторий, в котором находится полный исходник.
Конфигурация
Для подключения к LLM нам нужны три параметра: токен, адрес сервера и название модели. Хранить их в коде — плохая практика, поэтому вынесем все в .env-файл:
CLIENT_TOKEN=ВАШ_ТОКЕН
CLIENT_BASE_URL=https://qwen3-vllm.ru
LLM_MODEL_NAME=qwen3-14b
А считывать будем через pydantic-settings — это дает валидацию и типизацию из коробки: from pydantic_settings import BaseSettings, SettingsConfigDict.
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
)
client_token: str
client_base_url: str
llm_model_name: str
settings = Settings()
Если забыть указать любую из переменных — приложение падает при старте с понятной ошибкой валидации, а не где-то посреди работы с None вместо токена.
Пример-1: чат-агент
Начнем с минимального варианта — агент без инструментов, просто разговор с моделью. Это позволяет убедиться, что подключение работает, модель отвечает адекватно, а задержка приемлемая.
import asyncio
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from config import settings
def create_model(
temperature: float = 0.7,
max_tokens: int = 1024,
enable_thinking: bool = False,
) -> ChatOpenAI:
base_url = settings.client_base_url.rstrip("/")
extra_body = None
if not enable_thinking:
extra_body = {
"chat_template_kwargs": {"enable_thinking": False}
}
return ChatOpenAI(
model=settings.llm_model_name,
api_key=settings.client_token,
base_url=f"{base_url}/v1",
temperature=temperature,
max_completion_tokens=max_tokens,
extra_body=extra_body,
)
async def main():
model = create_model()
agent = create_agent(
model=model,
tools=[],
system_prompt="Ты полезный ассистент. Отвечай кратко и по делу на русском языке.",
)
print("Простой агент запущен. Введите 'exit' для выхода.\n")
while True:
user_input = input("Вы: ")
if user_input.strip().lower() in ("exit", "quit", "выход"):
break
result = await agent.ainvoke(
{"messages": [{"role": "user", "content": user_input}]}
)
messages = result.get("messages", [])
for msg in reversed(messages):
if msg.type == "ai" and msg.content:
print(f"\nАссистент: {msg.content}\n")
break
if __name__ == "__main__":
asyncio.run(main())
Ключевой момент — ChatOpenAI из LangChain работает с любым OpenAI-совместимым API. Мы просто подставляем base_url нашего vLLM-сервера, и все подключение готово. Никаких специальных клиентов или SDK не нужно.
Параметр extra_body с enable_thinking: False отключает «режим размышления» у моделей, которые его поддерживают (например, QwQ). Для простого чата он не нужен, а ответы приходят быстрее.
Запускаем через команду: python -m example.simple_agent.

Пример-2: агент с инструментами
Простой чат — это хорошо, но настоящая сила агентов в том, что они могут использовать инструменты. Модель сама решает, когда и какой инструмент вызвать, на основе запроса пользователя.
Определим несколько инструментов через декоратор @tool:
from langchain_core.tools import tool
import httpx
from datetime import datetime, timezone, timedelta
@tool
def get_current_time() -> str:
"""Возвращает текущую дату и время по Москве (UTC+3)."""
moscow_tz = timezone(timedelta(hours=3))
now = datetime.now(moscow_tz)
return now.strftime("%d.%m.%Y %H:%M:%S (МСК)")
@tool
async def get_random_joke() -> str:
"""Возвращает случайный анекдот/шутку на английском языке."""
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get("https://official-joke-api.appspot.com/random_joke")
resp.raise_for_status()
data = resp.json()
return f"{data['setup']}\n— {data['punchline']}"
@tool
async def get_random_quote() -> str:
"""Возвращает случайную вдохновляющую цитату."""
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get("https://zenquotes.io/api/random")
resp.raise_for_status()
data = resp.json()[0]
return f'«{data["q"]}»\n— {data["a"]}'
@tool
async def get_random_fact() -> str:
"""Возвращает случайный интересный факт."""
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get(
"https://uselessfacts.jsph.pl/api/v2/facts/random?language=en"
)
resp.raise_for_status()
data = resp.json()
return data["text"]
Обратите внимание: docstring каждого инструмента — это не просто документация для разработчика. Именно по этому описанию модель понимает, что делает инструмент и когда его стоит вызвать. Чем точнее описание, тем лучше модель выбирает нужный инструмент.
Теперь передаем инструменты агенту:
async def main():
model = create_model()
tools = [get_current_time, get_random_joke, get_random_quote, get_random_fact]
agent = create_agent(
model=model,
tools=tools,
system_prompt=(
"Ты полезный ассистент с доступом к инструментам:\n"
"- get_current_time — узнать текущее московское время\n"
"- get_random_joke — получить случайную шутку\n"
"- get_random_quote — получить случайную цитату\n"
"- get_random_fact — получить случайный интересный факт\n\n"
"Используй инструменты когда это уместно. "
"Данные от API приходят на английском — переводи их на русский в ответе."
),
)
tool_names = [t.name for t in tools]
print(f"Агент с инструментами запущен: {', '.join(tool_names)}")
print("Введите 'exit' для выхода.\n")
while True:
user_input = input("Вы: ")
if user_input.strip().lower() in ("exit", "quit", "выход"):
break
result = await agent.ainvoke(
{"messages": [{"role": "user", "content": user_input}]}
)
messages = result.get("messages", [])
for msg in messages:
if msg.type == "ai" and hasattr(msg, "tool_calls") and msg.tool_calls:
for tc in msg.tool_calls:
print(f" 🔧 Вызов: {tc['name']}({tc['args']})")
if msg.type == "tool":
print(f" 📎 Результат: {msg.content[:200]}")
for msg in reversed(messages):
if msg.type == "ai" and msg.content and not msg.tool_calls:
print(f"\nАссистент: {msg.content}\n")
break
Логика процесса устроена так: LangChain через LangGraph строит цикл, «модель → инструмент → модель». Когда пользователь спрашивает «Который час?», модель не пытается угадать время — она возвращает вызов get_current_time(). LangGraph выполняет функцию, передает результат обратно модели, и та формирует финальный ответ.
Запуск: python -m example.agent_with_tools.
Попробуйте спросить: «Расскажи шутку и интересный факт» — модель вызовет оба инструмента и скомбинирует результаты в один ответ.

Пример-3: агент с MCP-серверами
В этом примере мы подключаем два MCP-сервера:
- fetch — загрузка и парсинг веб-страниц по URL;
- filesystem — чтение, запись и навигация по файловой системе.
И комбинируем их с нашими кастомными инструментами из предыдущего примера.
from langchain_mcp_adapters.client import MultiServerMCPClient
MCP_SERVERS = {
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"],
"transport": "stdio",
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user"],
"transport": "stdio",
},
}
MCP-серверы запускаются как дочерние процессы и общаются с клиентом по stdio. Библиотека langchain-mcp-adapters автоматически превращает все доступные операции MCP-сервера в LangChain-инструменты:
async def main():
mcp_client = MultiServerMCPClient(MCP_SERVERS)
mcp_tools = await mcp_client.get_tools()
custom_tools = [get_current_time, get_random_joke, get_random_quote, get_random_fact]
all_tools = mcp_tools + custom_tools
model = create_model()
agent = create_agent(
model=model,
tools=all_tools,
system_prompt=(
"Ты ассистент с доступом к инструментам.\n\n"
"MCP инструменты:\n"
"- fetch — загрузка и чтение веб-страниц по URL\n"
"- filesystem — чтение, запись, поиск файлов и навигация по каталогам\n\n"
"Кастомные инструменты:\n"
"- get_current_time — текущее московское время\n"
"- get_random_joke — случайная шутка\n"
"- get_random_quote — случайная цитата\n"
"- get_random_fact — случайный интересный факт\n\n"
"Отвечай на русском языке. Данные от API на английском — переводи."
),
)
…
Запуск командой: python -m example.agent_with_mcp.
Теперь агент может, например, прочитать веб-страницу и пересказать ее содержание, или найти файл на диске и показать его содержимое — все в рамках одного диалога.
Попробуем с запросом: «прочитай эту статью https://ru.wikipedia.org/wiki/Qwen и сделай ее саммари с основными тезисами в файл summary.md».

Заключение
Мы убедились, что LLM доступна по API, корректно отвечает на вопросы, умеет вызывать инструменты и работать с внешними сервисами через MCP. Все это — с локального компьютера, GPU и без сложной настройки.
Следующий логичный шаг — обернуть эту логику в полноценный бэкенд на FastAPI, чтобы агент стал доступен не только из терминала, но и через HTTP-эндпоинты для фронтенда или других сервисов, чем мы и займемся в следующей части.
Поделитесь в комментариях: используете ли вы квантованные модели в продакшене и, если да, какое семейство моделей (Qwen, Llama, Mistral) лучше всего показало себя в ваших задачах?