Введение в LangChain: цепочки и модели | Курс по LangChain урок 2
Цель урока
Вы поймёте, что такое LangChain и зачем он нужен, научитесь делать вызовы LLM, строить первые цепочки с помощью LCEL и стримить ответы модели в реальном времени.
Необходимые знания:
- Python на среднем уровне
- Базовое понимание того, что такое API и HTTP запросы
- Установленный Python 3.10+
Ключевые концепции:
- LangChain как фреймворк для работы с LLM
- Runnable интерфейс
- LCEL (LangChain Expression Language)
- Chain - цепочка обработки данных
- ChatModel vs LLM
- Сообщения: HumanMessage, AIMessage, SystemMessage
Зачем нужен LangChain
Когда разработчик впервые начинает работать с языковыми моделями, первая мысль это использовать API напрямую. Это выглядит примерно так:
import openai
client = openai.OpenAI(api_key="...")
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Объясни, что такое рекурсия"}]
)
print(response.choices[0].message.content)
Для одного запроса это работает. Проблемы начинаются, когда нужно:
- передавать результат одного вызова в следующий
- добавить шаблоны промптов с переменными
- парсить структурированный вывод из текста
- добавить память и контекст диалога
- подключить внешние инструменты
Каждую из этих задач можно решить вручную, но это много кода, который придется поддерживать. LangChain предоставляет абстракции для этих задач.
Важный момент: LangChain - это не замена работы с API. Это слой абстракций, который решает конкретные инженерные задачи при построении приложений с LLM. Если ваша задача это один запрос к модели, LangChain избыточен.
Как устроен LangChain
LangChain построен вокруг одной центральной идеи, и здесь важно разграничить версии.
Раньше (v0.x) фреймворк строился вокруг Runnable как ключевой абстракции.
Все основные компоненты модели, промпты, парсеры, реализовывали единый интерфейс с методом .invoke(), что позволяло соединять их через оператор |.
prompt | model | parser
Каждый компонент получает данные, обрабатывает их и передает следующему. Это и есть LCEL (LangChain Expression Language).
Сейчас (v1+) фокус сместился на агентов. Runnable остался в langchain-core как низкоуровневый примитив для компоновки,
но центральной абстракцией стали агенты на базе LangGraph с поддержкой persistence, streaming и HITL.
# v1+: агенты
from langchain.agents import create_agent
agent = create_agent(
model="gpt-4o-mini",
tools=[search_web],
system_prompt="Помощник с инструментами"
)
result = agent.invoke({"messages": [{"role": "user", "content": "Привет!"}]})
Таким образом, идея единого интерфейса Runnable не устарела, она просто опустилась на уровень ниже, став строительным блоком для более высокоуровневых агентских абстракций.
Далее в курсе мы будем затрагивать тему создание агентов с помощью
create_agent. В примерах будет использоваться LCEL, это не влияет на суть и смысл использования инструментов. Вы это же сможете сделать с использованием агентов. Подробнее об агентах, буду рассказывать в новом курсе.
Первый вызов LLM с помощью LangChain
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
load_dotenv()
# Модель берём из .env, меняете там, а не в коде
model = ChatOpenAI(model=os.getenv("MODEL_NAME", "gpt-4o"))
# Простой вызов
response = model.invoke("Объясни, что такое рекурсия")
print(response.content)
Метод .invoke() возвращает объект AIMessage, у которого есть атрибут .content со строкой ответа.
LangChain v1: У
AIMessageпоявилось свойство.text(без скобок), синоним.content. Старый метод.text()со скобками устарел. В курсе используется.content— это корректно и является рекомендованным подходом.
Типы сообщений
LangChain работает с LLM через структурированные сообщения. Есть три основных типа:
SystemMessage- инструкция для модели, задает контекст и поведениеHumanMessage- сообщение от пользователяAIMessage- ответ модели
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
load_dotenv()
model = ChatOpenAI(model=os.getenv("MODEL_NAME", "gpt-4o"))
messages = [
SystemMessage(content="Ты опытный Python-разработчик. Отвечай кратко и по делу."),
HumanMessage(content="Что такое генератор в Python?"),
]
response = model.invoke(messages)
print(response.content)
# response - это AIMessage
Когда передаете простую строку в .invoke(), LangChain автоматически оборачивает её в HumanMessage. Явное использование типов сообщений дает больше контроля.
LCEL: строим первую цепочку
Теперь добавим шаблон промпта и соединим компоненты в цепочку:
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
load_dotenv()
model = ChatOpenAI(model=os.getenv("MODEL_NAME", "gpt-4o"))
# Создаем шаблона промпта
prompt = ChatPromptTemplate.from_messages([
("system", "Ты опытный разработчик. Объясняй технические концепции четко и с примерами кода."),
("human", "Объясни концепцию: {concept}"),
])
# Собираем цепочку через оператор |
chain = prompt | model
# Вызываем цепочку, передаем словарь с переменными шаблона
response = chain.invoke({"concept": "замыкание в Python"})
print(response.content)
Что происходит при вызове chain.invoke({"concept": "замыкание в Python"}):
promptполучает словарь, подставляет значения в шаблон, возвращает список сообщенийmodelполучает список сообщений, делает запрос к API, возвращаетAIMessage
Оператор | - это не просто синтаксический сахар. Он создает объект RunnableSequence, который сам является Runnable. Это значит, что цепочку можно включить в другую цепочку.
Потоковый вывод (Stream)
Для длинных ответов удобно получать текст по мере генерации, а не ждать полного ответа:
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
load_dotenv()
model = ChatOpenAI(model=os.getenv("MODEL_NAME", "gpt-4o"))
prompt = ChatPromptTemplate.from_messages([
("system", "Ты опытный разработчик. Объясняй четко и с примерами кода."),
("human", "Объясни концепцию: {concept}"),
])
chain = prompt | model
# .stream() возвращает итератор чанков
for chunk in chain.stream({"concept": "декоратор в Python"}):
print(chunk.content, end="", flush=True)
Каждый chunk это AIMessageChunk с частью текста в .content. Метод .stream() работает у любого Runnable.
Batch
Если нужно обработать несколько запросов, .batch() делает это параллельно:
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
load_dotenv()
model = ChatOpenAI(model=os.getenv("MODEL_NAME", "gpt-4o"))
prompt = ChatPromptTemplate.from_messages([
("system", "Ты опытный разработчик. Объясняй четко и с примерами кода."),
("human", "Объясни концепцию: {concept}"),
])
chain = prompt | model
concepts = [
{"concept": "итератор"},
{"concept": "контекстный менеджер"},
{"concept": "метакласс"},
]
# Параллельные запросы к API
responses = chain.batch(concepts)
for concept, response in zip(concepts, responses):
print(f"\n--- {concept['concept']} ---")
print(response.content)
Параметры модели
temperature это ключевой параметр.
При temperature=0 модель выбирает наиболее вероятный токен на каждом шаге.
Для задач с однозначным ответом (код, структурированные данные) используйте ближе к 0.
Для творческих задач 0.5 и выше.
max_tokens это ограничение длины ответа модели.
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
load_dotenv()
model = ChatOpenAI(
model=os.getenv("MODEL_NAME", "gpt-4o"),
temperature=0, # 0 = детерминированный вывод, 1 = более случайный
max_tokens=500, # ограничение длины ответа
timeout=30, # таймаут запроса в секундах
)
Распространенные ошибки
1. AuthenticationError
openai.AuthenticationError: Error code: 401 - Incorrect API key
Причина: API ключ не загружен или некорректный.
Решение: проверьте, что файл .env находится в той же директории, откуда запускаете скрипт, и что load_dotenv() вызывается до создания модели.
2. RateLimitError / Payment Required
openai.RateLimitError: Error code: 429
# или при использовании Rus-GPT:
# Error code: 402 - Payment Required
Причина: превышен лимит запросов или исчерпан баланс.
Решение: проверьте баланс в OpenAI dashboard или на rus-gpt.com.
3. ValidationError при вызове цепочки
pydantic.ValidationError: ... missing required field
Причина: в шаблоне промпта есть переменная {variable}, которую не передали в .invoke().
Решение: проверьте, что все переменные шаблона присутствуют в словаре.
4. Передача строки вместо словаря
chain.invoke("замыкание") # ошибка, если в промпте есть переменные
chain.invoke({"concept": "замыкание"}) # правильно
Полный пример кода урока
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import SystemMessage, HumanMessage
load_dotenv()
# --- Базовый вызов модели ---
model = ChatOpenAI(model=os.getenv("MODEL_NAME", "gpt-4o"))
response = model.invoke("Объясни, что такое рекурсия")
print(response.content)
# --- Типы сообщений ---
messages = [
SystemMessage(content="Ты опытный Python-разработчик. Отвечай кратко и по делу."),
HumanMessage(content="Что такое генератор в Python?"),
]
response = model.invoke(messages)
print(response.content)
# --- Цепочка с шаблоном промпта ---
prompt = ChatPromptTemplate.from_messages([
("system", "Ты опытный разработчик. Объясняй технические концепции четко и с примерами кода."),
("human", "Объясни концепцию: {concept}"),
])
chain = prompt | model
response = chain.invoke({"concept": "замыкание в Python"})
print(response.content)
print()
# --- Потоковый вывод ---
print("Потоковый вывод:")
prompt = ChatPromptTemplate.from_messages([
("system", "Ты опытный разработчик. Объясняй четко и с примерами кода."),
("human", "Объясни концепцию: {concept}"),
])
chain = prompt | model
for chunk in chain.stream({"concept": "декоратор в Python"}):
print(chunk.content, end="", flush=True)
print()
# --- Batch ---
print("\nBatch:")
prompt = ChatPromptTemplate.from_messages([
("system", "Ты опытный разработчик. Объясняй четко и с примерами кода."),
("human", "Объясни концепцию: {concept}"),
])
chain = prompt | model
concepts = [
{"concept": "итератор"},
{"concept": "контекстный менеджер"},
{"concept": "метакласс"},
]
responses = chain.batch(concepts)
for concept, response in zip(concepts, responses):
print(f"\n--- {concept['concept']} ---")
print(response.content)
Практическое задание
Создайте инструмент для генерации документации функций.
Требования:
- Промпт принимает два параметра:
code(код функции) иstyle(стиль документации: "Google", "NumPy" или "reStructuredText") - Модель должна возвращать только строку docstring, без лишних объяснений
- Реализуйте потоковый вывод результата
- Обработайте случай, когда переданный код не является функцией и добавьте соответствующую инструкцию в системный промпт
Пример входных данных:
code = """
def calculate_discount(price: float, discount_percent: float) -> float:
if discount_percent < 0 or discount_percent > 100:
raise ValueError("Discount must be between 0 and 100")
return price * (1 - discount_percent / 100)
"""
style = "Google"
Ожидаемый результат: готовый docstring в указанном стиле.
Итоги урока
В этом уроке промпт был простым, строки с переменными. В следующем уроке разберем PromptTemplate более детально
few-shot примеры, парциальные шаблоны и главное, это output parsers.
Это позволит получать от модели не просто текст, а структурированные данные: словари, списки, модели Pydantic.
Это важно для построения надежных пайплайнов.
Подписывайтесь на мой Telegram канал
Если вам нужен ментор и вы хотите научиться разрабатывать AI агентов, пишите, обсудим условия
Авторизуйтесь, чтобы оставить комментарий.
Нет комментариев.
Тут может быть ваша реклама
Пишите info@aisferaic.ru