# Rerankers Rerankers improve retrieval quality by scoring documents based on relevance to the query. ## Overview The library provides two reranker implementations: | Reranker | Method | Use Case | |----------|--------|----------| | **LLM** | LLM-based scoring | Production quality | | **Passthrough** | No-op | Testing/baselines | ## Reranker Behaviour All rerankers implement the `Rag.Reranker` behaviour: ```elixir @callback rerank(reranker, query, documents, opts) :: {:ok, [document()]} | {:error, term()} @type document :: %{ id: any(), content: String.t(), score: float(), metadata: map() } ``` ## LLM Reranker Uses an LLM to score document relevance on a 1-10 scale. ### Creating ```elixir alias Rag.Reranker.LLM alias Rag.Router # Default configuration reranker = LLM.new() # With custom router {:ok, router} = Router.new(providers: [:gemini, :claude]) reranker = LLM.new(router: router) # With custom prompt template = """ Score these documents for relevance to: {query} Documents: {documents} Return JSON: [{"doc_index": 0, "score": 8}, ...] """ reranker = LLM.new(prompt_template: template) ``` ### Reranking ```elixir alias Rag.Reranker documents = [ %{id: 1, content: "Elixir programming", score: 0.7, metadata: %{}}, %{id: 2, content: "Python basics", score: 0.8, metadata: %{}} ] # Basic reranking {:ok, reranked} = Reranker.rerank(reranker, "What is Elixir?", documents) # With options {:ok, reranked} = Reranker.rerank(reranker, "What is Elixir?", documents, top_k: 5, normalize_scores: true ) ``` ### Options | Option | Default | Description | |--------|---------|-------------| | `top_k` | all | Return only top K documents | | `normalize_scores` | false | Normalize scores to 0-1 range | ### Scoring The LLM scores documents on a 1-10 scale: - **1-3**: Not relevant or minimally relevant - **4-6**: Somewhat relevant - **7-9**: Highly relevant - **10**: Extremely relevant ### Default Prompt Template ``` You are a relevance scoring assistant. Given a query and a list of documents, score each document's relevance to the query on a scale from 1 to 10... Query: {query} Documents: {documents} Return ONLY a JSON array with the scores in this exact format: [{"doc_index": 0, "score": 8}, {"doc_index": 1, "score": 5}, ...] ``` ### Score Normalization When `normalize_scores: true`: - Maps LLM scores (1-10) to 0-1 range - Formula: `(score - min) / (max - min)` - Equal scores normalize to 1.0 ## Passthrough Reranker Returns documents unchanged. Useful for testing. ```elixir alias Rag.Reranker alias Rag.Reranker.Passthrough reranker = %Passthrough{} # Documents returned unchanged {:ok, same_docs} = Reranker.rerank(reranker, "query", documents) ``` ## Complete RAG Pipeline ```elixir alias Rag.Router alias Rag.Retriever alias Rag.Retriever.Hybrid alias Rag.Reranker alias Rag.Reranker.LLM # Setup {:ok, router} = Router.new(providers: [:gemini]) query = "How does GenServer handle state?" # 1. Get query embedding {:ok, [embedding], router} = Router.execute(router, :embeddings, [query], []) # 2. Hybrid retrieval retriever = %Hybrid{repo: Repo} {:ok, results} = Retriever.retrieve(retriever, {embedding, query}, limit: 20) # 3. Rerank with LLM reranker = LLM.new(router: router) {:ok, reranked} = Reranker.rerank(reranker, query, results, top_k: 5, normalize_scores: true ) # 4. Build context from top results context = reranked |> Enum.map(fn doc -> "- #{doc.content} (Score: #{Float.round(doc.score, 2)})" end) |> Enum.join("\n") # 5. Generate answer rag_prompt = """ Answer based on the following context: #{context} Question: #{query} """ {:ok, answer, _} = Router.execute(router, :text, rag_prompt, []) IO.puts(answer) ``` ## Comparison: With vs Without Reranking ```elixir # Without reranking - use initial retrieval scores {:ok, results} = Retriever.retrieve(hybrid_retriever, {embedding, query}, limit: 5) # With reranking - LLM improves relevance ordering {:ok, results} = Retriever.retrieve(hybrid_retriever, {embedding, query}, limit: 20) {:ok, reranked} = Reranker.rerank(reranker, query, results, top_k: 5) ``` **Benefits of reranking:** - More accurate relevance scoring - Better context for answer generation - Handles retriever score inconsistencies - Can catch false positives from vector search **Trade-offs:** - Additional LLM API call - Increased latency - Higher cost ## Pipeline Integration ```elixir alias Rag.Pipeline Pipeline.new(:rag_with_rerank) |> Pipeline.add_step( name: :retrieve, module: Steps, function: :retrieve, args: [limit: 20] ) |> Pipeline.add_step( name: :rerank, module: Steps, function: :rerank, inputs: [:retrieve], args: [top_k: 5], on_error: :continue # Skip reranking on error ) |> Pipeline.add_step( name: :generate, module: Steps, function: :generate, inputs: [:rerank] ) ``` ## Best Practices 1. **Retrieve more, rerank fewer** - Get 20 results, rerank to 5 2. **Use with hybrid search** - Reranking helps reconcile different scores 3. **Handle errors gracefully** - Fall back to unreranked results 4. **Normalize scores** - For consistent downstream processing 5. **Consider cost** - Reranking adds an LLM call ## Next Steps - [Retrievers](retrievers.md) - Different retrieval strategies - [Pipeline](pipelines.md) - Build complete RAG workflows