Sdk

Go SDK

Installation, configuration, and API reference for the Sluice Go SDK.

Sluice SDK -- Go

Installation

go get github.com/ontopix/sluice/go

Configuration

DefaultConfig

import sluice "github.com/ontopix/sluice/go"

cfg := sluice.DefaultConfig()

Returns a Config with:

type Config struct {
    TableName          string         // "ontopix-vendor-buckets-prod"
    Region             string         // "eu-central-1"
    EndpointURL        string         // "" (empty = standard AWS endpoint)
    LeaseTTL           time.Duration  // 60s
    MaxRetries         int            // 3
    DefaultSlotTimeout time.Duration  // 30s
    LogLevel           string         // "INFO"
}

ConfigFromEnv

cfg := sluice.ConfigFromEnv()

Reads these environment variables, falling back to the defaults above:

VariableTypeDefault
SLUICE_TABLE_NAMEstring"ontopix-vendor-buckets-prod"
SLUICE_AWS_REGIONstring"eu-central-1"
SLUICE_ENDPOINT_URLstring""
SLUICE_LEASE_TTLint (seconds)60
SLUICE_MAX_RETRIESint3
SLUICE_DEFAULT_SLOT_TIMEOUTfloat (seconds)30.0
SLUICE_LOG_LEVELstring"INFO"

WithConfig option

Pass a custom config to any function using the WithConfig option:

cfg := sluice.Config{
    TableName:  "my-custom-table",
    Region:     "us-east-1",
    MaxRetries: 5,
    LeaseTTL:   120 * time.Second,
}

err := sluice.WithSlot(ctx, "openai#gpt-4o#requests", 10*time.Second, fn, sluice.WithConfig(cfg))

When no WithConfig option is passed, ConfigFromEnv() is called automatically.


WithSlot acquires a slot, runs the callback, and guarantees release on return (success or error). This is the safest API because release cannot be forgotten.

import (
    "context"
    "time"

    sluice "github.com/ontopix/sluice/go"
)

err := sluice.WithSlot(ctx, "openai#gpt-4o#requests", 10*time.Second,
    func(ctx context.Context) error {
        resp, err := callOpenAI(ctx, prompt)
        if err != nil {
            return err
        }
        // process resp
        return nil
    },
)

Signature

func WithSlot(
    ctx context.Context,
    dimension string,
    timeout time.Duration,
    fn func(context.Context) error,
    opts ...Option,
) error

Multiple dimensions

WithSlotMany acquires slots for all dimensions atomically:

err := sluice.WithSlotMany(ctx,
    []string{"openai#gpt-4o#requests", "openai#gpt-4o#tokens"},
    15*time.Second,
    func(ctx context.Context) error {
        return callOpenAI(ctx, prompt)
    },
)

Signature

func WithSlotMany(
    ctx context.Context,
    dimensions []string,
    timeout time.Duration,
    fn func(context.Context) error,
    opts ...Option,
) error

Slot -- handle API

Slot returns a SlotHandle that the caller must release explicitly. Use defer to ensure release.

handle, err := sluice.Slot(ctx, "openai#gpt-4o#requests", 10*time.Second)
if err != nil {
    return err
}
defer handle.Release(ctx)

resp, err := callOpenAI(ctx, prompt)

Signatures

func Slot(
    ctx context.Context,
    dimension string,
    timeout time.Duration,
    opts ...Option,
) (*SlotHandle, error)

func SlotMany(
    ctx context.Context,
    dimensions []string,
    timeout time.Duration,
    opts ...Option,
) (*SlotHandle, error)

SlotHandle

type SlotHandle struct { /* ... */ }

func (h *SlotHandle) Release(ctx context.Context) error

Acquire / Release -- low-level API

Acquire performs a single attempt to obtain a slot. It returns immediately with either Granted or RetryIn. The caller is responsible for retry logic and calling Release.

result, err := sluice.Acquire(ctx, "openai#gpt-4o#requests")
if err != nil {
    return err
}

if result.Outcome == sluice.Granted {
    defer result.Release(ctx)
    resp, err := callOpenAI(ctx, prompt)
    // ...
} else {
    fmt.Printf("Retry after %.1fs\n", result.WaitSeconds)
}

Signatures

func Acquire(
    ctx context.Context,
    dimension string,
    opts ...Option,
) (*AcquireResult, error)

func AcquireMany(
    ctx context.Context,
    dimensions []string,
    opts ...Option,
) (*AcquireResult, error)

AcquireMany is all-or-nothing: if any dimension has insufficient tokens, none are acquired and a single RetryIn result is returned with the maximum wait time.

AcquireResult

type AcquireResult struct {
    Outcome     AcquireOutcome
    WaitSeconds float64
    LeaseKey    string
    Dimension   string
}

func (r *AcquireResult) Release(ctx context.Context) error

AcquireOutcome

const (
    Granted AcquireOutcome = "granted"
    RetryIn AcquireOutcome = "retry_in"
)

Penalize

Reduces the token count for a dimension by a factor. Use this when a vendor returns 429 despite the slot being granted.

Unlike the Python and TypeScript SDKs, the Go SDK returns the error rather than swallowing it.

err := sluice.Penalize(ctx, "openai#gpt-4o#requests", 0.8)
if err != nil {
    log.Printf("penalize failed: %v", err)
}

Signature

func Penalize(
    ctx context.Context,
    dimension string,
    factor float64,           // 0.0 to 1.0
    opts ...Option,
) error

Error handling

SlotTimeout

Returned by Slot, SlotMany, WithSlot, and WithSlotMany when the timeout expires.

handle, err := sluice.Slot(ctx, "openai#gpt-4o#requests", 5*time.Second)
if err != nil {
    var timeout sluice.SlotTimeout
    if errors.As(err, &timeout) {
        // Handle timeout
    }
    return err
}
defer handle.Release(ctx)

ConfigurationError

Returned when a dimension is not found in the DynamoDB table.

result, err := sluice.Acquire(ctx, "nonexistent#dimension")
if err != nil {
    var cfgErr *sluice.ConfigurationError
    if errors.As(err, &cfgErr) {
        fmt.Println("Missing dimension:", cfgErr.Dimension)
    }
}

Context cancellation

All functions accept a context.Context. If the context is cancelled or its deadline is exceeded, the function returns immediately with ctx.Err(). The Slot family of functions internally creates a child context with a deadline set to now + timeout.


Client -- connection reuse

Package-level functions (WithSlot, Acquire, etc.) create a new DynamoDB connection on each call. For multiple calls within the same process, use Client to hold the connection:

client, err := sluice.NewClient(ctx)
if err != nil {
    return err
}

err = client.WithSlot(ctx, "openai#gpt-4o#requests", 10*time.Second,
    func(ctx context.Context) error {
        _, err := callOpenAI(ctx, prompt)
        return err
    },
)

NewClient accepts the same ...Option arguments as other functions:

client, err := sluice.NewClient(ctx, sluice.WithConfig(sluice.Config{
    MaxRetries: 5,
}))

The client provides the same methods as the package-level functions: Acquire, AcquireMany, WithSlot, WithSlotMany, Slot, SlotMany, Penalize.


Full example

package main

import (
    "context"
    "errors"
    "fmt"
    "time"

    sluice "github.com/ontopix/sluice/go"
)

func callWithRateLimit(ctx context.Context, prompt string) (string, error) {
    var response string

    err := sluice.WithSlot(ctx, "openai#gpt-4o#requests", 10*time.Second,
        func(ctx context.Context) error {
            var err error
            response, err = callOpenAI(ctx, prompt)
            return err
        },
    )

    return response, err
}

func callWithBackpressure(ctx context.Context, prompt string) (string, error) {
    var response string

    err := sluice.WithSlot(ctx, "openai#gpt-4o#requests", 10*time.Second,
        func(ctx context.Context) error {
            var err error
            response, err = callOpenAI(ctx, prompt)
            if err != nil && isRateLimitError(err) {
                _ = sluice.Penalize(ctx, "openai#gpt-4o#requests", 0.5)
            }
            return err
        },
    )

    return response, err
}