Разработка ИИ-агентов · Модуль 2 · Урок 2.3

Память: краткосрочная vs долгосрочная, основы retrieval/RAG

Два вида памяти

У агента полезно различать два вида памяти. Краткосрочная — это текущий контекст диалога: история сообщений в окне модели. Она живёт в рамках сессии и ограничена окном (см. урок 2.2). Долгосрочная — это внешнее хранилище знаний и фактов, которое переживает сессию: документация, прошлые разговоры, профиль пользователя, база товаров.

Долгосрочную память нельзя целиком засунуть в контекст — она слишком большая и в основном нерелевантна текущему вопросу. Поэтому к ней обращаются выборочно: на каждый запрос достают только подходящие куски и подкладывают их в окно. Этот приём и называется retrieval.

RAG: retrieval-augmented generation

RAG — это паттерн «найди, потом сгенерируй». Вместо того чтобы надеяться на знания внутри модели, мы перед ответом ищем релевантные фрагменты во внешней базе и добавляем их в контекст как опору. Базовый конвейер:

  1. Заранее разбить документы на куски (chunks) и проиндексировать их.
  2. На запрос найти top-k наиболее релевантных кусков.
  3. Вставить найденное в промпт и попросить модель ответить, опираясь только на эти куски, со ссылками на источник.

RAG резко снижает выдумывание (галлюцинации) и позволяет работать с приватными/свежими данными, которых нет в модели. В этом курсе AI-тьютор устроен именно так: он ищет по теории и отвечает по найденному.

Лексический поиск vs эмбеддинги

Как искать релевантные куски? Два основных подхода.

  • Лексический (по словам): BM25, TF-IDF. Сравнивает совпадения терминов. Прост, без внешних зависимостей, отлично работает на точных терминах и кодах. Минус — не понимает синонимы и перефразы.
  • Семантический (эмбеддинги): тексты превращают в векторы, близость считают по косинусу. Ловит смысл и синонимы. Минус — нужна модель эмбеддингов и векторное хранилище.

Для старта и для небольших баз лексический поиск часто достаточен и его легко написать на Go без зависимостей. Лучшие результаты даёт гибрид: совместить лексический и семантический поиск. Начинайте с простого; усложняйте, когда упрётесь в качество.

Интерфейс ретривера и подстановка найденного в контекст
// Retriever абстрагирует способ поиска (лексический, эмбеддинги — неважно).
type Doc struct {
    Source string // откуда кусок (для ссылки в ответе)
    Text   string
}

type Retriever interface {
    Search(query string, k int) []Doc
}

// answerWithRAG: найти контекст и попросить модель ответить только по нему.
func answerWithRAG(r Retriever, question string) (string, error) {
    docs := r.Search(question, 4) // top-k релевантных кусков

    var sb strings.Builder
    for i, d := range docs {
        fmt.Fprintf(&sb, "[Источник %d: %s]\n%s\n\n", i+1, d.Source, d.Text)
    }

    sys := "Отвечай ТОЛЬКО по приведённым источникам и указывай номер источника. " +
        "Если ответа в них нет — скажи об этом прямо."
    choice, err := callLLM([]Message{
        {Role: "system", Content: sys},
        {Role: "user", Content: "Источники:\n" + sb.String() + "\nВопрос: " + question},
    }, nil)
    if err != nil {
        return "", err
    }
    return choice.Content, nil
}

Anti-patterns

Анти-паттернПочему плохоКак правильно
Считать контекст диалога «долгой памятью»Он исчезает с окном/сессиейДолгую память хранить во внешнем индексе и доставать retrieval'ом
Класть всю базу знаний в промптНе влезет, дорого, шумноДоставать только top-k релевантных кусков на запрос
Отвечать «из головы» при наличии базыГаллюцинации, устаревшие фактыRAG: грунтить ответ на найденных кусках со ссылками
Сразу строить векторный стек ради 50 документовПреждевременная сложностьНачать с лексического поиска; эмбеддинги — когда нужно качество/синонимы

Практическое задание

  • Сформулируйте, что для вашего агента краткосрочная память, а что — долгосрочная.
  • Соберите маленькую базу из 5–10 текстовых кусков с указанием источника.
  • Реализуйте Retriever с простым лексическим поиском (например, по совпадению слов/BM25-lite).
  • Подставьте top-k найденных кусков в промпт и попросите модель отвечать только по ним, со ссылками.
  • Задайте вопрос, которого нет в базе, и убедитесь, что агент честно говорит «не найдено», а не выдумывает.

Проверка знаний

Что описывает паттерн RAG?

  • A Дообучение модели на своих данных
  • B Поиск релевантных фрагментов во внешней базе и подстановка их в контекст перед генерацией ответа
  • C Сжатие истории диалога
  • D Кеширование системного промпта

Когда лексический поиск (BM25) предпочтительнее эмбеддингов для старта?

  • A Никогда — эмбеддинги всегда лучше
  • B На небольших базах и точных терминах/кодах: он прост, без зависимостей и быстро внедряется
  • C Только для изображений
  • D Когда важны синонимы и перефразы

Главный выигрыш RAG по сравнению с ответом «из памяти модели»?

  • A Ответы становятся длиннее
  • B Меньше галлюцинаций и возможность работать с приватными/свежими данными со ссылками на источник
  • C Не нужен системный промпт
  • D Модель перестаёт стоить денег