Token counting, model quotas, and sliding window rate limiter
- Go 100%
Wires the *RateLimiter surface into Core's service-registration plumbing behind a *core.ServiceRuntime[Config]. NewService() factory builds the limiter via NewWithConfig() and returns a *Service ready for c.Service() registration. Action handlers exposed via OnStartup: ratelimit.check — opts.model + opts.tokens → bool ratelimit.record — opts.model + opts.prompt_tokens + opts.output_tokens ratelimit.stats — opts.model → ModelStats (or full map) ratelimit.decide — opts.model + opts.tokens → Decision ratelimit.reset — opts.model → clear history WaitForCapacity (blocking) + BackgroundPrune (returns cancel func) + Models/Iter (iterators) stay direct method calls — not IPC-friendly. OnShutdown persists state to disk; OnStartup is idempotent via Once. Triplets + examples included; audit verdict COMPLIANT. Co-authored-by: Hephaestus <hephaestus@lthn.ai> |
||
|---|---|---|
| .core | ||
| .forgejo/workflows | ||
| .github/workflows | ||
| docs | ||
| external | ||
| go | ||
| specs | ||
| .editorconfig | ||
| .gitignore | ||
| .gitmodules | ||
| .golangci.yml | ||
| .woodpecker.yml | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| CONTRIBUTING.md | ||
| go.work | ||
| LICENCE | ||
| README.md | ||
| sonar-project.properties | ||
| threats.md | ||
go-ratelimit
Provider-agnostic sliding window rate limiter for LLM API calls. Enforces requests per minute (RPM), tokens per minute (TPM), and requests per day (RPD) quotas per model using an in-memory sliding window. 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 (multi-process, WAL mode). Includes a Gemini-specific token counting helper and a YAML-to-SQLite migration path.
Module: dappco.re/go/core/go-ratelimit
Licence: EUPL-1.2
Language: Go 1.26
Quick Start
import "dappco.re/go/core/go-ratelimit"
// YAML backend (default, single-process)
rl, err := ratelimit.New()
if err != nil {
log.Fatal(err)
}
// SQLite backend (multi-process)
rl, err = ratelimit.NewWithSQLite("/tmp/ratelimits.db")
if err != nil {
log.Fatal(err)
}
defer rl.Close()
if rl.CanSend("gemini-2.0-flash", 1500) {
rl.RecordUsage("gemini-2.0-flash", 1000, 500)
}
if err := rl.Persist(); err != nil {
log.Fatal(err)
}
For agent workflows, Decide returns a structured verdict with retry guidance:
decision := rl.Decide("gemini-2.0-flash", 1500)
if !decision.Allowed {
log.Printf("throttled (%s); retry after %s", decision.Code, decision.RetryAfter)
}
Documentation
- Architecture — sliding window algorithm, provider quotas, YAML and SQLite backends
- Development Guide — prerequisites, test patterns, coding standards
- Project History — completed phases with commit hashes, known limitations
Build & Test
go build ./...
go test ./...
go test -race ./...
go vet ./...
go test -cover ./...
go mod tidy
Licence
European Union Public Licence 1.2.