Advanced RAG retrieval with cross-encoders and reranking

Search in the WordPress world has settled on one piece of advice: throw your data into a vector database and call it done. That advice is what wrecks your accuracy. Anyone who has shipped an LLM feature has hit the same wall, and getting from “okay” results to something you would put in front of a customer takes advanced RAG retrieval.

I have spent 14 years wrestling with complex data structures in WooCommerce, and the same mistake turns up with every new wave of technology. We reach for bi-encoders, the models that turn text into embeddings, because they are fast. They are also limited: they squeeze all the meaning into a single vector before they have seen the query. Nuance and outright contradictions get flattened, and your users get results that have nothing to do with what they asked for.

Where bi-encoders run out of road

Most semantic search systems use a bi-encoder. It scales because you precompute embeddings for the whole document library, so the lookup itself is cheap. Someone searches for “cheap hotels in Tokyo” and the system returns the vectors sitting mathematically closest to that phrase. It will happily put “Luxury hotels” on top because “hotels” and “Tokyo” both match, while “cheap” quietly drops out of the equation.

That gap is what advanced RAG retrieval closes. Instead of one pass, you run two stages: the bi-encoder casts a wide net for recall, then a cross-encoder re-scores what came back for precision. A cross-encoder reads the query and the document together, with full self-attention across every token.

Building a two-stage reranking pipeline

You cannot run a heavy transformer model on every query in production WordPress. That bottleneck arrives faster than a badly written WP_Query against a million rows, so treat retrieval as a funnel instead. I covered the architectural side of this in an earlier post on treating AI memory properly.

  • Stage 1: Fast retrieval from a vector DB such as Pinecone, or from a local BM25 index. Pull the top 50 candidates.
  • Stage 2: Reranking with a cross-encoder model. The query and those 50 candidates go to a reranking API.

Here is roughly how that looks in a custom WordPress plugin talking to an external reranking service like Cohere Rerank:

<?php
/**
 * Example of integrating a reranking stage into a WP search pipeline.
 * Prefixing with bbioon_ as per senior dev standards.
 */
function bbioon_advanced_rag_rerank( $query, $initial_results ) {
    $api_key = 'YOUR_COHERE_API_KEY';
    $documents = array_column( $initial_results, 'content' );

    $response = wp_remote_post( 'https://api.cohere.ai/v1/rerank', [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ],
        'body' => json_encode([
            'model'     => 'rerank-english-v3.0',
            'query'     => $query,
            'documents' => $documents,
            'top_n'     => 5,
        ]),
    ]);

    if ( is_wp_error( $response ) ) {
        return $initial_results; // Fallback to raw retrieval if API fails
    }

    $data = json_decode( wp_remote_retrieve_body( $response ), true );
    
    // Sort and return the reranked documents
    return $data['results'];
}

What cross-attention actually buys you

Cross-attention is the reason the second pass earns its keep. In a bi-encoder the query and the document never talk to each other until the final comparison. A cross-encoder concatenates them, so every word in the query can attend to every word in the document at every layer of the model. That is how it catches a contradiction like “no sugar” against “contains sugar,” which plain embedding search usually misses. It is the same idea behind the way we handle RAG conflicts: make the model look harder at the context.

Keeping latency down under traffic (ColBERT and caching)

On a busy WooCommerce store, 200ms added to every search is a risky trade. For those sites I look at ColBERT late interaction, or at semantic query caching. Use a cross-encoder to spot duplicate queries at the cache layer and 70-80% of your traffic never reaches the reranker at all. A model like MiniLM-L6 also lands in a good spot between speed and accuracy.

If this retrieval work is eating your dev hours, hand it over. I have been wrestling with WordPress since the 4.x days.

What to change first

Do not trust your embedding model blindly. When a RAG pipeline returns “okay” answers, the fix is rarely a bigger LLM; it is a better retrieval strategy. Add reranking, try a couple of cross-encoders, and keep an eye on the latency budget while you do it. Finding the closest vector is different from working out what the user meant inside your particular domain.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.