Initial commit with translated description
This commit is contained in:
156
scripts/token_tracker.py
Normal file
156
scripts/token_tracker.py
Normal file
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Token usage tracker with budget alerts.
|
||||
Monitors API usage and warns when approaching limits.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
STATE_FILE = Path.home() / ".openclaw/workspace/memory/token-tracker-state.json"
|
||||
|
||||
def load_state():
|
||||
"""Load tracking state from file."""
|
||||
if STATE_FILE.exists():
|
||||
with open(STATE_FILE, 'r') as f:
|
||||
return json.load(f)
|
||||
return {
|
||||
"daily_usage": {},
|
||||
"alerts_sent": [],
|
||||
"last_reset": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
def save_state(state):
|
||||
"""Save tracking state to file."""
|
||||
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(STATE_FILE, 'w') as f:
|
||||
json.dump(state, f, indent=2)
|
||||
|
||||
def get_usage_from_session_status():
|
||||
"""Parse session status to extract token usage.
|
||||
Returns dict with input_tokens, output_tokens, and cost.
|
||||
"""
|
||||
# This would integrate with OpenClaw's session_status tool
|
||||
# For now, returns placeholder structure
|
||||
return {
|
||||
"input_tokens": 0,
|
||||
"output_tokens": 0,
|
||||
"total_cost": 0.0,
|
||||
"model": "anthropic/claude-sonnet-4-5"
|
||||
}
|
||||
|
||||
def check_budget(daily_limit_usd=5.0, warn_threshold=0.8):
|
||||
"""Check if usage is approaching daily budget.
|
||||
|
||||
Args:
|
||||
daily_limit_usd: Daily spending limit in USD
|
||||
warn_threshold: Fraction of limit to trigger warning (default 80%)
|
||||
|
||||
Returns:
|
||||
dict with status, usage, limit, and alert message if applicable
|
||||
"""
|
||||
state = load_state()
|
||||
today = datetime.now().date().isoformat()
|
||||
|
||||
# Reset if new day
|
||||
if today not in state["daily_usage"]:
|
||||
state["daily_usage"] = {today: {"cost": 0.0, "tokens": 0}}
|
||||
state["alerts_sent"] = []
|
||||
|
||||
usage = state["daily_usage"][today]
|
||||
percent_used = (usage["cost"] / daily_limit_usd) * 100
|
||||
|
||||
result = {
|
||||
"date": today,
|
||||
"cost": usage["cost"],
|
||||
"tokens": usage["tokens"],
|
||||
"limit": daily_limit_usd,
|
||||
"percent_used": percent_used,
|
||||
"status": "ok"
|
||||
}
|
||||
|
||||
# Check thresholds
|
||||
if percent_used >= 100:
|
||||
result["status"] = "exceeded"
|
||||
result["alert"] = f"⚠️ Daily budget exceeded! ${usage['cost']:.2f} / ${daily_limit_usd:.2f}"
|
||||
elif percent_used >= (warn_threshold * 100):
|
||||
result["status"] = "warning"
|
||||
result["alert"] = f"⚠️ Approaching daily limit: ${usage['cost']:.2f} / ${daily_limit_usd:.2f} ({percent_used:.0f}%)"
|
||||
|
||||
return result
|
||||
|
||||
def suggest_cheaper_model(current_model, task_type="general"):
|
||||
"""Suggest cheaper alternative models based on task type.
|
||||
|
||||
Args:
|
||||
current_model: Currently configured model
|
||||
task_type: Type of task (general, simple, complex)
|
||||
|
||||
Returns:
|
||||
dict with suggestion and cost savings
|
||||
"""
|
||||
# Cost per 1M tokens (input/output average)
|
||||
model_costs = {
|
||||
"anthropic/claude-opus-4": 15.0,
|
||||
"anthropic/claude-sonnet-4-5": 3.0,
|
||||
"anthropic/claude-haiku-4": 0.25,
|
||||
"google/gemini-2.0-flash-exp": 0.075,
|
||||
"openai/gpt-4o": 2.5,
|
||||
"openai/gpt-4o-mini": 0.15
|
||||
}
|
||||
|
||||
suggestions = {
|
||||
"simple": [
|
||||
("anthropic/claude-haiku-4", "12x cheaper, great for file reads, routine checks"),
|
||||
("google/gemini-2.0-flash-exp", "40x cheaper via OpenRouter, good for simple tasks")
|
||||
],
|
||||
"general": [
|
||||
("anthropic/claude-sonnet-4-5", "Balanced performance and cost"),
|
||||
("google/gemini-2.0-flash-exp", "Much cheaper, decent quality")
|
||||
],
|
||||
"complex": [
|
||||
("anthropic/claude-opus-4", "Best reasoning, use sparingly"),
|
||||
("anthropic/claude-sonnet-4-5", "Good balance for most complex tasks")
|
||||
]
|
||||
}
|
||||
|
||||
return {
|
||||
"current": current_model,
|
||||
"current_cost": model_costs.get(current_model, "unknown"),
|
||||
"suggestions": suggestions.get(task_type, suggestions["general"])
|
||||
}
|
||||
|
||||
def main():
|
||||
"""CLI interface for token tracker."""
|
||||
import sys
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: token_tracker.py [check|suggest|reset]")
|
||||
sys.exit(1)
|
||||
|
||||
command = sys.argv[1]
|
||||
|
||||
if command == "check":
|
||||
result = check_budget()
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
elif command == "suggest":
|
||||
task = sys.argv[2] if len(sys.argv) > 2 else "general"
|
||||
current = sys.argv[3] if len(sys.argv) > 3 else "anthropic/claude-sonnet-4-5"
|
||||
result = suggest_cheaper_model(current, task)
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
elif command == "reset":
|
||||
state = load_state()
|
||||
state["daily_usage"] = {}
|
||||
state["alerts_sent"] = []
|
||||
save_state(state)
|
||||
print("Token tracker state reset.")
|
||||
|
||||
else:
|
||||
print(f"Unknown command: {command}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user