Documentation
¶
Overview ¶
Package agentbudget provides real-time cost enforcement for AI agent sessions.
Set a hard dollar limit on any LLM session. Wrap API responses, track tool costs, and get automatic circuit breaking — no surprise bills.
Quickstart ¶
budget, err := agentbudget.New("$5.00")
if err != nil {
log.Fatal(err)
}
session := budget.NewSession()
defer session.Close()
// After calling your LLM, record usage:
err = session.WrapUsage("gpt-4o", resp.Usage.PromptTokens, resp.Usage.CompletionTokens)
if err != nil {
// *agentbudget.BudgetExhausted or *agentbudget.LoopDetected
}
fmt.Printf("spent: $%.4f remaining: $%.4f\n", session.Spent(), session.Remaining())
OpenAI Example ¶
client := openai.NewClient(os.Getenv("OPENAI_API_KEY"))
budget, _ := agentbudget.New(5.0)
session := budget.NewSession()
defer session.Close()
resp, _ := client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: openai.GPT4o,
Messages: messages,
})
if err := session.WrapUsage(
resp.Model,
int64(resp.Usage.PromptTokens),
int64(resp.Usage.CompletionTokens),
); err != nil {
log.Fatal(err)
}
Anthropic Example ¶
client, _ := anthropic.NewClient(os.Getenv("ANTHROPIC_API_KEY"))
resp, _ := client.Messages.New(ctx, anthropic.MessageNewParams{...})
if err := session.WrapUsage(
string(resp.Model),
resp.Usage.InputTokens,
resp.Usage.OutputTokens,
); err != nil {
log.Fatal(err)
}
Example (BasicSession) ¶
package main
import (
"fmt"
"log"
agentbudget "github.com/AgentBudget/agentbudget/sdks/go"
)
func main() {
budget, err := agentbudget.New("$5.00")
if err != nil {
log.Fatal(err)
}
session := budget.NewSession()
defer session.Close()
// After your LLM call, record usage:
err = session.WrapUsage("gpt-4o", 1000, 500)
if err != nil {
log.Fatal(err)
}
fmt.Printf("spent: $%.4f\n", session.Spent())
fmt.Printf("remaining: $%.4f\n", session.Remaining())
}
Output:
Example (BudgetExhausted) ¶
package main
import (
"errors"
"fmt"
agentbudget "github.com/AgentBudget/agentbudget/sdks/go"
)
func main() {
budget, _ := agentbudget.New(0.001) // very small budget
session := budget.NewSession()
defer session.Close()
err := session.WrapUsage("gpt-4o", 10000, 5000)
if err != nil {
var exhausted *agentbudget.BudgetExhausted
if errors.As(err, &exhausted) {
fmt.Printf("hard limit hit: budget=%.4f spent=%.4f\n",
exhausted.Budget, exhausted.Spent)
}
}
}
Output:
Example (Callbacks) ¶
package main
import (
"fmt"
agentbudget "github.com/AgentBudget/agentbudget/sdks/go"
)
func main() {
budget, _ := agentbudget.New("$1.00",
agentbudget.WithSoftLimit(0.8),
agentbudget.WithOnSoftLimit(func(r agentbudget.Report) {
fmt.Printf("⚠️ soft limit: spent $%.4f of $%.2f\n", r.TotalSpent, r.Budget)
}),
agentbudget.WithOnHardLimit(func(r agentbudget.Report) {
fmt.Printf("🛑 hard limit hit after $%.4f\n", r.TotalSpent)
}),
)
session := budget.NewSession()
defer session.Close()
_ = session.WrapUsage("gpt-4o", 100000, 50000)
}
Output:
Example (ChildSession) ¶
package main
import (
"fmt"
agentbudget "github.com/AgentBudget/agentbudget/sdks/go"
)
func main() {
budget, _ := agentbudget.New(10.0)
parent := budget.NewSession()
defer parent.Close()
child := parent.ChildSession(2.0)
defer func() {
child.Close()
// Roll up child spend to parent manually
if child.Spent() > 0 {
_ = parent.Track(child.Spent(), "child:"+child.ID())
}
}()
_ = child.WrapUsage("gpt-4o-mini", 5000, 2000)
fmt.Printf("child spent: $%.4f\n", child.Spent())
}
Output:
Example (CustomPricing) ¶
package main
import (
"fmt"
"log"
agentbudget "github.com/AgentBudget/agentbudget/sdks/go"
)
func main() {
if err := agentbudget.RegisterModel("my-model", 5.00, 20.00); err != nil {
log.Fatal(err)
}
budget, _ := agentbudget.New(5.0)
session := budget.NewSession()
defer session.Close()
_ = session.WrapUsage("my-model", 1000, 500)
fmt.Printf("spent: $%.6f\n", session.Spent())
}
Output:
Example (TrackTool) ¶
package main
import (
"fmt"
"log"
agentbudget "github.com/AgentBudget/agentbudget/sdks/go"
)
func main() {
budget, _ := agentbudget.New(10.0)
session := budget.NewSession()
defer session.Close()
// Track a tool/API call with a known cost
if err := session.Track(0.05, "serp_api"); err != nil {
log.Fatal(err)
}
fmt.Printf("spent: $%.4f\n", session.Spent())
}
Output:
Example (WouldExceed) ¶
package main
import (
"fmt"
agentbudget "github.com/AgentBudget/agentbudget/sdks/go"
)
func main() {
budget, _ := agentbudget.New(1.0, agentbudget.WithFinalizationReserve(0.05))
session := budget.NewSession()
defer session.Close()
// Do work...
_ = session.WrapUsage("gpt-4o", 50000, 25000)
// Before a final expensive call, check first
estimatedFinalCost := 0.10
if session.WouldExceed(estimatedFinalCost) {
fmt.Println("not enough budget for final call — wrapping up")
return
}
_ = session.WrapUsage("gpt-4o", 10000, 5000)
}
Output:
Index ¶
- func CalculateCost(model string, inputTokens, outputTokens int64) (float64, bool)
- func RegisterModel(model string, inputPerMillion, outputPerMillion float64) error
- type Budget
- type BudgetExhausted
- type CircuitBreaker
- type CostEvent
- type CostType
- type InvalidBudget
- type Ledger
- type LoopDetected
- type Option
- func WithFinalizationReserve(fraction float64) Option
- func WithLoopDetection(maxCalls int, windowSeconds float64) Option
- func WithOnHardLimit(fn func(Report)) Option
- func WithOnLoopDetected(fn func(Report)) Option
- func WithOnSoftLimit(fn func(Report)) Option
- func WithSoftLimit(fraction float64) Option
- type Report
- type Session
- func (s *Session) ChildSession(maxSpend float64, opts ...SessionOption) *Session
- func (s *Session) Close()
- func (s *Session) ID() string
- func (s *Session) Remaining() float64
- func (s *Session) Report() Report
- func (s *Session) Spent() float64
- func (s *Session) Track(cost float64, toolName string, metadata ...map[string]any) error
- func (s *Session) WouldExceed(cost float64) bool
- func (s *Session) WrapUsage(model string, inputTokens, outputTokens int64) error
- type SessionOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CalculateCost ¶
CalculateCost returns the cost in USD for the given model and token counts. Returns (cost, true) if the model is found, (0, false) otherwise.
func RegisterModel ¶
RegisterModel adds or overrides pricing for a model at runtime. Prices are in USD per million tokens.
Types ¶
type Budget ¶
type Budget struct {
// contains filtered or unexported fields
}
Budget is the entry point for AgentBudget. Create one per application or agent type, then call NewSession() for each agent run.
func New ¶
New creates a Budget with the given maximum spend. maxSpend may be a float64, int, or a string like "$5.00" or "5.00".
func (*Budget) MaxSpend ¶
MaxSpend returns the total configured budget including any finalization reserve.
func (*Budget) NewSession ¶
func (b *Budget) NewSession(opts ...SessionOption) *Session
NewSession creates a new budget-enforced session. Each call to the agent should use its own session.
type BudgetExhausted ¶
BudgetExhausted is raised when a recorded cost pushes total spend over the budget.
func (*BudgetExhausted) Error ¶
func (e *BudgetExhausted) Error() string
type CircuitBreaker ¶
type CircuitBreaker struct {
// contains filtered or unexported fields
}
CircuitBreaker monitors budget usage (soft limit) and detects runaway loops.
func (*CircuitBreaker) CheckBudget ¶
func (cb *CircuitBreaker) CheckBudget(spent, budget float64) string
CheckBudget returns a warning message if the soft limit is reached (fires once). Returns empty string if not triggered.
func (*CircuitBreaker) CheckLoop ¶
func (cb *CircuitBreaker) CheckLoop(key string) bool
CheckLoop records a call for the given key and returns true if a loop is detected.
type CostEvent ¶
type CostEvent struct {
Cost float64
Type CostType
Timestamp time.Time
Model string // set for LLM events
InputTokens int64 // set for LLM events
OutputTokens int64 // set for LLM events
ToolName string // set for tool events
Metadata map[string]any
}
CostEvent records a single costed action within a session.
type CostType ¶
type CostType string
CostType distinguishes LLM calls from manual tool/API cost entries.
type InvalidBudget ¶
type InvalidBudget struct {
Value string
}
InvalidBudget is raised when the budget value cannot be parsed or is non-positive.
func (*InvalidBudget) Error ¶
func (e *InvalidBudget) Error() string
type Ledger ¶
type Ledger struct {
// contains filtered or unexported fields
}
Ledger is a thread-safe running total of costs within a session.
func (*Ledger) Record ¶
Record adds a cost event. Returns *BudgetExhausted if the budget is exceeded.
func (*Ledger) WouldExceed ¶
WouldExceed returns true if adding cost would exceed the budget, without recording anything.
type LoopDetected ¶
type LoopDetected struct {
Key string
}
LoopDetected is raised when the same model or tool is called too many times within the configured time window.
func (*LoopDetected) Error ¶
func (e *LoopDetected) Error() string
type Option ¶
type Option func(*budgetConfig)
Option configures a Budget.
func WithFinalizationReserve ¶
WithFinalizationReserve reserves a fraction of the budget for a final step. E.g. WithFinalizationReserve(0.05) on a $1.00 budget fires the hard limit at $0.95.
func WithLoopDetection ¶
WithLoopDetection configures loop detection. maxCalls is the max number of repeated calls in window seconds before LoopDetected is raised.
func WithOnHardLimit ¶
WithOnHardLimit sets a callback invoked when BudgetExhausted is raised.
func WithOnLoopDetected ¶
WithOnLoopDetected sets a callback invoked when LoopDetected is raised.
func WithOnSoftLimit ¶
WithOnSoftLimit sets a callback invoked once when spending exceeds the soft limit.
func WithSoftLimit ¶
WithSoftLimit sets the fraction of budget at which the soft-limit callback fires. Default: 0.9 (90%).
type Report ¶
type Report struct {
SessionID string `json:"session_id"`
Budget float64 `json:"budget"`
TotalSpent float64 `json:"total_spent"`
Remaining float64 `json:"remaining"`
Breakdown map[string]any `json:"breakdown"`
DurationSecs *float64 `json:"duration_seconds"`
TerminatedBy string `json:"terminated_by"` // "" | "budget_exhausted" | "loop_detected"
EventCount int `json:"event_count"`
}
Report is the structured cost report returned by Session.Report().
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session tracks costs for a single agent run. Create via Budget.NewSession(). It is safe to use from multiple goroutines.
func (*Session) ChildSession ¶
func (s *Session) ChildSession(maxSpend float64, opts ...SessionOption) *Session
ChildSession creates a sub-session with its own budget cap. When the child is closed via Close(), its total spend is NOT automatically charged to the parent — call parent.Track(child.Spent(), "child:"+child.ID()) after closing if you want rollup.
func (*Session) Close ¶
func (s *Session) Close()
Close marks the session as ended. Safe to call multiple times.
func (*Session) Track ¶
Track manually records a fixed-cost action (tool call, API call, etc.). Returns *BudgetExhausted or *LoopDetected if limits are reached.
func (*Session) WouldExceed ¶
WouldExceed returns true if the given cost would exceed the remaining budget. Does not record anything.
type SessionOption ¶
type SessionOption func(*sessionConfig)
SessionOption configures a Session.
func WithSessionID ¶
func WithSessionID(id string) SessionOption
WithSessionID sets a custom session identifier.