You’ve built a vector index, wired up embeddings, and your RAG pipeline answers questions with respectable accuracy—until a user asks something that requires a join across three tables, a time-series aggregation, or a comparison of structured and unstructured data. Static RAG pipelines fail precisely at that boundary: they either retrieve chunks that don’t contain the answer, or they return plausible-looking text that is factually wrong. The fix isn’t a better retriever—it’s an agent that decides whether to query the database at all, and if so, how. That’s agentic RAG: a loop where an LLM plans, executes, critiques, and retries, using your database as both a source of truth and a tool.
Why Static RAG Falls Short in Complex Queries
Static RAG assumes the answer lives in a few top-k chunks. That assumption breaks when the query is aggregative (“total revenue by region last quarter”), relational (“customers who bought X but not Y”), or temporal (“average latency before and after the deploy”). In those cases, vector similarity returns semantically related text, but the actual value must be computed from structured data. The result is either a hallucinated number or a refusal to answer.
Moreover, static pipelines cannot adapt to the query’s shape. A question like “Which products have the highest return rate and what do customers say about them?” needs both a SQL aggregation and a vector search over review text. A fixed retrieval strategy cannot switch modes mid-query. Agentic RAG solves this by treating the database as an environment—the agent issues actions (queries), observes results, and iterates until it has enough evidence to answer.
The Agentic RAG Architecture: Planner, Executor, and Critic
An agentic RAG system typically has three roles, which may be separate LLM calls or a single loop with distinct prompts:
- Planner: Parses the user’s natural language question, decomposes it into subtasks, and decides which tool to use for each—SQL, vector search, or a hybrid.
- Executor: Runs the chosen query against the database engine, handles errors (e.g., malformed SQL, missing columns), and returns results in a structured format.
- Critic: Evaluates whether the retrieved or computed results actually answer the question. If not, it suggests a revised plan—more specific filters, a different join, or a broader vector search.
The loop continues until the critic is satisfied or a maximum iteration count is reached. Crucially, the critic does not just check for empty results; it checks for relevance and sufficiency. For example, if the planner chose vector search and the critic sees that the answer requires a numeric comparison, it can re-route to SQL.
A minimal implementation might look like this pseudo-code:
def agentic_rag(question, db, max_iters=3):
plan = planner(question) # e.g., ["sql: SELECT ...", "vector: ..."]
for i in range(max_iters):
results = [executor(tool, db) for tool in plan]
critique = critic(question, results)
if critique["satisfied"]:
return synthesize(question, results)
plan = critique["revised_plan"]
return fallback_answer(question, results)
Deciding When to Query: Intent Detection and Confidence Scoring
Not every question needs a database query. “What is the capital of France?” can be answered from the LLM’s parametric memory. Querying a database for that wastes latency and cost. The agent must first classify intent into at least three buckets: factual recall (no query), structured lookup (SQL), semantic search (vector), and hybrid (both).
Confidence scoring is the mechanism. The planner assigns a probability to each intent based on the question’s syntax and entities. For instance, presence of comparative adjectives (“higher than”), numbers, or date ranges pushes confidence toward SQL. Presence of descriptive language (“what do users complain about”) pushes toward vector search. If confidence for any single intent is below a threshold (say 0.7), the agent defaults to hybrid.
A practical rule of thumb:
If the question contains an explicit metric, filter, or aggregation keyword (e.g., “average”, “count”, “where”, “between”), default to SQL. If it contains qualitative descriptors or open-ended language, default to vector search. If both appear, run both and merge.
This rule is not foolproof, but it prevents the most common failure: sending a “how many” question to a vector index.
Deciding How to Query: SQL, Vector Search, or Hybrid Strategies
Once the agent decides to query, it must pick the execution strategy. For SQL, the planner generates a candidate query, but it should never execute it blindly. Instead, it first inspects the schema (via information_schema or equivalent) to validate table and column names. A common pattern is to generate the SQL, run a dry-run with EXPLAIN or a LIMIT 1 to catch syntax errors, then execute the full query.
For vector search, the planner must choose the embedding model and the similarity metric (cosine, dot product, etc.). It also decides whether to apply metadata filters—for example, restricting to a date range or a product category—before the vector similarity search. Hybrid strategies often use a two-stage approach: first a coarse SQL filter to narrow the candidate set, then a vector search within that set, or vice versa.
Here is an example of a hybrid query plan:
-- Stage 1: SQL to get candidate product IDs
SELECT product_id, name
FROM products
WHERE category = 'electronics'
AND release_date > '2023-01-01';
-- Stage 2 (in application code): vector search over reviews
-- WHERE product_id IN (candidate_ids)
-- ORDER BY embedding <-> 'query_embedding' LIMIT 10;
The agent must also decide whether to cache results. If the same question is asked repeatedly, the agent can store the final query plan and results in a cache keyed by a hash of the question. This is especially valuable for expensive aggregations.
Handling Multi-Turn Context and Query Refinement
Users rarely ask complete questions in one turn. “Show me the top customers” followed by “now only those in Europe” requires the agent to maintain state. The planner must carry forward the previous query’s filters and add new ones. This is not just string concatenation—the agent must understand that “now” refers to the previous result set, not the entire table.
A robust pattern is to maintain a query state object that holds the current filters, joins, and sort order. Each turn, the planner updates that object rather than generating SQL from scratch. For example:
# Turn 1: {"filters": [], "sort": "revenue DESC"}
# Turn 2: {"filters": ["region = 'EU'"], "sort": "revenue DESC"}
This also helps with disambiguation. If the user says “that one” or “the second result,” the agent can refer to the previous turn’s output. The critic plays a key role here: it checks whether the new query’s results are a subset of the previous results or a new independent set, and flags ambiguity if the user’s intent is unclear.
Cost, Latency, and Safety Guardrails for Autonomous Agents
Autonomous agents can burn money and time if left unchecked. Each LLM call for planning or critique adds latency and cost. Each SQL query consumes database resources. Guardrails must be explicit:
- Iteration cap: Hard limit on the number of planner-critic loops (e.g., 3). Beyond that, return the best partial answer with a disclaimer.
- Query cost limit: For expensive aggregations, set a
statement_timeoutor a row-count limit. UseEXPLAINto estimate cost before execution. - Read-only enforcement: The agent’s database user must have
SELECTonly. NoINSERT,UPDATE, orDELETE—ever. - PII and data masking: The planner should be instructed to avoid selecting sensitive columns unless the user has explicit permission. Better yet, use a view that masks PII.
- Rate limiting: Cache identical queries within a session to avoid repeated executions.
Latency is the harder problem. A single agentic loop can take 2–5 seconds, which is unacceptable for interactive dashboards. Mitigations include: running the planner and critic with a small, fast model (e.g., 7B parameters) and using a larger model only for final synthesis; or running the initial SQL and vector search in parallel, then merging results.
Implementation Patterns and Trade-offs Across Database Engines
Vendor-neutral advice: the agent should talk to the database through a thin abstraction layer, not raw drivers. This lets you swap engines without rewriting the planner. For PostgreSQL, use pgvector for vector search and standard SQL for structured queries. MySQL added a native VECTOR type with distance functions in 9.0, though the surrounding indexing story is younger than pgvector's — check your version before assuming you need a separate store. SQL Server has VECTOR support in recent versions, but it is not as mature as pgvector.
Key trade-offs:
- PostgreSQL + pgvector: Best balance of SQL and vector search in one engine. The agent can use a single connection and even do hybrid queries with
ORDER BYon combined scores. Downside: vector index build times are slower than dedicated vector databases at scale. - Dedicated vector DB (e.g., Qdrant, Weaviate): Faster similarity search at scale, but you must maintain a separate system and handle cross-engine joins manually. The agent will need to query two systems and merge results in application code.
- SQLite with extensions: Good for prototyping, but not for concurrent production workloads. Use it only for single-user demos.
Regardless of engine, the planner should generate engine-agnostic logical plans (e.g., “filter by category, then vector search”) and have a separate executor that translates to engine-specific syntax. This decoupling makes your agent portable and testable.
The short version
Agentic RAG replaces a fixed retrieval pipeline with a planner-executor-critic loop. The planner decides when to query based on intent confidence, and how to query by choosing SQL, vector search, or hybrid. The critic validates results and triggers refinement. Guardrails—iteration caps, read-only access, and cost limits—are non-negotiable. Start with PostgreSQL + pgvector for the simplest hybrid setup. The payoff is not just better answers, but the ability to answer questions that static RAG cannot even attempt.
About this article
Written by Connect·IT's AI authoring agents. Every factual claim — API names, defaults, limits, version support — is automatically verified against the vendor's own documentation before publication, and the post is rejected if a claim cannot be confirmed. That is a machine check, not a human sign-off: if something here looks wrong, tell us and we will fix it. Need this applied to your own systems? Talk to our team →