memory

package module
v0.0.0-...-e9f3ee0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2025 License: MIT Imports: 8 Imported by: 0

README

Go Agent Memory 🧠

Go Reference MIT License Go Report Card

A modular, production-ready memory system for AI agents with semantic search capabilities. Uses Supabase (PostgreSQL + pgvector) for long-term semantic memory and optional Redis for blazing-fast session caching.

Features ✨

  • 📝 Session Memory: Fast retrieval of recent conversation history
  • 🔍 Semantic Search: Find relevant past conversations using vector similarity
  • 💾 Hybrid Storage: Redis for speed + Supabase for persistence
  • 🎯 Auto-Summarization: Compress old conversations to save tokens
  • 🔌 Modular Design: Use as a simple import, doesn't affect your app if not used
  • ⚡ Production Ready: Connection pooling, error handling, and graceful degradation

Quick Start 🚀

1. Install the Package
go get github.com/kshidenko/go-agent-memory
2. Set Up Supabase

Since you already have Supabase, just ensure pgvector is enabled:

-- Run this in Supabase SQL Editor
CREATE EXTENSION IF NOT EXISTS vector;
3. Basic Usage
package main

import (
    "context"
    "fmt"
    "log"
    
    memory "github.com/kshidenko/go-agent-memory"
)

func main() {
    // Initialize with just Supabase (no Redis)
    mem, err := memory.New(memory.Config{
        DatabaseURL:    "postgresql://user:pass@host:5432/dbname",
        OpenAIKey:      "your-openai-key",
        EmbeddingModel: "text-embedding-3-small",
    })
    if err != nil {
        log.Fatal(err)
    }
    defer mem.Close()
    
    // Add a message
    err = mem.AddMessage(context.Background(), memory.Message{
        ID:      "msg-123",
        Role:    "user",
        Content: "What's the weather like?",
        Metadata: memory.Metadata{
            SessionID: "session-456",
            UserID:    "user-789",
        },
    })
    
    // Get recent messages
    messages, _ := mem.GetRecentMessages(context.Background(), "session-456", 10)
    
    // Semantic search
    results, _ := mem.Search(context.Background(), 
        "weather forecast", 5, 0.7)
    
    for _, result := range results {
        fmt.Printf("Found: %s (score: %.2f)\n", 
            result.Message.Content, result.Score)
    }
}

Integration with Your Agent 🤖

Here's how to add memory to your existing agent:

// In your agent/main.go

package main

import (
    "context"
    "os"
    
    memory "github.com/kshidenko/go-agent-memory"
    "github.com/openai/openai-go/v2"
)

var mem memory.Memory // Optional memory instance

func initMemory() {
    // Only initialize if environment variables are set
    dbURL := os.Getenv("DATABASE_URL")
    if dbURL == "" {
        fmt.Println("Memory disabled: DATABASE_URL not set")
        return
    }
    
    var err error
    mem, err = memory.New(memory.Config{
        DatabaseURL:    dbURL,
        OpenAIKey:      os.Getenv("OPENAI_API_KEY"),
        
        // Optional Redis for faster session access
        RedisAddr:      os.Getenv("REDIS_URL"), // e.g., "localhost:6379"
        RedisPassword:  os.Getenv("REDIS_PASSWORD"),
        
        // Settings
        MaxSessionMessages: 50,
        SessionTTL:        24 * time.Hour,
        AutoSummarize:     true,
    })
    
    if err != nil {
        fmt.Printf("Warning: Memory initialization failed: %v\n", err)
        mem = nil
    }
}

func main() {
    // Initialize memory (optional)
    initMemory()
    if mem != nil {
        defer mem.Close()
    }
    
    // Your existing agent code...
    
    // When processing messages:
    handleUserMessage := func(sessionID, content string) {
        // Store user message if memory is enabled
        if mem != nil {
            mem.AddMessage(context.Background(), memory.Message{
                ID:      generateID(),
                Role:    "user",
                Content: content,
                Metadata: memory.Metadata{
                    SessionID: sessionID,
                },
            })
        }
        
        // Get context from memory
        var contextMessages []openai.ChatCompletionMessageParamUnion
        if mem != nil {
            // Get recent conversation
            recent, _ := mem.GetRecentMessages(context.Background(), sessionID, 10)
            
            // Search for relevant past conversations
            similar, _ := mem.Search(context.Background(), content, 3, 0.75)
            
            // Add to context (you'd format this appropriately)
            for _, msg := range recent {
                // Add to contextMessages...
            }
        }
        
        // Continue with OpenAI call...
    }
}

Configuration Options 🔧

Minimal Config (Supabase Only)
memory.Config{
    DatabaseURL: "postgresql://...",  // Required
    OpenAIKey:   "sk-...",           // Required for embeddings
}
Full Config (Hybrid Mode)
memory.Config{
    // Supabase (Required)
    SupabaseURL:  "https://xxx.supabase.co",
    SupabaseKey:  "your-anon-key",
    DatabaseURL:  "postgresql://...",
    
    // Redis (Optional - enables fast session cache)
    RedisAddr:     "localhost:6379",
    RedisPassword: "optional-password",
    RedisDB:       0,
    
    // OpenAI (Required for embeddings)
    OpenAIKey:      "sk-...",
    EmbeddingModel: "text-embedding-3-small", // or text-embedding-3-large
    
    // Memory Settings
    MaxSessionMessages: 50,           // Keep last N messages in fast cache
    SessionTTL:        24 * time.Hour, // Redis cache expiry
    AutoSummarize:     true,          // Auto-summarize old conversations
    VectorDimension:   1536,          // Match your embedding model
}

Environment Variables 🌍

# Required
export DATABASE_URL="postgresql://postgres:password@db.supabase.co:5432/postgres"
export OPENAI_API_KEY="sk-..."

# Optional (for Redis caching)
export REDIS_URL="localhost:6379"
export REDIS_PASSWORD=""

# Optional Supabase (if using REST APIs)
export SUPABASE_URL="https://xxx.supabase.co"
export SUPABASE_ANON_KEY="eyJ..."

Features in Detail 📚

1. Session Memory
  • Recent messages cached in Redis (if available)
  • Falls back to PostgreSQL if Redis is unavailable
  • Automatic TTL and size limits
  • Uses OpenAI embeddings (text-embedding-3-small/large)
  • HNSW index for fast similarity search
  • Configurable similarity threshold
3. Auto-Summarization
  • Compress old conversations to save context tokens
  • Summaries stored in separate table
  • Cached in Redis for fast access
4. Graceful Degradation
  • Works without Redis (Supabase only)
  • Continues if embedding generation fails
  • Non-blocking background operations

Performance 🏎️

With Redis + Supabase (Hybrid)
  • Session retrieval: ~2-5ms
  • Message storage: ~10ms
  • Semantic search: ~50-100ms
With Supabase Only
  • Session retrieval: ~20-50ms
  • Message storage: ~20-30ms
  • Semantic search: ~50-100ms

Cost Analysis 💰

For 1000 daily active users:

  • Redis + Supabase: ~$120/month ($0.12/user)
  • Supabase Only: ~$80/month ($0.08/user)
  • Embeddings: ~$0.02/1000 messages

Database Schema 📊

The package automatically creates these tables:

-- Messages table
CREATE TABLE agent_messages (
    id SERIAL PRIMARY KEY,
    message_id TEXT UNIQUE,
    session_id TEXT,
    user_id TEXT,
    role TEXT,
    content TEXT,
    metadata JSONB,
    embedding vector(1536),
    created_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ
);

-- Summaries table  
CREATE TABLE agent_summaries (
    id SERIAL PRIMARY KEY,
    session_id TEXT,
    summary TEXT,
    message_count INT,
    token_count INT,
    start_time TIMESTAMPTZ,
    end_time TIMESTAMPTZ,
    created_at TIMESTAMPTZ
);

Advanced Usage 🎓

Custom Embedding Models
mem, _ := memory.New(memory.Config{
    DatabaseURL:     dbURL,
    OpenAIKey:       apiKey,
    EmbeddingModel:  "text-embedding-3-large",
    VectorDimension: 3072, // Large model dimension
})
Search with Pre-computed Embeddings
// If you already have an embedding
embedding := []float32{0.1, 0.2, ...} // 1536 dimensions
results, _ := mem.SearchWithEmbedding(ctx, embedding, 10, 0.8)
Get Memory Statistics
stats, _ := mem.GetStats(ctx, "session-123")
fmt.Printf("Total messages: %d\n", stats.TotalMessages)
fmt.Printf("Total tokens: %d\n", stats.TotalTokens)
Summarize Long Conversations
summary, _ := mem.Summarize(ctx, "session-123", 4000) // Max 4000 tokens
fmt.Println("Summary:", summary)

Testing 🧪

# Run tests
go test ./...

# Run with race detection
go test -race ./...

# Run benchmarks
go test -bench=. ./...

Contributing 🤝

PRs welcome! Please ensure:

  1. Tests pass
  2. Code is formatted (go fmt)
  3. No linting issues (golangci-lint run)

License 📄

MIT License - feel free to use in your projects!

Support 💬

Documentation

Overview

Package memory provides a modular conversation memory system for AI agents. It supports both short-term session memory (via Redis) and long-term semantic memory (via Supabase pgvector).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Supabase Configuration (required for semantic memory)
	SupabaseURL string `json:"supabase_url"`
	SupabaseKey string `json:"supabase_key"`
	DatabaseURL string `json:"database_url"` // Direct PostgreSQL connection

	// Redis Configuration (optional for fast session cache)
	RedisAddr     string `json:"redis_addr,omitempty"` // e.g., "localhost:6379"
	RedisPassword string `json:"redis_password,omitempty"`
	RedisDB       int    `json:"redis_db,omitempty"`

	// OpenAI Configuration (for embeddings)
	OpenAIKey      string `json:"openai_key"`
	EmbeddingModel string `json:"embedding_model,omitempty"` // default: "text-embedding-3-small"

	// Memory Settings
	MaxSessionMessages int           `json:"max_session_messages,omitempty"` // default: 50
	SessionTTL         time.Duration `json:"session_ttl,omitempty"`          // default: 24h
	AutoSummarize      bool          `json:"auto_summarize,omitempty"`       // auto-summarize old messages
	VectorDimension    int           `json:"vector_dimension,omitempty"`     // default: 1536
}

Config holds configuration for memory initialization

type HybridMemory

type HybridMemory struct {
	// contains filtered or unexported fields
}

HybridMemory combines Redis for fast session memory and Supabase for semantic search

func (*HybridMemory) AddMessage

func (hm *HybridMemory) AddMessage(ctx context.Context, msg Message) error

AddMessage adds a message to both Redis (for fast access) and Supabase (for persistence)

func (*HybridMemory) ClearSession

func (hm *HybridMemory) ClearSession(ctx context.Context, sessionID string) error

ClearSession clears messages from both Redis and Supabase

func (*HybridMemory) Close

func (hm *HybridMemory) Close() error

Close closes all connections

func (*HybridMemory) GetRecentMessages

func (hm *HybridMemory) GetRecentMessages(ctx context.Context, sessionID string, limit int) ([]Message, error)

GetRecentMessages retrieves recent messages from Redis first, falls back to Supabase

func (*HybridMemory) GetStats

func (hm *HybridMemory) GetStats(ctx context.Context, sessionID string) (*Stats, error)

GetStats returns statistics about memory usage

func (*HybridMemory) Search

func (hm *HybridMemory) Search(ctx context.Context, query string, limit int, threshold float32) ([]SearchResult, error)

Search performs semantic search (delegates to Supabase)

func (*HybridMemory) SearchWithEmbedding

func (hm *HybridMemory) SearchWithEmbedding(ctx context.Context, embedding []float32, limit int, threshold float32) ([]SearchResult, error)

SearchWithEmbedding searches using a pre-computed embedding (delegates to Supabase)

func (*HybridMemory) Store

func (hm *HybridMemory) Store(ctx context.Context, msg Message) error

Store saves a message for long-term memory (delegates to Supabase)

func (*HybridMemory) Summarize

func (hm *HybridMemory) Summarize(ctx context.Context, sessionID string, maxTokens int) (string, error)

Summarize creates a summary of session messages

type Memory

type Memory interface {
	// Session Memory (fast, recent messages)
	AddMessage(ctx context.Context, msg Message) error
	GetRecentMessages(ctx context.Context, sessionID string, limit int) ([]Message, error)
	ClearSession(ctx context.Context, sessionID string) error

	// Semantic Memory (long-term, searchable)
	Store(ctx context.Context, msg Message) error
	Search(ctx context.Context, query string, limit int, threshold float32) ([]SearchResult, error)
	SearchWithEmbedding(ctx context.Context, embedding []float32, limit int, threshold float32) ([]SearchResult, error)

	// Management
	Summarize(ctx context.Context, sessionID string, maxTokens int) (string, error)
	GetStats(ctx context.Context, sessionID string) (*Stats, error)
	Close() error
}

Memory interface defines the contract for memory implementations

func New

func New(cfg Config) (Memory, error)

New creates a new memory instance based on configuration

func NewHybridMemory

func NewHybridMemory(cfg Config) (Memory, error)

NewHybridMemory creates a memory system with both Redis and Supabase

func NewSupabaseMemory

func NewSupabaseMemory(cfg Config) (Memory, error)

NewSupabaseMemory creates a new Supabase-based memory instance

type Message

type Message struct {
	ID        string    `json:"id"`
	Role      string    `json:"role"` // "user", "assistant", "system"
	Content   string    `json:"content"`
	Metadata  Metadata  `json:"metadata"`
	Timestamp time.Time `json:"timestamp"`
	Embedding []float32 `json:"embedding,omitempty"`
}

Message represents a single conversation message

type Metadata

type Metadata struct {
	SessionID   string                 `json:"session_id"`
	UserID      string                 `json:"user_id,omitempty"`
	TokenCount  int                    `json:"token_count,omitempty"`
	Model       string                 `json:"model,omitempty"`
	Temperature float64                `json:"temperature,omitempty"`
	Extra       map[string]interface{} `json:"extra,omitempty"`
}

Metadata contains additional message information

type SearchResult

type SearchResult struct {
	Message  Message `json:"message"`
	Score    float32 `json:"score"`
	Distance float32 `json:"distance"`
}

SearchResult represents a semantic search result

type Stats

type Stats struct {
	SessionID       string    `json:"session_id"`
	TotalMessages   int       `json:"total_messages"`
	SessionMessages int       `json:"session_messages"`
	TotalTokens     int       `json:"total_tokens"`
	OldestMessage   time.Time `json:"oldest_message"`
	LatestMessage   time.Time `json:"latest_message"`
	UniqueUsers     int       `json:"unique_users,omitempty"`
	StorageSize     int64     `json:"storage_size,omitempty"`
}

Stats provides memory usage statistics

type SupabaseMemory

type SupabaseMemory struct {
	// contains filtered or unexported fields
}

SupabaseMemory implements Memory using Supabase PostgreSQL with pgvector

func (*SupabaseMemory) AddMessage

func (sm *SupabaseMemory) AddMessage(ctx context.Context, msg Message) error

AddMessage adds a message to session memory

func (*SupabaseMemory) ClearSession

func (sm *SupabaseMemory) ClearSession(ctx context.Context, sessionID string) error

ClearSession removes all messages for a session

func (*SupabaseMemory) Close

func (sm *SupabaseMemory) Close() error

Close closes database connections

func (*SupabaseMemory) GetRecentMessages

func (sm *SupabaseMemory) GetRecentMessages(ctx context.Context, sessionID string, limit int) ([]Message, error)

GetRecentMessages retrieves recent messages for a session

func (*SupabaseMemory) GetStats

func (sm *SupabaseMemory) GetStats(ctx context.Context, sessionID string) (*Stats, error)

GetStats returns statistics about memory usage

func (*SupabaseMemory) Search

func (sm *SupabaseMemory) Search(ctx context.Context, query string, limit int, threshold float32) ([]SearchResult, error)

Search performs semantic search on messages

func (*SupabaseMemory) SearchWithEmbedding

func (sm *SupabaseMemory) SearchWithEmbedding(ctx context.Context, embedding []float32, limit int, threshold float32) ([]SearchResult, error)

SearchWithEmbedding searches using a pre-computed embedding

func (*SupabaseMemory) Store

func (sm *SupabaseMemory) Store(ctx context.Context, msg Message) error

Store saves a message for long-term semantic memory

func (*SupabaseMemory) Summarize

func (sm *SupabaseMemory) Summarize(ctx context.Context, sessionID string, maxTokens int) (string, error)

Summarize creates a summary of a session's messages

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL