Руководство по RAG и MCP: Выбор правильного пути для LLM

Руководство по RAG и MCP: Выбор правильного пути для LLM


Введение

Большие языковые модели (LLMs) наиболее ценны, когда их используют с правильным подходом для решения проблемы. На высоком уровне можно выделить 2 подхода, которые вас заинтересуют: генерация с дополняющими данными и протокол контекста модели. Генерация с дополняющими данными (RAG) сосредоточена на том, чтобы обосновывать ответы существующими документами, базами знаний или руководствами. Протокол контекста модели (MCP), с другой стороны, заключается в предоставлении модели возможностей для извлечения актуальных данных или выполнения действий с помощью инструментов, API и рабочих процессов.

Если ваша цель – это отслеживаемые ответы на основе структурированных знаний, то RAG является ответом. Если ваше приложение требует актуальных данных или взаимодействия с системами, то MCP является логическим продолжением. Однако реальность реальных приложений часто является гибридной, где вы используете оба: RAG для предоставления контекста и обоснования, MCP для выполнения действий, и снова RAG для оформления объяснения обратно пользователю.

Этот гид предоставляет идеи и рекомендации по принятию решений о том, когда использовать каждый подход, а также известные потенциальные подводные камни. Вы также увидите, как рабочие процессы RAG → MCP → RAG являются основой производственных систем.

Основные выводы

  • Два взаимодополняющих паттерна: Взаимодействие с LLM обычно попадает в одну из двух общих схем: извлечение знаний (RAG) или выполнение действий с инструментами (MCP).
  • RAG превосходит в задачах поиска: Используйте RAG для статических или полустатических баз знаний, где ответы требуют обоснования, ссылок и явной прослеживаемости фактов.
  • MCP управляет живыми действиями: MCP лучше подходит, когда задача связана с работой с API, базами данных или рабочими процессами, которые требуют данных в реальном времени и изменений состояния.
  • У обоих есть подводные камни: RAG сталкивается с известными проблемами использования устаревшего контента, несоответствием подразделов или перегрузкой подсказок. MCP более рискован, когда инструменты плохо определены, вызовы инструментов могут застревать в циклах, или побочные эффекты инструментов потенциально небезопасны.
  • Многие сценарии производства сочетают оба подхода — извлечение знаний с помощью RAG, затем действия с помощью MCP и возвращение к RAG для объяснения и обоснования.
  • Многие сценарии производства используют гибридный поток, который смешивает оба — извлечение с помощью RAG, за которым следует действие с MCP, и возвращение к RAG для объяснения и обоснования.

Предварительные условия

  • Знакомство с LLM: Понимание того, что такое большие языковые модели и как они обрабатывают входные и выходные данные.
  • Удобно работать с API и базами данных: Понимание роли API, вызовов инструментов, структурированных источников данных и т. д.
  • Осведомленность о концепциях извлечения: Рабочее понимание поиска, индексации, векторного представления, TF-IDF или, по крайней мере, использование терминов, поможет вам следовать разделам о RAG.
  • Знание программирования (Python): Поскольку у нас есть пример кода (датаклассы, реестры инструментов, процессоры) на Python, полезно уметь понимать скрипты.
  • Системный подход: Ожидайте учитывать компромиссы, режимы сбоев и гибридные потоки при создании реальных AI приложений.

Понимание RAG и MCP (Краткие определения)

Мы разъясним значение каждого термина перед тем, как обсудить, как выбрать между ними.

  • Генерация с дополнением извлечения (RAG): Это включает обертывание модели больших языков (LLM) с шагом извлечения (поиск или векторная база данных). Вы индексируете содержимое (разбиваете документы на части, получаете встраивания и храните их в индексе). Когда поступает запрос, система извлекает наиболее релевантные части из индекса и дополняет их входными данными для LLM. Модель затем генерирует ответ на основе извлеченной информации.
  • Протокол Контекста Модели (MCP): Это официальный контракт, предназначенный для интеграции внешних источников данных и инструментов в модель. Настройка на основе MCP обычно будет иметь набор зарегистрированных инструментов (функции, API, запросы к базе данных и т. д.) с их интерфейсами (название и схема JSON для ввода/вывода). Когда модель получает задачу, она может, выбрав один из этих инструментов, вывести структурированный вызов (т.е. JSON с заполненными параметрами). Среда хостинга контролирует эти вызовы, затем инициирует соответствующий вызов функции/API и отправляет результат обратно в модель.

Определите сценарии, наиболее подходящие для RAG

В общем, RAG должен быть вашим стандартным решением в любой ситуации, когда информация, которую вы пытаетесь найти, уже задокументирована или относительно статична. Вы должны рассмотреть возможность использования RAG, если:

  • Ответ находится где-то в вашем статическом или полустатическом объеме знаний: документы политики, спецификации продуктов, руководства по эксплуатации, научные статьи и т.д.
  • Вам требуется прослеживаемость и основанные на фактах ответы. С RAG вы всегда можете заставить модель указывать источники или показывать вам, какой документ и раздел предоставили информацию.
  • Ультра-низкая задержка не является строгим требованием (в разумных пределах). Если ваша область может справляться с небольшими задержками и не требует вызова внешних API в реальном времени для каждого запроса, RAG будет хорошим выбором.

Определите сценарии, наиболее подходящие для MCP

MCP преуспевает, когда статических документов недостаточно. Вы должны выбрать подход MCP (с использованием инструмента), если:

  • Вы хотите включить актуальные или динамические данные, которые отсутствуют в документах. Когда данные, которые вам нужны, находятся за пределами API или базы данных и, вероятно, будут меняться со временем (такие как уровень запасов продукции, погода, информация о пользовательской учетной записи и многое другое), модель использует инструмент для получения последней версии этих данных.
  • Вы хотели бы, чтобы модель предприняла действие, а не просто дала ответ. Действия могут быть, например, “Создать тикет в службе поддержки по этой проблеме”, “Отправить приветственное письмо новому пользователю” или “Заказать еще 50 единиц продукта X”.
  • Выполняйте сложные многошаговые рабочие процессы. С MCP модель может планировать и выполнять последовательности вызовов инструментов. Например, используя данные, полученные от одного вызова API, как часть решения о вызове другого.

Определить потенциальные режимы отказа

Оба RAG и MCP имеют режимы отказа. Знание их заранее позволяет создать более надежную систему. Вот некоторые распространенные режимы отказа, о которых стоит помнить:

Режимы отказа RAG:

  • Устаревшее/Недостающее содержание: Если ваш документный корпус устарел или фактически не содержит ответ, поиск не извлечет его из ниоткуда.
  • Проблемы с делением на части или воспроизведением*:* Системы RAG часто разбивают документы на более мелкие части для индексации. Если соответствующий факт разбит по частям или запрос использует другие слова/синонимы, чем сохраненный текст, то извлекатель может не выбрать правильный фрагмент.
  • Перегруженный контекст*:* Утопление вашего запроса в слишком большом количестве извлеченных фрагментов (т.е. за пределами размера контекстного окна модели или слишком много нерелевантного текста) может повредить производительности модели.

Режимы отказа MCP:

  • Плохие определения инструментов*:* Если инструменты, доступные модели, имеют неясные названия, плохо сформулированные описания или входные/выходные данные с плохо определёнными схемами, модель может использовать их неправильно.
  • Планирование циклов или неправильное использование инструментов*:* Безграничная модель может попасть в бесконечный цикл или постоянно вызывать инструменты в циклах, когда не уверена, как решить проблему (особенно если вызовы инструментов не дают необходимого ответа, и нет защитных механизмов, чтобы предотвратить дальнейшие попытки).
  • Безопасность и Побочные Эффекты*:* Позволяя модели принимать действия, нам также следует учитывать потенциальные побочные эффекты. Например, если инструмент используется без разрешения, его можно злоупотребить (например, инструмент, который читает произвольную информацию, может быть неправомерно использован для чтения частной или ограниченной информации).

Осведомленность об этих режимах сбоев будет формировать соответствующие меры безопасности. Например, вам необходимо создать высококачественную базу знаний для RAG с хорошими стратегиями извлечения, предоставить четко определенные схемы инструментов и политики использования для MCP, а также установить границы для действий.

Выбор RAG или MCP – краткие правила

Ниже приведены несколько простых рекомендаций, которые могут помочь определить, с какого подхода начать для данного запроса или функции:

  • Если на вопрос можно ответить, просто прочитав некоторый существующий текст, используйте RAG. Спросите: «Есть ли информация, которая уже где-то записана, и к которой у системы есть доступ?» Если да, то, скорее всего, это проблема извлечения.
  • Если задача заключается в запросе к актуальным данным или в действии, используйте MCP (инструменты). Если человек решит запрос, проверив что-то в базе данных или нажав кнопку в системе, это очень сильный намек на то, что помощник должен использовать инструмент.
  • Если запрос пользователя требует как знаний, так и действий, рассмотрите гибридный подход. Некоторые проблемы из реальной жизни потребуют обоих. Сначала необходимо получить некоторые знания, такие как политика или правило; затем будет предпринято действие; и затем результат этого действия должен быть объяснён. Шаблон RAG → MCP → RAG является очень распространённым.

RAG против MCP Демонстрация: Ассистент операций книжного магазина

Этот демонстрационный пример иллюстрирует два режима для ответа на вопросы пользователей о том, как функционирует книжный магазин. На стороне RAG ответ извлекается из статического «справочника» с политиками. На стороне инструментов в стиле MCP ответ возвращается путем вызова живой функции для получения статуса запасов или выполнения действия. В этом коде RAG реализован как небольшой извлекатель TF-IDF по некоторым статическим текстам политик. Инструменты в стиле «MCP» симулируются простыми функциями на Python (обратите внимание, что фактически никакая LLM не используется).

Настройка и импорт

""" Run locally:   python -m pip install --upgrade pip   pip install gradio scikit-learn numpy pandas   python RAG_vs_MCP_Demo_App.py   """ from __future__ import annotations  import re import json from dataclasses import dataclass, asdict from typing import Any, Callable, Dict, List, Tuple, Optional  import numpy as np import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity import gradio as gr 

Статическая ручная справка (RAG)

Компонент RAG рассматривает политику «руководство» как небольшую базу знаний. Код сохраняет 4 документа политики (возвраты, доставка, участие, подарочные карты) в DOCS. Он подстраивает TfidfVectorizer(stop_words=«english ») на этих текстах для создания матрицы терм-документ.

TF-IDF (частота термина – обратная частота документа) — это числовая статистика, которая оценивает слово на основе его важности для документа в корпусе. Запрос пользователя векторизуется с использованием той же модели TF-IDF, и вычисляется косинусное сходство между векторами запроса и документа.

------------------------------ 1) Tiny knowledge base (handbook) for RAG ------------------------------ DOCS = [     {         "id": "policy_returns",         "title": "Returns & Refunds Policy",         "text": (             "Customers can return most new, unopened items within 30 days of delivery for a full refund. "             "Items must be in their original condition with receipt. Refunds are processed to the original payment method. "             "Defective or damaged items are eligible for free return shipping."         ),     },     {         "id": "policy_shipping",         "title": "Shipping Policy",         "text": (             "Standard shipping typically takes 3 to 5 business days. Expedited shipping options are available at checkout. "             "International orders may take 7 to 14 business days depending on destination and customs."         ),     },     {         "id": "policy_membership",         "title": "Membership Benefits",         "text": (             "Members earn 2 points per dollar spent, get early access to new releases, and receive a monthly newsletter with curated picks. "             "Points can be redeemed for discounts at checkout."         ),     },     {         "id": "policy_giftcards",         "title": "Gift Cards",         "text": (             "Gift cards are available in denominations from $10 to $200 and are redeemable online or in-store. "             "They do not expire and cannot be redeemed for cash except where required by law."         ),     }, ]  Fit a very small TF‑IDF retriever at startup VECTORIZER = TfidfVectorizer(stop_words="english") KB_TEXTS = [d["text"] for d in DOCS] KB_MATRIX = VECTORIZER.fit_transform(KB_TEXTS)   def rag_retrieve(query: str, k: int = 3) -> List[Dict[str, Any]]:     """Return top-k documents as {id,title,text,score}."""     if not query.strip():         return []     q_vec = VECTORIZER.transform([query])     sims = cosine_similarity(q_vec, KB_MATRIX)[0]     idxs = np.argsort(-sims)[:k]     results = []     for i in idxs:         results.append({             "id": DOCS[i]["id"],             "title": DOCS[i]["title"],             "text": DOCS[i]["text"],             "score": float(sims[i]),         })     return results   def rag_answer(query: str, k: int = 3) -> Tuple[str, List[Dict[str, Any]]]:     """Simple, template-y answer based on top-k docs."""     hits = rag_retrieve(query, k=k)     if not hits:         return ("I couldn't find anything relevant in the handbook.", [])      # Compose a short grounded answer using snippets     bullets = []     for h in hits:         # Take the first sentence as a snippet         first_sentence = h["text"].split(".")[0].strip()         if first_sentence:             bullets.append(f"- **{h['title']}**: {first_sentence}.")     answer = (         "**Handbook says:**n" + "n".join(bullets) +         "nn(Answer generated from retrieved policy snippets; no LLM used.)"     )     return answer, hits 

Запрос на получение RAG работает следующим образом:

  • Тексты Политики Индексов: Преобразуйте каждый абзац справочника в вектор TF-IDF.
  • Встраивание запроса: Векторизовать вопрос пользователя в вектор TF-IDF, используя тот же векторизатор.
  • Вычислить схожесть: Рассчитать коэффициент косинусного сходства между каждым документом политики и запросом.
  • Извлечение лучших документов: выберите k лучших документов с наивысшими значениями схожести в качестве релевантных результатов.

Это игрушечная замена более полноценному RAG конвейеру, который использует нейронные встраивания. После определения лучших результатов политики, rag_answer() используется для составления короткого ответа. Для каждого результата он извлекает первое предложение текста политики и форматирует его в виде маркера с заголовком политики.

MCP-стиль Инструменты (Живой Инвентарь)

Часть демонстрационного кода “MCP” представляет собой то, как ассистент будет вызывать функции для манипулирования данными о запасах в реальном времени.

------------------------------ 2) MCP-style tool registry + client executor ------------------------------ @dataclass class ToolParam:     name: str     type: str  # e.g., "string", "number", "integer"     description: str     required: bool = True   @dataclass class ToolSpec:     name: str     description: str     params: List[ToolParam]   @dataclass class ToolCall:     tool_name: str     args: Dict[str, Any]   @dataclass class ToolResult:     tool_name: str     args: Dict[str, Any]     result: Any     ok: bool     error: Optional[str] = None   In-memory "live" inventory INVENTORY: Dict[str, Dict[str, Any]] = {     "Dune": {"stock": 7, "price": 19.99},     "Clean Code": {"stock": 2, "price": 25.99},     "The Pragmatic Programmer": {"stock": 5, "price": 31.50},     "Deep Learning": {"stock": 1, "price": 64.00}, }   Define actual tool functions def tool_get_inventory(title: str) -> Dict[str, Any]:     rec = INVENTORY.get(title)     if not rec:         return {"title": title, "found": False, "message": f"'{title}' not in inventory."}     return {"title": title, "found": True, **rec}   def tool_set_price(title: str, new_price: float) -> Dict[str, Any]:     rec = INVENTORY.get(title)     if not rec:         return {"title": title, "updated": False, "message": f"'{title}' not in inventory."}     rec["price"] = float(new_price)     return {"title": title, "updated": True, **rec}   def tool_place_order(title: str, quantity: int) -> Dict[str, Any]:     rec = INVENTORY.get(title)     if not rec:         return {"title": title, "ordered": False, "message": f"'{title}' not in inventory."}     if quantity <= 0:         return {"title": title, "ordered": False, "message": "Quantity must be positive."}     rec["stock"] += int(quantity)     return {"title": title, "ordered": True, "added": int(quantity), **rec}   Registry of specs (like MCP manifests) TOOL_SPECS: Dict[str, ToolSpec] = {     "get_inventory": ToolSpec(         name="get_inventory",         description="Get stock and price for a given book title.",         params=[             ToolParam("title", "string", "Exact book title"),         ],     ),     "set_price": ToolSpec(         name="set_price",         description="Update the price for a book title.",         params=[             ToolParam("title", "string", "Exact book title"),             ToolParam("new_price", "number", "New price in dollars"),         ],     ),     "place_order": ToolSpec(         name="place_order",         description="Increase stock by ordering more copies.",         params=[             ToolParam("title", "string", "Exact book title"),             ToolParam("quantity", "integer", "How many copies to add"),         ],     ), }  Mapping tool names to callables TOOL_IMPLS: Dict[str, Callable[..., Any]] = {     "get_inventory": tool_get_inventory,     "set_price": tool_set_price,     "place_order": tool_place_order, }   def validate_and_call(call: ToolCall) -> ToolResult:     spec = TOOL_SPECS.get(call.tool_name)     if not spec:         return ToolResult(tool_name=call.tool_name, args=call.args, result=None, ok=False, error="Unknown tool")      # minimal validation     for p in spec.params:         if p.required and p.name not in call.args:             return ToolResult(tool_name=call.tool_name, args=call.args, result=None, ok=False, error=f"Missing param: {p.name}")      try:         fn = TOOL_IMPLS[call.tool_name]         result = fn(**call.args)         return ToolResult(tool_name=call.tool_name, args=call.args, result=result, ok=True)     except Exception as e:         return ToolResult(tool_name=call.tool_name, args=call.args, result=None, ok=False, error=str(e)) 

Сам код определяет список книг в памяти, каждая из которых имеет соответствующее количество на складе и цену. Он также определяет 3 вспомогательные функции для проверки/обновления этого инвентаря:

  • get_inventory(title: str): Найдите книгу по названию и верните ее наличие и цену, или сообщение о том, что книга не найдена.
  • установить_цену(название: str, новая_цена: float): Обновите цену книги до указанного значения.
  • place_order(title: str, quantity: int): Увеличьте запасы книги на указанное количество (симулируя заказ на большее количество экземпляров).

Все они зарегистрированы в простом реестре инструментов в памяти. ToolSpec для каждого инструмента содержит его имя, описание и схему параметров (имя, тип, описание). Это похоже на то, как MCP или фреймы вызова функций определяют инструменты с структурированными схемами ввода. Реальный API LLM аналогично предоставит эти инструменты с помощью JSON-схем для их названия и параметров цены.

MCP «стандартизирует, как инструменты определяются, размещаются и открываются для LLM» и упрощает их обнаружение и использование моделью. Наш код на Python использует простые датаклассы (ToolParam, ToolSpec и т.д.), чтобы зафиксировать эти схемы.

Функция validate_and_call() принимает предложенный ToolCall (имя инструмента + аргументы) и вызывает соответствующую ему Python-функцию, возвращая ToolResult (выходные данные или ошибку). Это аналогично тому, как бэкэнд получает запрос на вызов функции модели и выполняет этот API-вызов в развернутой системе LLM.

Маршрутизация запросов: RAG против инструментов против обоих

Приложение может запускаться в режиме Авто, только RAG или только Инструменты. В режиме Авто приложение использует базовые эвристики для определения, как обрабатывать каждый запрос.

  • Если в запросе есть ключевое слово политики, это активирует извлечение RAG.
  • Если он содержит такие ключевые слова, как «в наличии», «цена» или «заказ» и цитируемое название, это вызовет процесс вызова инструмента.
  • Если оба типа потребностей присутствуют (например, «Dune… политика возврата»), код устанавливает маршрут для обоих, чтобы он выполнил извлечение и вызвал инструмент.
  • В противном случае он по умолчанию переходит в один из этих режимов…

Это отражает общую идею, что RAG предназначен для статического поиска знаний, в то время как вызовы функций используются для динамических данных или действий.

------------------------------ 3) Simple planner/router: choose RAG vs Tools (MCP-style) vs Both ------------------------------ TOOL_KEYWORDS = {     "get_inventory": ["in stock", "stock", "available", "availability", "have", "inventory"],     "set_price": ["change price", "set price", "update price", "price to", "discount", "mark down"],     "place_order": ["order", "restock", "add", "increase stock"], }  BOOK_TITLE_PATTERN = r"'([^']+)'|"([^"]+)""  # capture 'Title' or "Title"   def extract_titles(text: str) -> List[str]:     titles = []     for m in re.finditer(BOOK_TITLE_PATTERN, text):         titles.append(m.group(1) or m.group(2))     return titles   def decide_tools(query: str) -> Optional[ToolCall]:     q = query.lower()     titles = extract_titles(query)      # get_inventory     if any(kw in q for kw in TOOL_KEYWORDS["get_inventory"]):         if titles:             return ToolCall(tool_name="get_inventory", args={"title": titles[0]})      # set_price  (look for a number)     if any(kw in q for kw in TOOL_KEYWORDS["set_price"]):         price_match = re.search(r"(d+.?d*)", q)         if titles and price_match:             return ToolCall(tool_name="set_price", args={"title": titles[0], "new_price": float(price_match.group(1))})      # place_order  (look for an integer quantity)     if any(kw in q for kw in TOOL_KEYWORDS["place_order"]):         qty_match = re.search(r"(d+)", q)         if titles and qty_match:             return ToolCall(tool_name="place_order", args={"title": titles[0], "quantity": int(qty_match.group(1))})      return None   def route_query(query: str, mode: str = "Auto") -> str:     if mode == "RAG only":         return "rag"     if mode == "Tools only":         return "tools"      # Auto: detect whether we need tools, rag, or both     # If a single sentence includes both a policy question + inventory check, we'll call it "both".     needs_tool = decide_tools(query) is not None     needs_rag = any(ch in query.lower() for ch in ["policy", "return", "refund", "shipping", "membership", "gift card", "gift cards", "benefits"])      if needs_tool and needs_rag:         return "both"     if needs_tool:         return "tools"     return "rag" 

В этом демонстрационном примере,

  • вопрос вроде “Какова наша политика возвратов? ” содержит слово “возвраты,” → что активирует RAG.
  • «У нас есть ‘Дюна’ в наличии?» — вопрос содержит «в наличии» и процитированное название → что вызывает инструмент get_inventory.
  • Если пользователь объединит эти два запроса: «У нас есть ‘Дюна’ в наличии, и какова наша политика возврата?», то будут активированы оба маршрута. Ответ, следовательно, будет включать часть из справочника и часть из актуального запаса.

Обработка запроса и составление ответа

Функция handle_query(q, mode, show_trace) ниже выполняет вышеуказанную маршрутизацию и выдает окончательный ответ.

------------------------------ 4) Orchestrator: build a human-friendly answer + trace ------------------------------  def handle_query(query: str, mode: str = "Auto", show_trace: bool = True) -> Tuple[str, str, pd.DataFrame]:     route = route_query(query, mode=mode)      tool_trace: List[Dict[str, Any]] = []     rag_hits: List[Dict[str, Any]] = []     parts: List[str] = []      if route in ("rag", "both"):         rag_ans, rag_hits = rag_answer(query)         parts.append(rag_ans)      if route in ("tools", "both"):         call = decide_tools(query)         if call:             res = validate_and_call(call)             tool_trace.append(asdict(call))             tool_trace[-1]["result"] = res.result             tool_trace[-1]["ok"] = res.ok             if res.error:                 tool_trace[-1]["error"] = res.error              # Compose a user-friendly tool result string             if res.ok and isinstance(res.result, dict):                 if call.tool_name == "get_inventory":                     if res.result.get("found"):                         parts.append(                             f"**Inventory:** '{res.result['title']}' -- stock: {res.result['stock']}, price: ${res.result['price']:.2f}"                         )                     else:                         parts.append(f"**Inventory:** {res.result.get('message','Not found')}" )                 elif call.tool_name == "set_price":                     if res.result.get("updated"):                         parts.append(                             f"**Price updated:** '{res.result['title']}' is now ${res.result['price']:.2f}"                         )                     else:                         parts.append(f"**Set price failed:** {res.result.get('message','Error')}" )                 elif call.tool_name == "place_order":                     if res.result.get("ordered"):                         parts.append(                             f"**Order placed:** Added {res.result['added']} copies of '{res.result['title']}'. New stock: {res.result['stock']}"                         )                     else:                         parts.append(f"**Order failed:** {res.result.get('message','Error')}" )             else:                 parts.append("Tool call failed.")         else:             parts.append("No suitable tool call inferred from your request.")      # Prepare trace artifacts     trace = {         "route": route,         "tool_calls": tool_trace,         "retrieved_docs": rag_hits,     }      # DataFrame for retrieved docs (for a quick visual)     df = pd.DataFrame([         {             "id": h["id"],             "title": h["title"],             "score": round(h["score"], 3),             "snippet": h["text"][:140] + ("..." if len(h["text"])>140 else ""),         }         for h in rag_hits     ])      answer_md = "nn".join(parts) if parts else "(No answer composed.)"     trace_json = json.dumps(trace, indent=2)      return answer_md, trace_json, df 

Это примерно делает следующее:

  • Часть RAG: Для процесса RAG мы вызываем rag_answer(q). Это возвращает строку в формате Markdown и извлеченные документы.
  • Часть инструмента: Если нам нужно запустить инструмент, мы вызываем decide_tools(q), чтобы получить ToolCall. validate_and_call() выполняет инструмент и возвращает результат.
  • Объединить ответы: Фрагмент ответа RAG и строка ответа инструмента объединяются с помощью переносов строк между ними. Если маршрут был «оба», то обе части будут присутствовать. Если используется только одна, то другая часть просто опускается.

Пользовательский интерфейс (Демо Gradio)

В Gradio пользовательский интерфейс (gr.Blocks) включает название и инструкции, текстовое поле для запроса пользователя, выпадающий список для выбора режима (Авто, только RAG, только инструменты) и кнопку «Запустить». Под вводами отображается Ответ (Markdown), Трассировка (JSON) и таблица извлеченных документов.

С Gradio, управляющим веб-сервером и рендерингом, код сосредоточен на логике. Когда пользователь нажимает «Запустить», вызывает функцию handle_query() с вводами. Интерфейс затем отображает составленный ответ и основную трассировку. Запуск скрипта откроет локальную веб-страницу, где можно вводить запросы и видеть живые результаты.

------------------------------ 5) Gradio UI ------------------------------ with gr.Blocks(title="RAG vs MCP Demo: Bookstore Ops Assistant") as demo:     gr.Markdown(         "# RAG vs MCP Demo: Bookstore Ops Assistantn"         "Use this sandbox to feel the difference between RAG (lookup from a handbook) and MCP-style tools (act on live data).nn"         "**Tips**: Put book titles in quotes, e.g., 'Dune' or "Clean Code"."     )      with gr.Row():         query = gr.Textbox(label="Your request", placeholder="e.g., Do we have 'Dune' in stock? Or: What is our returns policy?", lines=2)     with gr.Row():         mode = gr.Dropdown(["Auto", "RAG only", "Tools only"], value="Auto", label="Routing mode")         show_trace = gr.Checkbox(True, label="Show trace")         submit = gr.Button("Run", variant="primary")      answer = gr.Markdown(label="Answer")     trace = gr.JSON(label="Trace (route, tool calls, retrieved docs)")     table = gr.Dataframe(headers=["id", "title", "score", "snippet"], label="Retrieved docs (RAG)")      def _run(q, m, t):         ans, tr, df = handle_query(q or "", mode=m or "Auto", show_trace=bool(t))         return ans, json.loads(tr), df      submit.click(_run, inputs=[query, mode, show_trace], outputs=[answer, trace, table])  if __name__ == "__main__":     demo.launch() 

Вывод:

Вы можете попробовать следующие запросы:

  • Какова наша политика возвратов?
  • Как долго занимает стандартная доставка?
  • «У нас есть ‘Дюна’ в наличии?»
  • Закажите 3 копии ‘Прагматичного программиста’
  • Измените цену на ‘Чистый код’ на 29.99
  • «У нас есть ‘Дюна’ в наличии, и какова наша политика возврата?»

Эти примеры иллюстрируют RAG с использованием статической информации в формате часто задаваемых вопросов и инструментов, работающих с живыми данными. На практике многие современные ИИ-системы являются гибридными. Например, чат-бот для поддержки клиентов может получать данные учетной записи через API-запросы, а также ссылаться на документацию по продукту, хранящуюся в базе знаний.

РАЗДЕЛ ЧАСТО ЗАДАВАЕМЫХ ВОПРОСОВ

1. Когда я должен использовать RAG вместо MCP?

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

2. Когда MCP является лучшим выбором?

Выберите MCP, если вам нужно использовать или работать с живыми данными. Вы также можете выбрать его, если запрос зависит от API, баз данных или если модели необходимо выполнить действие (создать тикет, отправить электронное письмо и т. д.).

3. Каковы основные риски каждого подхода?

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

4. Могу ли я объединить RAG и MCP в одном рабочем процессе?

Да. На самом деле многие реальные случаи потребуют обоих решений. Например, ассистент может сначала получить политику гарантии через RAG, затем разместить заказ на замену устройства с помощью MCP, а в конце подтвердить действие, сославшись на соответствующую политику. Поток “RAG → MCP → RAG” очень распространен в производственных системах.

5. Как мне быстро решить, использовать ли RAG или MCP для нового запроса?

Простое правило:

  • Если бы человек “посмотрел это в документе,” используй RAG.
  • Если человек “нажмет кнопку или запросит базу данных,” используйте MCP.

Заключение

Думайте о RAG как о способе чтения того, что вы знаете, а о MCP как о способе выполнения задач (и доступа к живым данным). На практике вы, вероятно, начинаете с RAG, когда ответы уже есть в документах и требуются ссылки. Обращайтесь к MCP, когда задача связана с API, базами данных или каким-либо рабочим процессом. Как только оба подхода хорошо спроектированы, объедините их, чтобы создать сквозные потоки (RAG → MCP → RAG), которые обосновывают решения и предпринимают действия.

Если вы действительно хотите сервер MCP «из коробки» для управления вашей инфраструктурой, DigitalOcean предоставляет открытый сервер MCP, который взаимодействует с клиентами, совместимыми с MCP (Claude Desktop/Code, Cursor и т. д.). Вы предоставляете токен API DO, регистрируете сервер, а затем естественные языковые команды, такие как «развернуть этот репозиторий на App Platform» или «показать журналы для сервиса X», могут быть преобразованы в вызовы API. Это хороший способ подключить «действие» вашей системы без написания собственного кода.

Ресурсы

  • Протокол контекста модели (MCP): руководство разработчика по интеграции LLM с длительным контекстом
  • Интеграция агентного RAG с серверами MCP: Рекомендации по технической реализации
  • RAG-MCP: Смягчение избыточности запросов при выборе инструментов LLM с помощью генерации с дополнением извлечений

Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *