feat: add /usage command to show model info and token usage
Adds token usage tracking to AgentInstance using atomic counters (TotalPromptTokens, TotalCompletionTokens, TotalRequests) that accumulate after each LLM call. The /usage command displays current model name, max tokens, temperature, and session token statistics. Available in all interactive modes like /help. https://claude.ai/code/session_01PoHBfe7eKmhHvVF12W3xZ2
This commit is contained in:
parent
a961328127
commit
1ddec1673c
3 changed files with 79 additions and 2 deletions
|
|
@ -109,6 +109,7 @@ func agentCmd() {
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s Interactive mode (Ctrl+C to exit)\n", logo)
|
fmt.Printf("%s Interactive mode (Ctrl+C to exit)\n", logo)
|
||||||
fmt.Println(" /help - show detailed help")
|
fmt.Println(" /help - show detailed help")
|
||||||
|
fmt.Println(" /usage - show model info and token usage")
|
||||||
fmt.Println(" /cmd - switch to command mode")
|
fmt.Println(" /cmd - switch to command mode")
|
||||||
fmt.Println(" /pico - switch to chat mode")
|
fmt.Println(" /pico - switch to chat mode")
|
||||||
fmt.Println(" /hipico - AI assistance in command mode")
|
fmt.Println(" /hipico - AI assistance in command mode")
|
||||||
|
|
@ -163,11 +164,15 @@ func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// /help works in all modes
|
// /help and /usage work in all modes
|
||||||
if input == "/help" {
|
if input == "/help" {
|
||||||
printHelp()
|
printHelp()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if input == "/usage" {
|
||||||
|
printUsage(agentLoop)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case modePico:
|
case modePico:
|
||||||
|
|
@ -284,11 +289,15 @@ func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// /help works in all modes
|
// /help and /usage work in all modes
|
||||||
if input == "/help" {
|
if input == "/help" {
|
||||||
printHelp()
|
printHelp()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if input == "/usage" {
|
||||||
|
printUsage(agentLoop)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case modePico:
|
case modePico:
|
||||||
|
|
@ -383,6 +392,7 @@ PicoClaw has three interactive modes:
|
||||||
|
|
||||||
Commands (available in all modes):
|
Commands (available in all modes):
|
||||||
/help Show this help message
|
/help Show this help message
|
||||||
|
/usage Show model info and token usage
|
||||||
exit Exit PicoClaw
|
exit Exit PicoClaw
|
||||||
quit Exit PicoClaw
|
quit Exit PicoClaw
|
||||||
Ctrl+C Exit PicoClaw
|
Ctrl+C Exit PicoClaw
|
||||||
|
|
@ -410,6 +420,35 @@ Examples:
|
||||||
`, logo, logo, logo, logo)
|
`, logo, logo, logo, logo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// printUsage displays current model information and accumulated token usage.
|
||||||
|
func printUsage(agentLoop *agent.AgentLoop) {
|
||||||
|
info := agentLoop.GetUsageInfo()
|
||||||
|
if info == nil {
|
||||||
|
fmt.Println("No usage information available.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf(`%s Usage
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Model: %s
|
||||||
|
Max tokens: %d
|
||||||
|
Temperature: %.1f
|
||||||
|
|
||||||
|
Token usage (this session):
|
||||||
|
Prompt tokens: %d
|
||||||
|
Completion tokens:%d
|
||||||
|
Total tokens: %d
|
||||||
|
Requests: %d
|
||||||
|
`, logo,
|
||||||
|
info["model"],
|
||||||
|
info["max_tokens"],
|
||||||
|
info["temperature"],
|
||||||
|
info["prompt_tokens"],
|
||||||
|
info["completion_tokens"],
|
||||||
|
info["total_tokens"],
|
||||||
|
info["requests"],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// executeShellCommand runs a shell command in the current working directory
|
// executeShellCommand runs a shell command in the current working directory
|
||||||
// and prints the output. It also handles the cd command to change directories.
|
// and prints the output. It also handles the cd command to change directories.
|
||||||
func executeShellCommand(input string) {
|
func executeShellCommand(input string) {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
"github.com/sipeed/picoclaw/pkg/providers"
|
"github.com/sipeed/picoclaw/pkg/providers"
|
||||||
|
|
@ -31,6 +32,21 @@ type AgentInstance struct {
|
||||||
Subagents *config.SubagentsConfig
|
Subagents *config.SubagentsConfig
|
||||||
SkillsFilter []string
|
SkillsFilter []string
|
||||||
Candidates []providers.FallbackCandidate
|
Candidates []providers.FallbackCandidate
|
||||||
|
|
||||||
|
// Accumulated token usage counters (atomic for concurrent safety)
|
||||||
|
TotalPromptTokens atomic.Int64
|
||||||
|
TotalCompletionTokens atomic.Int64
|
||||||
|
TotalRequests atomic.Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUsage accumulates token usage from a single LLM response.
|
||||||
|
func (a *AgentInstance) AddUsage(usage *providers.UsageInfo) {
|
||||||
|
if usage == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.TotalPromptTokens.Add(int64(usage.PromptTokens))
|
||||||
|
a.TotalCompletionTokens.Add(int64(usage.CompletionTokens))
|
||||||
|
a.TotalRequests.Add(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAgentInstance creates an agent instance from config.
|
// NewAgentInstance creates an agent instance from config.
|
||||||
|
|
|
||||||
|
|
@ -594,6 +594,9 @@ func (al *AgentLoop) runLLMIteration(
|
||||||
return "", iteration, fmt.Errorf("LLM call failed after retries: %w", err)
|
return "", iteration, fmt.Errorf("LLM call failed after retries: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Accumulate token usage
|
||||||
|
agent.AddUsage(response.Usage)
|
||||||
|
|
||||||
// Check if no tool calls - we're done
|
// Check if no tool calls - we're done
|
||||||
if len(response.ToolCalls) == 0 {
|
if len(response.ToolCalls) == 0 {
|
||||||
finalContent = response.Content
|
finalContent = response.Content
|
||||||
|
|
@ -852,6 +855,25 @@ func (al *AgentLoop) GetStartupInfo() map[string]any {
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUsageInfo returns accumulated token usage for the default agent.
|
||||||
|
func (al *AgentLoop) GetUsageInfo() map[string]any {
|
||||||
|
agent := al.registry.GetDefaultAgent()
|
||||||
|
if agent == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
promptTokens := agent.TotalPromptTokens.Load()
|
||||||
|
completionTokens := agent.TotalCompletionTokens.Load()
|
||||||
|
return map[string]any{
|
||||||
|
"model": agent.Model,
|
||||||
|
"max_tokens": agent.MaxTokens,
|
||||||
|
"temperature": agent.Temperature,
|
||||||
|
"prompt_tokens": promptTokens,
|
||||||
|
"completion_tokens": completionTokens,
|
||||||
|
"total_tokens": promptTokens + completionTokens,
|
||||||
|
"requests": agent.TotalRequests.Load(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// formatMessagesForLog formats messages for logging
|
// formatMessagesForLog formats messages for logging
|
||||||
func formatMessagesForLog(messages []providers.Message) string {
|
func formatMessagesForLog(messages []providers.Message) string {
|
||||||
if len(messages) == 0 {
|
if len(messages) == 0 {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue