3. Создание вашего первого потокового CLI-чата
В главе 1 вы сделали свой первый вызов LLM и увидели, как появляется полный ответ сразу. В главе 2 вы изучили концептуальные основы агентного ИИ и поняли, зачем существует LangChain. Теперь пришло время создать что-то практическое: потоковое чат-приложение, которое ощущается отзывчивым и профессиональным.
Почему важна потоковая передача(streaming): Когда вы задаёте LLM сложный вопрос, ожидание 10-30 секунд полного ответа кажется неработающим. Потоковая передача позволяет токенам появляться по мере их генерации, создавая естественный разговорный поток. Эта глава создаёт CLI-чат-приложение с потоковым выводом, правильным управлением конфигурацией, возможностями отладки и надёжной обработкой ошибок.
Что вы создадите: К концу этой главы у вас будет рабочий скрипт chat.py, который:
- Передаёт ответы LLM токен за токеном в терминал
- Безопасно загружает API-ключи из переменных окружения
- Обрабатывает различные типы моделей (чат-модели vs модели рассуждения) с соответствующими параметрами
- Предоставляет инструменты отладки для проверки того, что фактически отправляется в LLM
- Корректно обрабатывает распространённые ошибки (отсутствующие API-ключи, сетевые сбои, некорректные входные данные)
3.1) Создание рабочей папки и установка пакетов
Прежде чем писать какой-либо код, вам нужна чистая структура проекта и правильные зависимости. Этот раздел устанавливает основу для поддерживаемого Python-проекта.
Структура проекта
Создайте новую директорию для вашего чат-приложения:
mkdir langchain-chat
cd langchain-chatНастройка окружения Python
Создайте виртуальное окружение для изоляции зависимостей:
# Создаём виртуальное окружение
python -m venv venv
# Активируем его (macOS/Linux)
source venv/bin/activate
# Активируем его (Windows)
venv\Scripts\activateЗачем виртуальные окружения? LangChain имеет много зависимостей (например, OpenAI SDK, Pydantic, асинхронные библиотеки). Виртуальное окружение обеспечивает:
- Чистоту системного Python
- Возможность использования разных версий LangChain в разных проектах
- Воспроизводимость зависимостей (через
requirements.txt)
Вы увидите (venv) в приглашении терминала, когда окружение активировано.
Установка LangChain
Установите основные пакеты LangChain:
pip install langchain-core==1.2.7 langchain-openai==1.1.7 python-dotenvРазбор пакетов:
langchain-core: Основные абстракции (сообщения, промпты, цепочки, runnable)langchain-openai: Реализации, специфичные для OpenAI (ChatOpenAI, эмбеддинги)python-dotenv: Загружает переменные окружения из файлов.env
Примечание о версии: Эта книга использует LangChain 1.2.x по состоянию на январь 2026 года. Если вы читаете это в будущем, проверьте документацию LangChain для последней версии.
Проверка установки
Создайте простой тест для подтверждения, что всё работает:
# test_install.py
try:
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
print("✓ langchain-core: OK")
print("✓ langchain-openai: OK")
print("\nУстановка успешна!")
except ImportError as e:
print(f"✗ Импорт не удался: {e}")
print("Убедитесь, что ваше виртуальное окружение активировано.")Запустите его:
python test_install.pyОжидаемый вывод:
✓ langchain-core: OK
✓ langchain-openai: OK
Установка успешна!Если вы видите "Установка успешна!", вы готовы продолжить. Если вы получаете ошибку импорта, дважды проверьте, что:
- Ваше виртуальное окружение активировано (ищите
(venv)в приглашении) - Пакеты были установлены успешно (попробуйте выполнить
pip list)
Создание requirements.txt
Вы только что установили пакеты с помощью команд pip install. Хотя это работает для обучения, есть лучший способ: файлы requirements.txt. Это стандартная практика в Python-проектах по нескольким причинам:
Зачем использовать requirements.txt?
- Воспроизводимость: Другие (или вы через 6 месяцев) могут установить точно те же версии пакетов
- Чёткое управление зависимостями: Видно с первого взгляда, какие пакеты нужны вашему проекту
- Совместная работа в команде: Члены команды используют идентичные версии, избегая проблем "работает на моей машине"
- Автоматизация: Серверы или CI/CD-пайплайны могут настроить окружение одной строкой:
pip install -r requirements.txt
Создайте файл requirements.txt в корне проекта:
# requirements.txt
langchain-core==1.2.7
langchain-openai==1.1.7
python-dotenvОбратите внимание на синтаксис:
==1.2.7фиксирует точную версию (рекомендуется для воспроизводимости)- Отсутствие спецификатора версии (как
python-dotenv) устанавливает последнюю стабильную версию - Строки, начинающиеся с
#, являются комментариями
Теперь любой может установить все зависимости одной командой:
pip install -r requirements.txtЭто намного лучше, чем вводить каждый пакет по отдельности. Если коллега клонирует ваш проект, ему нужно только:
- Создать виртуальное окружение
- Выполнить
pip install -r requirements.txt
Не нужно запоминать названия или версии пакетов — всё в файле.
Структура вашего проекта
После завершения этого раздела ваша папка должна выглядеть так:
langchain-chat/
├── venv/ # Виртуальное окружение (не коммитить в git)
├── requirements.txt # Список зависимостей
└── test_install.py # Скрипт проверки установкиДалее: Раздел 3.2 показывает, как безопасно загружать API-ключи с использованием файлов .env.
3.2) Переменные окружения с .env
API-ключи — это секреты. Жёсткое кодирование их в вашем коде — это риск безопасности (особенно если вы коммитите в git). Этот раздел показывает стандартный подход: переменные окружения, загружаемые из файла .env.
Зачем переменные окружения?
Проблема с жёстко закодированными ключами:
# ❌ НИКОГДА НЕ ДЕЛАЙТЕ ТАК
llm = ChatOpenAI(api_key="sk-proj-abc123...")Если вы закоммитите этот код на GitHub, ваш API-ключ станет публичным. Любой может использовать его, накрутить расходы на вашем аккаунте или получить отзыв вашего ключа.
Решение: Храните секреты в переменных окружения, загружайте их во время выполнения.
Создание файла .env
Создайте файл .env в корне проекта:
# .env
OPENAI_API_KEY=sk-proj-your-actual-key-hereПолучите ваш API-ключ:
- Перейдите на platform.openai.com/api-keys
- Создайте новый секретный ключ
- Скопируйте его немедленно (вы не сможете просмотреть его снова)
- Вставьте его в ваш файл
.env, заменивsk-proj-your-actual-key-here
Критический шаг безопасности: Прежде чем делать что-либо ещё, защитите ваш API-ключ от коммита в git.
Создайте файл .gitignore в корне проекта и добавьте эти строки:
# .gitignore
venv/
__pycache__/
*.pyc
.envСтрока .env говорит git игнорировать ваш файл с API-ключом. Это предотвращает случайный коммит секретов в систему контроля версий.
Структура вашего проекта теперь:
langchain-chat/
├── venv/
├── .env # Ваш API-ключ (игнорируется git)
├── .gitignore # Содержит: .env, venv/, и т.д.
├── requirements.txt
└── test_install.pyЗагрузка переменных окружения
Пакет python-dotenv загружает файлы .env в os.environ:
# chat.py
import os
from dotenv import load_dotenv
# Загружаем файл .env
load_dotenv()
# Получаем доступ к переменным окружения
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
raise ValueError("OPENAI_API_KEY не найден в окружении")
print(f"API-ключ загружен: {api_key[:8]}...") # Показываем только первые 8 символовКак работает load_dotenv():
- Ищет файл
.env, начиная с места, откуда вы запускаете скрипт - Читает каждую строку в формате
KEY=value - Добавляет каждую переменную в
os.environ - Если переменная уже установлена (например, вашей хостинг-платформой), она не будет перезаписана — существующее значение остаётся
Использование API-ключа с LangChain
Реализации OpenAI в LangChain (ChatOpenAI и т.д.) автоматически ищут OPENAI_API_KEY в os.environ:
from langchain_openai import ChatOpenAI
load_dotenv()
# Это автоматически использует os.environ["OPENAI_API_KEY"]
llm = ChatOpenAI(model="gpt-4o-mini")Соглашение LangChain: Когда вы создаёте ChatOpenAI() без параметра api_key, он автоматически ищет OPENAI_API_KEY в окружении. Это стандартный паттерн во всех интеграциях LangChain.
Явный API-ключ (для тестирования или нескольких ключей):
llm = ChatOpenAI(
model="gpt-4o-mini",
api_key=os.environ.get("OPENAI_API_KEY")
)Это полезно, когда у вас несколько API-ключей (разработка vs продакшен) или вы хотите быть явным о том, какой ключ используется.
Переменные окружения в продакшене
В продакшен-окружениях (облачные платформы, Docker-контейнеры) вы не используете файлы .env. Вместо этого вы настраиваете переменные окружения через настройки платформы:
- Docker: Используйте флаг
-eпри запуске контейнеров - Облачные платформы: Устанавливайте переменные окружения в панелях конфигурации
- CI/CD: Используйте инструменты управления секретами
Важная часть: ваш код не меняется. os.environ.get("OPENAI_API_KEY") работает одинаково, независимо от того, откуда приходит переменная — из файла .env или облачной платформы. Мы подробно рассмотрим развёртывание в последующих главах.
Проверка вашей настройки
Чтобы подтвердить, что всё работает, вы можете протестировать код загрузки переменных окружения, показанный ранее. Если ваш файл .env правильно настроен, os.environ.get("OPENAI_API_KEY") вернёт ваш API-ключ.
Если os.environ.get("OPENAI_API_KEY") возвращает None, проверьте, что:
- Вы вызвали
load_dotenv()перед доступом к переменной окружения .envсуществует в корне проектаOPENAI_API_KEY=sk-proj-...правильно записан в.env- Вы запускаете из корневой директории проекта
Далее: Раздел 3.3 реализует фактический цикл чата с потоковым выводом.
3.3) Реализация цикла чата с потоковым выводом
Теперь вы создадите основной цикл чата. Этот раздел вводит потоковую передачу(streaming) — ключевое различие между медленным чат-ботом и отзывчивым.
Понимание потоковой передачи
Без потоковой передачи (подход главы 1):
response = llm.invoke("Напиши эссе на 500 слов об ИИ")
print(response.content) # Ждём 20 секунд, затем появляется всё эссеС потоковой передачей:
for chunk in llm.stream("Напиши эссе на 500 слов об ИИ"):
print(chunk.content, end="", flush=True) # Токены появляются по мере генерацииПочему важна потоковая передача:
- Немедленная обратная связь: Вместо того чтобы смотреть на пустой экран 20 секунд, вы видите появляющиеся слова сразу
- Ощущение естественного разговора: Как при разговоре с человеком — ответы приходят постепенно, а не все сразу
- Экономия времени и денег: Если LLM начинает давать неправильный ответ, вы можете остановить его рано, вместо того чтобы ждать полного (бесполезного) ответа
- Лучшая отладка: При создании приложений вы можете замечать проблемы (например, ошибки форматирования) по мере их возникновения, а не после долгого ожидания
Что на самом деле представляет собой потоковая передача: Потоковая передача — это инкрементальная доставка того же текста ответа. Она не раскрывает скрытое рассуждение или внутренние процессы модели — она просто показывает вам частичный вывод по мере его поступления из API. Думайте об этом как о загрузке файла: вы видите прогресс по мере поступления частей, но содержимое файла одинаково, независимо от того, загружаете ли вы его целиком или по частям.
Примечание о границах частей: Части не гарантированно выравниваются по словам или предложениям. API отправляет токены небольшими пакетами для эффективности, поэтому часть может быть "При", "вет! Как", " я мо", "гу вам", " помочь", "?". Это нормально и ожидаемо — не пытайтесь извлекать смысл из отдельных частей.
Базовый цикл чата
Вот минимальный цикл чата с потоковой передачей:
# chat.py
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
def main():
load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini")
print("Чат начат. Введите 'quit' или 'exit' для остановки.\n")
while True:
user_input = input("Вы: ")
if user_input.lower() in ["quit", "exit"]:
print("До свидания!")
break
print("Ассистент: ", end="", flush=True)
for chunk in llm.stream([HumanMessage(content=user_input)]):
print(chunk.content, end="", flush=True)
print("\n")
if __name__ == "__main__":
main()Как это работает:
while True:: Бесконечный цикл для непрерывного разговораinput("Вы: "): Получаем ввод пользователя из терминалаllm.stream([HumanMessage(...)]): Потоковая передача ответа LLM- Потоковый вывод со специальными параметрами:
end="": Не добавлять новую строку после каждой части (держит вывод на одной строке)flush=True: Принудительный немедленный вывод в терминал без буферизации
Почему [HumanMessage(content=user_input)]?
Чат-модели LangChain ожидают список сообщений, а не простую строку. Каждое сообщение имеет роль:
- HumanMessage: Ввод пользователя
- AIMessage: Ответ LLM
- SystemMessage: Инструкции для LLM (рассматривается в главе 4)
Даже для одного сообщения пользователя вы передаёте список: [HumanMessage(content="Привет")].
Ключевое ограничение — Односторонние разговоры: Этот цикл чата намеренно не сохраняет состояние. Каждый запрос отправляет только текущее сообщение, а не предыдущую историю разговора. Это означает:
- LLM не будет помнить, что вы спрашивали раньше
- Уточняющие вопросы типа "А какое у него население?" не будут работать после вопроса "Какая столица Франции?"
- Это фундаментальная характеристика LLM — у них нет памяти, если вы явно не предоставляете контекст
Пример ограничения:
Вы: Какая столица Франции?
Ассистент: Париж.
Вы: Какое у него население?
Ассистент: У меня недостаточно контекста. О каком городе вы спрашиваете?Цикл while True обеспечивает непрерывность UX (вы можете продолжать общаться), но каждый ход независим. В главе 8: Мы реализуем память разговора, сохраняя и повторно отправляя историю сообщений с каждым запросом.
Запуск цикла чата
python chat.pyПример взаимодействия:
Чат начат. Введите 'quit' или 'exit' для остановки.
Вы: Что такое LangChain?
Ассистент: LangChain — это фреймворк для разработки приложений на основе языковых моделей. Он предоставляет инструменты для управления промптами, цепочек, агентов и памяти.
Вы: Дай мне простой пример
Ассистент: Вот базовый пример: ...
Вы: quit
До свидания!Понимание API потоковой передачи
Что такое "часть"?
Каждая часть — это объект AIMessageChunk с:
content: Сгенерированные текстовые токеныresponse_metadata: Информация о модели, количество токенов и т.д.
for chunk in llm.stream([HumanMessage(content="Привет")]):
print(f"Часть: {chunk}")
print(f"Содержимое: {chunk.content}")
print(f"Тип: {type(chunk)}")Вывод:
Часть: content='Привет' response_metadata={'model_provider': 'openai', ...}
Содержимое: Привет
Тип: <class 'langchain_core.messages.ai.AIMessageChunk'>
Часть: content='!' response_metadata={...}
Содержимое: !
Тип: <class 'langchain_core.messages.ai.AIMessageChunk'>
Часть: content=' Как' response_metadata={...}
Содержимое: Как
Тип: <class 'langchain_core.messages.ai.AIMessageChunk'>Накопление полного ответа
Иногда вам нужен полный ответ (для логирования, тестирования или дальнейшей обработки):
def chat_with_accumulation():
load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini")
user_input = input("Вы: ")
full_response = ""
print("Ассистент: ", end="", flush=True)
for chunk in llm.stream([HumanMessage(content=user_input)]):
print(chunk.content, end="", flush=True)
full_response += chunk.content
print("\n")
# Теперь у вас есть полный ответ
print(f"[DEBUG] Длина полного ответа: {len(full_response)} символов")
return full_responseЭтот паттерн распространён, когда вам нужно:
- Сохранить разговор в базу данных
- Разобрать ответ для структурированных данных
- Рассчитать использование токенов или затраты
Структура вашего проекта после этого раздела:
langchain-chat/
├── venv/
├── .env
├── .gitignore
├── requirements.txt
├── test_install.py
└── chat.py # Цикл чата с потоковой передачей (новое!)Далее: Раздел 3.4 показывает, как обрабатывать различные типы моделей с умной конфигурацией параметров.
3.4) Умная конфигурация: Обработка параметров для моделей рассуждения vs чат-моделей
OpenAI предлагает два типа моделей с различными возможностями и механизмами управления:
Чат-модели (gpt-4o, gpt-4o-mini):
- Быстрые и разговорные
- Поддерживают
temperatureдля управления случайностью и креативностью - Лучше всего для общих задач, креативного письма, рутинного кодирования
Модели рассуждения (o1, o3, GPT-5):
- Медленнее, но более логичные и последовательные
- НЕ поддерживают
temperature(используют внутреннее рассуждение вместо этого) - Лучше всего для сложной математики, многошагового планирования, формального анализа
Ключевое различие: Чат-модели используют вероятностную выборку (вы контролируете случайность), в то время как модели рассуждения используют детерминированную внутреннюю логику (модель контролирует свой собственный процесс рассуждения).
Понимание температуры (только для чат-моделей)
Что такое температура?
Температура — это число между 0.0 и 2.0, которое контролирует, насколько креативными будут ответы модели. При низких значениях (около 0) вы получаете последовательные, предсказуемые ответы. При высоких значениях (около 2.0) вы получаете креативные, разнообразные ответы. Думайте об этом как о "регуляторе креативности".
Как это работает: При генерации каждого слова модель видит много возможных следующих слов с разными вероятностями. Температура влияет на то, как модель выбирает:
- Низкая температура (0.0): Почти всегда выбирает слово с наивысшей вероятностью → последовательные, сфокусированные ответы
- Высокая температура (2.0): Более вероятно выберет слова с меньшей вероятностью → разнообразные, креативные ответы
Важно: Температура работает только с чат-моделями (gpt-4o, gpt-4o-mini). Она не применяется к моделям рассуждения (GPT-5, o1, o3), которые используют внутреннюю логику вместо вероятностной выборки.
Руководство по значениям температуры:
-
0.0: Высоко детерминированный, сфокусированный и последовательный
- Использовать для: фактических вопросов-ответов, рутинной генерации кода, структурированного вывода
- Один и тот же ввод → почти идентичный вывод каждый раз
- Пример: "Сколько будет 2+2?" → Всегда "4"
-
0.7–1.0: Стандартное поведение выборки (по умолчанию 1.0)
- Использовать для: общего разговора, объяснений, сбалансированных ответов
- Умеренная вариация в формулировках и примерах
- Пример: "Объясни фотосинтез" → Разные формулировки каждый раз, та же основная информация
-
1.2–2.0: Более креативный и разнообразный, менее предсказуемый
- Использовать для: креативного письма, мозгового штурма, генерации идей
- Высокая вариация в тоне, структуре и формулировках
- Пример: "Напиши стихотворение о луне" → Очень разные стили каждый раз
Примечание: Значения выше 1.0 увеличивают креативность, но могут снизить фактическую точность и связность. Максимальное значение — 2.0.
Пример: Влияние температуры (только для чат-моделей)
# Температура 0.0 - детерминированный, один и тот же ответ каждый раз
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.0)
response = llm.invoke([HumanMessage(content="Сколько будет 2+2?")])
print(response.content) # Вывод: 4
# Температура 1.0 - поведение по умолчанию, возможна небольшая вариация
llm = ChatOpenAI(model="gpt-4o-mini", temperature=1.0)
response = llm.invoke([HumanMessage(content="Сколько будет 2+2?")])
print(response.content) # Вывод: 4 (может включать краткое объяснение)Для закрытых, фактических вопросов температура мало влияет на правильность.
Для открытых или креативных задач температура значительно влияет на разнообразие, тон и стиль.
Что если вы используете параметры чат-модели на моделях рассуждения?
Это зависит от модели — некоторые отклоняют это, другие молча игнорируют:
# ❌ Это не сработает с моделями o3
llm = ChatOpenAI(model="o3-mini", temperature=0.7)Ошибка:
BadRequestError: Temperature is not supported with this modelРазные модели, разные политики:
- Модели o1 / o3: Явно отклоняют неподдерживаемые параметры. Если включена температура, API немедленно возвращает ошибку 400 BadRequest.
- Модели GPT-5: Более снисходительны — параметр принимается, но молча игнорируется. Ваш запрос успешен, но температура не имеет эффекта.
Почему это важно: Всегда проверяйте, какую модель вы используете, и настраивайте параметры соответственно. Использование неправильных параметров может либо вызвать ошибки, либо молча не сработать, тратя время на отладку.
Как контролировать поведение моделей рассуждения
Теперь вы знаете, что чат-модели используют temperature, а модели рассуждения — нет. Так как же контролировать модели рассуждения?
Модели рассуждения настраиваются через дизайн промпта, а не параметры:
- Модели рассуждения не предоставляют
temperatureили подобные элементы управления - Вместо этого вы направляете поведение тем, как вы пишете промпт:
- Явные инструкции: "Думай пошагово", "Покажи свою работу"
- Ограничения как правила: "Ты не должен предполагать...", "Всегда проверяй..."
- Структурированные требования: "Выведи в формате JSON", "Включи рассуждение перед ответом"
- Логика принятия решений: "Если условие A, то делай X, иначе делай Y"
Пример: Параметры чата vs Промпты рассуждения
# ❌ Подход чата - не будет работать с моделями рассуждения
llm = ChatOpenAI(model="o3-mini", temperature=0.5)
# Ошибка: BadRequestError: Temperature is not supported
# ✅ Подход рассуждения - направляем через структуру промпта
prompt = """
Реши эту задачу пошагово:
1. Укажи, что ты знаешь
2. Покажи свои вычисления
3. Проверь свой ответ
Задача: Если x + 5 = 12, чему равен x?
"""
llm = ChatOpenAI(model="o3-mini")
response = llm.invoke([HumanMessage(content=prompt)])
print(response.content)Вывод:
1. Что я знаю: x + 5 = 12
2. Вычисления: x = 12 - 5 = 7
3. Проверка: 7 + 5 = 12 ✓
Ответ: x = 7Ключевое понимание: Чат-модели контролируются параметрами, модели рассуждения контролируются промптами.
Таблица принятия решений по выбору модели
Теперь, когда вы понимаете, как контролировать оба типа моделей, вот когда использовать каждый:
| Тип задачи | Рекомендуемая модель | Почему |
|---|---|---|
| Общий разговор | gpt-4o-mini | Быстрая, недорогая, разговорная |
| Простые вопросы-ответы | gpt-4o-mini | Достаточно для фактического поиска |
| Креативное письмо | gpt-4o-mini (temp 0.8–1.0) | Температура обеспечивает креативность |
| Генерация кода | GPT-5 | Лучшее логическое планирование |
| Сложное рассуждение | GPT-5 | Оптимизирована для многошаговой логики |
| Математические задачи | o3 / o1 | Специализированные модели рассуждения |
| Многошаговое планирование | GPT-5 | Сильна в долгосрочном планировании |
| Формальный анализ (юридический/политический) | o3 | Строго детерминированная |
Компромиссы стоимости и задержки
Понимание практических компромиссов помогает вам выбрать правильную модель для вашего случая использования:
| Тип модели | Скорость (Типичная задержка) | Стоимость (Относительная) | Лучше всего для |
|---|---|---|---|
| gpt-4o-mini | Очень быстро (<2с) | Очень низкая | Общий разговор, простые задачи |
| gpt-4o | Быстро (1–4с) | Средняя | Чат более высокого качества, мультимодальные задачи |
| GPT-5 | Умеренно (3–8с) | Высокая | Сложное рассуждение, планирование |
| o1 / o3 | Медленнее всего (5–15с+) | Наивысшая | Детерминированное рассуждение, формальная логика |
Примечания:
- Скорость отражает типичную задержку ответа (варьируется в зависимости от длины промпта и сложности)
- Стоимость — это относительное сравнение — проверьте текущие цены на сайте OpenAI
- Модели рассуждения обменивают скорость и стоимость на последовательность и правильность
- Чат-модели приоритизируют отзывчивость и эффективность
Когда использовать модели рассуждения (GPT-5, o1, o3):
- Многошаговые математические и STEM(Science, Technology, Engineering, Mathematics)-задачи, требующие правильных промежуточных шагов
- Сложный логический анализ с зависимостями и ограничениями
- Отладка кода с несколькими взаимодействующими причинами
- Задачи планирования с множеством правил, граничных случаев или компромиссов
- Рабочие процессы агентов, требующие последовательности и долгосрочного мышления
Когда использовать чат-модели (gpt-4o, gpt-4o-mini):
- Общий разговор и интерактивный чат
- Простые вопросы-ответы с ограниченной глубиной рассуждения
- Генерация контента (блоги, резюме, креативное письмо)
- Рутинная генерация кода и шаблонные задачи
- Приложения, где скорость и стоимость важнее глубокого рассуждения
Далее: Раздел 3.5 показывает техники отладки для проверки того, что фактически отправляется в LLM.
3.5) Отладка: Проверка ответов и использования токенов
Когда ваш LLM ведёт себя неожиданно, вам нужно видеть точно, что было отправлено и получено. Этот раздел показывает, как проверять вызовы LLM и отлаживать проблемы.
Почему важна отладка
Распространённые сценарии отладки:
- "Почему LLM дал этот ответ?" → Проверьте точный промпт
- "Сколько стоил этот запрос?" → Проверьте использование токенов
- "Почему это так медленно?" → Измерьте задержку
- "Правильно ли форматирование моего сообщения?" → Проверьте структуру сообщения
Проблема: Когда вы вызываете llm.invoke(), вы получаете объект ответа. Но что на самом деле в нём? Какая информация доступна для отладки?
Понимание объекта ответа
Прежде чем отлаживать, вам нужно понять, что возвращает llm.invoke().
Базовая структура:
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Привет")])
# Что в ответе?
print(type(response)) # AIMessage
print(response.content) # Фактический текст
print(response.response_metadata) # Использование токенов, информация о модели и т.д.Вывод:
<class 'langchain_core.messages.ai.AIMessage'>
Привет! Как я могу помочь вам сегодня?
{
'token_usage': {
'completion_tokens': 9,
'prompt_tokens': 8,
'total_tokens': 17
},
'model_name': 'gpt-4o-mini-2024-07-18',
'finish_reason': 'stop',
...
}Ключевые части ответа:
response.content: Текст, сгенерированный LLMresponse.response_metadata: Словарь с:token_usage: Сколько токенов было использовано (для расчёта стоимости)model_name: Точная версия модели, которая ответилаfinish_reason: Почему генерация остановилась (см. раздел Режим отладки для деталей)
Доступ к использованию токенов:
token_usage = response.response_metadata['token_usage']
print(f"Токены промпта: {token_usage['prompt_tokens']}")
print(f"Токены ответа: {token_usage['completion_tokens']}")
print(f"Всего: {token_usage['total_tokens']}")Вывод:
Токены промпта: 8
Токены ответа: 9
Всего: 17Почему это важно: Вам нужны эти значения для отладки, отслеживания затрат и оптимизации ваших промптов.
Расчёт затрат на основе использования токенов
Использование токенов определяет стоимость. Каждая модель имеет разные цены:
GPT-4o-mini (по состоянию на январь 2026):
- Ввод: $0.15 за 1M токенов
- Вывод: $0.60 за 1M токенов
GPT-4o:
- Ввод: $2.50 за 1M токенов
- Вывод: $10.00 за 1M токенов
Функция расчёта стоимости:
def calculate_cost(token_usage, model_name):
"""Рассчитывает стоимость на основе использования токенов."""
prompt_tokens = token_usage.get('prompt_tokens', 0)
completion_tokens = token_usage.get('completion_tokens', 0)
# Цены за 1M токенов (по состоянию на январь 2026)
pricing = {
'gpt-4o-mini': {'input': 0.15, 'output': 0.60},
'gpt-4o': {'input': 2.50, 'output': 10.00},
'gpt-5': {'input': 1.25, 'output': 10.00},
}
if model_name not in pricing:
return None
input_cost = (prompt_tokens / 1_000_000) * pricing[model_name]['input']
output_cost = (completion_tokens / 1_000_000) * pricing[model_name]['output']
return input_cost + output_cost
# Пример
response = llm.invoke([HumanMessage(content="Объясни квантовые вычисления")])
token_usage = response.response_metadata['token_usage']
cost = calculate_cost(token_usage, "gpt-4o-mini")
print(f"Стоимость: ${cost:.6f}")Вывод:
Стоимость: $0.000123Почему это важно: Продакшен-приложения могут обрабатывать 50,000+ запросов/день. При $0.002 за запрос это $3,000/месяц. Используйте неправильную модель или раздутые промпты, и затраты подскочат до $30,000/месяц. Баг в цикле повторных попыток может сжечь тысячи за ночь. Отслеживайте использование токенов с первого дня.
Включение режима отладки (когда вам нужны детали сырого API)
Объект ответа и пользовательская обёртка обрабатывают большинство потребностей отладки. Но иногда вам нужно видеть точно, что LangChain отправляет в OpenAI — сырой JSON-запрос и ответ.
Когда вам это может понадобиться:
- Отладка форматирования сообщений LangChain
- Проверка правильности установки параметров API
- Исследование неожиданных ошибок API
- Понимание точной полезной нагрузки API
LangChain имеет встроенное логирование отладки через langchain_core.globals:
from langchain_core.globals import set_debug
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
set_debug(True)
# Теперь все вызовы LLM будут печатать информацию отладки
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Привет")])Вывод:
[llm/start] [llm:ChatOpenAI] Entering LLM run with input:
{
"prompts": [
"Human: Привет"
]
}
[llm/end] [llm:ChatOpenAI] [1.45s] Exiting LLM run with output:
{
"generations": [
[
{
"text": "Привет! Как я могу помочь вам сегодня?",
"generation_info": {
"finish_reason": "stop",
"logprobs": null
},
"type": "ChatGeneration",
...
}
]
],
"llm_output": {
"token_usage": {
"completion_tokens": 9,
"prompt_tokens": 8,
"total_tokens": 17,
...
},
"model_provider": "openai",
"model_name": "gpt-4o-mini-2024-07-18",
...
},
}Примечание: Формат вывода варьируется в зависимости от провайдера LLM. Этот пример показывает структуру OpenAI.
Что раскрывает вывод отладки:
Режим отладки показывает полный поток коммуникации LangChain → OpenAI:
1. Преобразование формата сообщения:
# Ваш код
[HumanMessage(content="Привет")]
# Что вы видите в выводе отладки
{
"prompts": ["Human: Привет"]
}Режим отладки показывает, как LangChain представляет ваше сообщение внутренне перед отправкой в LLM.
2. Статус завершения генерации:
"finish_reason": "stop"Почему генерация закончилась:
"stop": Модель завершила ответ естественным образом"length": Ответ был обрезан, потому что достиг лимита max_tokens"tool_calls": Модель завершила генерацию, создав инструкции вызова инструмента вместо финального текстового ответа (Глава 12)"content_filter": Ответ был заблокирован или подавлен из-за правил безопасности или модерации контента
Если вы видите "length", увеличьте max_tokens, чтобы получить полный ответ.
3. Разбивка использования токенов:
"token_usage": {
"completion_tokens": 9,
"prompt_tokens": 8,
"total_tokens": 17,
"completion_tokens_details": {
"reasoning_tokens": 0 # Для моделей рассуждения (o1/o3 и т.д.)
},
"prompt_tokens_details": {
"cached_tokens": 0 # Кэширование промптов (экономит затраты)
}
}Помимо базовых подсчётов, вы можете видеть:
- reasoning_tokens: Внутренние шаги рассуждения (только для моделей рассуждения)
- cached_tokens: Сколько токенов промпта было обслужено из кэша (снижает стоимость)
4. Версия модели и отпечаток:
"model_name": "gpt-4o-mini-2024-07-18",
"system_fingerprint": "fp_8bbc38b4db"- model_name: Точная версия снимка (объясняет, почему ответы меняются со временем)
- system_fingerprint: ID конфигурации бэкенда OpenAI (меняется, когда они обновляют системы)
5. Время запроса:
[llm/end] [llm:ChatOpenAI] [1.56s][1.45s] показывает общую длительность запроса — полезно для выявления медленных запросов.
Далее: Раздел 3.6 показывает, как корректно обрабатывать распространённые ошибки.
3.6) Обработка сбоев (Симуляция и исправление распространённых ошибок)
Продакшен-приложения LLM сталкиваются с предсказуемыми режимами сбоев: отсутствующие учётные данные, тайм-ауты сети, ограничения скорости и некорректные входные данные. Этот раздел показывает, как корректно обрабатывать эти ошибки и создавать надёжные приложения с первого дня.
Шесть распространённых ошибок
1. Отсутствующий API-ключ
Когда это происходит: Вы пытаетесь создать экземпляр ChatOpenAI, но OPENAI_API_KEY не установлен в вашем окружении.
Пример:
# Файл .env не существует, или OPENAI_API_KEY не определён
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Привет")])Ошибка, которую вы увидите:
OpenAIError: The api_key client option must be set either by passing api_key to the client or by setting the OPENAI_API_KEY environment variableКак исправить:
- Проверьте, что ваш файл
.envсуществует в корне проекта - Убедитесь, что имя ключа точно
OPENAI_API_KEY(распространённая опечатка:OPENAPI_KEY) - Убедитесь, что
load_dotenv()вызывается перед созданием LLM
2. Неправильный API-ключ
Когда это происходит: Ваш файл .env содержит недействительный, истёкший или неправильно скопированный API-ключ.
Пример:
# .env содержит: OPENAI_API_KEY=sk-invalid-key-12345
llm = ChatOpenAI(model="gpt-4o-mini")
response = llm.invoke([HumanMessage(content="Привет")])Ошибка, которую вы увидите:
AuthenticationError: Incorrect API key providedКак исправить:
- Перейдите на https://platform.openai.com/api-keys
- Убедитесь, что ваш ключ всё ещё активен (не отозван или истёк)
- Сгенерируйте новый ключ при необходимости
- Скопируйте весь ключ аккуратно (распространённая ошибка: пропущены первые/последние символы)
- Вставьте в
.envбез лишних пробелов:
OPENAI_API_KEY=sk-proj-exactkeyhere3. Сетевые сбои
Когда это происходит: Ваше интернет-соединение прерывается, или серверы OpenAI временно недоступны во время запроса.
Пример:
# WiFi отключается в середине запроса, или API OpenAI недоступен
response = llm.invoke([HumanMessage(content="Привет")])Ошибка, которую вы увидите:
APIConnectionError: Connection errorКак исправить:
- Проверьте ваше интернет-соединение
- Проверьте статус OpenAI на https://status.openai.com
4. Ограничения скорости
Когда это происходит: Вы отправляете слишком много запросов за короткое время и превышаете вашу квоту API.
Пример:
# Отправка 1000 запросов мгновенно
for i in range(1000):
llm.invoke([HumanMessage(content=f"Запрос {i}")])Ошибка, которую вы увидите:
RateLimitError: Rate limit reached for requestsКак исправить:
- Проверьте ваши ограничения скорости на https://platform.openai.com/account/limits
- Обновите ваш план, если вам нужны более высокие лимиты
- Используйте пакетную обработку для больших рабочих нагрузок (рассматривается в главе 6)
5. Неверное имя модели
Когда это происходит: Вы указываете имя модели, которое не существует или недоступно в вашем плане.
Пример:
llm = ChatOpenAI(model="gpt-99-ultra") # Не существует
response = llm.invoke([HumanMessage(content="Привет")])Ошибка, которую вы увидите:
NotFoundError: The model `gpt-99-ultra` does not exist or you do not have access to itКак исправить:
- Проверьте доступные модели в вашем плане на https://platform.openai.com/docs/models
6. Превышен лимит токенов
Когда это происходит: Ваш промпт слишком длинный и превышает максимальное контекстное окно модели.
Пример:
# Создание промпта на 1 миллион символов
huge_prompt = "x" * 1_000_000
response = llm.invoke([HumanMessage(content=huge_prompt)])Ошибка, которую вы увидите:
BadRequestError: This model's maximum context length is 128000 tokens. However, your messages resulted in 250000 tokens.Как исправить:
- Проверьте длину ввода перед отправкой
- Знайте лимиты вашей модели:
- gpt-4o-mini: 128K токенов
- gpt-4o: 128K токенов
- gpt-5: 400K токенов
- Для длинных документов используйте разбиение на части или суммаризацию (рассматривается в главе 9)
Следующие шаги: Глава 4 показывает, как проектировать переиспользуемые шаблоны промптов, которые отделяют промпт-инжиниринг от кода приложения.