Головна / Статті

Агентний RAG: надання ШІ можливості самостійно вирішувати, коли та де отримувати інформацію.

Чому статичний RAG не функціонує при багатоетапних та часочутливих запитах, та як агент LangGraph організовує маршрутизацію між Qdrant та веб-пошуком, з правилами та обмеженнями.

Видавець: REACTAPP.TOP9 хв читання2167 слів

Джерела: Your RAG Pipeline Is Lying to You. The Problem Is Not the Embeddings — Agentic RAG

«RAG — це переважно вже вирішена проблема». Цю фразу часто кажуть під час аналізу команд, і чесна відповідь полягає у тому, що все залежить від того, що ви маєте на увазі під отриманням інформації. Кожен, хто протягом деякого часу використовував підходи RAG у реальних умовах — для створення резюме медичних записів, роботи з потоками даних від природної мови до SQL чи у багатоагентних дослідницьких системах, — зазвичай стикається з однаковими обмеженнями.

Обмеженням є не самі ембеддинги. Це припущення, що отримання інформації відбувається лише один раз, на певному етапі, з одним джерелом.

Статичний RAG (вбудовування документів, отримання кращих результатів серед k варіантів та передача їх ШІ) добре справляється з простими, самодостатніми запитаннями, що ґрунтуються на стабільних внутрішніх знаннях. Однак коли запит стає багатоетапним, чутливим до часу або потребує як внутрішніх документів, так і актуальних даних, статична схема починає створювати хибні відповіді. Така схема може порекомендувати клінічну процедуру на основі застарілих внутрішніх інструкцій, хоча швидкий пошук у Інтернеті тривалістю півхвилини дозволив би дізнатися про зміни.

Кращі ембеддинги цю проблему не вирішать. Допоможе лише дозволити ШІ самостійно вирішувати, коли та де отримувати інформацію.

Проблема підходу „отримати один раз, відповісти завжди“

Уявіть бібліотекаря, який може відповідати лише шляхом підходу до однієї полиці — завжди до тієї самої — ще до того, як ви закінчите свою фразу.

Запитайте «Які є побічні ефекти метформіну?», і система надасть відповідь. Тепер запитайте щось складніше: які взаємодії з ліками нещодавно були виявлені FDA щодо метформіну у поєднанні з новішими препаратами типу GLP-1, і чи охоплюють їх поточні протоколи лікування пацієнтів? Для цього потрібні внутрішні документи та актуальні регуляторні дані, а також аналіз розбіжностей між ними. Статичний RAG бере інформацію лише з одного джерела, а ШІ лише швидко обробляє отримане.

Це не гіпотетичний сценарій. У внутрішній оцінці медичної системи [1] статичний RAG досяг 89% рівня релевантності відповідей на стабільні запитання, але знизився до 61%, коли потрібно було порівнювати внутрішні клінічні документи з зовнішніми рекомендаціями щодо лікування. Прості оцінки не виявили цього проблемного аспекту, тому що відповіді здавалися правильними; насправді вони були застарілими.

Рішення легко сформулювати, але складно реалізувати: отримання інформації стає рішенням, а не окремим кроком.

Що означає агентний RAG

У агентному RAG ШІ не здійснює пошук інформації за заздалегідь визначеним моментом. Він розглядає пошук як інструмент, подібний до калькулятора чи виконавця коду, та використовує його тоді, коли вважає доказів недостатньо. Це призводить до трьох форм поведінки, яких не може забезпечити статична схема обробки.

  • Умовний пошук. Якщо контекст вже містить відповідь на запитання, агент повністю пропускає пошук.
  • Багатоетапний пошук. Агент отримує інформацію, читає її, усвідомлює, що чогось бракує, і повертається за додатковими даними; кожен крок залежить від результатів попереднього. Саме так люди проводять дослідження.
  • Шлях пошуку даних. Агент обирає між сховищем векторів, пошуком у Інтернеті чи структурованою базою даних залежно від запитання. Запит „Що сказано в нашій політиці?“ направляється до сховища векторів, тоді як запит „Що оголосила FDA минулого тижня?“ — до Інтернету.
  • Жодна з цих ідей не є новою. У статті ReAct, опублікованій Яо та його колегами у 2022 році, описано схему чергування кроків міркування з викликами інструментів, а агентський підхід RAG — це застосування ReAct до процесу пошуку даних [2].

    Останніми змінами стали інструменти для реалізації цих технологій. LangGraph нарешті уможливив використання циклів із розгалуженнями. Tavily пропонує API для пошуку в Інтернеті, розроблене для агентів, яке повертає контент, готовий до обробки за допомогою LLM; згідно з їхніми даними, сервіс обробляє понад 100 мільйонів запитів на місяць із часом відповіді p50 у 180 мс [3]. Деталі можна знайти у пості Tavily 101.

    Три ключові елементи

    Уявіть детектива замість бібліотекаря. Бібліотекар бере потрібне за наказом. Детектив читає те, що є під рукою, визначає, чого бракує, йде його знайти, знову читає та продовжує, поки не можна буде обґрунтувати висновок. Цей цикл складається з трьох компонентів.

    1. Роутер визначає, якого типу пошук потрібен за запитом. Штучний інтелект розглядає запит та вирішує, чи достатньо наявного контексту, чи потрібні документи, чи потрібен Інтернет, або все це разом.
    2. Інструменти пошуку є джерелами доказів. Мінімум — два інструменти: один шукає у векторному сховищі внутрішніх документів, інший шукає в Інтернеті актуальну інформацію. Кожен з них — це звичайна функція, яку штучний інтелект викликає з рядком запиту.
  • Перевірка обґрунтованості полягає у з’ясуванні, чи достатньо доказів. Перш ніж написати остаточну відповідь, агент оцінює, чи є отримана інформація достатньою та релевантною, і якщо ні, то здійснює ще одну пошукову операцію. Статичні пайплайни не можуть реалізувати такий цикл.
  • Функціональна реалізація

    Ось гібридний агент у LangGraph, який чергує між векторним сховищем Qdrant та веб-пошуком Tavily. Він обирає інструмент, може використовувати обидва по черзі та здійснює ще один пошук, якщо перший результат не дозволяє сформулювати відповідь. Частина, пов’язана з Qdrant, працює згідно з посібником з інтеграції LangChain.

    from typing import Annotated, Literal
    from langgraph.graph import StateGraph, START, END
    from langgraph.prebuilt import ToolNode
    from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
    from langchain_core.tools import tool
    from langchain_openai import ChatOpenAI
    from langchain_qdrant import QdrantVectorStore
    from langchain_openai import OpenAIEmbeddings
    from tavily import TavilyClient
    from qdrant_client import QdrantClient
    import operator
    from typing import TypedDict, Sequence
    
    # --- State definition ---
    class AgentState(TypedDict):
        messages: Annotated[Sequence, operator.add]
        retrieval_count: int  # guard against infinite loops
    
    # --- Tool 1: Vector store retrieval (internal docs) ---
    qdrant_client = QdrantClient(url="http://localhost:6333")
    embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
    vector_store = QdrantVectorStore(
        client=qdrant_client,
        collection_name="internal_docs",
        embedding=embeddings,
    )
    @tool
    def retrieve_from_docs(query: str) -> str:
        """
        Search internal document knowledge base.
        Use for questions about internal policies, protocols, or proprietary knowledge.
        """
        results = vector_store.similarity_search(query, k=4)
        if not results:
            return "No relevant internal documents found for this query."
        return "\n\n---\n\n".join([
            f"[Source: {doc.metadata.get('source', 'internal')}]\n{doc.page_content}"
            for doc in results
        ])
    
    # --- Tool 2: Web retrieval (live data) ---
    tavily = TavilyClient(api_key="YOUR_TAVILY_API_KEY")
    @tool
    def retrieve_from_web(query: str) -> str:
        """
        Search the live web for current information.
        Use for recent news, regulatory updates, or anything that may have changed
        since the internal knowledge base was last updated.
        """
        results = tavily.search(
            query=query,
            max_results=5,
            search_depth="advanced",  # deep retrieval for better accuracy
        )
        if not results.get("results"):
            return "No relevant web results found."
        return "\n\n---\n\n".join([
            f"[Source: {r['url']}]\n{r['content']}"
            for r in results["results"]
        ])
    
    # --- Agent node ---
    tools = [retrieve_from_docs, retrieve_from_web]
    tool_node = ToolNode(tools)
    llm = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)
    SYSTEM_PROMPT = """You are a research assistant with access to two retrieval tools:
    - retrieve_from_docs: internal document knowledge base (policies, protocols, proprietary data)
    - retrieve_from_web: live web search (recent news, regulatory updates, real-time information)
    For any question:
    1. Decide whether you need to retrieve information or can answer from context.
    2. If retrieval is needed, choose the right source - or call both if the question spans internal and live knowledge.
    3. After retrieving, check if the evidence is sufficient. If not, retrieve again with a refined query.
    4. Always cite your sources in the final answer.
    Do not hallucinate. If you cannot find sufficient evidence, say so explicitly."""
    def call_agent(state: AgentState) -> AgentState:
        messages = [SystemMessage(content=SYSTEM_PROMPT)] + list(state["messages"])
        response = llm.invoke(messages)
        return {
            "messages": [response],
            "retrieval_count": state.get("retrieval_count", 0)
        }
    def update_retrieval_count(state: AgentState) -> AgentState:
        return {
            "messages": [],
            "retrieval_count": state.get("retrieval_count", 0) + 1
        }
    
    # --- Routing logic ---
    MAX_RETRIEVALS = 3  # prevent infinite loops
    def should_continue(state: AgentState) -> Literal["tools", "end"]:
        last_message = state["messages"][-1]
        # If the LLM called a tool and we haven't hit the loop guard, continue
        if hasattr(last_message, "tool_calls") and last_message.tool_calls:
            if state.get("retrieval_count", 0) < MAX_RETRIEVALS:
                return "tools"
        return "end"
    
    # --- Build the graph ---
    graph_builder = StateGraph(AgentState)
    graph_builder.add_node("agent", call_agent)
    graph_builder.add_node("tools", tool_node)
    graph_builder.add_node("count_retrieval", update_retrieval_count)
    graph_builder.add_edge(START, "agent")
    graph_builder.add_conditional_edges(
        "agent",
        should_continue,
        {"tools": "tools", "end": END}
    )
    graph_builder.add_edge("tools", "count_retrieval")
    graph_builder.add_edge("count_retrieval", "agent")
    agent = graph_builder.compile()
    
    # --- Run it ---
    if __name__ == "__main__":
        result = agent.invoke({
            "messages": [
                HumanMessage(content=(
                    "What does our current patient protocol say about GLP-1 usage, "
                    "and are there any FDA safety updates issued in 2025 we should be aware of?"
                ))
            ],
            "retrieval_count": 0,
        })
        print(result["messages"][-1].content)
    

    Деякі деталі заслуговують уваги.

    Захист MAX_RETRIEVALS у логіці маршрутизації — це не просто декоративний елемент. Без нього агенти можуть виконувати до 15 запитів для отримання інформації за одне неоднозначне запитання, витрачаючи ресурси та терпіння. Зазвичай достатньо трьох запитів. Якщо цього недостатньо, запитання занадто широке або інструменти повертають непотрібну інформацію.

    Поле retrieval_count у стані дозволяє зупинити цикл. StateGraph від LangGraph передає його під час кожної зміни вузла, тож воно зберігається протягом усього діалогу, а не лише одного циклу.

    Системний запит також має велике значення. Вказівка моделі, коли використовувати кожен інструмент, а не просто інформація про його існування, допомагає розрізнити агента, який розумно маршрутизує запити, від того, хто з ліні використовує retrieve_from_web для всього.

    Коли агентський підхід RAG погіршує ситуацію

    Він не замінює статичний RAG. Він замінює його лише для певних типів задач, проте призводить до негативних наслідків у чотирьох випадках.

    • Основною обмеженням є затримка. Кожен прохід через цикл коштує додаткового виклику моделі та виклику інструменту. Для базового асистента для запитань та відповідей, де затримка p95 має залишатися менше секунди, використання агента, який може виконувати два-три цикли, є неприйнятним. Добре налаштований статичний збирач інформації працює швидше та достатній.
    • База знань є стабільною та самодостатньою. Якщо на всі запитання користувачів можна впевнено відповісти за допомогою векторного сховища, додавання складності є зайвим. Спочатку виміряйте ефективність статичної системи за допомогою RAGAS (стаття). Якщо рівень відтворення контексту перевищує 85%, а точність відповідей — 90%, то використання агентського обгортки є зайвим.
    • Не можна довіряти вибору інструментів моделі. Завдяки ретельно написаному системному запиту GPT-4o та Claude Sonnet приблизно у 88% випадків, згідно з тими самими внутрішніми вимірюваннями, правильно вибирають між пошуком у документах та в Інтернеті. Решта 12% призводить до помилок у джерелах інформації, які складніше виправити, ніж звичайні галюцинації, оскільки відповідь є обґрунтованою, просто знаходиться у неправильному місці. З менш потужною моделлю необхідно попередньо вибрати інструменти.
    • Даним у реальному часі не можна довіряти. Пошук у Інтернеті створює додаткову поверхню для атак. Tavily фільтрує спроби вставки запитів у свій API, але жоден фільтр не є ідеальним, тому все, що агент робить на основі вмісту Інтернету, повинно розглядатися як потенційно ворожий матеріал.

    Використовуйте агентський підхід RAG, коли знання походять з кількох джерел, і деякі з них змінюються. Почніть зі статичного RAG та оновіть його, коли показники RAGAS виявлять конкретні проблеми, які вирішує ця схема.

    З чого почати

    Найменший крок – менше п’яти хвилин: додайте один інструмент Tavily до існуючого агента LangChain за допомогою from langchain_community.tools.tavily_search import TavilySearchResults. LangGraph поки не потрібен. Зробіть інструмент доступним, напишіть системний запит, який визначатиме момент його виклику, виконайте п’ять тестових запитів та подивіться, що він обере.

    Наступний рівень – приблизно один день: спрямуйте наведений вище приклад LangGraph на ваш власний сховище векторів, залишивши обидва інструменти. Спочатку не використовуйте лічильник отримання даних, щоб побачити, як часто агент повторює операції, а потім додайте його. Різниця дасть багато корисної інформації.

    Розширена версія: додавати крок оцінки після кожного результату інструменту та перед наступною дією агента. Менш потужний та дешевший LLM відповідає «так» або «ні» на запитання про те, чи достатній отриманий контекст, і лише після цього основна модель формує свою відповідь. Цей крок підвищує рівень надійності використання контексту з «достатньо гарного під час тестування» до «надійного у реальних умовах роботи».

    Помилки маршрутизації інструментів, які становлять 12% усіх проблем, займають найбільшу кількість часу під час виправлення помилок у продакшені. Необхідно виявляти їх на ранніх етапах, щоб вузол оцінки міг їх виявити раніше, ніж це зроблять користувачі.

    Джерела

    [1] Внутрішні тести на платформі RAG у сфері охорони здоров’я з використанням RAGAS 0.1.x на наборі з 200 клінічних запитів. Це не опубліковане дослідження.

    [2] Yao та ін. (2022), ReAct: Synergizing Reasoning and Acting in Language Models, arXiv:2210.03629.

    [3] Блог Tavily, Tavily 101, з показниками затримки та обсягу даних, наведеними на сторінці продукту у травні 2026 року.

    Пов’язані статті

  • grep Beat Vector Search in Agent Memory Tests, and the Harness Mattered as Much — Дослідження 116 запитань LongMemEval показало, що grep випереджає технології векторного пошуку, але спосіб передачі результатів впливає на точність не менше, ніж сам інструмент пошуку. Що можна з цього зрозуміти.
  • Expert RAG: HyDE, Compression, Self-Querying and Agentic Workflows — Чотири передові техніки пошуку для практичного використання в RAG у Haystack: HyDE, контекстуальне стиснення, самостійний пошук та агентні робочі процеси, а також ситуації, коли варто їх використовувати.
  • Шар за шаром: проектування справжньої системи отримання даних RAG для AI-агентів — Сплануйте систему RAG із використанням контрактів SourceDocument та DocumentChunk, процесу розділення на частини, генерації ембеддингів та ChromaDB, усе це через RetrievalService, який забезпечує стабільність агента.
  • Проєкт RAG — це не один величезний файл Python: структура папок, яка дозволяє масштабування — Розділіть демонстраційний додаток RAG на компоненти інтеграції даних, розділення на частини, генерація ембеддингів, сховище векторів, отримання даних, формулювання запитів, клієнт LLM, API, тести та журнали, щоб додаток міг розвиватися за межі початкової стадії.
  • Практичні нотатки: Чому вашій RAG-пайплайн потрібен ре-ранкер (і як це зробити) — Детальний посібник з практичних нотаток: Чому вашій RAG-пайплайн потрібен ре-ранкер (і як це зробити): контракти, перевірки та готові фрагменти коду для команд, які використовують цю схему.