Skills › AI & Agent Engineering › RAG & retrieval
Hybrid Search Implementation
Combine vector and keyword search for improved retrieval. Use when implementing RAG systems, building search engines, or when neither approach alone provides sufficient recall.
Tools: asyncpg,numpy,sentence_transformers
The full skill
—
name: hybrid-search-implementation
description: Combine vector and keyword search for improved retrieval. Use when implementing RAG systems, building search engines, or when neither approach alone provides sufficient recall.
—
# Hybrid Search Implementation
Patterns for combining vector similarity and keyword-based search.
## When to Use This Skill
– Building RAG systems with improved recall
– Combining semantic understanding with exact matching
– Handling queries with specific terms (names, codes)
– Improving search for domain-specific vocabulary
– When pure vector search misses keyword matches
## Core Concepts
### 1. Hybrid Search Architecture
“`
Query â â¬â⺠Vector Search ââ⺠Candidates ââ
â â
ââ⺠Keyword Search â⺠Candidates ââ´â⺠Fusion â⺠Results
“`
### 2. Fusion Methods
| Method | Description | Best For |
|——–|————-|———-|
| **RRF** | Reciprocal Rank Fusion | General purpose |
| **Linear** | Weighted sum of scores | Tunable balance |
| **Cross-encoder** | Rerank with neural model | Highest quality |
| **Cascade** | Filter then rerank | Efficiency |
## Templates
### Template 1: Reciprocal Rank Fusion
“`python
from typing import List, Dict, Tuple
from collections import defaultdict
def reciprocal_rank_fusion(
result_lists: List[List[Tuple[str, float]]],
k: int = 60,
weights: List[float] = None
) -> List[Tuple[str, float]]:
"""
Combine multiple ranked lists using RRF.
Args:
result_lists: List of (doc_id, score) tuples per search method
k: RRF constant (higher = more weight to lower ranks)
weights: Optional weights per result list
Returns:
Fused ranking as (doc_id, score) tuples
"""
if weights is None:
weights = [1.0] * len(result_lists)
scores = defaultdict(float)
for result_list, weight in zip(result_lists, weights):
for rank, (doc_id, _) in enumerate(result_list):
# RRF formula: 1 / (k + rank)
scores[doc_id] += weight * (1.0 / (k + rank + 1))
# Sort by fused score
return sorted(scores.items(), key=lambda x: x[1], reverse=True)
def linear_combination(
vector_results: List[Tuple[str, float]],
keyword_results: List[Tuple[str, float]],
alpha: float = 0.5
) -> List[Tuple[str, float]]:
"""
Combine results with linear interpolation.
Args:
vector_results: (doc_id, similarity_score) from vector search
keyword_results: (doc_id, bm25_score) from keyword search
alpha: Weight for vector search (1-alpha for keyword)
"""
# Normalize scores to [0, 1]
def normalize(results):
if not results:
return {}
scores = [s for _, s in results]
min_s, max_s = min(scores), max(scores)
range_s = max_s – min_s if max_s != min_s else 1
return {doc_id: (score – min_s) / range_s for doc_id, score in results}
vector_scores = normalize(vector_results)
keyword_scores = normalize(keyword_results)
# Combine
all_docs = set(vector_scores.keys()) | set(keyword_scores.keys())
combined = {}
for doc_id in all_docs:
v_score = vector_scores.get(doc_id, 0)
k_score = keyword_scores.get(doc_id, 0)
combined[doc_id] = alpha * v_score + (1 – alpha) * k_score
return sorted(combined.items(), key=lambda x: x[1], reverse=True)
“`
### Template 2: PostgreSQL Hybrid Search
“`python
import asyncpg
from typing import List, Dict, Optional
import numpy as np
class PostgresHybridSearch:
"""Hybrid search with pgvector and full-text search."""
def __init__(self, pool: asyncpg.Pool):
self.pool = pool
async def setup_schema(self):
"""Create tables and indexes."""
async with self.pool.acquire() as conn:
await conn.execute("""
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE IF NOT EXISTS documents (
id TEXT PRIMARY KEY,
content TEXT NOT NULL,
embedding vector(1536),
metadata JSONB DEFAULT '{}',
ts_content tsvector GENERATED ALWAYS AS (
to_tsvector('english', content)
) STORED
);
— Vector index (HNSW)
CREATE INDEX IF NOT EXISTS documents_embedding_idx
ON documents USING hnsw (embedding vector_cosine_ops);
— Full-text index (GIN)
CREATE INDEX IF NOT EXISTS documents_fts_idx
ON documents USING gin (ts_content);
""")
async def hybrid_search(
self,
query: str,
query_embedding: List[float],
limit: int = 10,
vector_weight: float = 0.5,
filter_metadata: Optional[Dict] = None
) -> List[Dict]:
"""
Perform hybrid search combining vector and full-text.
Uses RRF fusion for combining results.
"""
async with self.pool.acquire() as conn:
# Build filter clause
where_clause = "1=1"
params = [query_embedding, query, limit * 3]
if filter_metadata:
for key, value in filter_metadata.items():
params.append(value)
where_clause += f" AND metadata->>'{key}' = ${len(params)}"
results = await conn.fetch(f"""
WITH vector_search AS (
SELECT
id,
content,
metadata,
ROW_NUMBER() OVER (ORDER BY embedding <=> $1::vector) as vector_rank,
1 – (embedding <=> $1::vector) as vector_score
FROM documents
WHERE {where_clause}
ORDER BY embedding <=> $1::vector
LIMIT $3
),
keyword_search AS (
SELECT
id,
content,
metadata,
ROW_NUMBER() OVER (ORDER BY ts_rank(ts_content, websearch_to_tsquery('english', $2)) DESC) as keyword_rank,
ts_rank(ts_content, websearch_to_tsquery('english', $2)) as keyword_score
FROM documents
WHERE ts_content @@ websearch_to_tsquery('english', $2)
AND {where_clause}
ORDER BY ts_rank(ts_content, websearch_to_tsquery('english', $2)) DESC
LIMIT $3
)
SELECT
COALESCE(v.id, k.id) as id,
COALESCE(v.content, k.content) as content,
COALESCE(v.metadata, k.metadata) as metadata,
v.vector_score,
k.keyword_score,
— RRF fusion
COALESCE(1.0 / (60 + v.vector_rank), 0) * $4::float +
COALESCE(1.0 / (60 + k.keyword_rank), 0) * (1 – $4::float) as rrf_score
FROM vector_search v
FULL OUTER JOIN keyword_search k ON v.id = k.id
ORDER BY rrf_score DESC
LIMIT $3 / 3
""", *params, vector_weight)
return [dict(row) for row in results]
async def search_with_rerank(
self,
query: str,
query_embedding: List[float],
limit: int = 10,
rerank_candidates: int = 50
) -> List[Dict]:
"""Hybrid search with cross-encoder reranking."""
from sentence_transformers import CrossEncoder
# Get candidates
candidates = await self.hybrid_search(
query, query_embedding, limit=rerank_candidates
)
if not candidates:
return []
# Rerank with cross-encoder
model = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
pairs = [(query, c["content\9:[["$","$L1d",null,{"locale":"en","skill":{"id":"wshobson-agents-plugins-llm-application-dev-skills-hybrid-search-implementation-skill-md","name":"hybrid-search-implementation","author":"wshobson","authorAvatarUrl":"https://avatars.githubusercontent.com/u/553618?v=4"}}],["$","$L1e",null,{"locale":"en","skill":{"id":"wshobson-agents-plugins-llm-application-dev-skills-hybrid-search-implementation-skill-md","name":"hybrid-search-implementation","author":"wshobson","authorAvatar":"https://avatars.githubusercontent.com/u/553618?v=4","description":"Combine vector and keyword search for improved retrieval. Use when implementing RAG systems, building search engines, or when neither approach alone provides sufficient recall.","githubUrl":"https://github.com/wshobson/agents/tree/main/plugins/llm-application-dev/skills/hybrid-search-implementation","skillContent":null,"hasMarketplaceJson":true},"files":[{"id":"591bc413-1017-4bf8-bdda-ea2c8e2483d8","skillId":"wshobson-agents-plugins-llm-application-dev-skills-hybrid-search-implementation-skill-md","path":"SKILL.md","name":"SKILL.md","type":"file","size":18043,"content":"$1f","isBinary":false}],"fileTree":[{"name":"SKILL.md","path":"SKILL.md","type":"file","size":18043,"children":"$undefined"}],"hasFiles":true,"updatedAtText":"3 months ago","t":{"common":{"loading":"Loading…","error":"Error","search":"Search","searchPlaceholder":"AI Search","cancel":"Cancel","confirm":"Confirm","save":"Save","delete":"Delete","edit":"Edit","back":"Back","next":"Next","previous":"Previous","skills":"skills","skill":"skill","available":"available","signIn":"Sign In","signOut":"Sign Out","favorites":"favorites","history":"history"},"nav":{"categories":"cd /categories","stats":"watch stats","docs":"man docs","apiDocs":"api –docs","aiSearch":"ai –search"},"hero":{"title":"Agent Skills Marketplace","subtitle":"for Claude Code, Codex & ChatGPT","skillCount":"skills","description":"Discover open-source agent skills from GitHub","description2":"Search with AI semantics or keywords, browse by category, sort by popularity. All skills use the open SKILL.md standard and are ready to install","searchPlaceholder":"Type to filter, or press â for AI search. â§ + â for new line","noSkillsFound":"No skills found"},"categories":{"title":"Browse by Category","description":"Explore agent skills organized by their primary use case","pageTitle":"All Categories","breadcrumb":"Categories","skillCount":"skills","info":"Agent skills are organized into parent categories and subcategories. Each category shows the total number of available skills. Click on any category to explore the skills within.","viewAll":"cd /categories && ls -la"},"skills":{"title":"Browse Agent Skills","count":"count","available":"skills available","searchAi":"search –ai","noSkillsFound":"No skills found","install":"Install","download":"Download","similar":"Similar Skills","files":"Files","readme":"README","searchPlaceholder":"Search skills with AI: try 'skills about trading', 'data analysis related skills'…","showMarketplaceOnly":"Show only skills with","sortByStars":"Sort by stars","sortByRecent":"Sort by recent"},"about":{"title":"Why Agent Skills Marketplace?","description":"Finding the right agent skill among thousands of GitHub repositories can be overwhelming. Skills Marketplace solves this by providing smart search, category filtering, and quality indicators to help you quickly find exactly what you need.","description2":"Whether you're a developer automating workflows, a team lead building custom AI tools, or a hobbyist exploring AI coding assistants, you'll find skills for every use case. All skills use the open SKILL.md standard, compatible with Claude Code, OpenAI Codex CLI, and other tools adopting this format.","info":"Ready to explore the largest collection of agent skills for AI coding assistants?","browse":"Browse","easy":"Easy","github":"GitHub","browseDescription":"Search through 25,000+ agent skills with intelligent filtering by category, author, and popularity. Compatible with Claude Code, Codex CLI and ChatGPT.","easyDescription":"Install skills with a single command. Clone repositories directly to your skills directory. All skills use the open SKILL.md standard for maximum compatibility.","githubDescription":"All skills are sourced from public GitHub repositories. We continuously scan and index new skills, ensuring you always have access to the latest community contributions."},"faq":{"title":"Frequently Asked Questions","subtitle":"Everything you need to know about agent skills, Claude Code, OpenAI Codex, and AI coding tools in 2025","questions":[{"question":"What are Agent Skills?","answer":"Agent Skills are modular capabilities that extend AI coding assistants. Each skill consists of a SKILL.md file with instructions, plus optional scripts and templates. In December 2025, Anthropic released the Agent Skills specification as an open standard, and OpenAI adopted the same format for Codex CLI and ChatGPT. Skills are model-invokedâthe AI automatically decides when to use them based on context."},{"question":"How do I install an agent skill?","answer":"For Claude Code: Add to ~/.claude/skills/ (personal) or .claude/skills/ (project). For OpenAI Codex CLI: Add to ~/.codex/skills/. Both use the same SKILL.md format. Clone the GitHub repository and copy the skill folder to your chosen directory. The AI automatically discovers and loads skills from these locations."},{"question":"Are these skills safe to use?","answer":"Skills from this marketplace are sourced from public GitHub repositories. We filter out low-quality repos (minimum 2 stars) and scan for basic quality indicators, but you should always review the code before installing. Community skills should be treated like any open-source codeâinspect before use."},{"question":"Can I use multiple skills together?","answer":"Yes! Claude Code supports multiple skills simultaneously. Skills are modular and designed to work together. For example, you can combine a 'code-reviewer' skill with a 'git-automation' skill to automatically review and commit code changes. Claude intelligently selects the appropriate skills based on your request context."},{"question":"Can I create and share my own skills?","answer":"Yes! You can create custom Claude skills and share them on GitHub. Check out the skill-creator skill for detailed guidance on creating your own skills. The basic structure requires a SKILL.md file with instructions. You can also add optional scripts, templates, and a marketplace.json for easier distribution."},{"question":"How do skills differ from slash commands?","answer":"Skills are model-invokedâClaude automatically decides when to use them based on context. Slash commands are user-invokedâyou explicitly type the command to trigger them. Skills enable more intelligent, context-aware automation in your workflows."},{"question":"How often are skills updated?","answer":"Our scraper regularly syncs with GitHub to fetch the latest skills and updates. When a skill repository is updated on GitHub, the changes will appear on SkillsMP shortly after our next sync. You can see the last update timestamp on each skill card. We recommend checking back regularly for new skills and improvements to existing ones."},{"question":"What is marketplace.json?","answer":"marketplace.json is a metadata file that enables one-command installation of skills through Claude Code plugin marketplaces. When present, users can install skills with a simple command like '/plugin install skill-name' instead of manually copying files. Skills with marketplace.json are marked with a special badge on SkillsMP. This file contains information about the skill's name, description, version, and installation instructions."},{"question":"Is this website affiliated with Anthropic or OpenAI?","answer":"No, SkillsMP is an independent community project. We aggregate and showcase agent skills from GitHub to make discovery easier. For official skills and documentation, refer to Anthropic's github.com/anthropics/skills or OpenAI's Codex documentation."}],"stillHaveQuestions":"Still have questions about agent skills?","exploreDocs":"open /docs && explore"},"footer":{"readme":"cat README.md","title":"Skills Marketplace","description":"Discover and explore AI skills built by the community.","gitRemote":"git remote -v","resources":"ls ./resources/","legal":"ls ./legal/","skillsDocs":"ð Skills Docs","skillDocument":"ð Skill Document","officialSkills":"ð Official Skills","codexSkillsDocs":"ð Codex Skills Docs","agentSkillsSpec":"ð Agent Skills Spec","about":"ð About","changelog":"ð Changelog","privacyPolicy":"ð Privacy Policy","termsOfService":"ð Terms of Service","online":"online","copyright":"© 2025 Not affiliated with Anthropic."},"mentions":{"title":"Mentioned in","anthropic":{"quote":"This Skills marketplace shares even more examples to inspire you. Explore community-built skills that extend what Claude can doâfrom automated testing and data visualization to code review and domain name brainstorming.","author":"Anthropic","source":"Building skills for Claude Code"},"langchain":{"quote":"We've added skills to the deepagent-CLI, making it possible to use the large and growing collection of public skills.","author":"LangChain","source":"Using Skills with Deep Agents"}},"timeline":{"title":"Statistics","skillsOverTime":"Skills Over Time","categoriesDistribution":"Categories Distribution","pageTitle":"Skills Timeline","description":"Visualize skill activity over time. See when skills were published and track community growth.","aboutTitle":"About This Timeline","aboutInfo":"This timeline shows the number of skills pushed to GitHub over time. The data is based on the last push date of each skill's repository. Use the granularity controls to view daily, weekly, or monthly activity patterns.","totalSkills":"Total Skills","avgPerDay":"Avg/Day","peakDay":"Peak Day","dataPoints":"Data Points","skillsAdded":"s