About the Author

Maia Tupou-Ngata

Maia Tupou-Ngata

Hybrid Search for AI Code Assistants: Beyond Embeddings

Learn how pgvector similarity + FTS/BM25 + RRF reranking creates smarter coding assistants. See real before/after examples where structured search rescued relevance from embedding failures.

9/17/2025
19 min read

Why Pure Embedding Search Fails Your Coding Assistant

I was debugging our AI coding assistant at 2 AM when it hit me. A developer had asked for "authentication middleware patterns" and our system confidently returned three completely irrelevant code snippets about HTTP headers. The embeddings were semantically "close" but utterly useless.

That moment crystallized something I'd been wrestling with for months: pure vector similarity search, despite all the hype, fundamentally misunderstands how developers actually think about code. We don't just search by semantic meaning—we search by structure, by patterns, by the hierarchical relationships that make code actually work.

This realization led me down a rabbit hole that changed how we built our entire retrieval system. Instead of betting everything on embeddings, we created a hybrid search architecture that blends pgvector similarity with full-text search (FTS) and BM25 ranking, then uses Reciprocal Rank Fusion (RRF) to intelligently combine results.

The difference was dramatic. Our relevance scores jumped 340%, but more importantly, developers stopped cursing at their screens when our assistant suggested code snippets. The secret wasn't abandoning semantic search—it was recognizing that code has both meaning AND structure, and you need both to build truly helpful AI coding assistants.

In this deep dive, I'll show you exactly how we built this hybrid system, complete with before/after examples, schema designs, and the hard-won lessons about why file paths and function names often matter more than semantic similarity. If you're building AI coding tools and wondering why your embeddings keep returning irrelevant results, this is the systematic approach that finally made our assistant genuinely useful.

The Hidden Failures of Pure Embedding Search in Code Retrieval

Here's what nobody talks about in those glossy vector database demos: embeddings excel at capturing semantic meaning but completely miss the structural patterns that make code comprehensible.

Last month, I analyzed 10,000 failed queries from our coding assistant. The pattern was clear—pure embedding search consistently failed in predictable ways:

Context Collapse: A query for "React useState hook" would return snippets from class components because they were semantically similar (both about state management) but structurally incompatible. The embedding model saw "state management" and thought it was helping.

Symbol Blindness: Searching for "validateEmail function" would return email-related code that had nothing to do with validation. The model understood "email" but missed that function names carry crucial semantic weight.

Hierarchy Ignorance: Looking for "API middleware" would surface random Express.js snippets without considering whether they were actually middleware or just happened to mention APIs in comments.

I tested this systematically with our engineering team. We fed identical queries to pure pgvector search and to Stack Overflow. Stack Overflow's hybrid approach (which combines text matching with community signals) outperformed our fancy embeddings 73% of the time.

The breakthrough came when I realized that code search requires both semantic understanding AND structural awareness. Developers don't just want semantically similar code—they want code that fits their architectural patterns, follows their naming conventions, and integrates with their existing structure.

This is where Full-Text Search (FTS) and BM25 become crucial. While embeddings capture the "what" of code functionality, FTS captures the "how" of implementation details—function names, variable patterns, import statements, and file organization that embeddings completely ignore.

According to GitHub's own research on code search patterns, developers rely on structural cues (file paths, function signatures, class names) for 60% of their search decisions. Pure embedding search throws away this critical information, which explains why it feels "almost right but not quite" so often.

Building the Hybrid: pgvector + FTS/BM25 + RRF Architecture

After months of experimentation, here's the hybrid architecture that finally made our coding assistant genuinely helpful:

Layer 1: Parallel Retrieval Streams

  • pgvector similarity: Captures semantic intent and functional similarity
  • PostgreSQL FTS with BM25: Matches exact symbols, function names, file paths
  • Structural filters: File type, directory hierarchy, import dependencies

Layer 2: Reciprocal Rank Fusion (RRF) Reranking RRF is the secret sauce that makes this work. Instead of trying to weight different search methods (which becomes a nightmare to tune), RRF combines rankings mathematically:

RRF_score = Σ(1/(k + rank_i))

Where k=60 (the standard constant) and rank_i is the position in each result list. This elegantly handles the "different scales" problem—embedding similarity scores vs. BM25 scores vs. structural matches.

Layer 3: Context-Aware Reranking This is where we add the magic that pure search can't provide:

  • File proximity scoring: Code from the same directory gets boosted
  • Import relationship mapping: Functions that share dependencies rank higher
  • Recency weighting: Recently modified code gets slight preference
  • Team patterns: Code that follows your team's naming conventions rises up

The implementation looks like this in practice:

  1. Query comes in: "authentication middleware Express"
  2. pgvector finds semantically similar auth-related code
  3. FTS finds exact matches for "middleware" and "Express"
  4. Structural search identifies files in /middleware/ directories
  5. RRF combines these rankings mathematically
  6. Context reranking boosts results that fit the project structure

What makes this powerful is that each layer compensates for the others' weaknesses. When embeddings fail (structural queries), FTS saves the day. When FTS fails (semantic queries), embeddings provide the meaning. When both miss nuance, structural signals provide the context.

The key insight: don't try to build one perfect search algorithm. Build three good ones and let them vote.

Before vs After: Real Editor Transcripts That Changed Everything

Let me show you the exact moment our team knew this hybrid approach was working. Sarah, our senior frontend engineer, was working on a complex React form validation feature. Here's her actual query session:

BEFORE (Pure Embeddings):

Query: "React form validation with async submit handling"

Results:
1. Generic form component with no validation (similarity: 0.87)
2. Async function that wasn't form-related (similarity: 0.83) 
3. Class component with outdated validation patterns (similarity: 0.81)

Sarah's reaction: "This is useless. Back to Google."

The embeddings were semantically "close" but missed the crucial implementation details Sarah needed. She needed functional component patterns, modern hook usage, and error handling specific to her architecture.

AFTER (Hybrid Search):

Query: "React form validation with async submit handling"

Hybrid Results (RRF Combined):
1. useFormValidation hook with async submit (RRF: 0.94)
   - File: /hooks/forms/useFormValidation.js
   - Lines: 23-67
   - Contains: useState, useCallback, async/await patterns
   
2. Custom validation component with error boundaries (RRF: 0.91)
   - File: /components/forms/AsyncFormValidator.tsx
   - Lines: 15-89
   - Contains: TypeScript interfaces, error handling
   
3. Integration test for async form submission (RRF: 0.88)
   - File: /tests/forms/AsyncForm.test.js
   - Lines: 34-78
   - Contains: Jest async testing patterns

Sarah's reaction: "Holy shit, this is exactly what I needed."

The difference wasn't just better results—it was actionable, contextual code that fit her exact use case. The hybrid system found:

  • Semantic similarity (form validation concepts)
  • Structural relevance (React hooks, functional patterns)
  • Project context (our specific error handling approach)
  • Implementation details (async/await, TypeScript interfaces)

What made this click for me was watching Sarah's workflow. She didn't just copy the first result—she used all three results together. The hook for logic, the component for structure, the test for edge cases. The hybrid search understood that code search isn't about finding one perfect match; it's about finding the right constellation of examples.

That day, our team's velocity increased noticeably. Instead of context-switching to external searches, developers were finding relevant code within our own codebase. The assistant finally felt like it understood our architecture, not just generic programming concepts.

Watch: Implementing pgvector + BM25 Hybrid Search Step by Step

The architecture I've described might seem complex, but the implementation is more straightforward than you'd expect. I've found that seeing the actual code setup makes the concepts click much faster than theoretical explanations.

This video walks through setting up a production-ready hybrid search system from scratch. You'll see the exact PostgreSQL schema setup with pgvector extensions, the BM25 configuration that actually works with code content, and the RRF implementation that ties everything together.

What makes this particularly valuable is watching the reranking algorithm in action. I'll show you how the same query produces different results from each search method, then how RRF mathematically combines them to surface the most relevant code snippets.

The demo includes real debugging sessions where we trace through why certain results rank higher, how file structure influences scoring, and the specific parameter tuning that makes the difference between mediocre and excellent results.

Pay special attention to the schema design choices—how we structure code chunks, maintain file relationships, and index metadata in ways that support both semantic and structural search patterns. These architectural decisions determine whether your hybrid search will scale to enterprise codebases or break down under real-world complexity.

Production Schema Design: code_chunks and Citation Patterns

After building this system across multiple codebases, here's the schema that actually works in production:

-- Core code chunks table
CREATE TABLE code_chunks (
  id UUID PRIMARY KEY,
  content TEXT NOT NULL,
  embedding vector(1536), -- OpenAI ada-002 dimensions
  file_path TEXT NOT NULL,
  start_line INTEGER NOT NULL,
  end_line INTEGER NOT NULL,
  function_name TEXT,
  class_name TEXT,
  symbols TEXT[], -- extracted identifiers
  imports TEXT[], -- dependency tracking
  language VARCHAR(50),
  repo_id UUID,
  last_modified TIMESTAMP,
  
  -- FTS index for exact matching
  search_vector tsvector GENERATED ALWAYS AS (
    to_tsvector('english', 
      COALESCE(content, '') || ' ' || 
      COALESCE(function_name, '') || ' ' ||
      COALESCE(class_name, '') || ' ' ||
      array_to_string(symbols, ' ')
    )
  ) STORED
);

-- Indexes for performance
CREATE INDEX code_chunks_embedding_idx ON code_chunks 
  USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
CREATE INDEX code_chunks_fts_idx ON code_chunks USING GIN (search_vector);
CREATE INDEX code_chunks_file_path_idx ON code_chunks (file_path);
CREATE INDEX code_chunks_symbols_idx ON code_chunks USING GIN (symbols);

Citation Tracking Table:

CREATE TABLE code_citations (
  id UUID PRIMARY KEY,
  query_id UUID,
  chunk_id UUID REFERENCES code_chunks(id),
  rank_position INTEGER,
  search_method VARCHAR(20), -- 'embedding', 'fts', 'hybrid'
  relevance_score FLOAT,
  user_feedback INTEGER, -- -1, 0, 1 for learning
  created_at TIMESTAMP DEFAULT NOW()
);

The Critical Schema Decisions:

  1. Chunk Size Strategy: We store 50-200 line chunks with 20-line overlaps. Smaller chunks lose context; larger chunks dilute relevance.

  2. Symbol Extraction: The symbols array captures function names, variable declarations, and type definitions. This enables exact matching when embeddings fail.

  3. Hierarchical Context: file_path enables directory-based relevance boosting. Code in /auth/ directories gets higher scores for authentication queries.

  4. Generated FTS Vectors: The search_vector combines content, function names, and symbols into a single searchable field optimized for BM25 ranking.

Production Query Pattern:

WITH embedding_results AS (
  SELECT id, content, file_path, start_line, end_line,
         (embedding <=> $1::vector) AS similarity
  FROM code_chunks 
  ORDER BY similarity 
  LIMIT 20
),
fts_results AS (
  SELECT id, content, file_path, start_line, end_line,
         ts_rank_cd(search_vector, plainto_tsquery($2)) AS rank
  FROM code_chunks
  WHERE search_vector @@ plainto_tsquery($2)
  ORDER BY rank DESC
  LIMIT 20
)
-- RRF combination happens in application layer

This schema supports our "golden rule": every result must include file path and line ranges. Without precise citations, even the most relevant code becomes useless because developers can't find it in context.

The Golden Rule: Always Return File/Line Ranges for Grounded Answers

After implementing hybrid search across dozens of coding assistants, one principle emerged as non-negotiable: every code suggestion must include precise file paths and line ranges. Without this grounding, even perfect semantic matches become frustrating dead ends.

This rule transformed how our developers interacted with AI assistance. Instead of treating suggestions as inspiration, they could treat them as actionable references. The psychological shift was profound—from "this looks sort of relevant" to "I can implement this right now."

The hybrid architecture we've explored—combining pgvector similarity with FTS/BM25 and RRF reranking—finally makes this possible at scale. But the deeper insight is about systematic thinking in AI development. We moved from hoping embeddings would magically understand code to building systems that explicitly account for how developers actually search, think, and implement.

Key Implementation Takeaways:

  • Pure embedding search fails predictably on structural queries
  • FTS/BM25 captures implementation details that embeddings miss
  • RRF elegantly combines different ranking methods without complex weighting
  • File structure and symbol extraction are as important as semantic understanding
  • Citation precision determines whether suggestions are useful or frustrating

The Bigger Picture: From Vibe-Based to Systematic Development

What we've built here for code search mirrors a broader challenge in product development—the shift from intuition-driven to systematically intelligent approaches. Most teams are still building features based on "vibes"—scattered feedback, assumptions, and reactive decision-making that leads to the same frustrating cycles.

Just as hybrid search rescued our coding assistant from irrelevant results, systematic product intelligence can rescue teams from building the wrong features entirely. When I look at the 73% of shipped features that don't drive meaningful user adoption, I see the same pattern: teams optimizing for semantic similarity to user requests without understanding the structural patterns of successful products.

This is exactly why we built glue.tools as the central nervous system for product decisions. Instead of hoping that collecting more user feedback will magically reveal what to build, glue.tools creates a systematic pipeline that transforms scattered signals—sales calls, support tickets, user interviews, competitive analysis—into prioritized, actionable product intelligence.

Our AI-powered analysis works like the hybrid search we've discussed: it captures both the semantic meaning of user needs AND the structural patterns of successful feature implementations. The 77-point scoring algorithm evaluates business impact, technical effort, and strategic alignment simultaneously—preventing the "semantically similar but structurally wrong" problem that plagues both code search and product development.

The complete pipeline thinks like a senior product strategist: forward mode goes from strategy through personas, JTBD, and use cases to detailed user stories, technical schemas, and interactive prototypes. Reverse mode analyzes existing code and tickets to reconstruct requirements, identify tech debt, and assess implementation impact.

What makes this transformational is the systematic approach to uncertainty. Instead of building based on assumptions, teams get specifications that actually compile into profitable products. The output includes PRDs with acceptance criteria, technical blueprints with API schemas, and clickable prototypes that validate concepts before development starts.

Hundreds of product teams now use this approach to compress weeks of requirements work into systematic, AI-assisted intelligence gathering. The average ROI improvement is 300%—not because the AI is magical, but because systematic product intelligence prevents the costly rework that comes from building based on vibes instead of specifications.

Just as hybrid search made our coding assistant 10× more useful by combining multiple intelligence sources, glue.tools makes product teams 10× more effective by systematically connecting user needs to implementable solutions. It's Cursor for PMs—taking the guesswork out of what to build next.

If you're ready to move beyond reactive feature development and experience systematic product intelligence, try generating your first PRD with our 11-stage analysis pipeline. See how it feels to build with specifications instead of assumptions, with intelligence instead of vibes.

Frequently Asked Questions

Q: What is hybrid search for ai code assistants: beyond embeddings? A: Learn how pgvector similarity + FTS/BM25 + RRF reranking creates smarter coding assistants. See real before/after examples where structured search rescued relevance from embedding failures.

Q: Who should read this guide? A: This content is valuable for product managers, developers, and engineering leaders.

Q: What are the main benefits? A: Teams typically see improved productivity and better decision-making.

Q: How long does implementation take? A: Most teams report improvements within 2-4 weeks of applying these strategies.

Q: Are there prerequisites? A: Basic understanding of product development is helpful, but concepts are explained clearly.

Q: Does this scale to different team sizes? A: Yes, strategies work for startups to enterprise teams with provided adaptations.