Retrieval-Augmented Thoughts (RAT)

[object Object]

Retrieval-Augmented Thoughts (RAT, мысли, дополненные поиском) --- техника, которая встраивает retrieval в каждый шаг цепочки рассуждений. Обычный RAG ищет информацию один раз до генерации ответа, а RAT пересматривает и корректирует каждую промежуточную мысль с учётом найденных фактов.

В 2026 году RAT полезнее понимать не как “RAG, но побольше”, а как reasoning-orchestration pattern для длинных или fact-sensitive цепочек. Его цель не просто добавить документы в контекст, а не дать промежуточным шагам дрейфовать от источников по мере генерации.

Представьте студента, который решает задачу по истории. Обычный подход (CoT): студент пишет ответ из головы, рискуя перепутать даты. RAG: студент один раз заглядывает в учебник перед ответом, но потом пишет из памяти. RAT: студент после каждого предложения сверяется с учебником и исправляет ошибки. Результат --- каждый факт в ответе проверен.

Суть в двух словах

Обычный Chain of Thought генерирует все шаги рассуждения за раз. Если модель ошиблась в факте на шаге 2 --- ошибка каскадирует через все последующие шаги. RAG помогает, но ищет информацию только в начале --- к моменту, когда модель доходит до шага 5, контекст из поиска может быть уже нерелевантен.

RAT решает это, встраивая поиск в каждый шаг:

  1. Модель генерирует начальную цепочку мыслей (CoT)
  2. Для каждой мысли --- поиск по базе знаний
  3. Мысль пересматривается с учётом найденной информации
  4. Уточнённая мысль становится основой для следующих шагов
ПромптClaude
Вопрос: Какой город был столицей Японии до Токио и когда произошёл перенос столицы?

Используй RAT --- после каждого шага рассуждения проверяй факты через поиск.

Шаг 1 (мысль): До Токио столицей Японии был Киото.
[Поиск: 'столица Японии до Токио']
[Найдено: Киото (Heian-kyo) был столицей Японии с 794 года.]
Шаг 1 (уточнение): Киото был столицей Японии с 794 года. Подтверждено.

Шаг 2 (мысль): Перенос столицы в Токио произошёл в 1868 году.
[Поиск: 'перенос столицы Японии в Токио дата']
[Найдено: В 1868 году император Мэйдзи перенёс двор в Эдо, переименованный в Токио.]
Шаг 2 (уточнение): В 1868 году император Мэйдзи перенёс столицу в Эдо, который переименовали в Токио (буквально 'восточная столица').

Итог: Столицей Японии до Токио был Киото (с 794 г.). Перенос произошёл в 1868 году при императоре Мэйдзи.
Ответ модели

Каждый шаг рассуждения проверен через поиск: Киото --- подтверждено, дата 1868 --- подтверждена, добавлена деталь про переименование Эдо. Без RAT модель могла бы ошибиться в дате или не упомянуть историческое название.

Обычный CoT
Мысль 1: Столицей был Киото. ✓ Мысль 2: Перенос произошёл в 1854 году. ✗ (галлюцинация) Мысль 3: Это связано с открытием портов. ✗ (ошибка каскадировала) Итог: Частично верный ответ с неправильной датой
RAT (с retrieval на каждом шаге)
Мысль 1: Столицей был Киото → [поиск] → Подтверждено (с 794 г.) ✓ Мысль 2: Перенос в 1854 → [поиск] → Исправлено: 1868, Мэйдзи ✓ Мысль 3: Связано с реставрацией Мэйдзи → [поиск] → Подтверждено ✓ Итог: Полностью корректный ответ
RAG ищет один раз и передаёт контекст в промпт. RAT ищет на каждом шаге рассуждения и пересматривает промежуточные мысли. Это как разница между тем, чтобы прочитать главу учебника перед экзаменом (RAG) и сверяться с учебником после каждого написанного предложения (RAT).

Как работает RAT

Алгоритм RAT по шагам

Сравнение подходов

ПараметрCoTRAGCoT + RAGRAT
Когда retrievalНикогда1 раз до генерации1 раз до CoTНа каждом шаге
Что ревизируетсяНичегоНичегоНичегоКаждая мысль
ГаллюцинацииВысокиеСредниеСредниеНизкие
СтоимостьНизкаяСредняяСредняяВысокая
Лучше дляЛогика, математикаПростые фактыФакт + рассуждениеMulti-hop, сложные факты

Результаты бенчмарков

Точность RAT vs базовые методы (%)
RAT --- code generation84%
CoT+RAG --- code generation68%
RAT --- creative writing79%
CoT --- creative writing62%
RAT --- multi-hop QA76%
RAG --- multi-hop QA58%

RAT показывает наибольший прирост на задачах, где:

  • Нужно объединить факты из разных источников (multi-hop reasoning)
  • Каждый шаг рассуждения опирается на конкретные факты, а не на общие знания
  • Ошибка в промежуточном шаге критична для финального ответа

Когда использовать RAT

  • Фактоёмкие задачи: исследования, юридические вопросы, медицинская информация
  • Multi-hop reasoning: вопросы, требующие связывания фактов из нескольких документов
  • Code generation с документацией: генерация кода по актуальной документации API
  • Анализ длинных документов: когда контекст слишком велик для одного промпта
  • Простые задачи: если достаточно одного поиска --- используйте RAG
  • Творческие задачи: генерация стихов, историй --- retrieval может мешать креативности
  • Нет базы знаний: RAT требует quality retriever; без хорошего источника данных он бесполезен
  • Жёсткие ограничения по latency: каждый шаг --- это дополнительный поиск + LLM-вызов
ПромптChatGPT
Используй RAT для ответа на multi-hop вопрос:

Вопрос: Какой язык программирования использовался для создания операционной системы, на которой работает поисковый движок Google?

Мысль 1: Google использует поисковый движок собственной разработки.
[Retrieval: 'поисковый движок Google технологии']
[Найдено: Google Search работает на инфраструктуре Google, включая GFS, MapReduce, Bigtable. Серверы работают под модифицированной версией Linux.]
Мысль 1 (revised): Google Search работает на серверах под модифицированным Linux.

Мысль 2: Linux написан на языке C.
[Retrieval: 'язык программирования ядро Linux']
[Найдено: Ядро Linux написано преимущественно на C с ассемблерными вставками. С 2022 года поддерживается Rust для модулей ядра.]
Мысль 2 (revised): Ядро Linux написано на C (с ассемблерными вставками), с 2022 года также поддерживается Rust.

Итоговый ответ: ОС серверов Google --- модифицированный Linux. Ядро Linux написано на C. Таким образом, основной язык --- C.
Ответ модели

RAT пошагово уточнил: (1) Google работает на Linux, а не на собственной ОС, (2) ядро написано на C, а не на C++ --- типичная галлюцинация CoT. Дополнительно RAT нашёл факт о поддержке Rust с 2022 года.

Плюсы

  • Снижает галлюцинации на каждом шаге --- ошибки не каскадируют
  • +20% точности на фактоёмких задачах vs CoT
  • Каждая мысль подкреплена источником --- прозрачность рассуждений
  • Работает с любым retriever: vector DB, web search, API docs
  • Совместим с существующим RAG-пайплайном --- расширяет, а не заменяет

Минусы

  • Дорого: N шагов × (retrieval + LLM revision) = много вызовов
  • Медленно: последовательная обработка, каждый шаг ждёт retrieval
  • Качество зависит от retriever: плохой поиск = плохая ревизия
  • Overkill для простых задач, где достаточно одного поиска

Как понимать RAT в 2026

На практике RAT стоит между классическим RAG и агентным исследовательским workflow:

  • single-shot RAG ищет один раз и надеется, что этого контекста хватит на весь ответ
  • RAT пересобирает фактическую опору по ходу reasoning
  • deep research agents уже умеют планировать поиск, ветвить его и возвращаться назад

Поэтому RAT — хороший промежуточный слой. Он особенно полезен, когда вам ещё не нужен полноценный агентный контур, но уже недостаточно одного retrieval-прохода в начале.

Где техника действительно окупается

RAT обычно даёт лучший ROI в сценариях, где:

  • ответ состоит из нескольких зависимых фактических шагов
  • документы длинные, а релевантный контекст меняется от шага к шагу
  • ошибка на раннем утверждении каскадирует в итоговый ответ
  • вы хотите сохранить трассу reasoning, но сделать её более grounded

Типичные примеры: policy analysis, long-document QA, code generation по меняющейся документации, internal knowledge assistants и сложные research answers с несколькими источниками.

Где RAT переусложняет систему

Техника быстро становится слишком дорогой, если:

  • задача решается одним-двумя retrieval-вопросами
  • steps получаются расплывчатыми и не формируют хорошие поисковые запросы
  • retriever сам по себе слабее, чем reasoning-модель

Хорошее правило: если вы не можете чётко выделить проверяемые промежуточные мысли, RAT обычно не даст ожидаемого выигрыша. В таком случае лучше либо усилить обычный RAG, либо перейти к более явному planner-researcher workflow.

Техническая реализация

RAT с LangChain

from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_core.documents import Document

llm = ChatOpenAI(model="gpt-4o", temperature=0)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")


def build_retriever(documents: list[str], k: int = 3):
    """Создать retriever из списка текстовых документов."""
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=500, chunk_overlap=50
    )
    docs = [Document(page_content=d) for d in documents]
    chunks = splitter.split_documents(docs)
    vectorstore = Chroma.from_documents(chunks, embeddings)
    return vectorstore.as_retriever(search_kwargs={"k": k})


INITIAL_COT_PROMPT = """Разбей задачу на шаги рассуждения.

Задача: {question}

Верни список шагов в формате:
Thought 1: ...
Thought 2: ...
Thought N: ...

Каждый шаг --- отдельная мысль, конкретная и проверяемая."""

REVISION_PROMPT = """Пересмотри мысль с учётом найденной информации.

Задача: {question}

Предыдущие (уже пересмотренные) мысли:
{previous_thoughts}

Текущая мысль: {thought}

Найденная информация:
{retrieved_context}

Верни ИСПРАВЛЕННУЮ мысль. Если мысль корректна --- верни как есть.
Если найдена ошибка --- исправь и укажи, что изменилось."""

FINAL_ANSWER_PROMPT = """Сформулируй финальный ответ на основе пересмотренных мыслей.

Задача: {question}

Пересмотренные мысли:
{revised_thoughts}

Дай точный, фактологически верный ответ."""


class RATPipeline:
    """Retrieval-Augmented Thoughts: retrieval на каждом шаге CoT."""

    def __init__(self, retriever, llm_instance=None):
        self.retriever = retriever
        self.llm = llm_instance or llm

    def _generate_initial_thoughts(self, question: str) -> list[str]:
        """Шаг 1: генерация начальной цепочки мыслей."""
        response = self.llm.invoke(
            INITIAL_COT_PROMPT.format(question=question)
        )
        lines = response.content.strip().split("\n")
        thoughts = []
        for line in lines:
            line = line.strip()
            if line.startswith("Thought"):
                # Убираем префикс "Thought N: "
                thought = line.split(":", 1)[1].strip() if ":" in line else line
                thoughts.append(thought)
        return thoughts

    def _retrieve(self, query: str) -> str:
        """Шаг 2: поиск по базе знаний."""
        docs = self.retriever.invoke(query)
        return "\n---\n".join(doc.page_content for doc in docs)

    def _revise_thought(
        self,
        question: str,
        thought: str,
        context: str,
        previous: list[str]
    ) -> str:
        """Шаг 3: ревизия мысли с учётом retrieval."""
        prev_text = "\n".join(
            f"  {i+1}. {t}" for i, t in enumerate(previous)
        ) or "(нет)"
        response = self.llm.invoke(
            REVISION_PROMPT.format(
                question=question,
                previous_thoughts=prev_text,
                thought=thought,
                retrieved_context=context
            )
        )
        return response.content.strip()

    def run(self, question: str) -> dict:
        """Полный цикл RAT."""
        # 1. Начальные мысли
        thoughts = self._generate_initial_thoughts(question)

        # 2-3. Для каждой мысли: retrieval + revision
        revised = []
        retrieval_log = []

        for i, thought in enumerate(thoughts):
            context = self._retrieve(thought)
            retrieval_log.append({
                "thought": thought,
                "retrieved": context[:200] + "..."
            })
            revised_thought = self._revise_thought(
                question, thought, context, revised
            )
            revised.append(revised_thought)

        # 4. Финальный ответ
        revised_text = "\n".join(
            f"  {i+1}. {t}" for i, t in enumerate(revised)
        )
        final = self.llm.invoke(
            FINAL_ANSWER_PROMPT.format(
                question=question,
                revised_thoughts=revised_text
            )
        )

        return {
            "answer": final.content.strip(),
            "initial_thoughts": thoughts,
            "revised_thoughts": revised,
            "retrieval_log": retrieval_log,
            "num_retrievals": len(thoughts)
        }


# Пример использования
knowledge_base = [
    "Python 3.12 выпущен в октябре 2023 года.",
    "В Python 3.12 добавлены type parameter syntax (PEP 695).",
    "asyncio.TaskGroup появился в Python 3.11.",
    "match/case (structural pattern matching) добавлен в Python 3.10.",
    "GIL в Python планируется сделать опциональным в Python 3.13 (PEP 703).",
]

retriever = build_retriever(knowledge_base, k=2)
rat = RATPipeline(retriever)
result = rat.run("Какие основные новшества появились в Python 3.12?")
print(result["answer"])

Оптимизация: selective retrieval

Не обязательно делать retrieval на каждом шаге. Можно добавить confidence threshold --- если модель уверена в мысли, поиск пропускается:

CONFIDENCE_PROMPT = """Оцени свою уверенность в этом утверждении от 0 до 1.

Утверждение: {thought}

Верни только число (например, 0.85)."""


class SelectiveRAT(RATPipeline):
    """RAT с выборочным retrieval по confidence threshold."""

    def __init__(self, retriever, threshold: float = 0.7, **kwargs):
        super().__init__(retriever, **kwargs)
        self.threshold = threshold

    def _get_confidence(self, thought: str) -> float:
        """Оценить уверенность модели в мысли."""
        response = self.llm.invoke(
            CONFIDENCE_PROMPT.format(thought=thought)
        )
        try:
            return float(response.content.strip())
        except ValueError:
            return 0.0  # При ошибке парсинга --- всегда делаем retrieval

    def run(self, question: str) -> dict:
        thoughts = self._generate_initial_thoughts(question)
        revised = []
        retrieval_log = []
        skipped = 0

        for thought in thoughts:
            confidence = self._get_confidence(thought)

            if confidence >= self.threshold:
                # Модель уверена --- пропускаем retrieval
                revised.append(thought)
                skipped += 1
            else:
                # Низкая уверенность --- делаем retrieval
                context = self._retrieve(thought)
                retrieval_log.append({
                    "thought": thought,
                    "confidence": confidence,
                    "retrieved": context[:200]
                })
                revised_thought = self._revise_thought(
                    question, thought, context, revised
                )
                revised.append(revised_thought)

        revised_text = "\n".join(
            f"  {i+1}. {t}" for i, t in enumerate(revised)
        )
        final = self.llm.invoke(
            FINAL_ANSWER_PROMPT.format(
                question=question, revised_thoughts=revised_text
            )
        )

        return {
            "answer": final.content.strip(),
            "initial_thoughts": thoughts,
            "revised_thoughts": revised,
            "retrieval_log": retrieval_log,
            "retrievals_made": len(thoughts) - skipped,
            "retrievals_skipped": skipped
        }

Выбор retriever

RetrieverКогда использоватьLatencyСтоимость
Vector DB (Chroma, Qdrant)Внутренняя документация, корпус текстов10-50 мсНизкая (self-hosted)
Web Search (Tavily, Exa)Актуальные факты, новости200-500 мсСредняя
API Docs (прямой запрос)Code generation по документации100-300 мсНизкая
Hybrid (BM25 + vector)Точные совпадения + семантика50-100 мсСредняя

Стоимость и latency

Для задачи с N шагами рассуждения:

МетодLLM-вызовыRetrieval-вызовыПримерная стоимость (GPT-4o)
CoT10~$0.01
RAG11~$0.02
RATN + 2N~$0.05-0.15
Selective RATN + 2 - SN - S~$0.03-0.10

Где S --- число пропущенных шагов (selective retrieval). На практике threshold 0.7 пропускает 30-50% шагов без потери качества.

  1. Retrieval на слишком общий запрос --- мысль "нужно разобраться в архитектуре" даёт нерелевантные результаты. Решение: переформулируйте мысль в конкретный поисковый запрос перед retrieval.
  2. Слишком большой retrieved context --- модель теряется в длинном контексте. Ограничивайте k=2-3 документа и chunk_size=500.
  3. Retriever возвращает устаревшую информацию --- проверяйте актуальность базы знаний. Для API-документации используйте автоматическое обновление.
  4. Все шаги зависят друг от друга --- если мысль 3 нельзя пересмотреть без мысли 2, убедитесь, что ревизия идёт строго последовательно (не параллельно).

Проверьте себя

Проверьте себя

1. Чем RAT отличается от обычного RAG?

2. Когда RAT наиболее полезен?

3. Что делает Selective RAT для оптимизации?