【Claude】成本控制与用量监控实战 — 已解决
适用版本:Claude Code v1.0.x 及以上
受影响场景:API 费用管理、Token 消耗优化、团队用量追踪、预算控制
阅读时长:约 25 分钟
目录
- 问题现象
- 原理深挖:Token 计费模型
- 根因分析:成本失控的六大根因
- 多方案解决:从监控到优化
- 验证回归:成本控制验证
- 避坑最佳实践
- 附录:成本速查表
1. 问题现象
1.1 典型问题表现
问题一:API 费用突然飙升
# Anthropic Console 月账单 # 1月: $50 → 正常使用 # 2月: $500 → 10 倍增长! # 不知道哪里的消耗暴增问题二:单个会话消耗大量 Token
# 一个简单的代码审查任务 > 审查 src/auth.py # Claude Code 读取了整个代码库 (50个文件) # 消耗 200K input tokens # 单次操作成本 $0.60问题三:团队成员用量不透明
# 5人团队共享一个 API Key # 月账单 $300 # 不知道谁用了多少 # 无法按人/项目分摊成本问题四:缓存未命中导致重复消耗
# 每次对话都重新发送完整上下文 # 没有使用 prompt caching # 相同的系统提示和上下文被重复计费问题五:长会话 Token 累积
# 长时间会话 (50+ 轮) # 早期对话不断被重新发送 # 每轮的 input tokens 递增 # 后期单轮消耗 100K+ tokens2. 原理深挖:Token 计费模型
2.1 定价结构
┌──────────────────────────────────────────────────────────┐ │ Anthropic API 定价 (每百万 Token) │ ├──────────────────────────────────────────────────────────┤ │ │ │ 模型 Input Output Cache Read Cache Write │ │ ─────────────────────────────────────────────────────── │ │ Claude Opus 4 $15 $75 $1.50 $18.75 │ │ Claude Sonnet 4 $3 $15 $0.30 $3.75 │ │ Claude Haiku 4 $0.25 $1.25 $0.025 $0.3125 │ │ │ │ 计费规则: │ │ - Input: 用户消息 + 系统提示 + 历史对话 + 工具结果 │ │ - Output: Claude 生成的文本和工具调用 │ │ - Cache Read: 从缓存读取的 input (90% 折扣) │ │ - Cache Write: 写入缓存的 input (1.25x 标准价) │ │ - 缓存 TTL: 5 分钟 │ │ │ │ 隐藏成本: │ │ - 工具调用结果计入 input │ │ - 大文件读取 = 大量 input tokens │ │ - 长对话历史 = 递增 input │ │ - 错误重试 = 重复消耗 │ │ │ └──────────────────────────────────────────────────────────┘2.2 Token 计算规则
Token 估算: 英文: 1 token ≈ 4 字符 ≈ 0.75 单词 中文: 1 token ≈ 1-2 字符 (中文 token 密度高) 代码: 1 token ≈ 3-4 字符 示例: "Hello World" → ~3 tokens "你好世界" → ~4-6 tokens "def hello(): print('hi')" → ~12 tokens Claude Code 每轮消耗: Input = 系统提示(~500 tokens) + CLAUDE.md(~200-1000 tokens) + 历史对话(递增) + 工具调用结果 Output = Claude 的回复 + 工具调用 JSON2.3 长会话成本模型
会话成本递增模型 (无缓存): 第 1 轮: Input = 1K, Output = 0.5K → $0.00375 第 2 轮: Input = 2K, Output = 0.5K → $0.00750 第 3 轮: Input = 3K, Output = 0.5K → $0.01125 ... 第 N 轮: Input = N*1K, Output = 0.5K → $0.00375*N 总成本 = Σ(0.00375 * N) for N=1..50 = 0.00375 * (1+2+...+50) = 0.00375 * 1275 = $4.78 (50 轮对话) 带缓存优化后: 第 1 轮: Input = 1K (cache write), Output = 0.5K 第 2 轮: Input = 0.3K (cache read) + 1K (new), Output = 0.5K 第 3 轮: Input = 0.6K (cache read) + 1K (new), Output = 0.5K ... 总成本 ≈ $1.50 (约 70% 节省)2.4 Anthropic Console 用量统计
console.anthropic.com → Usage 页面: 按时间: 日/周/月用量趋势 按模型: 各模型的 Token 消耗 按项目: 不同 API Key 的用量 按类型: Input/Output/Cache 分布 限制: - 无法按用户区分 (共享 API Key) - 无法按会话区分 - 无实时告警 - 只有日级粒度3. 根因分析:成本失控的六大根因
3.1 根因一:未使用 Prompt Caching
每次请求都重新发送完整上下文,没有利用缓存,input 成本高出数倍。
3.2 根因二:长会话不截断
会话越长,每轮的 input tokens 越多(历史对话累积),成本呈二次增长。
3.3 根因三:大文件全量读取
Claude Code 读取大文件时消耗大量 input tokens,特别是 node_modules、lock 文件等。
3.4 根因四:模型选择不当
简单任务用 Opus($15/M),应该用 Haiku($0.25/M)或 Sonnet($3/M)。
3.5 根因五:无用量监控
没有实时监控 Token 消耗,成本在不知不觉中累积。
3.6 根因六:团队共享 Key
多人共用一个 API Key,无法按人/项目追踪和限制用量。
4. 多方案解决:从监控到优化
4.1 方案一:实时成本监控 Hook
#!/usr/bin/env python3 # .claude/hooks/cost-monitor.py — 实时成本监控 import json import sys import os from datetime import datetime from pathlib import Path COST_DIR = Path(".claude/costs") COST_DIR.mkdir(parents=True, exist_ok=True) PRICING = { "claude-opus-4-20250514": {"input": 15.0, "output": 75.0}, "claude-sonnet-4-20250514": {"input": 3.0, "output": 15.0}, "claude-haiku-4-20250422": {"input": 0.25, "output": 1.25}, } # 预算阈值 (美元) DAILY_BUDGET = float(os.environ.get("CLAUDE_DAILY_BUDGET", "10.0")) MONTHLY_BUDGET = float(os.environ.get("CLAUDE_MONTHLY_BUDGET", "200.0")) def calculate_cost(model, usage): """计算成本""" pricing = PRICING.get(model, PRICING["claude-sonnet-4-20250514"]) input_cost = usage.get("input_tokens", 0) * pricing["input"] / 1_000_000 output_cost = usage.get("output_tokens", 0) * pricing["output"] / 1_000_000 cache_read = usage.get("cache_read_input_tokens", 0) * pricing["input"] * 0.1 / 1_000_000 cache_write = usage.get("cache_creation_input_tokens", 0) * pricing["input"] * 1.25 / 1_000_000 return round(input_cost + output_cost + cache_read + cache_write, 6) def get_daily_total(): """获取今日总成本""" today = datetime.utcnow().strftime("%Y-%m-%d") log_file = COST_DIR / f"cost-{today}.jsonl" total = 0.0 if log_file.exists(): with open(log_file) as f: for line in f: try: data = json.loads(line) total += data.get("cost_usd", 0) except: pass return total def get_monthly_total(): """获取本月总成本""" month = datetime.utcnow().strftime("%Y-%m") total = 0.0 for log_file in COST_DIR.glob(f"cost-{month}-*.jsonl"): with open(log_file) as f: for line in f: try: data = json.loads(line) total += data.get("cost_usd", 0) except: pass return total # Hook 入口 try: hook_input = json.load(sys.stdin) if "usage" in hook_input: model = hook_input.get("model", "claude-sonnet-4-20250514") usage = hook_input["usage"] cost = calculate_cost(model, usage) # 记录 entry = { "timestamp": datetime.utcnow().isoformat() + "Z", "model": model, "input_tokens": usage.get("input_tokens", 0), "output_tokens": usage.get("output_tokens", 0), "cost_usd": cost, "project": os.path.basename(os.getcwd()), "session": os.environ.get("CLAUDE_SESSION_ID", "unknown") } today = datetime.utcnow().strftime("%Y-%m-%d") with open(COST_DIR / f"cost-{today}.jsonl", "a") as f: f.write(json.dumps(entry) + "\n") # 预算检查 daily_total = get_daily_total() monthly_total = get_monthly_total() if daily_total > DAILY_BUDGET: print(f"⚠ 日预算告警: ${daily_total:.2f} / ${DAILY_BUDGET:.2f}", file=sys.stderr) if monthly_total > MONTHLY_BUDGET: print(f"⚠ 月预算告警: ${monthly_total:.2f} / ${MONTHLY_BUDGET:.2f}", file=sys.stderr) # 实时显示 print(f"[Cost] ${cost:.4f} | 日: ${daily_total:.2f}/{DAILY_BUDGET} | 月: ${monthly_total:.2f}/{MONTHLY_BUDGET}", file=sys.stderr) except Exception as e: print(f"Cost monitor error: {e}", file=sys.stderr) sys.exit(0)4.2 方案二:Prompt Caching 配置
""" Prompt Caching 优化 通过缓存系统提示和上下文,减少重复 input 成本 """ import anthropic client = anthropic.Anthropic(api_key="sk-ant-xxx") # 缓存系统提示 (长期不变的部分) SYSTEM_PROMPT = """你是一个代码助手。 项目规则: - 使用 TypeScript - 代码风格遵循 ESLint 配置 - 测试使用 Jest - 文档使用 JSDoc 项目结构: - src/ → 源代码 - tests/ → 测试 - docs/ → 文档""" # 带 cache_control 的请求 response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=4096, system=[ { "type": "text", "text": SYSTEM_PROMPT, "cache_control": {"type": "ephemeral"} # 缓存 5 分钟 } ], messages=[ {"role": "user", "content": "修复 src/auth.ts 的 bug"} ] ) # 第一次请求: cache_creation_input_tokens > 0 (写入缓存) # 后续 5 分钟内的请求: cache_read_input_tokens > 0 (读取缓存, 90% 折扣) print(f"Input: {response.usage.input_tokens}") print(f"Output: {response.usage.output_tokens}") print(f"Cache Read: {response.usage.cache_read_input_tokens}") print(f"Cache Write: {response.usage.cache_creation_input_tokens}")# 多级缓存策略 class CachedConversation: """带多级缓存的对话""" def __init__(self, client, system_prompt, model="claude-sonnet-4-20250514"): self.client = client self.model = model self.messages = [] # 系统提示缓存 self.system = [ { "type": "text", "text": system_prompt, "cache_control": {"type": "ephemeral"} } ] # 对话历史缓存 (在历史达到一定长度时设置) self._cache_breakpoint = 4 # 每 4 轮设置一个缓存断点 def chat(self, user_message): """对话""" self.messages.append({"role": "user", "content": user_message}) # 在历史消息中设置缓存断点 # 最后一个消息不缓存(因为可能会变) system_with_cache = list(self.system) # 如果历史较长,在历史中设置缓存  if len(self.messages) > self._cache_breakpoint * 2: # 在倒数第 4 轮设置缓存断点 cache_idx = len(self.messages) - self._cache_breakpoint * 2 # 需要将消息转为 content block 格式并设置 cache_control pass # 实际实现需要更复杂的消息格式处理 response = self.client.messages.create( model=self.model, max_tokens=4096, system=system_with_cache, messages=self.messages ) self.messages.append({"role": "assistant", "content": response.content}) # 报告缓存命中 usage = response.usage cache_hit = usage.cache_read_input_tokens > 0 print(f" Cache: {'✓ 命中' if cache_hit else '✗ 未命中'} " f"(read: {usage.cache_read_input_tokens}, write: {usage.cache_creation_input_tokens})") return response.content[0].text4.3 方案三:模型分级策略
""" 模型分级策略: 根据任务复杂度选择模型 """ MODEL_TIERS = { "simple": { "model": "claude-haiku-4-20250422", "cost_per_million": {"input": 0.25, "output": 1.25}, "tasks": ["简单问答", "格式转换", "简单搜索", "变量重命名"] }, "medium": { "model": "claude-sonnet-4-20250514", "cost_per_million": {"input": 3.0, "output": 15.0}, "tasks": ["代码编写", "Bug 修复", "代码审查", "测试生成"] }, "complex": { "model": "claude-opus-4-20250514", "cost_per_million": {"input": 15.0, "output": 75.0}, "tasks": ["架构设计", "复杂重构", "安全分析", "算法优化"] } } # 任务分类 → 模型选择 TASK_CLASSIFICATION = { # 简单任务 → Haiku (最低成本) "解释这行代码": "simple", "变量重命名": "simple", "格式化代码": "simple", "查找 import": "simple", "简单的语法问题": "simple", # 中等任务 → Sonnet (性价比) "写一个函数": "medium", "修复 bug": "medium", "添加测试": "medium", "代码审查": "medium", "重构函数": "medium", # 复杂任务 → Opus (最高质量) "系统架构设计": "complex", "大规模重构": "complex", "安全漏洞分析": "complex", "算法优化": "complex", "复杂并发问题": "complex", } def select_model(task_description): """根据任务描述选择模型""" task_lower = task_description.lower() for pattern, tier in TASK_CLASSIFICATION.items(): if pattern in task_lower: return MODEL_TIERS[tier]["model"] # 默认中等 return MODEL_TIERS["medium"]["model"] # Claude Code 配置 # .claude/settings.json """ { "model": "claude-sonnet-4-20250514", // 主模型 (中等任务) "smallModel": "claude-haiku-4-20250422", // 小模型 (简单任务) } """4.4 方案四:上下文压缩
""" 上下文压缩: 减少长会话的 input token 消耗 """ import anthropic class ContextCompressor: """对话上下文压缩器""" def __init__(self, client, model="claude-haiku-4-20250422"): self.client = client self.model = model # 用便宜模型做压缩 def compress_history(self, messages, keep_recent=4): """ 压缩历史对话 保留最近 N 轮原始对话,之前的对话用摘要替代 """ if len(messages) <= keep_recent * 2: return messages # 不需要压缩 # 分割: 需要压缩的 + 保留的 to_compress = messages[:-keep_recent * 2] to_keep = messages[-keep_recent * 2:] # 用 Haiku 生成摘要 summary_prompt = "请总结以下对话的关键信息,保留: 讨论的问题、做出的决定、修改的文件、关键代码片段。\n\n" for msg in to_compress: role = msg["role"] content = msg["content"] if isinstance(msg["content"], str) else str(msg["content"]) summary_prompt += f"[{role}]: {content[:500]}\n" response = self.client.messages.create( model=self.model, max_tokens=1000, messages=[{"role": "user", "content": summary_prompt}] ) summary = response.content[0].text # 构建压缩后的消息列表 compressed = [ {"role": "user", "content": f"[之前对话的摘要]\n{summary}"}, {"role": "assistant", "content": "了解,我记住了之前的对话内容。请继续。"} ] + to_keep # 成本报告 original_tokens = sum(len(str(m.get("content", ""))) // 4 for m in to_compress) compressed_tokens = len(summary) // 4 savings = max(0, original_tokens - compressed_tokens) print(f" 上下文压缩: {original_tokens} → {compressed_tokens} tokens (节省 {savings})") return compressed # 使用 client = anthropic.Anthropic(api_key="sk-ant-xxx") compressor = ContextCompressor(client) # 长对话压缩 # messages = [很多轮对话...] # messages = compressor.compress_history(messages, keep_recent=4)4.5 方案五:团队用量追踪
#!/usr/bin/env python3 """ 团队用量追踪系统 按用户、项目、会话维度追踪 API 成本 """ import json import os from datetime import datetime, timedelta from pathlib import Path from collections import defaultdict class TeamCostTracker: """团队成本追踪器""" def __init__(self): self.cost_dir = Path(os.environ.get("CLAUDE_COST_DIR", ".claude/costs")) def generate_team_report(self, days=30): """生成团队报告""" stats = defaultdict(lambda: { "total_cost": 0.0, "total_input": 0, "total_output": 0, "api_calls": 0, "by_model": defaultdict(float), "by_project": defaultdict(float), "by_day": defaultdict(float) }) # 加载日志 for i in range(days): date = (datetime.utcnow() - timedelta(days=i)).strftime("%Y-%m-%d") log_file = self.cost_dir / f"cost-{date}.jsonl" if not log_file.exists(): continue with open(log_file) as f: for line in f: try: entry = json.loads(line) # 按 session (用户代理) 统计 session = entry.get("session", "unknown") stats[session]["total_cost"] += entry.get("cost_usd", 0) stats[session]["total_input"] += entry.get("input_tokens", 0) stats[session]["total_output"] += entry.get("output_tokens", 0) stats[session]["api_calls"] += 1 stats[session]["by_model"][entry.get("model", "unknown")] += entry.get("cost_usd", 0) stats[session]["by_project"][entry.get("project", "unknown")] += entry.get("cost_usd", 0) stats[session]["by_day"][date] += entry.get("cost_usd", 0) except json.JSONDecodeError: continue # 打印报告 print(f"=== 团队成本报告 ({days} 天) ===\n") grand_total = sum(s["total_cost"] for s in stats.values()) print(f"总成本: ${grand_total:.2f}") print(f"总 API 调用: {sum(s['api_calls'] for s in stats.values())}") print(f"总 Input: {sum(s['total_input'] for s in stats.values()):,} tokens") print(f"总 Output: {sum(s['total_output'] for s in stats.values()):,} tokens") print(f"\n--- 按会话 ---") for session, data in sorted(stats.items(), key=lambda x: -x[1]["total_cost"]): print(f"\n 会话: {session}") print(f" 成本: ${data['total_cost']:.4f}") print(f" 调用: {data['api_calls']}") print(f" Input: {data['total_input']:,} / Output: {data['total_output']:,}") if data["by_model"]: print(f" 按模型:") for model, cost in sorted(data["by_model"].items(), key=lambda x: -x[1]): print(f" {model}: ${cost:.4f}") if data["by_project"]: print(f" 按项目:") for project, cost in sorted(data["by_project"].items(), key=lambda x: -x[1]): print(f" {project}: ${cost:.4f}") # 日均趋势 print(f"\n--- 日均成本 ---") all_days = set() for data in stats.values(): all_days.update(data["by_day"].keys()) for date in sorted(all_days): day_total = sum(data["by_day"].get(date, 0) for data in stats.values()) bar = "█" * int(day_total * 10) # 每美元 10 个字符 print(f" {date}: ${day_total:.2f} {bar}") # 使用 tracker = TeamCostTracker() tracker.generate_team_report(days=30)4.6 方案六:文件读取优化
""" 文件读取 Token 优化 避免不必要的大文件全量读取 """ import os class FileReader: """优化的文件读取器""" # 应该跳过的文件/目录 SKIP_PATTERNS = [ "node_modules", ".git", "dist", "build", "__pycache__", "*.lock", "*.min.js", "*.min.css", "*.map", "package-lock.json", "yarn.lock", "pnpm-lock.yaml", "go.sum", "Cargo.lock", "Gemfile.lock", ] # 大文件阈值 (行数) MAX_LINES = 500 # 大文件阈值 (字节) MAX_SIZE = 50_000 # 50KB @classmethod def should_read(cls, filepath): """判断是否应该读取文件""" filepath = str(filepath) # 检查跳过模式 for pattern in cls.SKIP_PATTERNS: if pattern in filepath: return False, f"跳过: 匹配 {pattern}" # 检查文件大小 if os.path.exists(filepath): size = os.path.getsize(filepath) if size > cls.MAX_SIZE: return False, f"文件过大: {size} bytes (> {cls.MAX_SIZE})" return True, "OK" @classmethod def read_optimized(cls, filepath, max_lines=None): """优化的文件读取""" should, reason = cls.should_read(filepath) if not should: return None, reason max_lines = max_lines or cls.MAX_LINES with open(filepath, "r", errors="replace") as f: lines = [] for i, line in enumerate(f): if i >= max_lines: lines.append(f"\n... (截断,共 {i+1}+ 行,仅显示前 {max_lines} 行)") break lines.append(line) content = "".join(lines) estimated_tokens = len(content) // 4 return content, f"读取 {len(lines)} 行, ~{estimated_tokens} tokens" # CLAUDE.md 中配置读取策略 """ # 文件读取策略 ## 跳过的文件 - node_modules/ - *.lock 文件 - *.min.js / *.min.css - dist/ / build/ ## 大文件处理 - 超过 500 行的文件只读取相关部分 - 超过 50KB 的文件先摘要再选择性读取 - 使用 grep/search 定位再读取 ## 优先级 - 先用 search_content 定位 - 再用 read_file offset/limit 精确读取 - 避免全量读取大文件 """4.7 方案七:多 API Key 管理
""" 多 API Key 管理: 按项目/团队分配不同的 Key """ import os import json from pathlib import Path class APIKeyManager: """多 API Key 管理器""" def __init__(self): self.keys_file = Path.home() / ".claude" / "api-keys.json" self.keys = self._load_keys() def _load_keys(self): """加载 API Key 配置""" if not self.keys_file.exists(): return {} with open(self.keys_file) as f: return json.load(f) def get_key_for_project(self, project_name): """获取项目对应的 API Key""" project_config = self.keys.get("projects", {}).get(project_name) if project_config: key_env = project_config.get("env_var") if key_env: return os.environ.get(key_env) # 返回默认 Key return os.environ.get("ANTHROPIC_API_KEY") def get_budget_for_project(self, project_name): """获取项目预算""" project_config = self.keys.get("projects", {}).get(project_name, {}) return project_config.get("daily_budget", 10.0) # api-keys.json 示例 """ { "projects": { "my-app": { "env_var": "ANTHROPIC_API_KEY_APP", "daily_budget": 5.0, "monthly_budget": 100.0 }, "internal-tools": { "env_var": "ANTHROPIC_API_KEY_TOOLS", "daily_budget": 2.0, "monthly_budget": 50.0 }, "research": { "env_var": "ANTHROPIC_API_KEY_RESEARCH", "daily_budget": 20.0, "monthly_budget": 400.0 } }, "default": { "env_var": "ANTHROPIC_API_KEY", "daily_budget": 10.0, "monthly_budget": 200.0 } } """ # .zshrc 中配置多个 Key """ export ANTHROPIC_API_KEY="sk-ant-xxx-default" export ANTHROPIC_API_KEY_APP="sk-ant-xxx-app" export ANTHROPIC_API_KEY_TOOLS="sk-ant-xxx-tools" export ANTHROPIC_API_KEY_RESEARCH="sk-ant-xxx-research" """5. 验证回归:成本控制验证
5.1 成本验证脚本
#!/bin/bash # verify-cost-control.sh — 成本控制验证 echo "=== 成本控制验证 ===" # 1. 检查成本日志 if [ -d ".claude/costs" ]; then TODAY=$(date -u +%Y-%m-%d) LOG_FILE=".claude/costs/cost-${TODY}.jsonl" if [ -f "$LOG_FILE" ]; then TODAY_COST=$(python3 -c " import json total = 0 with open('$LOG_FILE') as f: for line in f: try: data = json.loads(line) total += data.get('cost_usd', 0) except: pass print(f'{total:.4f}') " 2>/dev/null) echo "今日成本: \$$TODAY_COST" fi fi # 2. 检查预算配置 DAILY_BUDGET=${CLAUDE_DAILY_BUDGET:-"未设置"} echo "日预算: $DAILY_BUDGET" # 3. 检查模型配置 if [ -f ".claude/settings.json" ]; then python3 -c " import json with open('.claude/settings.json') as f: data = json.load(f) print(f\"主模型: {data.get('model', '未设置')}\") print(f\"小模型: {data.get('smallModel', '未设置')}\") " 2>/dev/null fi echo "" echo "=== 验证完成 ==="5.2 验证清单
| # | 验证项 | 预期 | 方法 |
|---|---|---|---|
| 1 | 成本 Hook | 已配置 | settings.json |
| 2 | 成本日志 | 有记录 | 检查 .claude/costs/ |
| 3 | 预算告警 | 功能正常 | 超阈值告警 |
| 4 | Prompt Caching | 命中率高 | cache_read > 0 |
| 5 | 模型分级 | 按任务选模型 | 配置检查 |
| 6 | 上下文压缩 | 长会话压缩 | 压缩工具 |
| 7 | 文件跳过 | 大文件不读 | 读取策略 |
| 8 | 多 Key | 按项目分配 | Key 管理器 |
6. 避坑最佳实践
6.1 成本控制原则
原则 1: 实时监控 — 用 Hook 记录每次 API 调用的成本 原则 2: Prompt Caching — 缓存系统提示和上下文 原则 3: 模型分级 — 简单任务用 Haiku,复杂用 Opus 原则 4: 上下文压缩 — 长会话定期压缩历史 原则 5: 文件优化 — 跳过大文件和无用文件 原则 6: 预算告警 — 设置日/月预算阈值 原则 7: 多 Key — 按项目/团队分配 API Key 原则 8: 定期审查 — 周度/月度成本分析6.2 成本优化效果
| 优化措施 | 节省比例 | 实施难度 |
|---|---|---|
| Prompt Caching | 50-70% | 低 |
| 模型分级 (Haiku) | 80-90% | 低 |
| 上下文压缩 | 30-50% | 中 |
| 文件读取优化 | 20-40% | 低 |
| 长会话截断 | 40-60% | 低 |
| 多 Key 管理 | N/A | 中 |
6.3 常见陷阱
| # | 陷阱 | 后果 | 解决 |
|---|---|---|---|
| 1 | 无缓存 | 重复全价计费 | 开启 Prompt Caching |
| 2 | 简单任务用 Opus | 成本 60x | 模型分级 |
| 3 | 长会话不截断 | 二次增长 | 定期压缩 |
| 4 | 全量读大文件 | 浪费 input | 跳过/分页 |
| 5 | 无成本监控 | 不知花费 | Cost Hook |
| 6 | 共享 Key | 无法追踪 | 多 Key 管理 |
| 7 | 无预算限制 | 失控 | 预算告警 |
| 8 | 不审查账单 | 持续浪费 | 定期审查 |
7. 附录:成本速查表
7.1 模型定价对比
| 模型 | Input $/M | Output $/M | 适用场景 |
|---|---|---|---|
| Opus 4 | $15 | $75 | 架构/安全/复杂 |
| Sonnet 4 | $3 | $15 | 编码/审查/日常 |
| Haiku 4 | $0.25 | $1.25 | 简单/格式/搜索 |
7.2 成本优化优先级
| 优先级 | 措施 | 预期节省 |
|---|---|---|
| P0 | Prompt Caching | 50-70% |
| P0 | 模型分级 | 80%+ (简单任务) |
| P1 | 文件读取优化 | 20-40% |
| P1 | 长会话压缩 | 30-50% |
| P2 | 成本监控 | 可见性 |
| P2 | 预算告警 | 防失控 |
| P3 | 多 Key | 按项目追踪 |
7.3 预算推荐
| 团队规模 | 日预算 | 月预算 |
|---|---|---|
| 个人 | $5-10 | $100-200 |
| 小团队 (5人) | $20-50 | $400-1000 |
| 中团队 (20人) | $50-100 | $1000-2000 |
| 大团队 (50+) | $200+ | $4000+ |
结语
成本控制是长期使用 Claude Code 不可忽视的方面。通过实时成本监控、Prompt Caching、模型分级、上下文压缩、文件读取优化、预算告警和多 Key 管理,可以将 API 成本降低 50-80%,同时保持开发效率。
核心要点回顾:
- Prompt Caching:缓存系统提示,减少 50-70% 的 input 成本
- 模型分级:简单任务用 Haiku($0.25/M),日常用 Sonnet($3/M),复杂用 Opus($15/M)
- 上下文压缩:长会话定期压缩历史,避免 Token 二次增长
- 文件优化:跳过 node_modules、lock 文件、大文件,用 search 定位再读取
- 实时监控:用 Hook 记录每次 API 调用的成本和 Token 消耗
- 预算告警:设置日/月预算阈值,超限自动告警
- 多 Key 管理:按项目/团队分配不同 API Key,实现成本追踪
- 定期审查:周度/月度生成成本报告,识别优化空间