Retrievers и сборка RAG-цепочки

Урок соединяет всё в RAG: поиск контекста и генерацию ответа по нему.

Retriever — компонент с единым интерфейсом, который по текстовому запросу возвращает релевантные документы.

Retriever поверх хранилища

Векторное хранилище умеет искать, но в LCEL-цепочку удобнее встраивать Retriever — тонкую обёртку с методом, принимающим запрос и возвращающим документы. Любой VectorStore легко превращается в retriever:

retriever = store.as_retriever(search_kwargs={"k": 4})
docs = retriever.invoke("Что такое индекс?")

Полная RAG-цепочка

RAG собирается из знакомых блоков: на вопрос параллельно достаём контекст из retriever и пробрасываем сам вопрос, затем кладём оба в промпт и зовём модель.

from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_template(
    "Ответь на вопрос, опираясь ТОЛЬКО на контекст.\n"
    "Контекст:\n{context}\n\nВопрос: {question}"
)
rag = (
    RunnableParallel(context=retriever, question=RunnablePassthrough())
    | prompt | model | StrOutputParser()
)
print(rag.invoke("Что такое индекс в базе данных?"))

Схема пайплайна

вопрос
  ├──> retriever ──> релевантные чанки ─┐
  └──> passthrough ──> сам вопрос ───────┤
                                          v
                          prompt(context, question)
                                          v
                                        model
                                          v
                                       ответ

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

При invoke("вопрос") RunnableParallel отдаёт строку вопроса сразу двум веткам: retriever кодирует её в вектор, ищет в хранилище и возвращает чанки в context; passthrough кладёт исходный вопрос в question. Получившийся словарь идёт в промпт, который собирает финальный текст с контекстом, затем в модель и парсер. Инструкция «опираясь ТОЛЬКО на контекст» снижает галлюцинации, заземляя ответ на найденных документах.

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

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

Итог

  • Retriever — единый интерфейс поиска документов по запросу.
  • RAG-цепочка собирается из RunnableParallel (context + question) → prompt → model → parser.
  • Инструкция «только по контексту» уменьшает галлюцинации.
  • Параметр k регулирует баланс полноты и шума.
Проверьте себя
1. Чем retriever удобнее прямого вызова векторного хранилища в цепочке?
AОн быстрее в 10 раз
BЭто единый интерфейс «запрос → документы», который легко встроить в LCEL
CОн не требует эмбеддингов
DОн хранит историю диалога
2. Зачем в промпт RAG добавляют инструкцию «отвечай только по контексту»?
AЧтобы ускорить поиск
BЧтобы заземлить ответ на найденных документах и снизить галлюцинации
CЧтобы уменьшить размер эмбеддингов
DЧтобы модель отвечала на английском