费用看板 Agent(Anthropic SDK 教程)
用 Anthropic SDK 在 SandBase 上构建自监控 Agent:追踪每次 API 调用的 token 用量和成本,产出日/周费用报告,实现预算控制。
结论先行 — 构建一个自监控 Agent:追踪自己的每次 API 调用、实时计算成本、强制预算限制、产出日/周支出报告。使用 Anthropic SDK 在 SandBase 上调 Claude。实现按次计费文章中的预算控制模式。含完整代码。
我们在构建什么
一个 Agent:
- 执行有用任务(回答问题、分析数据)
- 记录每次 API 调用的 token 数和成本
- 按日/周/月预算追踪支出
- 按需或按计划产出费用报告
- 接近预算上限时自动降级
这是”可观测性优先”的 Agent 模式——Agent 理解自己的经济账并做出成本感知的决策。
为什么自监控重要
多数 Agent 对成本是盲的。API 调用发出去、token 累积、月底账单到达。这带来问题:
- 预算惊吓: 一个推理循环中的 Agent 可以在几分钟内烧掉 $50
- 无成本归因: 哪个任务或用户驱动了支出?
- 无自适应行为: Agent 不知道该不该用更便宜的模型
- 无早期预警: 等你注意到时预算已经爆了
自监控 Agent 通过让成本成为决策一等输入来解决这四个问题。
前置要求
pip install anthropic pydantic
需要:
- SandBase API key,有权访问 Claude 模型
- Anthropic SDK(兼容 SandBase 的 Anthropic 兼容端点)
完整 Agent 代码
核心:成本追踪基础设施
"""
费用看板 Agent — Anthropic SDK on SandBase
自监控 Agent,追踪和报告自身 API 支出。
"""
import json
import time
from datetime import datetime
from dataclasses import dataclass
from typing import Optional
from anthropic import Anthropic
# --- 配置 ---
SANDBASE_API_KEY = "your-sandbase-api-key"
DAILY_BUDGET_USD = 5.00
WEEKLY_BUDGET_USD = 25.00
MONTHLY_BUDGET_USD = 80.00
# --- Anthropic 客户端(通过 SandBase) ---
client = Anthropic(
base_url="https://api.sandbase.ai/anthropic",
api_key=SANDBASE_API_KEY,
)
# --- 定价表(每百万 token,2026) ---
PRICING = {
"claude-sonnet-5": {"input": 3.00, "output": 15.00},
"claude-haiku-4": {"input": 0.80, "output": 4.00},
"claude-opus-5": {"input": 15.00, "output": 75.00},
}
# --- 数据模型 ---
@dataclass
class APICallLog:
timestamp: str
model: str
input_tokens: int
output_tokens: int
cache_read_tokens: int = 0
cache_write_tokens: int = 0
cost_usd: float = 0.0
task_label: str = ""
duration_ms: int = 0
@dataclass
class BudgetState:
daily_spent: float = 0.0
weekly_spent: float = 0.0
monthly_spent: float = 0.0
daily_limit: float = 5.00
weekly_limit: float = 25.00
monthly_limit: float = 80.00
total_calls: int = 0
last_reset_daily: str = ""
last_reset_weekly: str = ""
# --- 成本计算器 ---
class CostCalculator:
@staticmethod
def calculate(model: str, input_tokens: int, output_tokens: int,
cache_read_tokens: int = 0, cache_write_tokens: int = 0) -> float:
pricing = PRICING.get(model, PRICING["claude-haiku-4"])
input_cost = (input_tokens / 1_000_000) * pricing["input"]
output_cost = (output_tokens / 1_000_000) * pricing["output"]
# 缓存读取比普通输入便宜 90%
cache_read_cost = (cache_read_tokens / 1_000_000) * pricing["input"] * 0.1
# 缓存写入比普通输入贵 25%
cache_write_cost = (cache_write_tokens / 1_000_000) * pricing["input"] * 1.25
return input_cost + output_cost + cache_read_cost + cache_write_cost
成本感知 Agent 封装
class CostDashboardAgent:
"""追踪自身支出并强制预算的 Agent。"""
def __init__(self, model: str = "claude-sonnet-5"):
self.model = model
self.logs: list[APICallLog] = []
self.budget = BudgetState(
daily_limit=DAILY_BUDGET_USD,
weekly_limit=WEEKLY_BUDGET_USD,
monthly_limit=MONTHLY_BUDGET_USD,
last_reset_daily=datetime.now().strftime("%Y-%m-%d"),
last_reset_weekly=datetime.now().strftime("%Y-%W"),
)
def _check_budget(self) -> tuple[bool, str]:
"""检查是否在预算内。"""
today = datetime.now().strftime("%Y-%m-%d")
week = datetime.now().strftime("%Y-%W")
if self.budget.last_reset_daily != today:
self.budget.daily_spent = 0.0
self.budget.last_reset_daily = today
if self.budget.last_reset_weekly != week:
self.budget.weekly_spent = 0.0
self.budget.last_reset_weekly = week
if self.budget.daily_spent >= self.budget.daily_limit:
return False, f"日预算已用完 (${self.budget.daily_spent:.2f}/${self.budget.daily_limit:.2f})"
if self.budget.weekly_spent >= self.budget.weekly_limit:
return False, f"周预算已用完"
if self.budget.monthly_spent >= self.budget.monthly_limit:
return False, f"月预算已用完"
return True, "OK"
def _select_model(self, task_complexity: str = "normal") -> str:
"""根据预算压力动态选择模型。"""
remaining_daily = self.budget.daily_limit - self.budget.daily_spent
# 日预算用了 > 80%,降级到便宜模型
if remaining_daily < self.budget.daily_limit * 0.2:
print(f" ⚠ 预算压力:切换到 claude-haiku-4")
return "claude-haiku-4"
# 简单任务始终用便宜模型
if task_complexity == "simple":
return "claude-haiku-4"
return self.model
def call(self, messages: list[dict], task_label: str = "",
task_complexity: str = "normal", system: str = None) -> Optional[str]:
"""带成本追踪和预算强制的 API 调用。"""
# 预算检查
within_budget, reason = self._check_budget()
if not within_budget:
print(f" 🛑 已阻止:{reason}")
return None
# 模型选择
model = self._select_model(task_complexity)
# 发起调用
start = time.time()
kwargs = {"model": model, "max_tokens": 1024, "messages": messages}
if system:
kwargs["system"] = system
response = client.messages.create(**kwargs)
duration_ms = int((time.time() - start) * 1000)
# 提取用量
usage = response.usage
input_tokens = usage.input_tokens
output_tokens = usage.output_tokens
cache_read = getattr(usage, 'cache_read_input_tokens', 0) or 0
cache_write = getattr(usage, 'cache_creation_input_tokens', 0) or 0
# 计算成本
cost = CostCalculator.calculate(model, input_tokens, output_tokens, cache_read, cache_write)
# 记录
log = APICallLog(
timestamp=datetime.now().isoformat(),
model=model,
input_tokens=input_tokens,
output_tokens=output_tokens,
cache_read_tokens=cache_read,
cache_write_tokens=cache_write,
cost_usd=cost,
task_label=task_label,
duration_ms=duration_ms,
)
self.logs.append(log)
# 更新预算
self.budget.daily_spent += cost
self.budget.weekly_spent += cost
self.budget.monthly_spent += cost
self.budget.total_calls += 1
# 实时成本输出
print(f" 💰 ${cost:.4f} | {model} | {input_tokens}→{output_tokens} tok | {duration_ms}ms | {task_label}")
return response.content[0].text
def get_daily_report(self) -> dict:
"""生成今日支出报告。"""
today = datetime.now().strftime("%Y-%m-%d")
today_logs = [l for l in self.logs if l.timestamp.startswith(today)]
model_costs = {}
for log in today_logs:
if log.model not in model_costs:
model_costs[log.model] = {"calls": 0, "cost": 0.0, "tokens": 0}
model_costs[log.model]["calls"] += 1
model_costs[log.model]["cost"] += log.cost_usd
model_costs[log.model]["tokens"] += log.input_tokens + log.output_tokens
task_costs = {}
for log in today_logs:
label = log.task_label or "unlabeled"
task_costs[label] = task_costs.get(label, 0) + log.cost_usd
top_tasks = sorted(task_costs.items(), key=lambda x: x[1], reverse=True)[:5]
total_cost = sum(l.cost_usd for l in today_logs)
return {
"period": f"日报 ({today})",
"total_cost": total_cost,
"total_calls": len(today_logs),
"avg_cost_per_call": total_cost / len(today_logs) if today_logs else 0,
"model_breakdown": model_costs,
"top_tasks": [{"task": t, "cost": c} for t, c in top_tasks],
"budget_remaining": {
"daily": self.budget.daily_limit - self.budget.daily_spent,
"weekly": self.budget.weekly_limit - self.budget.weekly_spent,
"monthly": self.budget.monthly_limit - self.budget.monthly_spent,
},
"recommendations": self._generate_recommendations(today_logs),
}
def _generate_recommendations(self, logs: list[APICallLog]) -> list[str]:
"""生成成本优化建议。"""
recs = []
if not logs:
return ["暂无数据,无法生成建议。"]
expensive_calls = [l for l in logs if l.model == "claude-opus-5"]
if expensive_calls:
avg_output = sum(l.output_tokens for l in expensive_calls) / len(expensive_calls)
if avg_output < 200:
recs.append(f"建议对短回复使用 claude-haiku-4。{len(expensive_calls)} 次 Opus 调用平均仅 {avg_output:.0f} 输出 token。")
total_input = sum(l.input_tokens for l in logs)
total_cache_read = sum(l.cache_read_tokens for l in logs)
if total_input > 10000 and total_cache_read < total_input * 0.1:
recs.append("缓存利用率低 (<10%)。对重复系统提示启用 prompt caching 可降低最多 90% 缓存内容成本。")
daily_pct = self.budget.daily_spent / self.budget.daily_limit
if daily_pct > 0.7:
recs.append(f"日预算已使用 {daily_pct*100:.0f}%。考虑推迟非紧急任务或切换到更便宜模型。")
return recs if recs else ["支出在正常范围内。"]
运行 Agent
def demo():
"""演示费用看板 Agent。"""
agent = CostDashboardAgent(model="claude-sonnet-5")
print("🤖 费用看板 Agent — 演示")
print(f" 日预算:${DAILY_BUDGET_USD}")
print(f" 模型:claude-sonnet-5(压力下自动降级)")
print()
# 任务 1:简单问题(应使用便宜模型)
print("📌 任务 1:简单问题")
result = agent.call(
messages=[{"role": "user", "content": "2+2 等于几?"}],
task_label="simple_math",
task_complexity="simple"
)
print(f" → {result}\n")
# 任务 2:复杂分析
print("📌 任务 2:复杂分析")
result = agent.call(
messages=[{"role": "user", "content":
"分析运行 100 个 AI Agent 的成本影响,"
"每个每天 50 次 API 调用,平均 $0.01/次。"
"包含月度预估和优化策略。"}],
task_label="cost_analysis",
task_complexity="normal"
)
print(f" → {result[:100]}...\n")
# 任务 3:缓存系统提示
system_prompt = "你是专注于 API 成本优化的财务分析 AI。始终提供具体金额和百分比。"
print("📌 任务 3:缓存系统提示复用")
for i, question in enumerate([
"GPT-4.1 和 Claude Sonnet 5 的成本差异是什么?",
"Prompt caching 在重复查询上能省多少钱?",
]):
result = agent.call(
messages=[{"role": "user", "content": question}],
system=system_prompt,
task_label=f"financial_q{i+1}",
task_complexity="normal"
)
print(f" → {result[:80]}...\n")
# 生成报告
print("\n" + "="*60)
print("📊 日费用报告")
print("="*60)
report = agent.get_daily_report()
print(f" 周期:{report['period']}")
print(f" 总成本:${report['total_cost']:.4f}")
print(f" 总调用:{report['total_calls']}")
print(f" 平均成本/调用:${report['avg_cost_per_call']:.4f}")
print(f"\n 模型分解:")
for model, stats in report['model_breakdown'].items():
print(f" {model}:{stats['calls']} 次,${stats['cost']:.4f}")
print(f"\n 按成本排序的任务:")
for task in report['top_tasks']:
print(f" {task['task']}:${task['cost']:.4f}")
print(f"\n 剩余预算:")
for period, remaining in report['budget_remaining'].items():
print(f" {period}:${remaining:.2f}")
print(f"\n 建议:")
for rec in report['recommendations']:
print(f" → {rec}")
if __name__ == "__main__":
demo()
预算控制的三个层级
Agent 实现了按次计费文章中的三层成本控制:
层级 1:硬预算限制
if self.budget.daily_spent >= self.budget.daily_limit:
return None # 拒绝发起调用
预算耗尽后 Agent 不再发起 API 调用。防止失控。
层级 2:自适应模型选择
if remaining_daily < self.budget.daily_limit * 0.2:
return "claude-haiku-4" # 比 Sonnet 便宜 4 倍
预算压力增大时自动降级到更便宜模型。质量优雅退化而非完全停止。
层级 3:任务感知路由
if task_complexity == "simple":
return "claude-haiku-4" # 简单任务不浪费昂贵模型
简单问题无论预算状态都路由到便宜模型。最便宜的优化——零实现成本,在简单调用上节省 60-80%。
缓存定价集成
Agent 追踪 Anthropic 缓存定价以展示真实节省:
# 缓存读取:比普通输入便宜 90%
cache_read_cost = (cache_read_tokens / 1_000_000) * pricing["input"] * 0.1
# 缓存写入:首次使用加 25% 溢价
cache_write_cost = (cache_write_tokens / 1_000_000) * pricing["input"] * 1.25
对有重复系统提示的 Agent(大多数生产 Agent),缓存利用可降低输入成本 90%。费用报告暴露此指标让你知道缓存是否生效。
有无自监控的成本对比
| 场景 | 无监控 | 有监控 | 节省 |
|---|---|---|---|
| 正常一周 | $25.00 | $18.00 | 28%(模型路由) |
| 预算飙升(循环 bug) | $150+(月底账单才发现) | $5.00(硬限制触发) | 97% |
| 低流量日 | $15.00(始终用同一模型) | $4.00(自动降级) | 73% |
| 重复查询 | $8.00(无缓存) | $2.40(缓存命中) | 70% |
最大节省来自防止失控支出和自动将简单任务路由到便宜模型。自监控本身的开销(每次追踪调用 ~$0.001)可忽略。
生产部署模式
import schedule
def run_production_agent():
"""带定时报告的生产部署。"""
agent = CostDashboardAgent(model="claude-sonnet-5")
schedule.every().day.at("09:00").do(
lambda: send_report(agent.get_daily_report(), channel="slack")
)
schedule.every().monday.at("09:00").do(
lambda: send_report(generate_weekly_report(agent), channel="email")
)
while True:
schedule.run_pending()
task = get_next_task()
if task:
agent.call(
messages=[{"role": "user", "content": task.prompt}],
task_label=task.label,
task_complexity=task.complexity
)
time.sleep(1)
要点总结
- 成本是 Agent 一等输入。 Agent 应该知道自己花了多少并相应调整行为。
- 三层控制: 硬限制(防灾)、自适应模型(优雅降级)、任务路由(消除浪费)。
- 缓存可见性很重要。 不能测量缓存命中就不能优化。见 Anthropic 缓存定价深度解析。
- 报告闭环。 日/周报告让成本从惊吓变成受管理的指标。
- SandBase 上的 Anthropic SDK 与直连 Anthropic 工作完全一致——相同 SDK、相同模式、相同缓存行为——加上跨所有模型的统一计费。


