Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

85 lines
3.1 KiB
Go

package review
import (
"github.com/zzet/gortex/internal/llm"
"github.com/zzet/gortex/internal/llm/registry"
"github.com/zzet/gortex/internal/tokens"
)
// CostBreakdown is the per-review token + cost accounting surfaced on a
// ReviewReport. It carries the token split a provider reported (input /
// output / prompt-cache read / prompt-cache write), the wall-clock cost
// of the LLM phase, and a USD estimate derived from the active
// provider's pricing. Estimated is false when no usage was available
// (a subprocess provider, or a provider whose usage block is not
// decoded) — the cost block is still emitted, just zero.
type CostBreakdown struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
CacheReadTokens int `json:"cache_read_tokens"`
CacheWriteTokens int `json:"cache_write_tokens"`
USD float64 `json:"usd"`
Estimated bool `json:"estimated"`
ElapsedMs int64 `json:"elapsed_ms"`
}
// CostFromUsage builds a CostBreakdown from a provider's token usage, the
// USD-per-1M-token pricing, and the elapsed LLM time. The price carries
// the input / output rates; CostFromUsage uses registry.EstimateCost so
// the USD math is never re-implemented here. Estimated is true whenever
// any token was reported — a non-zero usage means the number is grounded
// in a real accounting rather than a heuristic.
func CostFromUsage(usage llm.TokenUsage, price llm.ProviderPricing, elapsedMs int64) CostBreakdown {
cb := CostBreakdown{
InputTokens: usage.InputTokens,
OutputTokens: usage.OutputTokens,
CacheReadTokens: usage.CacheReadTokens,
CacheWriteTokens: usage.CacheWriteTokens,
ElapsedMs: elapsedMs,
Estimated: !usage.IsZero(),
}
cb.USD = registry.EstimateCost(
llm.CustomProvider{Pricing: price},
int64(usage.InputTokens),
int64(usage.OutputTokens),
)
return cb
}
// AttributeFindingTokens distributes the run's output tokens across the
// findings in proportion to each finding's body+message token weight,
// writing the share onto Finding.GenTokens. The deterministic rulepack
// findings carry no generated text, so weight falls naturally on the LLM
// findings; a finding with no body/message gets a minimum weight of one
// so the total is conserved. Rounding remainders are folded into the
// last finding so the per-finding shares sum to the total output tokens.
func AttributeFindingTokens(findings []Finding, total llm.TokenUsage) {
if len(findings) == 0 || total.OutputTokens <= 0 {
return
}
weights := make([]int, len(findings))
sum := 0
for i := range findings {
w := tokens.Count(findings[i].Body) + tokens.Count(findings[i].Message)
if w <= 0 {
w = 1
}
weights[i] = w
sum += w
}
if sum <= 0 {
return
}
assigned := 0
for i := range findings {
share := total.OutputTokens * weights[i] / sum
findings[i].GenTokens = share
assigned += share
}
// Fold the rounding remainder into the last finding so the shares
// sum exactly to the output-token total.
if rem := total.OutputTokens - assigned; rem != 0 {
findings[len(findings)-1].GenTokens += rem
}
}