Token = Währung in der LLM-Welt. Dieses Dokument zeigt wie du 50-90% der Kosten sparst durch intelligente Tokenisierung.
Warum Token wichtig sind
Tokens sind das Messmaß für:
- Kosten: $0.003 - $0.075 pro 1k Tokens
- Latenz: Mehr Tokens = längere Verarbeitung
- Context Limits: z.B. Claude Haiku: 200k, aber teuer bei großen Inputs
Das Token Bloat Problem
Szenario: Production Agent mit schlechter Tokenisierung
User Input:
"Analysiere diese CSV"
System Prompt: 500 tokens ← Schon 500 vor dem Nutzer-Input!
Tool Descriptions: 2000 tokens
Agent Instructions: 1500 tokens
Memory/Context: 3000 tokens
User Input: 100 tokens
___________________________
Total: 7100 tokens
← Für 100 Token echte Info!
Token Overhead Ratio: 7100/100 = 71:1
Token Verschwendung Sources
# Beispiel: Unoptimierter Code
# SCHLECHT - Full System Prompt jedes Mal
SYSTEM_PROMPT = """
Du bist ein AI Agent mit folgenden Fähigkeiten:
- Read files
- Write files
- Execute bash commands
- Query databases
- Send emails
- Analyze data
...
[Wiederholt sich 500+ mal pro Session]
"""
# GUT - Komprimiert und gecacht
SYSTEM_PROMPT = """You are an AI agent with standard capabilities."""
# Tools werden via Hooks geladen, nicht via Prompt
mcp2cli: Die Revolution
mcp2cli = CLI Wrapper um MCP Servers, der 96-99% der Tokens spart.
Wie es funktioniert
Standard OpenAI Ansatz:
LLM →
full JSON schema von allen Tools
+ Dokumentation
+ Examples
→ Immer im Context
mcp2cli Ansatz:
LLM → Minimale Tool References
(nur Name + kurze Beschreibung)
↓
Externe CLI wird aufgerufen
↓
Ergebnis zurück an LLM
Token Vergleich
Task: "Implementiere einen Datei-Manager"
STANDARD APPROACH:
- System Prompt: 1500 tokens
- Tool Schemas (JSON): 3200 tokens
- Per Tool Call: +500 tokens
- Total für 5 Calls: 1500 + 3200 + 2500 = 7200 tokens
MCP2CLI APPROACH:
- System Prompt: 100 tokens
- Tool References: 50 tokens
- Per Tool Call: +50 tokens (CLI invocation)
- Total für 5 Calls: 100 + 50 + 250 = 400 tokens
Einsparung: 94.4% ✓
mcp2cli Installation & Nutzung
# Installation
pip install mcp2cli
# Konfiguration
mcp2cli config add \
--name file-manager \
--command "python /opt/tools/file-manager.py" \
--description "File read/write operations"
# Nutzen im Code
from mcp2cli import Client
client = Client()
result = client.call("file-manager", "read", path="/tmp/data.txt")
Prompt Caching (Anthropic)
Caching = Kostenfreie Wiederverwendung von Tokens innerhalb 5 Minuten.
Cache Hit Beispiel
from anthropic import Anthropic
client = Anthropic()
# FIRST CALL - keine Cache
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1000,
system=[
{
"type": "text",
"text": "You are a helpful assistant.",
"cache_control": {"type": "ephemeral"}
}
],
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "What is 2 + 2?",
"cache_control": {"type": "ephemeral"}
}
]
}
]
)
print(f"Input tokens: {response.usage.input_tokens}")
print(f"Cache creation tokens: {response.usage.cache_creation_input_tokens}")
# Output:
# Input tokens: 10
# Cache creation tokens: 10 (cached)
# SECOND CALL - Cache Hit!
response2 = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1000,
system=[
{
"type": "text",
"text": "You are a helpful assistant.",
"cache_control": {"type": "ephemeral"}
}
],
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "What is 2 + 2?",
"cache_control": {"type": "ephemeral"}
}
]
}
]
)
print(f"Input tokens: {response2.usage.input_tokens}")
print(f"Cache read tokens: {response2.usage.cache_read_input_tokens}")
# Output:
# Input tokens: 0 (served from cache!)
# Cache read tokens: 10 (read from cache, 90% cheaper)
Cache Pricing
| Operation | Kosten |
|---|---|
| Erste 5min Nutzung | 100% |
| Reads (nach 5min) | 10% des Original-Preises |
| Eviction (nach 5min inaktiv) | Automatisch |
Cache Best Practices
class CachedAgent:
def __init__(self):
self.client = Anthropic()
self.base_system_prompt = "..." # Long system prompt
self.cache_ttl = 300 # 5 minutes
def call_with_cache(self, user_input):
"""Make LLM call with caching"""
return self.client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1000,
system=[{
"type": "text",
"text": self.base_system_prompt,
"cache_control": {"type": "ephemeral"}
}],
messages=[{
"role": "user",
"content": [{
"type": "text",
"text": user_input,
# Neu Inputs NICHT cachen
# "cache_control": nicht setzen
}]
}]
)
def calculate_savings(self, num_calls, cached_calls):
"""Calculate token savings from caching"""
# First call: 100% price
# Subsequent: 10% price
total_tokens = 1000 # example
cost_without_cache = num_calls * total_tokens * 0.003
cost_with_cache = (1 * total_tokens * 0.003) + \
((cached_calls - 1) * total_tokens * 0.0003)
savings_pct = ((cost_without_cache - cost_with_cache) / \
cost_without_cache) * 100
return f"{savings_pct:.1f}% savings"
Context Window Management
Sliding Window Strategy
class SlidingContextManager:
def __init__(self, window_size=4000):
self.window_size = window_size
self.history = []
def add_interaction(self, user_msg, assistant_msg):
"""Add and maintain sliding window"""
self.history.append({
"role": "user",
"content": user_msg,
"tokens": count_tokens(user_msg)
})
self.history.append({
"role": "assistant",
"content": assistant_msg,
"tokens": count_tokens(assistant_msg)
})
# Trim to fit window
self._trim_to_window()
def _trim_to_window(self):
"""Keep only recent messages that fit in window"""
total_tokens = 0
keep_from_idx = len(self.history)
# Iterate backwards
for i in range(len(self.history) - 1, -1, -1):
msg_tokens = self.history[i]["tokens"]
if total_tokens + msg_tokens <= self.window_size:
total_tokens += msg_tokens
keep_from_idx = i
else:
break
self.history = self.history[keep_from_idx:]
def get_context(self):
"""Return trimmed context"""
return self.history
Summarization für alte Context
class SummarizationManager:
def __init__(self, client, summary_threshold=2000):
self.client = client
self.summary_threshold = summary_threshold
def summarize_if_needed(self, conversation_history):
"""Summarize old messages if they exceed threshold"""
total_tokens = sum(
count_tokens(msg["content"])
for msg in conversation_history
)
if total_tokens > self.summary_threshold:
# Take oldest 10 messages
old_msgs = conversation_history[:10]
new_msgs = conversation_history[10:]
# Summarize using Claude
summary_prompt = f"""
Erstelle eine Zusammenfassung dieser Konversation:
{json.dumps(old_msgs)}
Kurz und prägnant, max 500 tokens.
"""
summary = self.client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=500,
messages=[{
"role": "user",
"content": summary_prompt
}]
)
# Replace old msgs with summary
return [{
"role": "assistant",
"content": f"[Summary]: {summary.content[0].text}"
}] + new_msgs
return conversation_history
Structured Output vs Free-Form
Structured Output spart Tokens durch explizite Format-Vorgabe.
# INEFFIZIENT - Free Form
prompt = "Analyze this data and tell me what you see"
# LLM kann beliebig formatieren, repetitiv
# EFFIZIENT - Structured
prompt = "Analyze this data in JSON format: {\"key_findings\": [...], \"metrics\": {...}}"
# LLM muss sich an Schema halten, prägnanter
# Token Ersparnis: ~30%
Model Selection Strategy
| Task | Modell | Grund | Tokens/Call |
|---|---|---|---|
| Simple Classification | Haiku | Günstig + schnell | 100-300 |
| Standard Analysis | Sonnet | Balanced | 300-1000 |
| Complex Multi-Step | Opus | Vollständig | 500-2000 |
| Batch Processing | Haiku | Billig im Scale | 100k+ |
Dynamische Model Wahl
class AdaptiveModelSelector:
def __init__(self):
self.models = {
"simple": "claude-3-5-haiku-20241022",
"standard": "claude-3-5-sonnet-20241022",
"complex": "claude-3-opus-20240229"
}
def select_model(self, task_complexity, available_budget):
"""Choose model based on complexity and budget"""
if available_budget < 0.01:
return self.models["simple"]
elif task_complexity in ["classification", "simple_qa"]:
return self.models["simple"]
elif task_complexity in ["analysis", "writing"]:
return self.models["standard"]
else:
return self.models["complex"]
def calculate_budget_needed(self, task, estimated_tokens):
"""Calculate cost for task"""
costs = {
"haiku": 0.00080,
"sonnet": 0.003,
"opus": 0.015
}
model = self.select_model(task, float('inf'))
cost_per_token = costs[model.split("-")[-1].replace("-20241022", "")]
return (estimated_tokens / 1000) * cost_per_token
Measuring Token Usage
class TokenAnalytics:
def __init__(self):
self.calls = []
def log_call(self, model, input_tokens, output_tokens, cache_read=0, cache_creation=0):
"""Log token usage"""
self.calls.append({
"model": model,
"input": input_tokens,
"output": output_tokens,
"cache_read": cache_read,
"cache_creation": cache_creation,
"timestamp": datetime.now()
})
def get_stats(self, hours=24):
"""Get analytics for time period"""
recent = [c for c in self.calls
if (datetime.now() - c["timestamp"]).total_seconds() < hours * 3600]
total_input = sum(c["input"] for c in recent)
total_output = sum(c["output"] for c in recent)
cache_savings = sum(c["cache_read"] for c in recent)
pricing = {"haiku": 0.00080, "sonnet": 0.003, "opus": 0.015}
return {
"total_input_tokens": total_input,
"total_output_tokens": total_output,
"cache_tokens_read": cache_savings,
"calls": len(recent),
"avg_tokens_per_call": (total_input + total_output) / len(recent) if recent else 0,
"cache_hit_rate": cache_savings / total_input if total_input > 0 else 0
}
Claude Code Spezifisches Token Management
---
# .claude/CLAUDE.md
name: Token-Efficient Agent
temperature: 0.7
model: claude-3-5-sonnet-20241022 # Balanced
# Use caching for static content
system:
cache_control: ephemeral
# Limit context to last 10 interactions
max_history_turns: 10
# Use structured output
output_format: json
# Compress repetitive instructions
instructions_compressed: true
---
