PostgreSQL Extension: Document Reranking¶
This guide covers the document reranking functionality in the SteadyText PostgreSQL extension.
Overview¶
Document reranking allows you to reorder search results based on their relevance to a query using the Qwen3-Reranker-4B model. This is particularly useful for improving search quality in retrieval-augmented generation (RAG) systems.
Core Functions¶
Basic Reranking¶
-- Rerank a list of documents based on relevance to a query
SELECT * FROM steadytext_rerank(
'machine learning applications',
ARRAY['Introduction to ML', 'Python cookbook', 'Deep learning guide']
);
-- Returns:
-- position | document | score
-- 1 | Deep learning guide | 0.89
-- 2 | Introduction to ML | 0.76
-- 3 | Python cookbook | 0.32
Batch Reranking¶
-- Rerank documents for multiple queries
SELECT * FROM steadytext_rerank_batch(
ARRAY['AI ethics', 'Python programming'],
ARRAY[
ARRAY['AI safety paper', 'Python tutorial'],
ARRAY['Django guide', 'Ethics in technology']
]
);
Async Reranking¶
-- Start async reranking job
SELECT request_id FROM steadytext_rerank_async(
'customer support query',
ARRAY(SELECT content FROM support_docs)
);
-- Check results
SELECT * FROM steadytext_check_async('uuid-here');
Real-World Examples¶
Search Result Improvement¶
-- Improve PostgreSQL full-text search results
WITH search_results AS (
SELECT
doc_id,
title,
content,
ts_rank(search_vector, query) as pg_score
FROM documents,
plainto_tsquery('english', 'machine learning') query
WHERE search_vector @@ query
ORDER BY pg_score DESC
LIMIT 20
)
SELECT
r.doc_id,
r.title,
r.pg_score,
rerank.score as ai_score,
rerank.position
FROM search_results r
CROSS JOIN LATERAL (
SELECT * FROM steadytext_rerank(
'machine learning',
ARRAY[r.content]
)
) rerank
ORDER BY rerank.score DESC
LIMIT 10;
Multi-Stage Retrieval¶
-- Stage 1: Fast vector search
WITH vector_candidates AS (
SELECT doc_id, content, embedding
FROM documents
ORDER BY embedding <-> steadytext_embed('user query')
LIMIT 100
),
-- Stage 2: Rerank top candidates
reranked AS (
SELECT
v.*,
rerank.score,
rerank.position
FROM vector_candidates v
CROSS JOIN LATERAL (
SELECT * FROM steadytext_rerank(
'user query',
ARRAY(SELECT content FROM vector_candidates)
)
) rerank
WHERE rerank.document = v.content
)
SELECT * FROM reranked
ORDER BY score DESC
LIMIT 10;
Customer Support Optimization¶
-- Find most relevant support articles for a ticket
CREATE OR REPLACE FUNCTION find_relevant_articles(
ticket_text TEXT,
limit_count INT DEFAULT 5
) RETURNS TABLE(
article_id INT,
title TEXT,
relevance_score FLOAT
) AS $$
BEGIN
RETURN QUERY
WITH candidates AS (
-- Get initial candidates using FTS
SELECT
a.article_id,
a.title,
a.content
FROM support_articles a
WHERE to_tsvector('english', a.content) @@
plainto_tsquery('english', ticket_text)
LIMIT 50
)
SELECT
c.article_id,
c.title,
r.score as relevance_score
FROM candidates c
CROSS JOIN LATERAL (
SELECT score
FROM steadytext_rerank(ticket_text, ARRAY[c.content])
) r
ORDER BY r.score DESC
LIMIT limit_count;
END;
$$ LANGUAGE plpgsql;
Advanced Techniques¶
Custom Task Descriptions¶
-- Use custom task description for domain-specific reranking
SELECT * FROM steadytext_rerank(
'patient symptoms: headache, fever, fatigue',
ARRAY[
'Common cold treatment guide',
'Migraine management',
'COVID-19 symptoms'
],
'Given a patient''s symptoms, rank medical articles by relevance for diagnosis'
);
Combining with Embeddings¶
-- Hybrid scoring: embeddings + reranking
CREATE OR REPLACE FUNCTION hybrid_search(
query_text TEXT,
weight_embedding FLOAT DEFAULT 0.3,
weight_rerank FLOAT DEFAULT 0.7
) RETURNS TABLE(
doc_id INT,
final_score FLOAT
) AS $$
BEGIN
RETURN QUERY
WITH embedding_scores AS (
SELECT
doc_id,
1 - (embedding <-> steadytext_embed(query_text)) as embed_score,
content
FROM documents
ORDER BY embed_score DESC
LIMIT 100
),
rerank_scores AS (
SELECT
e.doc_id,
e.embed_score,
r.score as rerank_score
FROM embedding_scores e
CROSS JOIN LATERAL (
SELECT score
FROM steadytext_rerank(query_text, ARRAY[e.content])
) r
)
SELECT
doc_id,
(embed_score * weight_embedding +
rerank_score * weight_rerank) as final_score
FROM rerank_scores
ORDER BY final_score DESC;
END;
$$ LANGUAGE plpgsql;
Performance Optimization¶
-- Materialized view for frequently reranked content
CREATE MATERIALIZED VIEW popular_queries_reranked AS
WITH popular_queries AS (
SELECT query, COUNT(*) as frequency
FROM search_logs
WHERE created_at > NOW() - INTERVAL '7 days'
GROUP BY query
ORDER BY frequency DESC
LIMIT 100
)
SELECT
q.query,
d.doc_id,
r.score,
r.position
FROM popular_queries q
CROSS JOIN documents d
CROSS JOIN LATERAL (
SELECT score, position
FROM steadytext_rerank(q.query, ARRAY[d.content])
) r
WHERE r.score > 0.5;
-- Refresh periodically
CREATE OR REPLACE FUNCTION refresh_reranked_cache()
RETURNS void AS $$
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY popular_queries_reranked;
END;
$$ LANGUAGE plpgsql;
Configuration and Tuning¶
Cache Configuration¶
-- Check reranking cache statistics
SELECT * FROM steadytext_cache_stats()
WHERE cache_type = 'reranking';
-- Clear reranking cache if needed
SELECT steadytext_clear_cache('reranking');
Performance Considerations¶
- Batch Size: Rerank in batches of 10-50 documents for optimal performance
- Caching: Frequently reranked queries are cached automatically
- Async Operations: Use async functions for large document sets
- Indexing: Ensure proper indexes on columns used in WHERE clauses
Error Handling¶
-- Safe reranking with error handling
CREATE OR REPLACE FUNCTION safe_rerank(
query_text TEXT,
documents TEXT[]
) RETURNS TABLE(
position INT,
document TEXT,
score FLOAT,
error TEXT
) AS $$
BEGIN
BEGIN
RETURN QUERY
SELECT r.*
FROM steadytext_rerank(query_text, documents) r;
EXCEPTION
WHEN OTHERS THEN
RETURN QUERY
SELECT
generate_series(1, array_length(documents, 1)),
unnest(documents),
0.0::FLOAT,
SQLERRM;
END;
END;
$$ LANGUAGE plpgsql;
Best Practices¶
1. Two-Stage Retrieval¶
Always use a fast first-stage retrieval (FTS, vector search) before reranking:
-- Good: Narrow down candidates first
WITH candidates AS (
SELECT * FROM documents
WHERE search_vector @@ query
LIMIT 100
)
SELECT * FROM steadytext_rerank(
'query',
ARRAY(SELECT content FROM candidates)
);
-- Bad: Reranking entire table
SELECT * FROM steadytext_rerank(
'query',
ARRAY(SELECT content FROM documents) -- Too many!
);
2. Score Thresholds¶
Filter results by relevance score:
-- Only return highly relevant results
SELECT * FROM steadytext_rerank(query, docs)
WHERE score > 0.7;
3. Monitoring¶
Track reranking performance:
-- Create reranking metrics table
CREATE TABLE reranking_metrics (
query_id UUID PRIMARY KEY,
query_text TEXT,
num_documents INT,
execution_time_ms INT,
avg_score FLOAT,
created_at TIMESTAMP DEFAULT NOW()
);
-- Log reranking operations
CREATE OR REPLACE FUNCTION log_reranking(
query_text TEXT,
num_docs INT,
exec_time_ms INT,
avg_score FLOAT
) RETURNS void AS $$
INSERT INTO reranking_metrics
(query_id, query_text, num_documents, execution_time_ms, avg_score)
VALUES
(gen_random_uuid(), query_text, num_docs, exec_time_ms, avg_score);
$$ LANGUAGE sql;
Integration Examples¶
With pgvector¶
-- Combine with pgvector for hybrid search
CREATE OR REPLACE FUNCTION vector_rerank_search(
query TEXT,
limit_results INT DEFAULT 10
) RETURNS TABLE(
id INT,
content TEXT,
vector_similarity FLOAT,
rerank_score FLOAT,
combined_score FLOAT
) AS $$
BEGIN
RETURN QUERY
WITH vector_search AS (
SELECT
doc_id as id,
content,
1 - (embedding <-> steadytext_embed(query)) as similarity
FROM documents
ORDER BY similarity DESC
LIMIT limit_results * 5 -- Get more candidates
),
reranked AS (
SELECT
v.*,
r.score as rerank_score
FROM vector_search v
CROSS JOIN LATERAL (
SELECT score
FROM steadytext_rerank(query, ARRAY[v.content])
) r
)
SELECT
id,
content,
similarity as vector_similarity,
rerank_score,
(similarity * 0.3 + rerank_score * 0.7) as combined_score
FROM reranked
ORDER BY combined_score DESC
LIMIT limit_results;
END;
$$ LANGUAGE plpgsql;
With Full-Text Search¶
-- Enhance PostgreSQL FTS with reranking
CREATE OR REPLACE FUNCTION fts_with_rerank(
search_query TEXT,
limit_results INT DEFAULT 10
) RETURNS TABLE(
doc_id INT,
headline TEXT,
fts_rank FLOAT,
ai_rank FLOAT
) AS $$
BEGIN
RETURN QUERY
WITH fts_results AS (
SELECT
d.doc_id,
ts_headline('english', d.content, query) as headline,
ts_rank(d.search_vector, query) as rank,
d.content
FROM documents d,
plainto_tsquery('english', search_query) query
WHERE d.search_vector @@ query
ORDER BY rank DESC
LIMIT limit_results * 3
),
reranked AS (
SELECT
f.*,
r.score as ai_score
FROM fts_results f
CROSS JOIN LATERAL (
SELECT score
FROM steadytext_rerank(search_query, ARRAY[f.content])
) r
)
SELECT
doc_id,
headline,
rank as fts_rank,
ai_score as ai_rank
FROM reranked
ORDER BY ai_score DESC
LIMIT limit_results;
END;
$$ LANGUAGE plpgsql;