Skip to main content
Standard RAG is a fixed pipeline: embed the query, search the vector database, stuff the top-K results into a prompt, and call the LLM. This works for simple factual questions but fails in practice because:
  • Not all queries need retrieval. “What is 2 + 2?” should skip the vector database entirely. Sending it through retrieval wastes latency and may inject irrelevant context.
  • Different queries need different retrieval strategies. A factual lookup (“What is the capital of France?”) needs high-precision single-pass search. An exploratory question (“How does authentication work in the system?”) needs broad multistage retrieval across multiple document types.
  • Retrieval confidence varies. If the top result has a score of 0.92, the LLM probably has enough context. If the best score is 0.35, the system should either try a different search strategy or tell the user it does not know.
  • User feedback should improve future retrieval. When a user marks a response as unhelpful, the system should learn which documents were not relevant.
An Adaptive RAG system solves these problems by making the retrieval pipeline dynamic. Instead of one fixed strategy, the system classifies each query, selects the appropriate retrieval approach, evaluates result quality, and adapts based on feedback. This tutorial builds a complete adaptive RAG pipeline on Actian VectorAI DB. By the end, you will have:
  • A knowledge base collection with payload indexes for routing, feedback, and analytics.
  • A keyword-signal query classifier that maps queries to four retrieval strategies.
  • Three retrieval strategies (precise, broad multistage, and nested troubleshooting prefetch) plus an automatic fallback.
  • A confidence evaluator that decides whether results are good enough or a fallback is needed.
  • A user feedback loop that updates per-document usefulness scores over time.
  • A feedback-aware retrieval function that boosts historically helpful documents.
  • An analytics function that shows which documents are most retrieved and most useful.
  • A prompt-assembly step that packages context and confidence instructions for any LLM.

Architecture overview

The following diagram shows how queries flow through the adaptive RAG pipeline, from classification through strategy selection, confidence evaluation, and the feedback loop back into the knowledge base:

Environment setup

Run the following command to install the Actian VectorAI SDK and the sentence-transformers library used for embedding:

Step 1: Import dependencies and configure the environment

The following block imports all SDK symbols used throughout the tutorial, loads the all-MiniLM-L6-v2 embedding model, and defines three constants (SERVER, COLLECTION, EMBED_DIM) that every subsequent step shares. Running it prints the active configuration so you can confirm the setup before proceeding:

Expected output

This block loads the all-MiniLM-L6-v2 sentence-transformer model and defines the three shared constants—SERVER, COLLECTION, and EMBED_DIM—that every subsequent step references. The three print calls confirm the active server address, the target collection name, and the embedding model with its vector dimensionality, so you can verify the configuration is correct before proceeding.

Step 2: Create the knowledge base collection

The following block creates the Adaptive-RAG collection with a cosine-distance HNSW index and registers six payload field indexes. Running it prints a confirmation message when the collection and all indexes are ready:
Each index serves a specific role in the adaptive pipeline. The table below explains what each field enables: The combination of keyword, integer, float, and datetime indexes means the adaptive router can filter, sort, and range-query on any payload field without a full collection scan.

Step 3: Ingest documents into the knowledge base

The following block defines 20 sample documents across five categories (API reference, tutorials, conceptual guides, troubleshooting, and changelog) and upserts them into the collection. Each point is assigned initial metadata values: retrieval_count: 0, usefulness_score: 0.5, and a UTC timestamp. Running it prints the total number of documents confirmed in the collection:

Expected output

This block embeds all 20 document texts in a single batch call and upserts them as PointStruct objects, each carrying its source metadata alongside the initial tracking fields (retrieval_count: 0, usefulness_score: 0.5, feedback_count: 0). After upserting, it calls flush to persist the writes to disk and then queries get_vector_count to confirm the exact number of vectors now stored in the collection.

Step 4: Build the query classifier

The classifier inspects keyword signals in a query and returns a ClassifiedQuery that names the query type and the target document categories to search. The following block defines the QueryType enum, the ClassifiedQuery dataclass, and the classify_query function, then runs it against four test queries and prints the assigned type and confidence for each:

Expected output

The classifier inspects each query for keyword signals and maps it to one of four QueryType values. The four test queries are designed to exercise every branch: a “how to use” phrase triggers factual, an open-ended “how does” triggers exploratory, an error-related phrase triggers troubleshooting, and a greeting triggers no_retrieval. Each line of output shows the assigned type right-aligned, the classifier’s confidence score, and the original query text.

Step 5: Strategy 1—Precise retrieval for factual queries

Factual queries require high precision. The following block defines precise_retrieval, which searches only within the specified document type categories, applies a score_threshold of 0.5 to discard low-similarity results, and uses hnsw_ef=256 to maximise recall accuracy. Running the test query prints each result’s score, document type, and a truncated text preview:
The table below explains why each parameter is configured this way for factual queries: With these settings the search either returns a small number of highly confident matches or nothing at all—both are useful signals. An empty result set tells the router to invoke the fallback strategy rather than hallucinate an answer.

Step 6: Strategy 2—Broad multistage retrieval for exploratory queries

Exploratory queries need breadth across multiple document types. The following block defines broad_retrieval, which creates one prefetch stream per document type plus an unfiltered catch-all stream, then merges all candidates with RRF fusion. Running the test query prints each result’s score, document type, and a text preview:
The following diagram shows the four prefetch streams and how RRF fusion merges them into a single ranked result set:
Documents that appear across multiple prefetch streams rank higher, giving the LLM a well-rounded context. The lower hnsw_ef=128 per stream is a deliberate trade-off: the four parallel streams compensate for any individual miss, so per-stream precision matters less than overall breadth.

Step 7: Strategy 3—Troubleshooting retrieval with nested prefetch

Troubleshooting queries benefit from a wide net across both FAQ-style documents and changelogs, which often contain relevant fixes. The following block defines troubleshooting_retrieval, which uses a nested prefetch pipeline: inner prefetch stages gather candidates from troubleshooting docs and changelogs, DBSF fusion merges them, and a final rerank pass uses the query vector to surface the most relevant results. Running the test query prints each result’s score, document type, and a text preview:
The troubleshooting strategy uses three stages to progressively narrow candidates before the final rerank:
DBSF normalizes the scores from both inner streams before merging, giving a fair comparison between troubleshooting tips and changelog notes. The final rerank with the query vector ensures the most relevant results surface at the top.

Step 8: Build the confidence evaluator

After retrieval, the pipeline needs to decide whether the results are strong enough to pass to the LLM or whether a fallback is needed. The following block defines the RetrievalResult dataclass and the evaluate_confidence function, then runs it against a test query and prints the confidence level, top score, average score, and document count:
The evaluator maps score thresholds to one of four confidence levels, each of which drives a different downstream action:
When initial retrieval has low confidence, the fallback strategy removes all filters, raises the candidate pool size, and merges the original results with an unfiltered search using client-side RRF. The following block defines fallback_retrieval, simulates a low-confidence query, and prints the fallback confidence level and the top results returned:
Sample.Random returns random points from the collection. In the fallback function above, it acts as a last-resort “did you mean?” response: if neither the original filtered search nor the unfiltered widening returns any results, the function returns these random documents so the user can see what is in the knowledge base and reformulate the query. Both fallback queries run inside a single client connection to avoid an extra round-trip.

Step 10: Build the adaptive router

The router is the central coordinator. It classifies the incoming query, dispatches it to the appropriate retrieval strategy, evaluates the result confidence, invokes the fallback when needed, and increments a retrieval counter on every returned document. The following block defines the AdaptiveRAGRouter class:
The following block runs the router against five representative queries and prints the assigned strategy, confidence level, top score, and document count for each. It covers all four query types including the fallback path:

Expected output

The demo_router function passes five representative queries through the full adaptive pipeline. Each query is first classified, then dispatched to the appropriate strategy—precise for factual, broad for exploratory, troubleshooting for error queries, and no_retrieval for the greeting. The final query about a non-existent “quantum flux capacitor” does not match any document closely enough, so the primary broad search scores poorly and the router automatically invokes the fallback strategy, producing the broad+fallback label with a low confidence rating and a reduced top score.

Step 11: User feedback loop

When a user marks a response as helpful or unhelpful, the usefulness_score of every retrieved document is updated. The following block defines record_feedback, simulates a helpful feedback event on a real retrieval result, and prints a confirmation with the number of documents updated:
Each feedback event nudges a document’s score toward 1.0 (helpful) or toward 0.0 (unhelpful) using an exponential moving-average formula so that no single event dominates the history: After many feedback cycles, frequently helpful documents accumulate high scores while unhelpful ones sink. The feedback-aware retrieval function in the next step uses these scores to boost useful documents.

Step 12: Feedback-aware retrieval

The usefulness_score accumulated in step 11 can be used to bias future retrieval toward documents that users have consistently found helpful. The following block defines feedback_aware_retrieval, runs a test query, and prints each result’s score, usefulness score, retrieval count, document type, and a text preview:
The function runs two prefetch streams in parallel and merges them with RRF, so documents that satisfy both criteria rank above those that satisfy only one:
A document that is both semantically relevant and historically useful gets a double boost. A document that is semantically relevant but has been marked unhelpful appears in only one stream and ranks lower.

Step 13: Analytics—what is the system learning?

As the system accumulates retrieval events and feedback, payload fields like retrieval_count and usefulness_score reflect its usage patterns. The following block queries the collection for the five most-retrieved documents, the five most-useful documents, and any documents that are frequently retrieved but consistently rated unhelpful, then prints all three groups:

Step 14: Prepare the prompt for LLM integration

The final pipeline step assembles the retrieved context chunks and a confidence-adjusted instruction into a prompt string. The following block defines adaptive_rag_answer, runs it against four test queries, and prints the strategy, confidence level, and source document count for each. The actual LLM call is left as a stub (# answer = await llm.generate(prompt)) so any provider can be plugged in:

Step 15: Collection cleanup

The following block retrieves the current document count, flushes all pending writes to disk, and prints a confirmation. Uncomment the delete lines to remove the collection entirely:

Adaptive strategies summary

The following table summarizes the retrieval strategy, search configuration, and fusion method used for each query type:

APIs and features used in this tutorial

The following table lists every VectorAI DB API and feature demonstrated across the fifteen steps:

Next steps

Reranking search results

Improve relevance with multistage reranking

Building multimodal systems

Add image search to your RAG pipeline

Optimizing retrieval quality

Tune HNSW, quantization, and search parameters

Predicate filters

Master the full Filter DSL