Lesson 08b: Monitoring Service Deep Dive
"If you can't measure it, you can't improve it."
Introduction
Cost tracking is critical for LLM applications. Without monitoring, you're flying blind - spending unknown amounts on AI operations with no visibility into what's working or what's wasteful.
monitoring_service.py provides the observability layer for tracking token usage and estimating costs.
File Overview
Location: backend/app/services/monitoring_service.py
Size: 1,194 bytes
Functions: 2
Purpose: Track LLM resource usage and costs
Complete File Walkthrough
Function 1: estimate_tokens()
def estimate_tokens(text: str | None) -> int:
"""
Simple token estimation.
Approximation: 1 token ≈ 4 characters in English.
This is not exact, but useful for monitoring dashboard.
"""
if not text:
return 0
return max(1, len(text) // 4)Purpose
Quickly estimate token count without calling expensive tokenization API.
The Token Problem
What are tokens?
- LLMs process text as tokens, not characters
- 1 token ≈ 4 characters (English average)
- Examples:
- "Hello" = 1 token
- "Hello world" = 2 tokens
- "artificial intelligence" = 2-3 tokens
Why estimate? OpenAI charges by tokens, not characters:
- gpt-4o-mini: $0.15 per 1M input tokens
- Need to track usage for billing
Exact vs Estimate:
# Exact (requires tiktoken library)
import tiktoken
encoder = tiktoken.encoding_for_model("gpt-4o-mini")
tokens = len(encoder.encode(text)) # Accurate but slow
# Estimate (this function)
tokens = len(text) // 4 # Fast but approximateAlgorithm Breakdown
Step 1: Handle None
if not text:
return 0Why needed: Prevents crashes on null input
Use case: Optional fields like error_message
Step 2: Estimate
return max(1, len(text) // 4)len(text): Character count
// 4: Integer division (1 token per 4 chars)
max(1, ...): Always return at least 1
Why max(1, ...)?
# Without max
estimate_tokens("Hi") # len=2, 2//4=0 ❌ Wrong!
# With max
estimate_tokens("Hi") # max(1, 0)=1 ✅ Correct!Accuracy Analysis
Test cases:
# Short text
"Hello" (5 chars) → 1 token estimated, 1 actual ✅
# Medium text
"The quick brown fox" (19 chars) → 4 tokens estimated, 4 actual ✅
# Long text
"Artificial intelligence is transforming..." (200 chars)
→ 50 tokens estimated, 45-55 actual ✅ Close enough
# Special cases
"" → 0 tokens ✅
None → 0 tokens ✅
"A" → 1 token (due to max) ✅Accuracy: ±10-20% for English text
Why acceptable?
- Dashboard needs approximate values
- Exact tracking happens on OpenAI's side
- Performance > precision for monitoring
Limitations
Inaccurate for:
-
Non-English text
- Chinese: 1 token ≈ 2 characters
- Arabic: Different ratios
-
Code
python"def hello():" # Estimate: 3 tokens (13/4) # Actual: 4-5 tokens (punctuation counted separately) -
Special characters
- Emojis, Unicode symbols
- Often more than 1 token each
When to upgrade:
If billing discrepancies > 20%, integrate tiktoken:
import tiktoken
def estimate_tokens_exact(text: str | None, model: str = "gpt-4o-mini") -> int:
if not text:
return 0
encoder = tiktoken.encoding_for_model(model)
return len(encoder.encode(text))Function 2: estimate_openai_cost()
def estimate_openai_cost(
input_text: str | None,
output_text: str | None,
model_name: str = "gpt-4o-mini"
) -> dict:
"""
Estimate tokens and approximate cost.
Prices here are approximate and can be updated later.
"""Purpose
Calculate estimated dollar cost for an LLM operation.
Parameters
input_text: Prompt sent to LLM
output_text: Response from LLM
model_name: Which model used (default: gpt-4o-mini)
Returns:
{
"model_name": "gpt-4o-mini",
"estimated_tokens": 150,
"estimated_cost": "0.000045" # $0.000045 (4.5¢ per 100K)
}Step 1: Calculate Token Counts
input_tokens = estimate_tokens(input_text)
output_tokens = estimate_tokens(output_text)
total_tokens = input_tokens + output_tokensWhy separate input/output? OpenAI charges different rates:
- Input tokens: $0.15 per 1M
- Output tokens: $0.60 per 1M (4x more expensive!)
Example:
input_text = "What is AI?" (12 chars)
input_tokens = 3
output_text = "AI is artificial intelligence, a field of computer science..." (400 chars)
output_tokens = 100
total_tokens = 103Step 2: Define Pricing (GPT-4o-mini)
# Approximate price for gpt-4o-mini:
# input: $0.15 per 1M tokens
# output: $0.60 per 1M tokens
input_cost_per_1m = 0.15
output_cost_per_1m = 0.60Current OpenAI Pricing (as of code):
| Model | Input (per 1M) | Output (per 1M) |
|---|---|---|
| gpt-4o-mini | $0.15 | $0.60 |
| gpt-4 | $5.00 | $15.00 |
| gpt-4-turbo | $10.00 | $30.00 |
Why hardcoded?
- Pricing stable enough for estimates
- Can be updated when prices change
- Real billing comes from OpenAI directly
Future improvement:
# Config-driven pricing
PRICING = {
"gpt-4o-mini": {"input": 0.15, "output": 0.60},
"gpt-4": {"input": 5.00, "output": 15.00},
}Step 3: Calculate Cost
input_cost = (input_tokens / 1_000_000) * input_cost_per_1m
output_cost = (output_tokens / 1_000_000) * output_cost_per_1m
total_cost = input_cost + output_costFormula:
Cost = (tokens / 1,000,000) × price_per_1MExample calculation:
# Chat: "What is AI?" → 400-char response
input_tokens = 3
output_tokens = 100
input_cost = (3 / 1,000,000) × $0.15 = $0.00000045
output_cost = (100 / 1,000,000) × $0.60 = $0.00006000
total_cost = $0.00006045 ≈ $0.00006 (6¢ per 100K requests)Why divide by 1_000_000? Pricing is "per million tokens", convert to "per token" first.
Step 4: Format Response
return {
"model_name": model_name,
"estimated_tokens": total_tokens,
"estimated_cost": f"{total_cost:.6f}"
}Why .6f format?
- Shows 6 decimal places
- Captures micro-costs (fractions of cents)
- Example:
"0.000045"= $0.000045
Why string for cost?
# Float precision issues
0.1 + 0.2 # = 0.30000000000000004 ❌
# String storage (from calculation)
f"{0.000045:.6f}" # = "0.000045" ✅Complete Usage Example
Scenario: Chat Request
# User asks question
user_message = "Explain machine learning in simple terms" # ~50 chars
# AI responds
ai_response = """
Machine learning is a way for computers to learn from examples
rather than being explicitly programmed. Instead of writing rules,
we show the computer many examples and it finds patterns.
""" # ~180 chars
# Calculate cost
result = estimate_openai_cost(
input_text=user_message,
output_text=ai_response,
model_name="gpt-4o-mini"
)
print(result)
# Output:
# {
# "model_name": "gpt-4o-mini",
# "estimated_tokens": 57, # 12 input + 45 output
# "estimated_cost": "0.000028" # $0.000028
# }Per-request cost: $0.000028
Cost per 1,000 requests: $0.028 (2.8¢)
Cost per 1M requests: $28
Integration with ToolCall Logging
In routers/agents.py
# Before LLM call
email_tool_start = time.time()
# Make LLM call
result = analyze_email(email_data.email_text)
# Calculate metrics
email_tool_latency = int((time.time() - email_tool_start) * 1000)
monitoring = estimate_openai_cost(
input_text=email_data.email_text,
output_text=json.dumps(result),
model_name=os.getenv("OPENAI_MODEL", "gpt-4o-mini")
)
# Log to database
log_tool_call(
db=db,
agent_id=agent.id,
tool_name="email_analyzer",
tool_input=email_data.email_text,
tool_output=json.dumps(result),
success=True,
latency_ms=email_tool_latency,
model_name=monitoring["model_name"],
estimated_tokens=monitoring["estimated_tokens"],
estimated_cost=monitoring["estimated_cost"],
)Flow:
- Capture input text
- Call LLM
- Capture output text
- Estimate cost with
estimate_openai_cost() - Store in database via
log_tool_call()
Dashboard Usage
Query Total Costs
# Get all tool calls for an agent
tool_calls = db.query(ToolCall).filter(ToolCall.agent_id == agent_id).all()
# Sum costs
total_cost = sum(
float(call.estimated_cost) if call.estimated_cost else 0
for call in tool_calls
)
print(f"Total spent: ${total_cost:.2f}")
# Output: Total spent: $2.45Average Cost Per Request
avg_cost = total_cost / len(tool_calls)
print(f"Average per request: ${avg_cost:.6f}")
# Output: Average per request: $0.000245Token Usage Analysis
total_tokens = sum(
call.estimated_tokens if call.estimated_tokens else 0
for call in tool_calls
)
print(f"Total tokens: {total_tokens:,}")
# Output: Total tokens: 1,234,567Cost Optimization Strategies
1. Choose Right Model
# Expensive
estimate_openai_cost(text, response, "gpt-4")
# $5/$15 per 1M tokens
# Cost-effective
estimate_openai_cost(text, response, "gpt-4o-mini")
# $0.15/$0.60 per 1M tokens
# Savings: 97% cheaper!2. Reduce Output Length
# Expensive (verbose output)
"Generate a detailed explanation..."
# → 500 tokens output = $0.00030
# Cheaper (concise output)
"Summarize in 2 sentences..."
# → 50 tokens output = $0.00003
# Savings: 90% cheaper!3. Cache Responses
# Without caching
for i in range(1000):
response = call_openai("What is AI?")
# Cost: $0.028 × 1000 = $28
# With caching
cache = {}
for i in range(1000):
if "What is AI?" in cache:
response = cache["What is AI?"]
else:
response = call_openai("What is AI?")
cache["What is AI?"] = response
# Cost: $0.028 × 1 = $0.028
# Savings: 99.9% cheaper!4. Prompt Optimization
# Expensive (redundant prompt)
system_prompt = """
You are a helpful assistant.
You should be helpful.
Always help users.
Be as helpful as possible.
""" # 40 tokens
# Optimized
system_prompt = "You are a helpful assistant." # 7 tokens
# Savings: 82% fewer input tokensMonitoring Best Practices
1. Alert on Unusual Costs
# Daily cost check
daily_cost = sum_costs_for_date(today)
if daily_cost > 50: # $50 threshold
send_alert(f"High usage: ${daily_cost}")2. Track Cost by Feature
# Chat costs
chat_costs = sum(call.estimated_cost for call in tool_calls
if call.tool_name == "chat")
# Email analysis costs
email_costs = sum(call.estimated_cost for call in tool_calls
if call.tool_name == "email_analyzer")
# Identify expensive features3. Per-User Cost Tracking
# Track which users consume most
user_costs = {}
for call in tool_calls:
user_id = call.agent.user_id # If you add users
user_costs[user_id] = user_costs.get(user_id, 0) + float(call.estimated_cost)
# Find top spenders
top_users = sorted(user_costs.items(), key=lambda x: x[1], reverse=True)[:10]Limitations & Improvements
Current Limitations
-
Approximate Only
- ±10-20% accuracy
- Not suitable for exact billing
-
Hardcoded Pricing
- Must manually update when prices change
- No multi-model pricing lookup
-
No Real-Time Tracking
- Can't track actual OpenAI API usage
- Relies on estimates
Improvements
Upgrade 1: Exact Tokenization
import tiktoken
def estimate_tokens_exact(text: str | None, model: str) -> int:
if not text:
return 0
try:
encoder = tiktoken.encoding_for_model(model)
return len(encoder.encode(text))
except Exception:
# Fallback to estimate
return estimate_tokens(text)Upgrade 2: Dynamic Pricing
PRICING_TABLE = {
"gpt-4o-mini": {"input": 0.15, "output": 0.60},
"gpt-4": {"input": 5.00, "output": 15.00},
"gpt-4-turbo": {"input": 10.00, "output": 30.00},
}
def get_model_pricing(model_name: str) -> dict:
return PRICING_TABLE.get(model_name, PRICING_TABLE["gpt-4o-mini"])Upgrade 3: OpenAI API Tracking
# OpenAI API returns actual usage
response = client.chat.completions.create(...)
actual_tokens = response.usage.total_tokens
actual_cost = calculate_from_actual(
response.usage.prompt_tokens,
response.usage.completion_tokens,
model
)
# Store actual instead of estimateKey Takeaways
🎯 Token estimation uses 1 token ≈ 4 characters rule (±20% accurate)
🎯 Cost calculation applies model-specific pricing per million tokens
🎯 Output tokens cost 4x more than input tokens for gpt-4o-mini
🎯 String storage for costs avoids floating-point precision issues
🎯 Monitoring enables optimization - track, analyze, improve
🎯 Estimates sufficient for dashboard - OpenAI tracks actual billing
Interview Questions
Junior
Q1: Why estimate tokens instead of counting exactly?
A: Speed. Estimation is instant, exact tokenization requires API calls or heavy libraries. For monitoring dashboards, approximate is sufficient.
Q2: Why do output tokens cost more than input tokens?
A: LLM generation (output) is computationally expensive - requires sampling, decoding, and multiple forward passes. Reading (input) is simpler.
Mid-Level
Q3: How would you improve accuracy beyond the 4:1 ratio?
A: Integrate tiktoken library for model-specific tokenization. Handle different languages with different ratios. Cache tokenization results.
Q4: Design a cost alerting system for this monitoring service.
A: Background job runs hourly, sums estimated_cost from ToolCall table for last 24h. If > threshold, send email/Slack alert. Track trends (sudden spikes).
Senior
Q5: The estimate shows $50/day but actual OpenAI bill is $65. Debug and fix.
A: (1) Check if all tool calls logged. (2) Verify pricing matches OpenAI current rates. (3) Compare estimated vs actual tokens from OpenAI API response. (4) Integrate tiktoken for exact counts. (5) Add reconciliation job comparing estimates vs OpenAI dashboard.
Q6: Design a cost allocation system for multi-tenant SaaS.
A: Add tenant_id to ToolCall. Create TenantUsage table aggregating costs daily. Implement usage-based billing tiers. Add cost prediction based on historical patterns. Include overage alerts per tenant.
Exercises
Exercise 1: Test Accuracy
Calculate actual vs estimated tokens for 10 different prompts. Measure accuracy percentage.
Exercise 2: Cost Calculator
Build CLI tool that takes text input and model name, outputs estimated cost.
Exercise 3: Dashboard Query
Write SQL query to find top 10 most expensive tool calls by estimated_cost.
Exercise 4: Budget Alert
Implement function that checks if daily costs exceed budget and returns alert message.
Exercise 5: Multi-Model Pricing
Extend estimate_openai_cost() to support GPT-4 and GPT-4-turbo with correct pricing.
👉 Next: Multi-Agent Email Service →
← Previous: OpenAI Service | ↑ Index
Lesson 08b Complete! Services: 2/5 documented (40%)