Skip to main content

pghybrid

Hybrid search on the Postgres you already have.

Vector similarity + full-text search, combined by Reciprocal Rank Fusion, on plain pgvector. No pg_search, no VectorChord, no Elasticsearch, no vector database, no extension you need superuser to install.

pip install pghybrid

Why

Hybrid search on Postgres is a solved problem if you can install a C extension. pg_search, VectorChord and pg_textsearch are all excellent, and all of them are extensions, and on managed Postgres you usually cannot install extensions at all.

pgvector is available almost everywhere. BM25 extensions are available almost nowhere: not on RDS, Aurora, Cloud SQL, Azure Database, Supabase or Heroku, and removed from Neon for new projects in March 2026.

This is not a claim to search better than ParadeDB, pg_search's BM25 genuinely beats ts_rank_cd, and if you can install it you probably should. It is search you can actually install.

The case it exists for

A contract. Someone asks "renewal notice period". The clause that answers it says "sixty days written notice prior to the anniversary date", it never uses the word renewal. Vector search puts a plausible-but-wrong clause first. Keyword search puts the clause that uses all three words and answers none of them first. The right answer is second on both signals and first on neither, which is exactly what rank fusion is for.

Use it

from pghybrid import Config, HybridSearch

search = HybridSearch(
    Config(table="chunks", text_column="content", vector_column="embedding",
           tsvector_column="fts", extra_columns=["title"], paramstyle="pyformat"),
    execute=lambda sql, params: conn.execute(sql, params).fetchall(),
)

for row in search.search("renewal notice period", embedding=query_vector, limit=10):
    print(row.score, row.matched_by, row.get("title"))

You pass the embedding in. pghybrid never calls a model, so it works with OpenAI, Cohere, Voyage, a local sentence-transformer or anything else, and needs no API key. It never opens a connection either, it generates SQL and hands it to the driver you already use. for_psycopg, for_sqlalchemy, for_asyncpg and for_django set the placeholder style for you.

row.matched_by tells you which signal found each row (both, vector or text), which is usually the first thing you want when a search returns the wrong thing.

Read the statement instead of running it

sql, params = search.build_sql("renewal notice period", query_vector, limit=10)

It is one query with a candidate CTE per signal, fused by RRF. Nothing is hidden, and pghybrid sql prints it without a database at all.

Command line

pghybrid init   --dsn "$DATABASE_URL" --table chunks   # inspects, writes the migration
pghybrid doctor --dsn "$DATABASE_URL" --table chunks   # measured recall, read-only

doctor measures recall@k against exact search, sweeps ef_search/probes, catches queries that silently fall back to a sequential scan, and reports a tsvector that has stopped matching its text. That failure returns wrong answers without erroring.

explain decomposes a single result set: both ranks, both raw scores, each signal's contribution, and the near-miss band just below your cut-off.

Also in TypeScript

npm install pghybrid generates byte-identical SQL, checked on every commit rather than assumed.


Full documentation, the reasoning, and copy-and-paste SQL: github.com/pavangupta352/pghybrid

MIT © Pavan Gupta

Release files for pghybrid 0.1.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pghybrid 0.1.4
File Size Uploaded
pghybrid-0.1.4.tar.gz 362.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pghybrid 0.1.4
File Interpreter ABI Platform
pghybrid-0.1.4-py3-none-any.whl Python 3 none any Details

Total release size: 452.3 kB

Release files / pghybrid-0.1.4.tar.gz

Download URL pghybrid-0.1.4.tar.gz
Size 362.9 kB
Tags Source
SHA-256 checksum
How to use checksums
76673edc109ef8fb10192a85b92bb3451700fb739070548f9897b575ea4a001a
BLAKE2b-256 checksum
How to use checksums
488b0b9a57b17c358012e4ce23e2ba033bec22fda97488982b8e630f9cb53381
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.

Transparency log

Release files / pghybrid-0.1.4-py3-none-any.whl

Download URL pghybrid-0.1.4-py3-none-any.whl
Size 89.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c9f365ced8e333e00614c825d81c6a961db104b3cbb52bb18b75f409a25890c1
BLAKE2b-256 checksum
How to use checksums
917a1dce2a3f8be1958f125c49e7f21f5728027f76343970aff1e4df077b3cb7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.4 This release

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page