mirror of
https://github.com/alibaba/open-code-review.git
synced 2026-07-17 13:19:16 +00:00
feat: token used 展示拆分成 input 和 output
This commit is contained in:
parent
9589cb682e
commit
1c76de02d4
4 changed files with 80 additions and 10 deletions
|
|
@ -122,7 +122,7 @@ func runReview(args []string) error {
|
|||
if len(comments) > 0 {
|
||||
telemetry.RecordCommentsGenerated(ctx, int64(len(comments)))
|
||||
}
|
||||
telemetry.PrintTraceSummary(ag.FilesReviewed(), int64(len(comments)), ag.TotalTokensUsed(), duration)
|
||||
telemetry.PrintTraceSummary(ag.FilesReviewed(), int64(len(comments)), ag.TotalInputTokens(), ag.TotalOutputTokens(), ag.TotalTokensUsed(), duration)
|
||||
|
||||
if opts.outputFormat == "json" {
|
||||
return outputJSON(comments)
|
||||
|
|
|
|||
|
|
@ -94,7 +94,9 @@ type Agent struct {
|
|||
totalDeletions int64
|
||||
currentDate string
|
||||
session *session.SessionHistory
|
||||
totalTokensUsed int64 // accumulated tokens from all LLM calls, accessed atomically
|
||||
totalTokensUsed int64 // accumulated total tokens from all LLM calls, accessed atomically
|
||||
totalInputTokens int64 // accumulated input/prompt tokens, accessed atomically
|
||||
totalOutputTokens int64 // accumulated completion tokens, accessed atomically
|
||||
}
|
||||
|
||||
// CommentWorkerPool manages a fixed-size pool of workers dedicated to
|
||||
|
|
@ -225,6 +227,16 @@ func (a *Agent) TotalTokensUsed() int64 {
|
|||
return atomic.LoadInt64(&a.totalTokensUsed)
|
||||
}
|
||||
|
||||
// TotalInputTokens returns the accumulated input/prompt tokens from all LLM calls.
|
||||
func (a *Agent) TotalInputTokens() int64 {
|
||||
return atomic.LoadInt64(&a.totalInputTokens)
|
||||
}
|
||||
|
||||
// TotalOutputTokens returns the accumulated completion tokens from all LLM calls.
|
||||
func (a *Agent) TotalOutputTokens() int64 {
|
||||
return atomic.LoadInt64(&a.totalOutputTokens)
|
||||
}
|
||||
|
||||
// loadDiffs populates the diff-related fields.
|
||||
func (a *Agent) loadDiffs() error {
|
||||
var provider *diff.Provider
|
||||
|
|
@ -460,6 +472,8 @@ func (a *Agent) executePlanPhase(ctx context.Context, newPath, rawDiff, changeFi
|
|||
rec.SetResponse(resp, time.Since(startTime))
|
||||
if resp.Usage != nil {
|
||||
atomic.AddInt64(&a.totalTokensUsed, int64(resp.Usage.TotalTokens))
|
||||
atomic.AddInt64(&a.totalInputTokens, int64(resp.Usage.PromptTokens+resp.Usage.CacheReadTokens))
|
||||
atomic.AddInt64(&a.totalOutputTokens, int64(resp.Usage.CompletionTokens+resp.Usage.CacheWriteTokens))
|
||||
}
|
||||
fmt.Printf("[ocr] Plan completed for %s\n", newPath)
|
||||
return resp.Content(), nil
|
||||
|
|
@ -538,6 +552,8 @@ func (a *Agent) performLlmCodeReview(ctx context.Context, messages []llm.Message
|
|||
totalTokens := int64(0)
|
||||
if resp.Usage != nil {
|
||||
totalTokens = resp.Usage.TotalTokens
|
||||
atomic.AddInt64(&a.totalInputTokens, int64(resp.Usage.PromptTokens+resp.Usage.CacheReadTokens))
|
||||
atomic.AddInt64(&a.totalOutputTokens, int64(resp.Usage.CompletionTokens+resp.Usage.CacheWriteTokens))
|
||||
}
|
||||
telemetry.RecordLLMRequest(ctx, a.args.Model, duration, totalTokens, "ok")
|
||||
atomic.AddInt64(&a.totalTokensUsed, totalTokens)
|
||||
|
|
@ -764,6 +780,8 @@ func (a *Agent) compressAndRecord(msgs []llm.Message, filePath string) []llm.Mes
|
|||
rec.SetResponse(resp, duration)
|
||||
if resp.Usage != nil {
|
||||
atomic.AddInt64(&a.totalTokensUsed, int64(resp.Usage.TotalTokens))
|
||||
atomic.AddInt64(&a.totalInputTokens, int64(resp.Usage.PromptTokens+resp.Usage.CacheReadTokens))
|
||||
atomic.AddInt64(&a.totalOutputTokens, int64(resp.Usage.CompletionTokens+resp.Usage.CacheWriteTokens))
|
||||
}
|
||||
|
||||
summary := resp.Content()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,35 @@ import (
|
|||
|
||||
// UsageInfo holds token usage extracted from an LLM API response.
|
||||
type UsageInfo struct {
|
||||
TotalTokens int64 `json:"total_tokens"`
|
||||
TotalTokens int64 `json:"total_tokens"`
|
||||
PromptTokens int64 `json:"prompt_tokens"`
|
||||
CompletionTokens int64 `json:"completion_tokens"`
|
||||
CacheReadTokens int64 `json:"cache_read_tokens,omitempty"`
|
||||
CacheWriteTokens int64 `json:"cache_write_tokens,omitempty"`
|
||||
}
|
||||
|
||||
var promptTokensPaths = []string{
|
||||
"usage.prompt_tokens", // OpenAI standard
|
||||
"prompt_tokens", // flat at root
|
||||
"data.usage.prompt_tokens", // wrapped in data layer
|
||||
}
|
||||
|
||||
var completionTokensPaths = []string{
|
||||
"usage.completion_tokens", // OpenAI standard
|
||||
"completion_tokens", // flat at root
|
||||
"data.usage.completion_tokens", // wrapped in data layer
|
||||
}
|
||||
|
||||
var cacheReadTokensPaths = []string{
|
||||
"usage.cache_read_input_tokens", // Anthropic
|
||||
"cache_read_input_tokens", // flat at root
|
||||
"usage.prompt_tokens_details.cache_tokens_hit", // some providers
|
||||
"usage.prompt_tokens_details.cache_tokens", // some providers
|
||||
}
|
||||
|
||||
var cacheWriteTokensPaths = []string{
|
||||
"usage.cache_creation_input_tokens", // Anthropic / proxy
|
||||
"cache_creation_input_tokens", // flat at root
|
||||
}
|
||||
|
||||
// totalTokensPaths is an ordered list of JSON paths to try when extracting
|
||||
|
|
@ -20,18 +48,37 @@ var totalTokensPaths = []string{
|
|||
}
|
||||
|
||||
// resolveUsage parses raw JSON bytes into a map and extracts token usage
|
||||
// by probing totalTokensPaths sequentially. Returns nil if no path matches.
|
||||
// by probing configured paths sequentially. Returns nil if no total_tokens found.
|
||||
func resolveUsage(raw []byte) *UsageInfo {
|
||||
var rawBody map[string]any
|
||||
if err := json.Unmarshal(raw, &rawBody); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
total, ok := probePath(rawBody, totalTokensPaths)
|
||||
if !ok {
|
||||
total, hasAny := probePath(rawBody, totalTokensPaths)
|
||||
prompt, _ := probePath(rawBody, promptTokensPaths)
|
||||
completion, _ := probePath(rawBody, completionTokensPaths)
|
||||
cacheRead, _ := probePath(rawBody, cacheReadTokensPaths)
|
||||
cacheWrite, _ := probePath(rawBody, cacheWriteTokensPaths)
|
||||
|
||||
if !hasAny && prompt == 0 && completion == 0 {
|
||||
return nil
|
||||
}
|
||||
return &UsageInfo{TotalTokens: total}
|
||||
|
||||
ui := &UsageInfo{
|
||||
TotalTokens: total,
|
||||
PromptTokens: prompt,
|
||||
CompletionTokens: completion,
|
||||
CacheReadTokens: cacheRead,
|
||||
CacheWriteTokens: cacheWrite,
|
||||
}
|
||||
|
||||
// If TotalTokens wasn't explicitly available but we have prompt+completion, compute it.
|
||||
if total == 0 && (prompt > 0 || completion > 0) {
|
||||
ui.TotalTokens = prompt + completion + cacheRead + cacheWrite
|
||||
}
|
||||
|
||||
return ui
|
||||
}
|
||||
|
||||
// probePath walks through each candidate path in order, returning the first
|
||||
|
|
|
|||
|
|
@ -60,9 +60,14 @@ func FormatDuration(dur time.Duration) string {
|
|||
}
|
||||
|
||||
// PrintTraceSummary prints a one-line summary of the review to stdout.
|
||||
func PrintTraceSummary(filesReviewed, commentsGenerated int64, totalTokens int64, duration time.Duration) {
|
||||
fmt.Printf("[ocr] Summary: %d file(s) reviewed, %d comment(s), ~%d token(s) used, %s elapsed\n",
|
||||
filesReviewed, commentsGenerated, totalTokens, FormatDuration(duration))
|
||||
func PrintTraceSummary(filesReviewed, commentsGenerated int64, inputTokens, outputTokens, totalTokens int64, duration time.Duration) {
|
||||
if inputTokens > 0 || outputTokens > 0 {
|
||||
fmt.Printf("[ocr] Summary: %d file(s) reviewed, %d comment(s), ~%d token(s) used (input: ~%d, output: ~%d), %s elapsed\n",
|
||||
filesReviewed, commentsGenerated, totalTokens, inputTokens, outputTokens, FormatDuration(duration))
|
||||
} else {
|
||||
fmt.Printf("[ocr] Summary: %d file(s) reviewed, %d comment(s), ~%d token(s) used, %s elapsed\n",
|
||||
filesReviewed, commentsGenerated, totalTokens, FormatDuration(duration))
|
||||
}
|
||||
}
|
||||
|
||||
// PrintToolCallStarted prints a line when a tool begins execution.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue