LCEL и pipe-синтаксис: цепочки через |

Урок знакомит с LCEL — способом собирать цепочки оператором конвейера.

LCEL (LangChain Expression Language) — декларативный способ соединять компоненты в конвейер оператором |, где выход одного звена становится входом следующего.

Идея конвейера

Знакомый из shell оператор | передаёт вывод одной команды на вход другой. LCEL делает то же с компонентами LangChain: prompt | model | parser читается как «заполни шаблон, отправь в модель, разбери ответ». Каждый элемент — это Runnable, и составленная цепочка тоже Runnable.

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template("Дай определение термина: {term}")
model = ChatOpenAI(model="gpt-4o-mini")
parser = StrOutputParser()

chain = prompt | model | parser
print(chain.invoke({"term": "идемпотентность"}))

Единый интерфейс Runnable

Любая LCEL-цепочка умеет одинаковый набор методов:

МетодЧто делает
invoke(x)один вход → один выход
batch([x1, x2])пакет входов параллельно
stream(x)стримит ответ по частям
ainvoke(x)асинхронный вариант

Принцип «функция-композиция» легко смоделировать на чистом Python:

def compose(*funcs):
    def run(x):
        for f in funcs:
            x = f(x)
        return x
    return run

pipeline = compose(str.strip, str.upper, lambda s: f"[{s}]")
print(pipeline("  langchain  "))

Вывод:

[LANGCHAIN]

Как работает под капотом

Оператор | у Runnable перегружен: он создаёт объект RunnableSequence, который хранит список звеньев. При invoke() он прогоняет вход через звенья по очереди, прокидывая выход предыдущего на вход следующего. Поскольку у всей последовательности тот же интерфейс Runnable, к ней автоматически применимы batch, stream и асинхронные методы — их не нужно реализовывать вручную для каждой цепочки.

Частые ошибки

  • Несовпадение типов между звеньями. Выход одного звена должен подходить на вход следующего (например, парсер ждёт AIMessage, а не строку).
  • Забыть парсер. Без StrOutputParser на выходе останется AIMessage, а не чистая строка.
  • Ожидать, что цепочка «сама поймёт» порядок. Порядок задаёте вы, слева направо.

Итог

  • LCEL соединяет компоненты оператором | в конвейер.
  • Каждое звено и вся цепочка — это Runnable с методами invoke/batch/stream/ainvoke.
  • Порядок звеньев — слева направо, типы между ними должны совпадать.
Проверьте себя
1. Как читается LCEL-цепочка prompt | model | parser?
AСлучайный порядок выполнения
BЗаполнить шаблон, отправить в модель, разобрать ответ — слева направо
CСначала parser, потом model, потом prompt
DВсе три выполняются одновременно и независимо
2. Какой метод вызовет цепочку сразу на списке входов?
Ainvoke
Bbatch
Cstream
Dcompile
3. Что останется на выходе, если убрать StrOutputParser из prompt | model?
AЧистая строка
BОбъект AIMessage, а не строка
CNone
DСписок токенов