Skip to content

go-ratelimit

Module: forge.lthn.ai/core/go-ratelimit Licence: EUPL-1.2 Go version: 1.26+

go-ratelimit enforces requests-per-minute (RPM), tokens-per-minute (TPM), and requests-per-day (RPD) quotas on a per-model basis using an in-memory sliding window. It ships with default quota profiles for Gemini, OpenAI, Anthropic, and a local inference provider. State persists across process restarts via YAML (single-process) or SQLite with WAL mode (multi-process). A YAML-to-SQLite migration helper is included.

Quick Start

import "forge.lthn.ai/core/go-ratelimit"

// Create a limiter with Gemini defaults (YAML backend).
rl, err := ratelimit.New()
if err != nil {
    log.Fatal(err)
}

// Check capacity before sending.
if rl.CanSend("gemini-2.0-flash", 1500) {
    // Make the API call...
    rl.RecordUsage("gemini-2.0-flash", 1000, 500) // promptTokens, outputTokens
}

// Persist state to disk for recovery across restarts.
if err := rl.Persist(); err != nil {
    log.Printf("persist failed: %v", err)
}

Multi-provider configuration

rl, err := ratelimit.NewWithConfig(ratelimit.Config{
    Providers: []ratelimit.Provider{
        ratelimit.ProviderGemini,
        ratelimit.ProviderAnthropic,
    },
    Quotas: map[string]ratelimit.ModelQuota{
        // Override a specific model's limits.
        "gemini-3-pro-preview": {MaxRPM: 50, MaxTPM: 500000, MaxRPD: 200},
        // Add a custom model not in any profile.
        "llama-3.3-70b": {MaxRPM: 5, MaxTPM: 50000, MaxRPD: 0},
    },
})

SQLite backend (multi-process safe)

rl, err := ratelimit.NewWithSQLite("~/.core/ratelimits.db")
if err != nil {
    log.Fatal(err)
}
defer rl.Close()

// Load persisted state.
if err := rl.Load(); err != nil {
    log.Fatal(err)
}

// Use exactly as the YAML backend -- CanSend, RecordUsage, Persist, etc.

Blocking until capacity is available

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

if err := rl.WaitForCapacity(ctx, "claude-opus-4", 2000); err != nil {
    log.Printf("timed out waiting for capacity: %v", err)
    return
}
// Capacity is available; proceed with the API call.

Package Layout

The module is a single package with no sub-packages.

File Purpose
ratelimit.go Core types (RateLimiter, ModelQuota, Config, Provider), sliding window logic, provider profiles, YAML persistence, CountTokens helper
sqlite.go SQLite persistence backend (sqliteStore), schema creation, load/save operations
ratelimit_test.go Tests for core logic, provider profiles, concurrency, and benchmarks
sqlite_test.go Tests for SQLite backend, migration, and error recovery
error_test.go Tests for SQLite and YAML error paths
iter_test.go Tests for Models() and Iter() iterators, plus CountTokens edge cases

Dependencies

Dependency Purpose Category
gopkg.in/yaml.v3 YAML serialisation for the legacy persistence backend Direct
modernc.org/sqlite Pure Go SQLite driver (no CGO required) Direct
github.com/stretchr/testify Test assertions (assert, require) Test only

All indirect dependencies are pulled in by modernc.org/sqlite. No C toolchain or system SQLite library is needed.

Further Reading

  • Architecture -- sliding window algorithm, provider quotas, YAML and SQLite backends, concurrency model
  • Development -- build commands, test patterns, coding standards, commit conventions
  • History -- completed phases with commit hashes, known limitations