feat(observability): LLM token-usage recorder interface (issue #168)#197
Merged
Conversation
Rescues v168-Commit (PR #188) by supplying the missing Recorder interface, NopRecorder, SessionIDFromContext, and the SourceAdHoc constant. The v168-Commit referenced these symbols without defining them, blocking the build. What ships: - Recorder interface in internal/llm/recorder.go with NopRecorder default + Source enum (AdHoc/Chat/Agent) - WithSessionID / SessionIDFromContext / DefaultSessionID for grouping usage by session in dashboards - randomSessionID() generates a stable 64-bit hex id per process so CLI invocations are grouped into a session even without explicit WithSessionID calls - 9 tests covering the interface, concurrency, error reporting, type-mismatch resilience, and id format - internal/usage/ storage package (Store, Record, RecordFromChatUsage, Aggregate group_by day|month| model|source|session, Tail, Count, SetPricing) - DefaultPricing for NIM / Anthropic / OpenAI / Fireworks - internal/summary/summary.go updated to surface token usage in the deterministic auto-summary builder - sin-code tokens CLI: show / tail / aggregate subcmds - docs/TOKEN-TRACKING.md spec (event schema, source taxonomy, aggregation semantics, dashboard queries) Hard mandates honored: - M2: pure-Go, no CGO, modernc/sqlite for storage - M3: usage write failures are logged, never break the LLM call (token accounting is best-effort) - M4: token writes go through the standard error path; no special permissions required - M7: race-clean (-race passes for llm, usage, summary) Build status: go build ./... clean. Race: 9/9 + usage + summary tests all green. Closes: #168 Refs: PR #188 (rescued via rebase on this commit)
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🏆 CEO Audit — A+ (100.0/100)
📥 Download full report (Markdown)
|
🏆 CEO Audit — A+ (100.0/100)
📥 Download full report (Markdown) Run ID:
|
| if path == "" { | ||
| path = DefaultPath() | ||
| } | ||
| if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { |
Comment on lines
+285
to
+296
| topSQL := ` | ||
| SELECT | ||
| COALESCE(SUM(input_tokens), 0), | ||
| COALESCE(SUM(output_tokens), 0), | ||
| COALESCE(SUM(total_tokens), 0), | ||
| COALESCE(SUM(cost_usd), 0), | ||
| COUNT(*), | ||
| COALESCE(MIN(created_at), ''), | ||
| COALESCE(MAX(created_at), ''), | ||
| COUNT(DISTINCT session_id) | ||
| FROM usage_events | ||
| ` + where |
| } | ||
|
|
||
| func scanBreakdowns(ctx context.Context, db *sql.DB, f Filter, where string, args []any, top *Aggregation) error { | ||
| rows, err := db.QueryContext(ctx, `SELECT model, SUM(total_tokens) FROM usage_events `+where+` GROUP BY model`, args...) |
| return err | ||
| } | ||
|
|
||
| rows, err = db.QueryContext(ctx, `SELECT source, SUM(total_tokens) FROM usage_events `+where+` GROUP BY source`, args...) |
Comment on lines
+430
to
+432
| rows, err := s.db.QueryContext(ctx, ` | ||
| SELECT id, session_id, model, source, input_tokens, output_tokens, total_tokens, cost_usd, created_at | ||
| FROM usage_events `+where+` ORDER BY created_at DESC, id DESC LIMIT ?`, |
| // table-wide COUNT — no large scan). | ||
| func (s *Store) Count(ctx context.Context, f Filter) (int, error) { | ||
| where, args := buildWhere(f) | ||
| row := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM usage_events `+where, args...) |
| if p == "" { | ||
| continue | ||
| } | ||
| data, err := os.ReadFile(p) |
Mechanical whitespace alignment, no semantic change. Kept the gofmt -s style consistent with the rest of the package (spaces in single-line struct fields).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rescues PR #188 by supplying the missing Recorder interface, NopRecorder, SessionIDFromContext, SourceAdHoc constant. The v168-Commit referenced these symbols without defining them, blocking the build at
cmd/sin-code/internal/llm/provider.go:66,85,141,142.What's new
Recorderinterface ininternal/llm/recorder.gowithNopRecorderdefault + Source enum (AdHoc/Chat/Agent)WithSessionID/SessionIDFromContext/DefaultSessionIDfor grouping usage by sessionrandomSessionID()generates a stable 64-bit hex id per processinternal/usage/storage package (Store, Record, RecordFromChatUsage, Aggregate, Tail, Count, SetPricing)DefaultPricingfor NIM / Anthropic / OpenAI / Fireworksinternal/summary/summary.goupdated to surface token usage in the deterministic auto-summary buildersin-code tokensCLI: show / tail / aggregate subcommandsdocs/TOKEN-TRACKING.mdspec (event schema, source taxonomy, aggregation semantics, dashboard queries)Hard mandates honored
modernc.org/sqlitefor storagego test -race -count=1passes forllm,usage,summaryBuild status
go build ./...clean. Race: 9/9 + usage + summary tests all green.Diffstat
Closes