accountant is the context accountant: a Go library that keeps the books for a context optimizer. It says how many tokens each change to an LLM request saved or caused, for each billing tier.
A tool such as a context optimizer edits a request before it goes to the provider. It may shorten a tool output, remove unused tool declarations, or replace old messages with a summary. accountant answers one question for each change:
If this change had never been made, how would the provider have billed these tokens on this request?
The answer depends on the provider's prompt cache. A change inside a cached prefix can break the cache and cost more than it saves. accountant keeps that state for each conversation thread and prices every change in tokens for the input, cache write (5-minute and 1-hour) and cache read tiers. The host turns tokens into money with its own rates.
The library is stateful, and Compute is deterministic. For each request:
Trackers.BeginRequesttakes the body the client sent.Request.Addrecords each change an operator reports (rewrite,spend,setting).Request.EndRequesttakes the body that was forwarded and the cache facts.Request.Computetakes the billedUsageand returns aResult.
ts := accountant.NewTrackers()
r := ts.BeginRequest(accountant.RequestMeta{RequestID: "r2", Session: "s1", At: now}, client, clientBreakpoints)
r.Add(accountant.Operation{Kind: accountant.KindRewrite, ChangeID: "mask:1", At: at,
SizeBefore: accountant.Size{Bytes: 8000}, SizeAfter: accountant.Size{Bytes: 1000}})
r.EndRequest(accountant.Thread{ID: "main"}, forwarded, forwardedBreakpoints, facts)
res := r.Compute(accountant.Usage{CacheRead: 4000, CacheWrite: 1000})
// res.Ops[0].Saved is a accountant.TierTokensexample_test.go is a complete program that runs under go test.
A change is reported once, on its first request. Compute applies it again on later requests until it
ends, so one thread's result depends on the requests before it. Results fail open: a failed check goes
into Result.Checks and is logged as a warning, and pricing goes on.
The module imports only the standard library, so any host can use it. boundary_test.go enforces that.
| Path | What it holds |
|---|---|
accountant.go |
The whole public API: type aliases, constants and four functions |
internal/engine/ |
The implementation and its unit tests. Not importable by hosts |
test/cases/ |
The JSON cases and the loader (import "github.com/rossoctl/accountant/test/cases") |
test/gen/ |
The Python generator that writes the cases |
docs/ |
Specification, design, examples |
| Document | What it holds |
|---|---|
| docs/spec.md | The specification: words, requirements (R1 and on), scope |
| docs/design.md | The design: types, Compute, cache effects, checks |
| docs/testing.md | How the library is tested, and the exhaustive test-case list |
| docs/examples.md | Eight worked examples with figures |
| docs/more_examples.md | The plan for scenarios beyond the first eight, and which are written as cases |
| docs/issue_cases.md | Test cases drawn from issues, and which are written as cases |
| docs/cached-prefix.md | How providers decide what a cache read covers |
| docs/cortex-wiring.md | How Cortex and context-guru plug it in |
test/ is the list of cases an implementation must meet: JSON files, each a sequence
of requests with the expected Result. A host can play them through its own pricing code and compare.
The cases are generated by test/gen/gen_cases.py (go generate ./test/cases), which computes the body
hashes.
go vet ./...
go test ./...Pre-release. The API follows the design in docs/design.md and may change before the first tag.
Not implemented yet: Result.Interaction and the order check. Each is marked "Not implemented in v0" in
the design. The Trackers hold about 10,000 threads and drop the oldest above that. A dropped thread loses
its active changes (design §6.1, Memory).
Apache 2.0. See LICENSE.