Skip to content

Vector Databases in AgenticGoKit

Overview

Vector databases enable sophisticated similarity search and retrieval-augmented generation (RAG) by storing and querying high-dimensional embeddings. This tutorial covers setting up and using vector databases with AgenticGoKit, including pgvector and Weaviate integration.

Vector databases are essential for production-ready memory systems that need to handle large amounts of data with fast, semantically-aware search capabilities.

Prerequisites

  • Understanding of Basic Memory Operations
  • Knowledge of vector embeddings and similarity search
  • Basic database administration skills
  • Familiarity with Docker (for setup examples)

Vector Database Concepts

What are Vector Embeddings?

Vector embeddings are numerical representations of text, images, or other data that capture semantic meaning in high-dimensional space:

"The cat sat on the mat" → [0.1, -0.3, 0.8, ..., 0.2] (1536 dimensions)
"A feline rested on the rug" → [0.2, -0.2, 0.7, ..., 0.3] (similar vector)

Vector databases use distance metrics to find similar content:

  • Cosine Similarity: Measures angle between vectors
  • Euclidean Distance: Measures straight-line distance
  • Dot Product: Measures vector alignment

pgvector Setup and Configuration

1. Database Setup with Docker

bash
# Create docker-compose.yml for pgvector
version: '3.8'
services:
  postgres:
    image: pgvector/pgvector:pg15
    environment:
      POSTGRES_DB: agentdb
      POSTGRES_USER: agent_user
      POSTGRES_PASSWORD: agent_pass
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql

volumes:
  postgres_data:
sql
-- init.sql
CREATE EXTENSION IF NOT EXISTS vector;

-- Create memory table
CREATE TABLE agent_memory (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    content TEXT NOT NULL,
    content_type VARCHAR(100) NOT NULL,
    embedding vector(1536), -- OpenAI embedding dimensions
    metadata JSONB,
    session_id VARCHAR(255),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Create indexes for performance
CREATE INDEX idx_agent_memory_content_type ON agent_memory(content_type);
CREATE INDEX idx_agent_memory_session_id ON agent_memory(session_id);
CREATE INDEX idx_agent_memory_created_at ON agent_memory(created_at);
CREATE INDEX idx_agent_memory_embedding ON agent_memory USING ivfflat (embedding vector_cosine_ops);

-- Create conversation history table
CREATE TABLE conversation_history (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    session_id VARCHAR(255) NOT NULL,
    role VARCHAR(50) NOT NULL,
    content TEXT NOT NULL,
    metadata JSONB,
    timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_conversation_session_id ON conversation_history(session_id);
CREATE INDEX idx_conversation_timestamp ON conversation_history(timestamp);

2. pgvector Configuration

go
package main

import (
    "context"
    "database/sql"
    "encoding/json"
    "fmt"
    "log"
    "os"
    "strings"
    "sync"
    "time"
    
    "github.com/kunalkushwaha/agenticgokit/core"
    // Note: In production, you would use a cron library like:
    // "github.com/robfig/cron/v3"
)

func main() {
    // Configure pgvector memory with current API
    config := core.AgentMemoryConfig{
        Provider:   "pgvector",
        Connection: "postgres://agent_user:agent_pass@localhost:5432/agentdb?sslmode=disable",
        MaxResults: 10,
        Dimensions: 1536,
        AutoEmbed:  true,
        
        // RAG-enhanced settings
        EnableRAG:               true,
        EnableKnowledgeBase:     true,
        KnowledgeMaxResults:     20,
        KnowledgeScoreThreshold: 0.7,
        ChunkSize:               1000,
        ChunkOverlap:            200,
        
        // RAG context assembly settings
        RAGMaxContextTokens: 4000,
        RAGPersonalWeight:   0.3,
        RAGKnowledgeWeight:  0.7,
        RAGIncludeSources:   true,
        
        Embedding: core.EmbeddingConfig{
            Provider:        "openai",
            Model:           "text-embedding-3-small",
            APIKey:          os.Getenv("OPENAI_API_KEY"),
            CacheEmbeddings: true,
            MaxBatchSize:    100,
            TimeoutSeconds:  30,
        },
        
        Documents: core.DocumentConfig{
            AutoChunk:                true,
            SupportedTypes:           []string{"pdf", "txt", "md", "web", "code"},
            MaxFileSize:              "10MB",
            EnableMetadataExtraction: true,
            EnableURLScraping:        true,
        },
        
        Search: core.SearchConfigToml{
            HybridSearch:         true,
            KeywordWeight:        0.3,
            SemanticWeight:       0.7,
            EnableReranking:      false,
            EnableQueryExpansion: false,
        },
    }
    
    // Create memory instance
    memory, err := core.NewMemory(config)
    if err != nil {
        log.Fatalf("Failed to create pgvector memory: %v", err)
    }
    defer memory.Close()
    
    // Test basic operations
    ctx := context.Background()
    
    // Store some test data
    err = memory.Store(ctx, "pgvector is a PostgreSQL extension for vector similarity search", "database", "vector")
    if err != nil {
        log.Fatalf("Failed to store data: %v", err)
    }
    
    // Query the data
    results, err := memory.Query(ctx, "PostgreSQL vector search", 5)
    if err != nil {
        log.Fatalf("Failed to query: %v", err)
    }
    
    fmt.Printf("Found %d results:\n", len(results))
    for _, result := range results {
        fmt.Printf("- %s (Score: %.3f)\n", result.Content, result.Score)
    }
}

3. Advanced pgvector Operations

go
func demonstratePgvectorFeatures(memory core.Memory) error {
    ctx := context.Background()
    
    // Store documents using current Document API
    documents := []core.Document{
        {
            ID:      "ml-def-1",
            Title:   "Machine Learning Definition",
            Content: "Machine learning is a subset of artificial intelligence that enables computers to learn from data.",
            Source:  "textbook",
            Type:    core.DocumentTypeText,
            Metadata: map[string]any{
                "topic":      "machine-learning",
                "difficulty": "beginner",
                "category":   "definition",
            },
            Tags:      []string{"ai", "ml", "definition"},
            CreatedAt: time.Now(),
        },
        {
            ID:      "nn-def-1",
            Title:   "Neural Networks Definition",
            Content: "Neural networks are computing systems inspired by biological neural networks.",
            Source:  "research-paper",
            Type:    core.DocumentTypeText,
            Metadata: map[string]any{
                "topic":      "neural-networks",
                "difficulty": "intermediate",
                "category":   "definition",
            },
            Tags:      []string{"ai", "neural-networks", "definition"},
            CreatedAt: time.Now(),
        },
        {
            ID:      "dl-def-1",
            Title:   "Deep Learning Definition",
            Content: "Deep learning uses neural networks with multiple layers to model complex patterns.",
            Source:  "textbook",
            Type:    core.DocumentTypeText,
            Metadata: map[string]any{
                "topic":      "deep-learning",
                "difficulty": "advanced",
                "category":   "definition",
            },
            Tags:      []string{"ai", "deep-learning", "definition"},
            CreatedAt: time.Now(),
        },
    }
    
    // Ingest documents using current API
    err := memory.IngestDocuments(ctx, documents)
    if err != nil {
        return fmt.Errorf("failed to ingest documents: %w", err)
    }
    
    // Perform knowledge search with current API
    results, err := memory.SearchKnowledge(ctx, "What is AI and how does it learn?",
        core.WithLimit(3),
        core.WithScoreThreshold(0.7),
        core.WithTags([]string{"ai", "definition"}),
    )
    if err != nil {
        return fmt.Errorf("knowledge search failed: %w", err)
    }
    
    fmt.Printf("Found %d relevant documents:\n", len(results))
    for _, result := range results {
        fmt.Printf("- %s (Score: %.3f)\n", result.Content, result.Score)
        fmt.Printf("  Source: %s, Document ID: %s\n", result.Source, result.DocumentID)
        if len(result.Tags) > 0 {
            fmt.Printf("  Tags: %v\n", result.Tags)
        }
    }
    
    // Demonstrate hybrid search (personal memory + knowledge base)
    hybridResults, err := memory.SearchAll(ctx, "machine learning concepts",
        core.WithLimit(5),
        core.WithIncludePersonal(true),
        core.WithIncludeKnowledge(true),
    )
    if err != nil {
        return fmt.Errorf("hybrid search failed: %w", err)
    }
    
    fmt.Printf("\nHybrid search results:\n")
    fmt.Printf("Personal Memory: %d results\n", len(hybridResults.PersonalMemory))
    fmt.Printf("Knowledge Base: %d results\n", len(hybridResults.Knowledge))
    fmt.Printf("Total Results: %d\n", hybridResults.TotalResults)
    
    return nil
}

Weaviate Integration

1. Weaviate Setup with Docker

bash
# docker-compose.yml for Weaviate
version: '3.4'
services:
  weaviate:
    command:
    - --host
    - 0.0.0.0
    - --port
    - '8080'
    - --scheme
    - http
    image: semitechnologies/weaviate:1.22.4
    ports:
    - 8080:8080
    restart: on-failure:0
    environment:
      QUERY_DEFAULTS_LIMIT: 25
      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
      PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
      DEFAULT_VECTORIZER_MODULE: 'none'
      ENABLE_MODULES: 'text2vec-openai,generative-openai'
      CLUSTER_HOSTNAME: 'node1'
    volumes:
      - weaviate_data:/var/lib/weaviate

volumes:
  weaviate_data:

2. Weaviate Configuration

go
func setupWeaviateMemory() (core.Memory, error) {
    config := core.AgentMemoryConfig{
        Provider:   "weaviate",
        Connection: "http://localhost:8080",
        MaxResults: 10,
        Dimensions: 1536,
        AutoEmbed:  true,
        
        // RAG-enhanced settings
        EnableRAG:               true,
        EnableKnowledgeBase:     true,
        KnowledgeMaxResults:     20,
        KnowledgeScoreThreshold: 0.7,
        ChunkSize:               1000,
        ChunkOverlap:            200,
        
        // RAG context assembly settings
        RAGMaxContextTokens: 4000,
        RAGPersonalWeight:   0.3,
        RAGKnowledgeWeight:  0.7,
        RAGIncludeSources:   true,
        
        Embedding: core.EmbeddingConfig{
            Provider:        "openai",
            Model:           "text-embedding-3-small",
            APIKey:          os.Getenv("OPENAI_API_KEY"),
            CacheEmbeddings: true,
            MaxBatchSize:    100,
            TimeoutSeconds:  30,
        },
        
        Documents: core.DocumentConfig{
            AutoChunk:                true,
            SupportedTypes:           []string{"pdf", "txt", "md", "web", "code"},
            MaxFileSize:              "10MB",
            EnableMetadataExtraction: true,
            EnableURLScraping:        true,
        },
        
        Search: core.SearchConfigToml{
            HybridSearch:         true,
            KeywordWeight:        0.3,
            SemanticWeight:       0.7,
            EnableReranking:      false,
            EnableQueryExpansion: false,
        },
    }
    
    memory, err := core.NewMemory(config)
    if err != nil {
        return nil, fmt.Errorf("failed to create Weaviate memory: %w", err)
    }
    
    return memory, nil
}

3. Advanced Weaviate Features

go
func demonstrateWeaviateFeatures(memory core.Memory) error {
    ctx := context.Background()
    
    // Create multi-modal documents using current Document API
    documents := []core.Document{
        {
            ID:      "car-red-mountain",
            Title:   "Red Sports Car",
            Content: "A red sports car driving on a mountain road",
            Source:  "image-description",
            Type:    core.DocumentTypeText,
            Metadata: map[string]any{
                "category": "automotive",
                "color":    "red",
                "setting":  "mountain",
                "vehicle_type": "sports-car",
            },
            Tags:      []string{"automotive", "red", "mountain", "sports-car"},
            CreatedAt: time.Now(),
        },
        {
            ID:      "car-blue-urban",
            Title:   "Blue Sedan",
            Content: "A blue sedan parked in a city street",
            Source:  "image-description",
            Type:    core.DocumentTypeText,
            Metadata: map[string]any{
                "category": "automotive",
                "color":    "blue",
                "setting":  "urban",
                "vehicle_type": "sedan",
            },
            Tags:      []string{"automotive", "blue", "urban", "sedan"},
            CreatedAt: time.Now(),
        },
    }
    
    // Ingest documents using current API
    err := memory.IngestDocuments(ctx, documents)
    if err != nil {
        return fmt.Errorf("failed to ingest documents: %w", err)
    }
    
    // Perform complex search with current SearchKnowledge API
    results, err := memory.SearchKnowledge(ctx, "vehicle in urban environment",
        core.WithLimit(5),
        core.WithScoreThreshold(0.5),
        core.WithTags([]string{"automotive", "urban"}),
    )
    if err != nil {
        return fmt.Errorf("knowledge search failed: %w", err)
    }
    
    fmt.Printf("Found %d matching items:\n", len(results))
    for _, result := range results {
        fmt.Printf("- %s (Score: %.3f)\n", result.Content, result.Score)
        fmt.Printf("  Document ID: %s, Source: %s\n", result.DocumentID, result.Source)
        if len(result.Tags) > 0 {
            fmt.Printf("  Tags: %v\n", result.Tags)
        }
    }
    
    // Demonstrate RAG context building
    ragContext, err := memory.BuildContext(ctx, "Tell me about cars in different environments",
        core.WithMaxTokens(2000),
        core.WithKnowledgeWeight(0.8),
        core.WithPersonalWeight(0.2),
        core.WithIncludeSources(true),
    )
    if err != nil {
        return fmt.Errorf("failed to build RAG context: %w", err)
    }
    
    fmt.Printf("\nRAG Context built:\n")
    fmt.Printf("Query: %s\n", ragContext.Query)
    fmt.Printf("Knowledge results: %d\n", len(ragContext.Knowledge))
    fmt.Printf("Personal memory results: %d\n", len(ragContext.PersonalMemory))
    fmt.Printf("Token count: %d\n", ragContext.TokenCount)
    fmt.Printf("Sources: %v\n", ragContext.Sources)
    
    return nil
}

Embedding Strategies

1. OpenAI Embeddings

go
func setupOpenAIEmbeddings() core.EmbeddingConfig {
    return core.EmbeddingConfig{
        Provider:        "openai",
        Model:           "text-embedding-3-small", // or text-embedding-3-large
        APIKey:          os.Getenv("OPENAI_API_KEY"),
        CacheEmbeddings: true,
        MaxBatchSize:    100,
        TimeoutSeconds:  30,
    }
}

2. Ollama Embeddings

go
func setupOllamaEmbeddings() core.EmbeddingConfig {
    return core.EmbeddingConfig{
        Provider:        "ollama",
        Model:           "mxbai-embed-large", // or other Ollama embedding models
        BaseURL:         "http://localhost:11434", // Ollama server URL
        CacheEmbeddings: true,
        MaxBatchSize:    32,
        TimeoutSeconds:  60, // Ollama might be slower
    }
}

3. Dummy Embeddings (for testing)

go
func setupDummyEmbeddings() core.EmbeddingConfig {
    return core.EmbeddingConfig{
        Provider:        "dummy",
        Model:           "dummy-model",
        CacheEmbeddings: false, // No need to cache dummy embeddings
        MaxBatchSize:    100,
        TimeoutSeconds:  5,
    }
}

Performance Optimization

1. Index Configuration

go
// pgvector index optimization
func optimizePgvectorIndexes(db *sql.DB) error {
    // Create HNSW index for better performance
    _, err := db.Exec(`
        CREATE INDEX CONCURRENTLY idx_agent_memory_embedding_hnsw 
        ON agent_memory USING hnsw (embedding vector_cosine_ops)
        WITH (m = 16, ef_construction = 64);
    `)
    if err != nil {
        return fmt.Errorf("failed to create HNSW index: %w", err)
    }
    
    // Update table statistics
    _, err = db.Exec("ANALYZE agent_memory;")
    if err != nil {
        return fmt.Errorf("failed to analyze table: %w", err)
    }
    
    return nil
}

2. Batch Operations

go
func performBatchOperations(memory core.Memory) error {
    ctx := context.Background()
    
    // Prepare batch data using current Document structure
    documents := make([]core.Document, 100) // Smaller batch for example
    for i := 0; i < 100; i++ {
        documents[i] = core.Document{
            ID:      fmt.Sprintf("batch-doc-%d", i),
            Title:   fmt.Sprintf("Batch Document %d", i),
            Content: fmt.Sprintf("This is the content of document %d in the batch operation", i),
            Source:  "batch-operation",
            Type:    core.DocumentTypeText,
            Metadata: map[string]any{
                "batch_id": "batch-001",
                "index":    i,
                "category": "batch-document",
            },
            Tags:      []string{"batch", "document", fmt.Sprintf("doc-%d", i)},
            CreatedAt: time.Now(),
        }
    }
    
    // Batch ingest operation using current API
    err := memory.IngestDocuments(ctx, documents)
    if err != nil {
        return fmt.Errorf("batch ingest failed: %w", err)
    }
    
    fmt.Printf("Successfully ingested %d documents in batch\n", len(documents))
    
    // Test batch search
    results, err := memory.SearchKnowledge(ctx, "batch document content",
        core.WithLimit(10),
        core.WithTags([]string{"batch"}),
    )
    if err != nil {
        return fmt.Errorf("batch search failed: %w", err)
    }
    
    fmt.Printf("Found %d documents from batch search\n", len(results))
    return nil
}

3. Optimized Memory Configuration

go
func createOptimizedMemory() (core.Memory, error) {
    config := core.AgentMemoryConfig{
        Provider:   "pgvector",
        Connection: "postgres://user:pass@localhost:5432/agentdb?pool_max_conns=20&pool_min_conns=5",
        MaxResults: 10,
        Dimensions: 1536,
        AutoEmbed:  true,
        
        // Optimized RAG settings
        EnableRAG:               true,
        EnableKnowledgeBase:     true,
        KnowledgeMaxResults:     50, // Higher for better context
        KnowledgeScoreThreshold: 0.75, // Higher threshold for quality
        ChunkSize:               1500, // Larger chunks for better context
        ChunkOverlap:            300,  // More overlap for continuity
        
        // Optimized RAG context settings
        RAGMaxContextTokens: 8000, // Larger context window
        RAGPersonalWeight:   0.2,  // Focus more on knowledge
        RAGKnowledgeWeight:  0.8,
        RAGIncludeSources:   true,
        
        Embedding: core.EmbeddingConfig{
            Provider:        "openai",
            Model:           "text-embedding-3-small",
            APIKey:          os.Getenv("OPENAI_API_KEY"),
            CacheEmbeddings: true, // Important for performance
            MaxBatchSize:    200,  // Larger batches
            TimeoutSeconds:  60,   // Longer timeout for large batches
        },
        
        Documents: core.DocumentConfig{
            AutoChunk:                true,
            SupportedTypes:           []string{"pdf", "txt", "md", "web", "code", "json"},
            MaxFileSize:              "50MB", // Larger files
            EnableMetadataExtraction: true,
            EnableURLScraping:        true,
        },
        
        Search: core.SearchConfigToml{
            HybridSearch:         true,
            KeywordWeight:        0.2, // Favor semantic search
            SemanticWeight:       0.8,
            EnableReranking:      false, // Can be enabled if available
            EnableQueryExpansion: false,
        },
    }
    
    return core.NewMemory(config)
}

Memory Monitoring and Metrics

1. Performance Metrics

go
type VectorDBMetrics struct {
    SearchLatency       []time.Duration
    KnowledgeSearches   int64
    PersonalSearches    int64
    HybridSearches      int64
    ContextBuilds       int64
    DocumentIngestions  int64
    mu                  sync.RWMutex
}

func (m *VectorDBMetrics) RecordSearch(searchType string, duration time.Duration) {
    m.mu.Lock()
    defer m.mu.Unlock()
    
    m.SearchLatency = append(m.SearchLatency, duration)
    
    switch searchType {
    case "knowledge":
        m.KnowledgeSearches++
    case "personal":
        m.PersonalSearches++
    case "hybrid":
        m.HybridSearches++
    case "context":
        m.ContextBuilds++
    case "ingest":
        m.DocumentIngestions++
    }
    
    // Keep only recent measurements
    if len(m.SearchLatency) > 1000 {
        m.SearchLatency = m.SearchLatency[len(m.SearchLatency)-1000:]
    }
}

func (m *VectorDBMetrics) GetAverageLatency() time.Duration {
    m.mu.RLock()
    defer m.mu.RUnlock()
    
    if len(m.SearchLatency) == 0 {
        return 0
    }
    
    var total time.Duration
    for _, latency := range m.SearchLatency {
        total += latency
    }
    
    return total / time.Duration(len(m.SearchLatency))
}

func (m *VectorDBMetrics) GetStats() map[string]interface{} {
    m.mu.RLock()
    defer m.mu.RUnlock()
    
    return map[string]interface{}{
        "average_latency":      m.GetAverageLatency(),
        "knowledge_searches":   m.KnowledgeSearches,
        "personal_searches":    m.PersonalSearches,
        "hybrid_searches":      m.HybridSearches,
        "context_builds":       m.ContextBuilds,
        "document_ingestions":  m.DocumentIngestions,
        "total_operations":     m.KnowledgeSearches + m.PersonalSearches + m.HybridSearches + m.ContextBuilds + m.DocumentIngestions,
    }
}

2. Health Monitoring

go
func monitorVectorDBHealth(memory core.Memory, metrics *VectorDBMetrics) {
    ticker := time.NewTicker(1 * time.Minute)
    defer ticker.Stop()
    
    for range ticker.C {
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        
        // Check basic connectivity with personal memory
        start := time.Now()
        _, err := memory.Query(ctx, "health check query", 1)
        personalLatency := time.Since(start)
        
        if err != nil {
            log.Printf("Personal memory health check failed: %v", err)
        } else {
            metrics.RecordSearch("personal", personalLatency)
            if personalLatency > 1*time.Second {
                log.Printf("Personal memory latency high: %v", personalLatency)
            }
        }
        
        // Check knowledge base connectivity
        start = time.Now()
        _, err = memory.SearchKnowledge(ctx, "health check query", core.WithLimit(1))
        knowledgeLatency := time.Since(start)
        
        if err != nil {
            log.Printf("Knowledge base health check failed: %v", err)
        } else {
            metrics.RecordSearch("knowledge", knowledgeLatency)
            if knowledgeLatency > 2*time.Second {
                log.Printf("Knowledge search latency high: %v", knowledgeLatency)
            }
        }
        
        // Check hybrid search
        start = time.Now()
        hybridResult, err := memory.SearchAll(ctx, "health check query",
            core.WithLimit(1),
            core.WithIncludePersonal(true),
            core.WithIncludeKnowledge(true),
        )
        hybridLatency := time.Since(start)
        
        if err != nil {
            log.Printf("Hybrid search health check failed: %v", err)
        } else {
            metrics.RecordSearch("hybrid", hybridLatency)
            log.Printf("Vector DB Health - Personal: %dms, Knowledge: %dms, Hybrid: %dms (Total: %d results)", 
                personalLatency.Milliseconds(),
                knowledgeLatency.Milliseconds(),
                hybridLatency.Milliseconds(),
                hybridResult.TotalResults)
        }
        
        cancel()
    }
}

Production Deployment

1. High Availability Setup

yaml
# docker-compose.prod.yml for pgvector HA
version: '3.8'
services:
  postgres-primary:
    image: pgvector/pgvector:pg15
    environment:
      POSTGRES_DB: agentdb
      POSTGRES_USER: agent_user
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_REPLICATION_USER: replicator
      POSTGRES_REPLICATION_PASSWORD: ${REPLICATION_PASSWORD}
    volumes:
      - postgres_primary_data:/var/lib/postgresql/data
      - ./postgresql.conf:/etc/postgresql/postgresql.conf
    command: postgres -c config_file=/etc/postgresql/postgresql.conf
    
  postgres-replica:
    image: pgvector/pgvector:pg15
    environment:
      PGUSER: agent_user
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_MASTER_SERVICE: postgres-primary
      POSTGRES_REPLICATION_USER: replicator
      POSTGRES_REPLICATION_PASSWORD: ${REPLICATION_PASSWORD}
    volumes:
      - postgres_replica_data:/var/lib/postgresql/data
    depends_on:
      - postgres-primary

volumes:
  postgres_primary_data:
  postgres_replica_data:

2. Backup and Recovery

go
func setupBackupStrategy(memory core.Memory) error {
    // Example backup strategy using Go's time.Ticker
    // In production, use a proper cron library like github.com/robfig/cron/v3
    
    // Daily full backup ticker
    dailyTicker := time.NewTicker(24 * time.Hour)
    go func() {
        for range dailyTicker.C {
            err := performFullBackup(memory)
            if err != nil {
                log.Printf("Full backup failed: %v", err)
            }
        }
    }()
    
    // Hourly incremental backup ticker
    hourlyTicker := time.NewTicker(1 * time.Hour)
    go func() {
        for range hourlyTicker.C {
            err := performIncrementalBackup(memory)
            if err != nil {
                log.Printf("Incremental backup failed: %v", err)
            }
        }
    }()
    
    log.Printf("Backup strategy initialized - daily full backups and hourly incremental backups")
    return nil
}

func performFullBackup(memory core.Memory) error {
    ctx := context.Background()
    
    backupFile := fmt.Sprintf("backup-full-%s.json", 
        time.Now().Format("2006-01-02-15-04-05"))
    
    // Since there's no ExportAll method in current API, we'll demonstrate
    // a backup strategy using available methods
    
    // Create backup structure
    backup := struct {
        Timestamp        time.Time                `json:"timestamp"`
        PersonalMemory   []core.Result           `json:"personal_memory"`
        KnowledgeBase    []core.KnowledgeResult  `json:"knowledge_base"`
        ChatHistory      []core.Message          `json:"chat_history"`
    }{
        Timestamp: time.Now(),
    }
    
    // Backup personal memory (get recent entries)
    personalResults, err := memory.Query(ctx, "", 1000) // Get up to 1000 entries
    if err != nil {
        log.Printf("Warning: Failed to backup personal memory: %v", err)
    } else {
        backup.PersonalMemory = personalResults
    }
    
    // Backup knowledge base (search with broad query)
    knowledgeResults, err := memory.SearchKnowledge(ctx, "",
        core.WithLimit(1000),
        core.WithScoreThreshold(0.0), // Include all results
    )
    if err != nil {
        log.Printf("Warning: Failed to backup knowledge base: %v", err)
    } else {
        backup.KnowledgeBase = knowledgeResults
    }
    
    // Backup chat history
    chatHistory, err := memory.GetHistory(ctx, 1000)
    if err != nil {
        log.Printf("Warning: Failed to backup chat history: %v", err)
    } else {
        backup.ChatHistory = chatHistory
    }
    
    // Serialize to JSON
    data, err := json.MarshalIndent(backup, "", "  ")
    if err != nil {
        return fmt.Errorf("JSON marshal failed: %w", err)
    }
    
    // Write to file
    err = os.WriteFile(backupFile, data, 0644)
    if err != nil {
        return fmt.Errorf("write backup failed: %w", err)
    }
    
    log.Printf("Full backup completed: %s (%d personal, %d knowledge, %d messages)", 
        backupFile, len(backup.PersonalMemory), len(backup.KnowledgeBase), len(backup.ChatHistory))
    return nil
}

func performIncrementalBackup(memory core.Memory) error {
    ctx := context.Background()
    
    // Get timestamp for incremental backup (last hour)
    since := time.Now().Add(-1 * time.Hour)
    backupFile := fmt.Sprintf("backup-incremental-%s.json", 
        time.Now().Format("2006-01-02-15-04-05"))
    
    // Create incremental backup structure
    backup := struct {
        Timestamp     time.Time               `json:"timestamp"`
        Since         time.Time               `json:"since"`
        RecentMemory  []core.Result          `json:"recent_memory"`
        RecentHistory []core.Message         `json:"recent_history"`
    }{
        Timestamp: time.Now(),
        Since:     since,
    }
    
    // Get recent personal memory entries
    recentMemory, err := memory.Query(ctx, "", 100) // Get recent entries
    if err != nil {
        log.Printf("Warning: Failed to backup recent memory: %v", err)
    } else {
        // Filter by timestamp (simplified - in production, use proper filtering)
        var filtered []core.Result
        for _, result := range recentMemory {
            if result.CreatedAt.After(since) {
                filtered = append(filtered, result)
            }
        }
        backup.RecentMemory = filtered
    }
    
    // Get recent chat history
    recentHistory, err := memory.GetHistory(ctx, 100)
    if err != nil {
        log.Printf("Warning: Failed to backup recent history: %v", err)
    } else {
        // Filter by timestamp
        var filtered []core.Message
        for _, msg := range recentHistory {
            if msg.CreatedAt.After(since) {
                filtered = append(filtered, msg)
            }
        }
        backup.RecentHistory = filtered
    }
    
    // Serialize to JSON
    data, err := json.MarshalIndent(backup, "", "  ")
    if err != nil {
        return fmt.Errorf("JSON marshal failed: %w", err)
    }
    
    // Write to file
    err = os.WriteFile(backupFile, data, 0644)
    if err != nil {
        return fmt.Errorf("write incremental backup failed: %w", err)
    }
    
    log.Printf("Incremental backup completed: %s (%d memory, %d messages)", 
        backupFile, len(backup.RecentMemory), len(backup.RecentHistory))
    return nilemory) error {
    ctx := context.Background()
    
    backupFile := fmt.Sprintf("backup-incremental-%s.json", 
        time.Now().Format("2006-01-02-15-04-05"))
    
    // For incremental backup, we'll get recent data (last hour)
    // This is a simplified approach - in production, you'd track changes
    
    backup := struct {
        Timestamp        time.Time                `json:"timestamp"`
        PersonalMemory   []core.Result           `json:"personal_memory"`
        KnowledgeBase    []core.KnowledgeResult  `json:"knowledge_base"`
        ChatHistory      []core.Message          `json:"chat_history"`
    }{
        Timestamp: time.Now(),
    }
    
    // Get recent personal memory entries
    personalResults, err := memory.Query(ctx, "", 100) // Smaller batch for incremental
    if err != nil {
        log.Printf("Warning: Failed to backup recent personal memory: %v", err)
    } else {
        backup.PersonalMemory = personalResults
    }
    
    // Get recent knowledge base entries
    knowledgeResults, err := memory.SearchKnowledge(ctx, "",
        core.WithLimit(100),
        core.WithScoreThreshold(0.0),
    )
    if err != nil {
        log.Printf("Warning: Failed to backup recent knowledge base: %v", err)
    } else {
        backup.KnowledgeBase = knowledgeResults
    }
    
    // Get recent chat history
    chatHistory, err := memory.GetHistory(ctx, 100)
    if err != nil {
        log.Printf("Warning: Failed to backup recent chat history: %v", err)
    } else {
        backup.ChatHistory = chatHistory
    }
    
    // Serialize to JSON
    data, err := json.MarshalIndent(backup, "", "  ")
    if err != nil {
        return fmt.Errorf("JSON marshal failed: %w", err)
    }
    
    // Write to file
    err = os.WriteFile(backupFile, data, 0644)
    if err != nil {
        return fmt.Errorf("write incremental backup failed: %w", err)
    }
    
    log.Printf("Incremental backup completed: %s (%d personal, %d knowledge, %d messages)", 
        backupFile, len(backup.PersonalMemory), len(backup.KnowledgeBase), len(backup.ChatHistory))
    return nil
}

Troubleshooting Common Issues

1. Performance Issues

go
func diagnosePerformanceIssues(memory core.Memory) {
    ctx := context.Background()
    
    log.Printf("=== Vector Database Performance Diagnosis ===")
    
    // Test personal memory performance
    queries := []string{
        "machine learning algorithms",
        "neural network architecture", 
        "data processing pipeline",
        "artificial intelligence concepts",
    }
    
    log.Printf("Testing Personal Memory Performance:")
    for _, query := range queries {
        start := time.Now()
        results, err := memory.Query(ctx, query, 5)
        duration := time.Since(start)
        
        if err != nil {
            log.Printf("  Query '%s' failed: %v", query, err)
        } else {
            log.Printf("  Query '%s': %dms (%d results)", 
                query, duration.Milliseconds(), len(results))
        }
    }
    
    log.Printf("Testing Knowledge Base Performance:")
    for _, query := range queries {
        start := time.Now()
        results, err := memory.SearchKnowledge(ctx, query, core.WithLimit(5))
        duration := time.Since(start)
        
        if err != nil {
            log.Printf("  Knowledge search '%s' failed: %v", query, err)
        } else {
            log.Printf("  Knowledge search '%s': %dms (%d results)", 
                query, duration.Milliseconds(), len(results))
        }
    }
    
    log.Printf("Testing Hybrid Search Performance:")
    for _, query := range queries {
        start := time.Now()
        hybridResult, err := memory.SearchAll(ctx, query,
            core.WithLimit(10),
            core.WithIncludePersonal(true),
            core.WithIncludeKnowledge(true),
        )
        duration := time.Since(start)
        
        if err != nil {
            log.Printf("  Hybrid search '%s' failed: %v", query, err)
        } else {
            log.Printf("  Hybrid search '%s': %dms (%d total results)", 
                query, duration.Milliseconds(), hybridResult.TotalResults)
        }
    }
}or _, query := range queries {
        start := time.Now()
        results, err := memory.Query(ctx, query, 10)
        duration := time.Since(start)
        
        if err != nil {
            log.Printf("  ❌ Personal query failed: %s - %v", query, err)
        } else {
            log.Printf("  ✅ Personal query: %s - %d results in %v", 
                query, len(results), duration)
        }
    }
    
    // Test knowledge base performance
    log.Printf("\nTesting Knowledge Base Performance:")
    for _, query := range queries {
        start := time.Now()
        results, err := memory.SearchKnowledge(ctx, query,
            core.WithLimit(10),
            core.WithScoreThreshold(0.5),
        )
        duration := time.Since(start)
        
        if err != nil {
            log.Printf("  ❌ Knowledge query failed: %s - %v", query, err)
        } else {
            log.Printf("  ✅ Knowledge query: %s - %d results in %v", 
                query, len(results), duration)
        }
    }
    
    // Test hybrid search performance
    log.Printf("\nTesting Hybrid Search Performance:")
    for _, query := range queries {
        start := time.Now()
        results, err := memory.SearchAll(ctx, query,
            core.WithLimit(10),
            core.WithIncludePersonal(true),
            core.WithIncludeKnowledge(true),
        )
        duration := time.Since(start)
        
        if err != nil {
            log.Printf("  ❌ Hybrid query failed: %s - %v", query, err)
        } else {
            log.Printf("  ✅ Hybrid query: %s - %d total results (%d personal, %d knowledge) in %v", 
                query, results.TotalResults, len(results.PersonalMemory), 
                len(results.Knowledge), duration)
        }
    }
    
    // Test RAG context building performance
    log.Printf("\nTesting RAG Context Building Performance:")
    start := time.Now()
    ragContext, err := memory.BuildContext(ctx, "Explain machine learning concepts",
        core.WithMaxTokens(2000),
        core.WithPersonalWeight(0.3),
        core.WithKnowledgeWeight(0.7),
        core.WithIncludeSources(true),
    )
    duration := time.Since(start)
    
    if err != nil {
        log.Printf("  ❌ RAG context building failed: %v", err)
    } else {
        log.Printf("  ✅ RAG context built in %v - %d tokens, %d sources", 
            duration, ragContext.TokenCount, len(ragContext.Sources))
    }
}

2. Connection Issues

go
func handleConnectionIssues(config core.AgentMemoryConfig) core.Memory {
    var memory core.Memory
    var err error
    
    maxRetries := 5
    baseDelay := 1 * time.Second
    
    for attempt := 0; attempt < maxRetries; attempt++ {
        memory, err = core.NewMemory(config)
        if err == nil {
            log.Printf("Connected to vector database on attempt %d", attempt+1)
            return memory
        }
        
        delay := baseDelay * time.Duration(1<<attempt) // Exponential backoff
        log.Printf("Connection attempt %d failed: %v, retrying in %v", 
            attempt+1, err, delay)
        time.Sleep(delay)
    }
    
    log.Fatalf("Failed to connect after %d attempts: %v", maxRetries, err)
    return nil
}

Best Practices

1. Schema Design

sql
-- Optimized schema for pgvector
CREATE TABLE agent_memory (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    content TEXT NOT NULL,
    content_type VARCHAR(100) NOT NULL,
    embedding vector(1536),
    metadata JSONB,
    session_id VARCHAR(255),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    
    -- Add constraints
    CONSTRAINT content_not_empty CHECK (length(content) > 0),
    CONSTRAINT valid_content_type CHECK (content_type ~ '^[a-z][a-z0-9_-]*$')$')
);

-- Optimized indexes
CREATE INDEX CONCURRENTLY idx_agent_memory_embedding_hnsw 
ON agent_memory USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

CREATE INDEX idx_agent_memory_content_type_session 
ON agent_memory(content_type, session_id);

CREATE INDEX idx_agent_memory_metadata_gin 
ON agent_memory USING gin (metadata);

2. Embedding Optimization

go
func optimizeEmbeddings() core.EmbeddingConfig {
    return core.EmbeddingConfig{
        Provider:        "openai",
        Model:           "text-embedding-3-small", // Good balance of cost/performance
        CacheEmbeddings: true, // Important for performance and cost
        MaxBatchSize:    100,  // Optimize for API rate limits
        TimeoutSeconds:  30,   // Reasonable timeout
    }
}

// For high-performance scenarios
func optimizeEmbeddingsHighPerformance() core.EmbeddingConfig {
    return core.EmbeddingConfig{
        Provider:        "openai",
        Model:           "text-embedding-3-large", // Higher quality embeddings
        CacheEmbeddings: true,
        MaxBatchSize:    200, // Larger batches for better throughput
        TimeoutSeconds:  60,  // Longer timeout for large batches
    }
}

// For cost-optimized scenarios
func optimizeEmbeddingsCostEffective() core.EmbeddingConfig {
    return core.EmbeddingConfig{
        Provider:        "ollama",
        Model:           "mxbai-embed-large", // Free local embeddings
        BaseURL:         "http://localhost:11434",
        CacheEmbeddings: true,
        MaxBatchSize:    50,  // Smaller batches for local processing
        TimeoutSeconds:  120, // Longer timeout for local processing
    }
}

3. Query Optimization

go
func optimizeQueries(memory core.Memory) {
    ctx := context.Background()
    
    // Example query to optimize
    query := "machine learning algorithms"
    
    // 1. Use appropriate limits - don't retrieve more than needed
    results, err := memory.SearchKnowledge(ctx, query,
        core.WithLimit(10), // Reasonable limit
        core.WithScoreThreshold(0.7), // Filter low-relevance results
    )
    if err != nil {
        log.Printf("Optimized knowledge search failed: %v", err)
        return
    }
    
    // 2. Use tag filters to reduce search space
    results, err = memory.SearchKnowledge(ctx, query,
        core.WithLimit(5),
        core.WithTags([]string{"ai", "algorithms"}), // Filter by relevant tags
        core.WithScoreThreshold(0.8), // Higher threshold for quality
    )
    if err != nil {
        log.Printf("Tag-filtered search failed: %v", err)
        return
    }
    
    // 3. Use document type filters for specific content
    results, err = memory.SearchKnowledge(ctx, query,
        core.WithLimit(10),
        core.WithDocumentTypes([]core.DocumentType{
            core.DocumentTypeMarkdown,
            core.DocumentTypePDF,
        }),
    )
    if err != nil {
        log.Printf("Document type filtered search failed: %v", err)
        return
    }
    
    // 4. Optimize hybrid search by controlling weights
    hybridResults, err := memory.SearchAll(ctx, query,
        core.WithLimit(15), // Slightly higher for hybrid
        core.WithIncludePersonal(true),
        core.WithIncludeKnowledge(true),
    )
    if err != nil {
        log.Printf("Hybrid search failed: %v", err)
        return
    }
    
    // 5. Cache frequent queries (simple in-memory cache example)
    var queryCache = make(map[string][]core.KnowledgeResult)
    var cacheMutex sync.RWMutex
    
    cacheKey := fmt.Sprintf("knowledge:%s", query)
    
    // Check cache first
    cacheMutex.RLock()
    if cached, exists := queryCache[cacheKey]; exists {
        cacheMutex.RUnlock()
        log.Printf("Cache hit for query: %s", query)
        results = cached
        return
    }
    cacheMutex.RUnlock()
    
    // Perform search and cache results
    results, err = memory.SearchKnowledge(ctx, query, core.WithLimit(10))
    if err == nil {
        cacheMutex.Lock()
        queryCache[cacheKey] = results
        cacheMutex.Unlock()
        log.Printf("Cached results for query: %s", query)
    }
    
    // 6. Optimize RAG context building
    ragContext, err := memory.BuildContext(ctx, query,
        core.WithMaxTokens(4000), // Appropriate context size
        core.WithPersonalWeight(0.2), // Focus more on knowledge
        core.WithKnowledgeWeight(0.8),
        core.WithHistoryLimit(5), // Limited history for focus
        core.WithIncludeSources(true), // Include sources for transparency
    )
    if err != nil {
        log.Printf("RAG context building failed: %v", err)
        return
    }
    
    log.Printf("Optimized RAG context: %d tokens, %d sources", 
        ragContext.TokenCount, len(ragContext.Sources))
}

Error Handling and Troubleshooting

1. Common Connection Issues

go
func handleConnectionErrors(config core.AgentMemoryConfig) (core.Memory, error) {
    // Implement retry logic for connection failures
    maxRetries := 3
    backoff := time.Second
    
    for attempt := 0; attempt < maxRetries; attempt++ {
        memory, err := core.NewMemory(config)
        if err == nil {
            // Test the connection
            ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
            _, testErr := memory.Query(ctx, "connection test", 1)
            cancel()
            
            if testErr == nil {
                log.Printf("Successfully connected to %s on attempt %d", config.Provider, attempt+1)
                return memory, nil
            }
            
            memory.Close() // Clean up failed connection
            err = testErr
        }
        
        if attempt == maxRetries-1 {
            return nil, fmt.Errorf("failed to connect after %d attempts: %w", maxRetries, err)
        }
        
        log.Printf("Connection attempt %d failed, retrying in %v: %v", 
            attempt+1, backoff, err)
        time.Sleep(backoff)
        backoff *= 2 // Exponential backoff
    }
    
    return nil, fmt.Errorf("connection failed after all retries")
}

2. Embedding Service Issues

go
func handleEmbeddingErrors(memory core.Memory) error {
    ctx := context.Background()
    
    // Test embedding generation with fallback
    testContent := "This is a test for embedding generation"
    
    err := memory.Store(ctx, testContent, "test", "embedding-check")
    if err != nil {
        if strings.Contains(err.Error(), "embedding") {
            log.Printf("Embedding service error: %v", err)
            log.Printf("Check your embedding configuration:")
            log.Printf("- API key is valid")
            log.Printf("- Model name is correct")
            log.Printf("- Service is accessible")
            log.Printf("- Rate limits are not exceeded")
            return fmt.Errorf("embedding service unavailable: %w", err)
        }
        return fmt.Errorf("storage error: %w", err)
    }
    
    // Clean up test data
    memory.ClearSession(ctx)
    return nil
}

3. Performance Troubleshooting

go
func diagnosePerformanceIssues(memory core.Memory) {
    ctx := context.Background()
    
    // Test query performance
    queries := []string{
        "machine learning",
        "artificial intelligence",
        "neural networks",
        "deep learning",
    }
    
    for _, query := range queries {
        start := time.Now()
        results, err := memory.SearchKnowledge(ctx, query, core.WithLimit(10))
        duration := time.Since(start)
        
        if err != nil {
            log.Printf("Query failed: %s - %v", query, err)
            continue
        }
        
        log.Printf("Query: '%s' - %d results in %v", query, len(results), duration)
        
        if duration > 2*time.Second {
            log.Printf("WARNING: Slow query detected. Consider:")
            log.Printf("- Optimizing database indexes")
            log.Printf("- Reducing search scope with filters")
            log.Printf("- Checking database resource usage")
            log.Printf("- Reviewing embedding service performance")
        }
    }
}

Conclusion

Vector databases provide the foundation for sophisticated memory systems in AgenticGoKit. Key takeaways:

  • Choose the right vector database for your use case (pgvector for SQL integration, Weaviate for specialized features)
  • Configure embedding services properly with appropriate models and settings
  • Optimize indexes and query patterns for performance
  • Implement proper monitoring, backup, and error handling strategies
  • Use current AgentMemoryConfig structure with RAG-enhanced settings
  • Leverage SearchKnowledge, SearchAll, and BuildContext methods for advanced operations

Vector databases enable semantic search, RAG systems, and intelligent memory that can significantly enhance agent capabilities.

Next Steps

Now that you have production-ready vector storage, build on this foundation:

📄 Content Processing

  • Document Ingestion - Process and ingest documents into your vector database
  • Learn chunking strategies, metadata extraction, and batch processing

🧠 Intelligent Retrieval

  • RAG Implementation - Build retrieval-augmented generation systems
  • Combine your vector database with LLMs for intelligent responses
  • Knowledge Bases - Create comprehensive knowledge systems
  • Advanced search patterns and multi-modal content handling

Performance & Scale

  • Memory Optimization - Advanced performance tuning
  • Database optimization, caching strategies, and scaling patterns

Prerequisites Complete

✅ You now have production-ready vector storage
🎯 Next: Add content with document ingestion

Further Reading

Released under the Apache 2.0 License.