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.2

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.2
File Size Uploaded
pghybrid-0.1.2.tar.gz 360.1 kB Details

Built distribution (wheel)

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

Total release size: 448.6 kB

Release files / pghybrid-0.1.2.tar.gz

Download URL pghybrid-0.1.2.tar.gz
Size 360.1 kB
Tags Source
SHA-256 checksum
How to use checksums
2d8d62ea18c51a36e261e41b7536f266b388655af714994dda84067497c4e8f3
BLAKE2b-256 checksum
How to use checksums
66d77186212bcf372f28958ac099c6f2efd70146dae9392c14deb9ca821ac48e
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.2-py3-none-any.whl

Download URL pghybrid-0.1.2-py3-none-any.whl
Size 88.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
619496d66c0001de02c8f1deaeb300bc54bb47c3bba24cf3a733c26bf9978506
BLAKE2b-256 checksum
How to use checksums
3b66f0529b89cdcf0c5db88f818c5d5f289fae359d7cd02b43cd599ccc73ad7a
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

0.1.4

2 release files

0.1.3

2 release files

This release

0.1.2 This release

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