RAG, MCP и агенты
Фрагмент лекции: «Все что нужно знать про ИИ айтишнику», 01:14:51 — отсюда взято содержание этой главы.
До этого момента трек описывал модель как замкнутую систему: на входе текст, внутри веса, на выходе распределение вероятностей следующего токена. Всё, что модель «знает», зафиксировано в момент обучения и лежит в весах. Из этого следует неприятное: в весах нет ваших документов. Нет вашего репозитория, вчерашней выгрузки из базы, внутреннего регламента, переписки по проекту. И нет способа спросить у весов, что изменилось после того, как обучение закончилось.
Эта глава — про три приёма, которые чинят разные части этой проблемы. Дать модели ваши данные — это RAG. Дать ей ваши инструменты — это function calling и MCP. Дать ей право действовать в цикле, а не отвечать одним ходом — это агенты. Приёмы независимы, комбинируются в любом порядке и часто путаются друг с другом, потому что в продуктах приходят вместе.
Три способа выйти за пределы весов
Общий знаменатель у всех трёх — контекст запроса. Модель по-прежнему остаётся чистой функцией от входа: у неё нет памяти, нет доступа к диску, нет сети. Единственный способ что-то ей сообщить — положить это в текст запроса. Все три приёма отвечают на один вопрос: что именно и в какой момент положить в контекст.
Почему нельзя просто дообучить модель
Первая мысль обычно такая: раз в весах нет наших документов — давайте их туда положим, то есть дообучим модель на своих данных. Это работает не так, как хочется, по трём причинам.
- Дорого. Дообучение требует железа, размеченных данных и инженеров, которые умеют отличать улучшение от переобучения. Глава «Железо» объясняет, откуда берётся ценник.
- Не решает свежесть. Документ, который появился сегодня, попадёт в веса только в следующем цикле обучения. Задача «отвечай по актуальной версии регламента» дообучением не решается в принципе — она решается доставкой актуальной версии в запрос.
- Учит стилю лучше, чем фактам. Дообучение хорошо ставит формат ответа, тон, следование внутренним соглашениям. Заучивание конкретных фактов — его слабая сторона: модель начинает уверенно смешивать выученное с придуманным.
Отсюда рабочий вывод: нужное знание приносят в контекст запроса, а не в веса. И сразу — ограничение из главы «Токены, контекстное окно и память диалога»: контекст конечен и оплачивается целиком на каждом ходу. Значит, приносить надо не всё, что у вас есть, а релевантное конкретному вопросу. Именно эта оговорка и превращает простую идею «дай модели наши файлы» в отдельную инженерную дисциплину.
RAG: не «флешка», а поиск под конкретный вопрос
RAG — Retrieval-Augmented Generation, «генерация, дополненная поиском»; термин и схема закреплены работой Lewis et al., Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks (2020). В лекции RAG описан как «выгрузили часть знаний на флешку и подключили её к системному промпту». Интуиция верная — знание действительно живёт снаружи модели, — но в формулировке спрятана подмена, из-за которой RAG обычно и понимают неправильно. К промпту подключается не всё хранилище, а несколько фрагментов, найденных под конкретный вопрос. Если бы можно было подставить всё, никакой RAG был бы не нужен: сложили бы весь корпус в контекст и жили спокойно. Весь смысл приёма — в шаге отбора, а не в шаге подстановки.
Конвейер по шагам
Работа делится на две фазы: индексация выполняется заранее, поиск — на каждый вопрос.
инструкция + фрагменты + вопрос"] P --> M[Языковая модель] M --> A["Ответ с указанием фрагментов"] end
Пять шагов словами:
- Нарезка. Документы режутся на фрагменты — обычно от нескольких сотен до пары тысяч символов, с перекрытием, чтобы мысль на стыке не потерялась целиком.
- Векторизация. Каждый фрагмент прогоняется через модель эмбеддингов и превращается в вектор фиксированной длины.
- Вопрос тоже становится вектором. Обязательно той же самой моделью: векторы из разных моделей лежат в разных пространствах и несравнимы.
- Поиск ближайших. Считается близость вектора вопроса ко всем векторам фрагментов, берутся k лучших.
- Сборка промпта. Найденные фрагменты подставляются в запрос вместе с инструкцией «отвечай только по ним» и самим вопросом. Модель отвечает по подставленному тексту.
Ключевая деталь, которую видно на схеме: модель на пятом шаге ничего не «ищет». Она получает обычный текстовый запрос, в котором просто оказались нужные куски. Поиск — это отдельный, полностью детерминированный код, который вы пишете и отлаживаете сами.
Почему близость векторов означает близость смысла
Это прямое следствие главы «Представления». Эмбеддинг — точка в пространстве из сотен или тысяч измерений, и обучен он так, что тексты, встречающиеся в похожих контекстах, оказываются рядом. Практическое следствие: поиск по эмбеддингам находит фрагмент про «увольнение по собственному желанию» по запросу «как уйти с работы», хотя ни одно слово не совпало. И он же промахивается там, где важна точная строка — артикул, номер версии, имя функции: для таких запросов обычный полнотекстовый поиск работает лучше, и в продакшене эти два способа комбинируют.
Та же схема кодом
Ниже — весь RAG без единого внешнего сервиса: списки, numpy и арифметика. Модель эмбеддингов заменена заглушкой, чтобы код был самодостаточным; всё остальное — настоящая механика.
import numpy as np
D = 256 # размерность игрушечного представления
def normalize(matrix: np.ndarray) -> np.ndarray:
"""Приводим каждый вектор к единичной длине.
После этого косинусная близость равна обычному скалярному произведению —
и весь поиск сводится к одному матричному умножению."""
norms = np.linalg.norm(matrix, axis=1, keepdims=True)
return matrix / np.maximum(norms, 1e-9)
def embed(texts: list[str]) -> np.ndarray:
"""Заглушка вместо модели эмбеддингов: мешок слов, разложенный по D координатам
хешированием. Настоящая модель ставит рядом синонимы, эта — только совпадающие
слова; механику отбора это показывает точно так же. В рабочем коде берут
устойчивый хеш: встроенный hash() для строк солится при каждом запуске."""
vectors = np.zeros((len(texts), D), dtype=np.float32)
for row, text in enumerate(texts):
for word in text.lower().split():
vectors[row, hash(word) % D] += 1.0
return normalize(vectors)
def split(text: str, size: int = 400, overlap: int = 80) -> list[str]:
"""Нарезка на фрагменты с перекрытием: предложение, попавшее на стык,
целиком остаётся хотя бы в одном фрагменте."""
step = size - overlap
return [text[i:i + size] for i in range(0, max(len(text), 1), step)]
def search(index: np.ndarray, question_vec: np.ndarray, k: int = 4) -> np.ndarray:
"""Возвращает индексы k ближайших фрагментов, от самого близкого к дальнему."""
scores = index @ question_vec # N скалярных произведений за одно умножение
k = min(k, len(scores))
best = np.argpartition(-scores, k - 1)[:k] # частичная сортировка: O(N)
return best[np.argsort(-scores[best])] # k лучших упорядочиваем: O(k log k)
TEMPLATE = """Ответь на вопрос, опираясь ТОЛЬКО на фрагменты ниже.
Если ответа в них нет — так и напиши, не додумывай.
После каждого утверждения указывай номер фрагмента в квадратных скобках.
{context}
Вопрос: {question}"""
def build_prompt(question: str, chunks: list[str], found: np.ndarray) -> str:
"""Сборка финального запроса. Нумерация фрагментов — не украшение:
без неё нечем проверить, откуда модель взяла утверждение."""
context = "\n\n".join(f"[{n}] {chunks[i]}" for n, i in enumerate(found, start=1))
return TEMPLATE.format(context=context, question=question)
# --- всё вместе ---
chunks = split(open("regulations.txt", encoding="utf-8").read())
index = embed(chunks) # индексация: делается заранее
question = "какой срок согласования отпуска"
prompt = build_prompt(question, chunks, search(index, embed([question])[0], k=4))
# дальше prompt уходит в модель обычным текстовым запросом
Сложность поиска. Наивный перебор — это N скалярных произведений по d координат: O(N * d) по времени на каждый запрос и O(N * d) по памяти на хранение индекса. Отбор топ-k через argpartition добавляет O(N), финальная сортировка — O(k log k), то есть на общую картину не влияет. При N в десятки тысяч фрагментов такой перебор занимает миллисекунды, и никакой базы векторов не нужно. При N в миллионы линейный проход становится узким местом, и переходят на приближённый поиск ближайших соседей — HNSW, IVF и подобные индексы дают порядка O(log N) на запрос ценой того, что иногда лучший сосед не находится. Обратите внимание на честный размен: вы платите не деньгами и не памятью, а точностью поиска.
Чего RAG не решает
Одну вещь стоит запомнить сразу: RAG заземляет ответ на документы, но не мешает модели исказить найденное — без обязательных цитат и их проверки вы получаете правдоподобную выдумку со ссылками, что хуже выдумки без ссылок, потому что выглядит убедительнее. Всё остальное — стратегии нарезки, гибридный поиск, реранжирование, оценка качества выдачи — это уже продакшн-RAG, и он подробно разобран в соседнем треке: «RAG» и «Продвинутый RAG». Пересказывать здесь незачем.
Банки памяти и личные базы знаний
В лекции звучит верное наблюдение: memory bank в редакторе с ИИ и личная база знаний вроде Logseq — по сути тот же приём в упрощённом виде. Точная формулировка такая: файл с проектными договорённостями (соглашения по стилю кода, список сущностей, решения, которые не надо переоткрывать), который инструмент автоматически подкладывает в контекст каждого запроса, — это ручной RAG без векторного поиска. Фаза индексации отсутствует, фаза отбора вырождена: подставляется всё содержимое файла целиком.
Из этого следуют границы применимости, и они жёсткие:
- Приём работает ровно до тех пор, пока файл влезает в окно и остаётся дешёвым. Разросшийся банк памяти начинает съедать контекст, который был нужен под саму задачу.
- Он работает, пока файл весь релевантен. Как только там появляются разделы про пять разных подсистем, вы на каждом запросе платите за четыре ненужных и вдобавок разбавляете внимание модели.
- Длинный контекст к тому же используется неравномерно: середина работает хуже краёв — эффект показан в работе Lost in the Middle.
Практический вывод простой: банк памяти — отличное решение на масштабе одного проекта и плохое на масштабе корпоративной базы знаний. Когда файл перестаёт помещаться, вы возвращаетесь к шагу отбора, то есть к полноценному RAG.
Навыки: упаковка инструкций
Навык (skill) — заранее описанная в markdown последовательность шагов, которую агент подключает по запросу или когда сам сочтёт задачу подходящей. Внутри — обычный текст: что делать, в каком порядке, чего избегать, какие форматы использовать.
Механика та же, что у RAG: короткое описание навыка лежит в контексте постоянно, полное содержимое подгружается только тогда, когда навык понадобился. И тот же вывод — не приносить всё сразу.
Важно не переоценивать приём. Навык — это упаковка инструкций, а не новая способность модели. Всё, что навык умеет, модель умела и до него; навык лишь избавляет вас от необходимости писать одни и те же полторы страницы указаний в каждом запросе и делает поведение воспроизводимым между сессиями. Это дисциплина, а не магия.
MCP: единый способ объявить инструмент
Дальше — вторая линия: данные мы модели дать научились, теперь дадим ей действия. Механизм в основе простой. Вместе с запросом вы передаёте список описаний инструментов: имя, человекочитаемое описание и схема аргументов. Модель, если сочтёт нужным, отвечает не текстом, а структурированным вызовом: «вызови search_docs с такими аргументами». Выполняет вызов ваш код — модель ничего не запускает и запустить не может.
Проблема начинается на второй интеграции. Описания инструментов, форматы аргументов, способ доставки результата у каждого агента свои, и интеграция с Jira, написанная для одного инструмента, не переносится в другой.
Ровно эту проблему решает MCP — Model Context Protocol, открытый протокол, представленный Anthropic в ноябре 2024 года (modelcontextprotocol.io, анонс). В лекции авторство ошибочно приписано OpenAI — это не так.
Что именно стандартизует MCP:
- Сервер выставляет три вида сущностей: инструменты (что можно вызвать), ресурсы (что можно прочитать) и промпты (готовые шаблоны запросов).
- Клиент, встроенный в агента, подключается к серверу, обнаруживает этот список и вызывает нужное по единому контракту — одинаково для любого сервера.
Аналогия из лекции — «это бэкенды для агентов» — рабочая, но требует уточнения. MCP не про бизнес-логику, а про единый способ её объявить. Внутри сервера может быть что угодно: обращение к чужому API, запрос в базу, чтение файлов. Протокол не описывает, что делать, — он описывает, как рассказать о себе агенту, чтобы одну интеграцию можно было переиспользовать в разных агентах, а не переписывать под каждый. Пример с Figma из лекции корректный: существуют MCP-серверы, которые отдают агенту структуру макета в машиночитаемом виде — компоненты, размеры, цвета, — вместо картинки, которую пришлось бы угадывать глазами. Агент получает структурированный текст и работает с ним как с данными.
Как поднять свой MCP-сервер, чем отличаются транспорты и как не отдать агенту лишних прав — «MCP».
Как выглядит объявление инструмента
Минимальный пример: имя, описание, JSON-схема аргументов.
{
"name": "search_docs",
"description": "Ищет фрагменты во внутренней документации компании. Вызывай, когда ответ зависит от внутренних регламентов или проектных документов, которых нет в общих знаниях.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Поисковый запрос на естественном языке"
},
"top_k": {
"type": "integer",
"description": "Сколько фрагментов вернуть, по умолчанию 5"
}
},
"required": ["query"],
"additionalProperties": false
}
}
Три вещи, которые тут неочевидны и стоят дороже, чем кажется:
- Описание — это промпт, а не комментарий. Единственное, по чему модель решает, звать ли инструмент, — текст в
description. Поэтому в нём пишут не «ищет по документации», а когда именно его надо звать. Половина проблем с агентами, которые «не пользуются инструментом» или «зовут его всегда», лечится переписыванием одной этой строки. - Схема — это контракт.
requiredиadditionalProperties: falseозначают, что невалидный вызов не пройдёт проверку. Это дешевле, чем разбирать испорченные аргументы в своём коде. - Всё это — токены. Схема выше занимает порядка сотни токенов, и они уходят в модель на каждом ходу диалога, а не один раз.
Агенты: цикл вместо одного ответа
Инструменты сами по себе ещё не делают агента. Агент появляется, когда добавляется цикл: модель выбирает инструмент → оркестратор выполняет вызов → результат возвращается в контекст → модель смотрит на результат и решает, что делать дальше — и так до завершения задачи. Схема известна как ReAct (Reasoning + Acting) по работе Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models (2022), и с тех пор принципиально не менялась.
живут на стороне оркестратора, не модели
Обратите внимание на последнюю рамку — это самое важное в схеме и это же чаще всего понимают неправильно. Лимиты, таймауты и права инструментов живут на стороне оркестратора, а не модели. Модель не «решает остановиться» и не «соблюдает ограничения» — она в принципе не имеет такой способности, потому что не помнит предыдущих итераций иначе, чем через переданный ей контекст, и не знает, сколько их уже было. Всё, что она делает, — предлагает следующий вызов. Останавливает цикл, отказывает в опасной операции, обрывает по таймауту и спрашивает подтверждение у человека — обычный код вокруг модели.
Отсюда практическое следствие: качество агента определяется обвязкой не меньше, чем моделью. Ошибки накапливаются — если каждый шаг верен с вероятностью 0,95, то двадцать шагов подряд дают около 36% успеха, и никакой промпт этого не исправит. Лечится это проверяемыми шагами, откатом и ограничением глубины цикла — то есть инженерией оркестратора.
Подробности — «Агенты и ReAct». Anthropic формулирует общий принцип в статье Building Effective Agents: большинство работающих продакшн-систем — не агенты, а простые композиции вызовов, и подниматься на ступень выше стоит только после того, как текущая измеримо провалилась.
Чем за это платят
Здесь пора вспомнить арифметику из главы «Токены, контекстное окно и память диалога». У модели нет памяти: вся история пересылается заново на каждом ходу. В агентном цикле каждый ход — это отдельный запрос, и в нём заново оплачиваются:
- системный промпт;
- описания всех инструментов — целиком, даже тех, которые в этой задаче не понадобятся;
- вся предыдущая переписка, включая результаты всех инструментов, которые уже отработали.
Посчитаем на явно названных допущениях. Пусть системный промпт — 500 токенов, двенадцать инструментов дают примерно 1800 токенов описаний, вопрос — 100 токенов, каждый результат инструмента — 500 токенов, каждый ход модели — 150 токенов. Тогда на ходе номер n на вход уходит 2400 + 650 * (n - 1) токенов. За восемь ходов суммарно оплачивается около 37 000 входных токенов. Тот же вопрос одним запросом без инструментов — 600 входных токенов. Разница — примерно в шестьдесят раз, и это на скромном цикле из восьми шагов.
Числа здесь условные, зависят от ваших промптов и задачи; неусловна форма роста. Она квадратичная по числу ходов: каждый новый шаг оплачивает всё, что было до него. Кэширование неизменного префикса — системного промпта и списка инструментов — снимает существенную часть счёта, но не меняет самой зависимости.
И вторая сторона того же счёта: токены — это работа видеокарты. Каждый лишний токен контекста — это лишние умножения матриц на инференсе, о чём глава «Железо». Отсюда правило, которое стоит запомнить раньше, чем вы напишете первого агента: сокращать надо число инструментов и длину их описаний, а не только длину ответа. Реальная экономика продакшн-агентов — «Продакшн и стоимость».
Безопасность одной фразой
Всё, что попало в контекст, — письмо, веб-страница, содержимое файла, результат инструмента — может содержать инструкции, и модель не отличает ваши указания от чужих. Если у агента есть инструменты, чужая инструкция превращается в выполненное действие. Это отдельная большая тема, и она разобрана в «Безопасность и инъекции». Здесь достаточно одного: помнить об этом до того, как выдать агенту права на запись.
Мнение автора: для кого вся эта обвязка
Мнение. Позиция лектора: RAG, банки памяти и прочая обвязка не предназначены для бытового пользователя — они усиливают того, кто уже разбирается в предмете, и не приносят пользы тому, кто не разбирается.
Это оценочное суждение, а не проверяемый факт. По существу с ним можно согласиться в мягкой форме, и у согласия есть техническое обоснование. Обвязка не добавляет модели понимания вашей задачи — она доставляет модели то, что вы сумели сформулировать. RAG находит фрагменты по вашему запросу: если запрос расплывчат, найдутся расплывчатые фрагменты. Инструмент вызывается по вашему описанию: если описание не объясняет, когда его звать, модель будет звать невпопад. Банк памяти содержит те договорённости, которые вы в него записали.
Отсюда мягкая формулировка вместо резкой: чем точнее вы формулируете задачу и чем лучше понимаете предметную область, тем больше даёт обвязка. Не потому что она «не для всех», а потому что каждый её слой усиливает качество вашей постановки — в обе стороны.
Что в лекции сказано неточно
- RAG расшифровывается как Retrieval-Augmented Generation. В лекции расшифровка названа бесполезной; на самом деле она и есть краткое описание схемы: сначала retrieval — поиск, потом augmented generation — генерация по найденному.
- «Выгрузили цепь Маркова на флешку» — в RAG никакая цепь Маркова наружу не выгружается. Наружу выносятся тексты и их векторные представления; модель остаётся на месте и получает найденное обычным текстом в запросе.
- MCP представлен Anthropic в ноябре 2024 года. В другом месте лекции протокол приписан OpenAI — это ошибка.
- ReAct — это Reasoning + Acting, название схемы «рассуждай и действуй», а не «реактор».
- Про конкретные готовые навыки, выпущенные вендорами, в лекции звучит непроверенная деталь; механика навыков от конкретных примеров не зависит и описана выше нейтрально.
- Прогнозы о том, кого из специалистов заменит, а кого не заменит эта технология, — тоже мнение; они разбираются в главе «SDD, оркестрация и разговоры о замене специалистов».
Источники
- Lewis et al., Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks — исходная работа по RAG
- Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models — базовая петля агента
- Liu et al., Lost in the Middle — почему длинный контекст не решает проблему сам по себе
- Model Context Protocol — спецификация и серверы; анонс Anthropic, ноябрь 2024
- Anthropic, Building Effective Agents — почему простое обычно выигрывает у агентного
- Logseq — открытая база знаний, пример «ручного RAG» на одном пользователе
Мини-итог
- В весах модели нет ваших документов и вчерашних данных, а дообучение дорого и не решает свежесть — значит, нужное знание приносят в контекст запроса; контекст конечен, поэтому приносить надо релевантное, а не всё.
- RAG — это поиск, а не подключённое хранилище: документы режутся на фрагменты, фрагменты и вопрос превращаются в векторы одной и той же моделью, в промпт подставляются только ближайшие k фрагментов.
- Наивный поиск стоит
O(N * d)на запрос и до десятков тысяч фрагментов не требует никакой базы векторов; на миллионах переходят к приближённому поиску и платят за это точностью. - RAG заземляет ответ на документы, но не мешает модели исказить найденное: без обязательных цитат получается правдоподобная выдумка со ссылками.
- Банк памяти и личная база знаний — тот же приём без векторного поиска; он работает ровно до тех пор, пока файл влезает в окно и остаётся целиком релевантным. Навык — упаковка инструкций, а не новая способность модели.
- MCP — открытый протокол Anthropic (ноябрь 2024), стандартизующий не бизнес-логику, а способ её объявить: сервер выставляет инструменты, ресурсы и промпты, клиент-агент обнаруживает их и вызывает по единому контракту, поэтому одна интеграция переиспользуется разными агентами.
- Агент — это цикл «модель выбирает вызов, оркестратор выполняет, результат возвращается в контекст»; лимиты итераций, таймауты и права инструментов живут на стороне оркестратора, а не модели, и агентный цикл расходует токены кратно — описания инструментов оплачиваются на каждом ходу.
Что дальше
Мы научились приносить модели свои данные и свои инструменты. Осталась часть, которую нельзя ни принести, ни настроить: то, как поставщик модели решил, что она вообще может отвечать. Один и тот же вопрос, заданный в двух сервисах, даёт ответы, различающиеся не точностью, а смыслом — и причина не в размере модели, а в том, на чём и как её выравнивали.