Toolkit · Part of the lamina workspace
Go toolkit for building LLM-powered web services. Provides HTTP server lifecycle, auth, metrics, SSE streaming, and token stream filtering. Each package can be used independently. Database management is handled by axon-base.
go get github.com/benaskins/axon@latest
A minimal service with health checks and graceful shutdown:
package main
import (
"context"
"log/slog"
"net/http"
"time"
"github.com/benaskins/axon"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /hello", func(w http.ResponseWriter, r *http.Request) {
axon.WriteJSON(w, http.StatusOK, map[string]string{"msg": "hello"})
})
axon.ListenAndServe("8080", mux,
axon.WithDrainTimeout(10*time.Second),
axon.WithShutdownHook(func(ctx context.Context) {
slog.Info("cleanup complete")
}),
)
}Server lifecycle — ListenAndServe with graceful shutdown (SIGINT/SIGTERM), shutdown hooks, configurable drain and hook timeouts.
axon.ListenAndServe("8080", mux,
axon.WithShutdownHook(cleanup),
axon.WithDrainTimeout(30*time.Second),
)Auth — SessionValidator interface with AuthClient implementation. Validates sessions against a remote auth service with caching.
client := axon.NewAuthClientPlain(authURL,
axon.WithEndpointPath("/auth/check"),
axon.WithCacheTTL(time.Minute),
)
mux.Handle("/api/", axon.RequireAuth(client)(apiHandler))
// In handlers:
userID := axon.UserID(r.Context())
session := axon.Session(r.Context())
role := session.Claim("role")Database — Use axon-base for PostgreSQL pool, migrations, and repository patterns.
p, _ := pool.NewPool(ctx, dsn, "myapp")
db, _ := p.StdDB()
migration.Run(db, migrationsFS, "migrations")SPA — Serve embedded static files with client-side routing fallback.
mux.Handle("/", axon.SPAHandler(staticFS, "build",
axon.WithStaticPrefix("/_app/"),
))Health checks — ListenAndServe auto-wires /health and /metrics. Add named checks with WithHealthCheck:
axon.ListenAndServe("8080", mux,
axon.WithHealthCheck("database", func() error { return db.Ping() }),
)Config and helpers:
axon.MustLoadConfig(&cfg) // env vars -> struct
axon.DecodeJSON[MyRequest](w, r) // decode + validate request bodysse.SetSSEHeaders(w)
sse.SendEvent(w, flusher, payload)
bus := sse.NewEventBus[Event]()
ch := bus.Subscribe("client-1")
bus.Publish(Event{Type: "update"})Buffered token filter with lookahead for processing LLM output in real time.
filter := stream.NewStreamFilter(
stream.NewToolCallMatcher(),
stream.NewContentSafetyMatcher(blockedPatterns),
)
for token := range tokens {
emitted, held := filter.Process(token)
// emitted: safe to send to client
// held: buffered pending matcher decisions
}MIT