Агентний RAG: надання ШІ можливості самостійно вирішувати, коли та де отримувати інформацію.
Чому статичний RAG не функціонує при багатоетапних та часочутливих запитах, та як агент LangGraph організовує маршрутизацію між Qdrant та веб-пошуком, з правилами та обмеженнями.
Джерела: 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 ШІ не здійснює пошук інформації за заздалегідь визначеним моментом. Він розглядає пошук як інструмент, подібний до калькулятора чи виконавця коду, та використовує його тоді, коли вважає доказів недостатньо. Це призводить до трьох форм поведінки, яких не може забезпечити статична схема обробки.
- Умовний пошук. Якщо контекст вже містить відповідь на запитання, агент повністю пропускає пошук.
- Багатоетапний пошук. Агент отримує інформацію, читає її, усвідомлює, що чогось бракує, і повертається за додатковими даними; кожен крок залежить від результатів попереднього. Саме так люди проводять дослідження.
Жодна з цих ідей не є новою. У статті ReAct, опублікованій Яо та його колегами у 2022 році, описано схему чергування кроків міркування з викликами інструментів, а агентський підхід RAG — це застосування ReAct до процесу пошуку даних [2].
Останніми змінами стали інструменти для реалізації цих технологій. LangGraph нарешті уможливив використання циклів із розгалуженнями. Tavily пропонує API для пошуку в Інтернеті, розроблене для агентів, яке повертає контент, готовий до обробки за допомогою LLM; згідно з їхніми даними, сервіс обробляє понад 100 мільйонів запитів на місяць із часом відповіді p50 у 180 мс [3]. Деталі можна знайти у пості Tavily 101.
Три ключові елементи
Уявіть детектива замість бібліотекаря. Бібліотекар бере потрібне за наказом. Детектив читає те, що є під рукою, визначає, чого бракує, йде його знайти, знову читає та продовжує, поки не можна буде обґрунтувати висновок. Цей цикл складається з трьох компонентів.
- Роутер визначає, якого типу пошук потрібен за запитом. Штучний інтелект розглядає запит та вирішує, чи достатньо наявного контексту, чи потрібні документи, чи потрібен Інтернет, або все це разом.
- Інструменти пошуку є джерелами доказів. Мінімум — два інструменти: один шукає у векторному сховищі внутрішніх документів, інший шукає в Інтернеті актуальну інформацію. Кожен з них — це звичайна функція, яку штучний інтелект викликає з рядком запиту.
Функціональна реалізація
Ось гібридний агент у 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 року.
Пов’язані статті
- Агентний пошук та достатній контекст: коли RAG не знає достатньо — Стандартний RAG часто відповідає занадто рано. Дізнайтеся, як планувати пошуки, направляти джерела, оцінювати докази та повторювати пошук, поки контекст не стане достатнім, з прикладом від Acme.
- Chroma Context-1: підагент пошуку на 20B для дешевшого та ефективнішого агентного RAG — Як Chroma Context-1 на 20B функціонує як підагент лише для пошуку, який скорочує власний контекст, як його навчали, що бракує без цього інструменту та де він має обмеження.