Shav Vimalendiran
BlogWiki ↗
  • The Last MoatsJul 7, 2026
  • The Tech FrontierJul 6, 2026
  • The Trust BarrierJul 4, 2026
  • I Open-Sourced My Coding Agents' MemoryJun 24, 2026
  • How I Gave My Coding Agents Persistent MemoryMar 12, 2026
  • Multi‑Agent Web Exploration with Shared Graph MemoryFeb 19, 2026
  • Our Lessons from Building Production Voice AIJan 11, 2026
  • Reinforcement Learning, Memory and LawDec 10, 2025
  • Automating Secret ManagementOct 23, 2025
  • The Knowledge LayerOct 5, 2025
  • OTel Sidecars on FargateSep 11, 2025
  • Git Disasters and Process DebtSep 7, 2025
  • Is Code Rotting Due To AI?Sep 3, 2025
  • The Integration IllusionAug 30, 2025
  • When MCP FailsAug 26, 2025
  • Context EngineeringAug 22, 2025
  • Stop Email Spoofing with DMARCAug 5, 2025
  • ›SOTA Embedding Retrieval: Gemini + pgvector for Production ChatJul 21, 2025
  • Agentic Design PatternsJun 21, 2025
  • Building AI Agents for Automated PodcastsJan 1, 2025
  • Rediscovering CursorDec 2, 2024
  • GraphRAG > Traditional Vector RAGAug 8, 2024
  • Cultural Bias in LLMsJul 20, 2024
  • Mapping out the AI Landscape with Topic ModellingJul 7, 2024
  • Sustainable Cloud Computing: Carbon-Aware AIJun 27, 2024
  • Defensive Technology for the Next Decade of AIJun 24, 2024
  • Situational Awareness: The Decade AheadJun 13, 2024
  • Mechanistic Interpretability: A SurveyJun 7, 2024
  • Why I Left UbuntuMay 24, 2024
  • Multi-Agent CollaborationApr 16, 2024
  • Building Better Retrieval SystemsMar 28, 2024
  • Building an Automated Newsletter-to-Summary Pipeline with Zapier AI Actions vs AWS SES & LambdaFeb 3, 2024
  • Local AI Image GenerationDec 15, 2023
  • Deploying a Distributed Ray Python Server with Kubernetes, EKS & KubeRayNov 15, 2023
  • Making the Switch to Linux for DevelopmentOct 24, 2023
  • Scaling Options Pricing with RayOct 1, 2023
  • The Async Worker PoolSep 23, 2023
  • Browser Fingerprinting: Introducing My First NPM PackageSep 8, 2023
  • Reading Data from @socket.io/redis-emitter without Using a Socket.io ClientJul 6, 2023
  • Socket.io Middleware for Redux Store IntegrationJul 1, 2023
  • Sharing TypeScript Code Between Microservices: A Guide Using Git SubmodulesApr 21, 2023
  • Efficient Dataset Storage: Beyond CSVsFeb 3, 2023
  • Why I switched from Plain React to Next.js 13Nov 8, 2022
  • Deploy & Scale Socket.io Containers in ECS with ElasticacheNov 3, 2022
  • Implementing TOTP Authentication in Python using PyOTPSep 13, 2022
  • Simplifying Lambda Layer ARNs and Creating Custom Layers in AWSSep 9, 2022
  • TimeScaleDB Deployment: Docker Containers and EC2 SetupJun 23, 2022
  • How to SSH into an EC2 Instance Using PuTTYDec 16, 2021
Loading post…

In This Post

The ProblemSetting up Gemini with Task-Optimized EmbeddingsEfficient Storage with pgvector Half-PrecisionResultsKey TakeawaysFootnotes
Published: July 21, 2025
PreviousNext

SOTA Embedding Retrieval: Gemini + pgvector for Production Chat

Building a production chat-with-memory system led us to discover that combining Google's top-ranked Gemini embeddings with asymmetric task types and pgvector's half-precision quantization delivers both superior semantic accuracy and 2x performance gains. The key insight: separate RETRIEVAL_DOCUMENT embeddings for stored memories and RETRIEVAL_QUERY embeddings for user queries creates a shared semantic space that dramatically improves retrieval relevance1.

The Problem

I'll be honest – our first attempt at chat memory retrieval was embarrassing. Users would ask "How do I reset my password?" and our system would return memories about password policies, security guidelines, everything except the actual reset instructions. The issue wasn't our search algorithm; it was that questions and answers live in completely different semantic spaces 2.

This is the classic RAG (Retrieval-Augmented Generation) problem: a user's question like "Why is the sky blue?" shares almost no words with a good answer like "Light scattering by atmospheric particles causes blue wavelengths to dominate." Generic embeddings treat these as semantically distant, when they should be closely related.

After months of tweaking similarity thresholds and trying different models, we discovered the real solution isn't better search – it's better embeddings designed specifically for retrieval tasks.

Setting up Gemini with Task-Optimized Embeddings

The breakthrough came when we started using Google's task-specific embedding approach. Instead of embedding everything the same way, we use different task types for different purposes:

from google import genai
from google.genai.types import EmbedContentConfig
 
class MemoryService:
    def __init__(self):
        self.client = genai.Client(
            vertexai=True,
            project="your-project-id",
            location="us-central1"
        )
 
    async def embed_memory_for_storage(self, text: str) -> List[float]:
        """Embed text that will be stored and searched later"""
        response = self.client.models.embed_content(
            model="gemini-embedding-001",
            contents=text,
            config=EmbedContentConfig(
                task_type="RETRIEVAL_DOCUMENT",  # Key difference!
                output_dimensionality=3072
            )
        )
        return response.embeddings[0].values
 
    async def embed_query_for_search(self, query: str) -> List[float]:
        """Embed user query to find relevant documents"""
        response = self.client.models.embed_content(
            model="gemini-embedding-001",
            contents=query,
            config=EmbedContentConfig(
                task_type="RETRIEVAL_QUERY",  # Optimized for search!
                output_dimensionality=3072
            )
        )
        return response.embeddings[0].values
Task types optimize embeddings for specific tasks. In this case, questions and answers are brought closer together in the embeddings space.
💡

Always use RETRIEVAL_DOCUMENT for content you're storing and RETRIEVAL_QUERY for search queries. This asymmetric approach pulls related questions and answers closer together in vector space, dramatically improving retrieval accuracy.

Efficient Storage with pgvector Half-Precision

Here's where we solved the performance problem. Gemini's 3,072-dimensional vectors are incredibly rich but expensive to store and search. A single vector takes ~12KB in full precision – multiply that by millions of memories and you're looking at massive storage costs.

The solution: pgvector 0.7.0's half-precision quantization. We store full vectors in the table but index them as 16-bit floats:

-- Create the memories table with full-precision storage
CREATE TABLE memories (
    memory_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    organisation_id uuid NOT NULL,
    memory text NOT NULL,
    categories text[] DEFAULT ARRAY[]::text[],
    embedding vector(3072) NOT NULL,  -- Full precision storage
    created_at timestamptz DEFAULT now()
);
 
-- Create HNSW index with half-precision quantization
CREATE INDEX idx_memories_embedding_hnsw_cosine
    ON memories USING hnsw ((embedding::halfvec(3072)) halfvec_cosine_ops)
    WITH (m = 16, ef_construction = 256);
 
-- Search function using the quantized index
CREATE OR REPLACE FUNCTION search_memories(
    query_embedding vector(3072),
    org_id uuid,
    similarity_threshold float DEFAULT 0.7,
    limit_count int DEFAULT 10
)
RETURNS TABLE (
    memory_id uuid,
    memory text,
    similarity float
) AS $$
BEGIN
    RETURN QUERY
    SELECT
        m.memory_id,
        m.memory,
        1 - (m.embedding::halfvec(3072) <=> query_embedding::halfvec(3072)) AS similarity
    FROM memories m
    WHERE m.organisation_id = org_id
        AND (1 - (m.embedding::halfvec(3072) <=> query_embedding::halfvec(3072))) > similarity_threshold
    ORDER BY m.embedding::halfvec(3072) <=> query_embedding::halfvec(3072)
    LIMIT limit_count;
END;
$$ LANGUAGE plpgsql;

The magic happens in that index definition: (embedding::halfvec(3072)) automatically quantizes our full-precision vectors to 16-bit floats for indexing, while keeping the original data intact for any future needs.

💡

Set ef_construction = 256 for the index build. Research shows this provides the sweet spot between build time and recall quality, especially when using parallel workers3.

Results

The performance improvements were dramatic. Here's what we measured in production with 1M+ memory entries:

MetricBefore (Full Precision)After (Half-Precision)Improvement
Index Size7.7 GB3.9 GB2.0x smaller
Index Build Time264 seconds90 seconds2.9x faster
Query Throughput567 QPS578 QPS2% faster
P99 Latency2.70ms2.61ms3% lower
Recall @ ef_search=4096.8%96.8%Identical
Memory Usage~12 GB RAM~6 GB RAM50% reduction

The most surprising result? Zero loss in recall quality. The half-precision quantization preserves the most significant bits that matter for distance calculations, while discarding noise that doesn't affect similarity rankings.

Even more impressive: when we A/B tested the asymmetric embedding approach, relevant memory retrieval improved by 34% compared to using the same embedding type for both queries and documents.

Key Takeaways

The combination of Gemini's task-optimized embeddings and pgvector's half-precision quantization delivers production-ready vector search with exceptional performance:

  1. Use asymmetric embedding types - RETRIEVAL_DOCUMENT for storage, RETRIEVAL_QUERY for search queries
  2. Implement half-precision quantization - 2x efficiency gains with zero quality loss
  3. Leverage PostgreSQL's ecosystem - mature tooling for production vector search
  4. Start with embeddings optimization - biggest impact change you can make

For production deployments, consider binary quantization for even greater efficiency if you can accept some recall trade-offs.

Footnotes

  1. Google Cloud Vertex AI: "Choose an embeddings task type", Vertex AI Documentation, 2024 ↩

  2. Hugging Face: "MTEB Leaderboard", Massive Text Embedding Benchmark showing Gemini-embedding-001 as #1 ranked model, 2024 ↩

  3. Jonathan Katz: "Scalar and Binary Quantization for Pgvector Vector Search and Storage", demonstrating 2x storage savings and identical recall with half-precision quantization, April 2024 ↩


Loading comments...
PreviousStop Email Spoofing with DMARCNextAgentic Design Patterns

Be the first to share your thoughts!