feat: 添加 tokens 统计和耗时显示功能
- 添加进程级别累计 CompletionTokens 统计 - 显示此次消耗 tokens 和耗时 - 显示累计 tokens(hxclaw 进程级别) - 使用 lipgloss 样式(icon #ffcc80, text #5c7a9a) - 更新 AGENTS.md 构建说明
This commit is contained in:
@@ -3,8 +3,12 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
|
||||
"github.com/hxclaw/hxclaw/cmd/hxclaw/internal"
|
||||
"github.com/muesli/termenv"
|
||||
@@ -14,6 +18,8 @@ import (
|
||||
"github.com/sipeed/picoclaw/pkg/providers"
|
||||
)
|
||||
|
||||
var totalCompletionTokens int
|
||||
|
||||
const Logo = "🦐"
|
||||
|
||||
func main() {
|
||||
@@ -122,6 +128,8 @@ func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
||||
|
||||
// runWithStreaming 尝试使用流式输出,如果 Provider 不支持则回退到普通模式
|
||||
func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string) {
|
||||
startTime := time.Now()
|
||||
|
||||
agentInstance := agentLoop.GetRegistry().GetDefaultAgent()
|
||||
if agentInstance == nil {
|
||||
fmt.Println("错误:无法获取 Agent 实例")
|
||||
@@ -160,7 +168,7 @@ func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string) {
|
||||
var result strings.Builder
|
||||
var printedLen int
|
||||
firstToken := true
|
||||
_, err := sp.ChatStream(ctx, messages, toolDefs, agentInstance.Model, nil, func(accumulated string) {
|
||||
resp, err := sp.ChatStream(ctx, messages, toolDefs, agentInstance.Model, nil, func(accumulated string) {
|
||||
if firstToken && len(accumulated) > 0 {
|
||||
spinner.Stop()
|
||||
firstToken = false
|
||||
@@ -175,6 +183,7 @@ func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string) {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
spinner.Stop()
|
||||
fmt.Printf("流式调用错误: %v\n", err)
|
||||
return
|
||||
}
|
||||
@@ -183,7 +192,6 @@ func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string) {
|
||||
allOutput := result.String()
|
||||
rendered := internal.RenderMarkdown(allOutput)
|
||||
if rendered != allOutput && rendered != "" {
|
||||
// 计算流式输出的行数,清除
|
||||
lines := strings.Count(allOutput, "\n") + 1
|
||||
output := termenv.DefaultOutput()
|
||||
output.CursorUp(1)
|
||||
@@ -197,6 +205,9 @@ func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string) {
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
elapsed := time.Since(startTime)
|
||||
printStats(resp, elapsed)
|
||||
|
||||
agentInstance.Sessions.AddMessage(sessionKey, "user", input)
|
||||
agentInstance.Sessions.AddMessage(sessionKey, "assistant", allOutput)
|
||||
}
|
||||
@@ -214,3 +225,45 @@ func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
iconStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ffcc80"))
|
||||
textStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#5c7a9a"))
|
||||
)
|
||||
|
||||
func printStats(resp *providers.LLMResponse, elapsed time.Duration) {
|
||||
if resp == nil || resp.Usage == nil {
|
||||
return
|
||||
}
|
||||
|
||||
completionTokens := resp.Usage.CompletionTokens
|
||||
if completionTokens <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
totalCompletionTokens += completionTokens
|
||||
|
||||
elapsedSec := math.Round(elapsed.Seconds()*10) / 10
|
||||
|
||||
thisTokens := formatTokens(completionTokens)
|
||||
totalTokens := formatTokens(totalCompletionTokens)
|
||||
elapsedStr := formatDuration(elapsedSec)
|
||||
|
||||
icon := iconStyle.Render("▣ ")
|
||||
text := textStyle.Render(fmt.Sprintf("Tokens: %s · 耗时: %s · 总Tokens: %s", thisTokens, elapsedStr, totalTokens))
|
||||
fmt.Printf(" %s%s\n\n", icon, text)
|
||||
}
|
||||
|
||||
func formatTokens(n int) string {
|
||||
if n >= 1000 {
|
||||
return fmt.Sprintf("%.1fk", float64(n)/1000)
|
||||
}
|
||||
return fmt.Sprintf("%d", n)
|
||||
}
|
||||
|
||||
func formatDuration(s float64) string {
|
||||
if s >= 60 {
|
||||
return fmt.Sprintf("%.1fm", s/60)
|
||||
}
|
||||
return fmt.Sprintf("%.1fs", s)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user