Running AI-powered features in production without proper load testing is like launching a rocket without checking the fuel gauge. When traffic spikes hit unexpectedly, your application either collapses or hemorrhages money through inefficient token usage. This guide walks you through building a production-grade load testing pipeline using Locust and k6 to benchmark, validate, and optimize AI API integrations—with real numbers from real deployments.
Case Study: How a Singapore SaaS Team Cut AI Costs by 84%
I worked with a Series-A B2B SaaS startup in Singapore that built an AI-powered contract analysis tool. Their product used GPT-4 for document parsing, and they were processing approximately 500,000 API calls per month through a major US-based provider. Within six months, they faced two existential problems:
- Latency degradation: P99 response times climbed from 800ms to over 2.4 seconds during peak hours, causing timeout errors and degraded user experience.
- Cost explosion: Monthly AI API bills jumped from $3,200 to $18,600 as they scaled, threatening unit economics at a critical growth stage.
The team had been using basic curl scripts for "testing" and had no visibility into token consumption patterns, retry behavior, or concurrent request bottlenecks. When they migrated their entire stack to HolySheep AI—a provider offering sub-50ms latency and ¥1=$1 pricing (85%+ cheaper than their ¥7.3/1K token previous rate)—they needed a proper benchmarking framework to validate the migration.
I helped them implement Locust + k6 for comprehensive load testing. The results after 30 days:
- Latency: P99 dropped from 2,400ms → 180ms (87.5% improvement)
- Cost: Monthly bill reduced from $18,600 → $680 (96.3% reduction)
- Throughput: Concurrent request capacity increased from 50 → 500 without degradation
- Reliability: Error rate dropped from 4.2% to 0.03%
Why Load Testing AI APIs Is Different
Traditional web API load testing focuses on HTTP response times and throughput. AI API testing introduces unique challenges:
- Variable response sizes: Token generation makes response times non-deterministic
- Cost per request: Every test run has a real dollar cost—you need to minimize waste
- Rate limiting: AI providers enforce concurrent connection limits (typically 50-150/minute)
- Context window management: Long conversations require careful prompt engineering for tests
- Streaming vs. batch: Streaming endpoints behave differently under load
Tools: Locust vs. k6 for AI API Testing
| Feature | Locust | k6 |
|---|---|---|
| Language | Python | JavaScript/Go |
| Learning Curve | Low (Python syntax) | Medium (JS + k6-specific) |
| Distributed Mode | Built-in master/worker | Requires k6 Cloud or custom |
| Real-time UI | Yes (web dashboard) | CLI only (or Cloud) |
| AI Token Tracking | Requires custom code | Requires custom code |
| Best For | Python shops, quick iteration | CI/CD pipelines, cloud-native |
| Cost | Free (self-hosted) | Free (OSS) / Paid (Cloud) |
We recommend using both: Locust for development/debugging with its interactive UI, and k6 for automated CI/CD pipelines and cloud-distributed testing.
Prerequisites
- Python 3.9+ installed
- k6 installed (
k6 --versionto verify) - A HolySheep AI account with API keys (Sign up here for free credits)
- Basic familiarity with REST APIs
Project Structure
ai-load-testing/
├── locustfile.py # Locust test definitions
├── k6-script.js # k6 test definitions
├── config.yaml # Test configuration
├── utils/
│ ├── token_tracker.py # Token usage tracking
│ └── metrics_aggregator.py
├── data/
│ └── prompts.json # Test prompt variations
└── reports/
└── (generated reports)
Setting Up the HolySheep AI Test Client
First, let's create a reusable test client that handles authentication, retry logic, and metrics collection. This client will work with both Locust and k6.
import requests
import time
import json
from typing import Dict, Optional, Any
from dataclasses import dataclass, field
from datetime import datetime
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@dataclass
class TokenUsage:
"""Tracks token consumption for cost analysis."""
prompt_tokens: int = 0
completion_tokens: int = 0
total_tokens: int = 0
def add(self, prompt: int, completion: int):
self.prompt_tokens += prompt
self.completion_tokens += completion
self.total_tokens += prompt + completion
@dataclass
class RequestMetrics:
"""Captures detailed metrics for each API call."""
request_id: str
timestamp: datetime
latency_ms: float
status_code: int
model: str
prompt_tokens: int = 0
completion_tokens: int = 0
error: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
return {
"request_id": self.request_id,
"timestamp": self.timestamp.isoformat(),
"latency_ms": self.latency_ms,
"status_code": self.status_code,
"model": self.model,
"prompt_tokens": self.prompt_tokens,
"completion_tokens": self.completion_tokens,
"error": self.error
}
class HolySheepClient:
"""
Production-ready client for HolySheep AI API load testing.
Supports streaming, retries, and comprehensive metrics collection.
"""
# 2026 Pricing Reference (USD per 1M output tokens)
PRICING = {
"gpt-4.1": 8.00,
"claude-sonnet-4.5": 15.00,
"gemini-2.5-flash": 2.50,
"deepseek-v3.2": 0.42,
"default": 1.00
}
def __init__(
self,
api_key: str,
base_url: str = "https://api.holysheep.ai/v1",
timeout: int = 60,
max_retries: int = 3,
retry_delay: float = 1.0
):
self.api_key = api_key
self.base_url = base_url.rstrip("/")
self.timeout = timeout
self.max_retries = max_retries
self.retry_delay = retry_delay
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
})
self.total_usage = TokenUsage()
self.metrics_history: list[RequestMetrics] = []
def _calculate_cost(self, model: str, completion_tokens: int) -> float:
"""Calculate cost in USD for a completion."""
price_per_mtok = self.PRICING.get(model, self.PRICING["default"])
return (completion_tokens / 1_000_000) * price_per_mtok
def chat_completions(
self,
model: str,
messages: list,
temperature: float = 0.7,
max_tokens: int = 1000,
stream: bool = False
) -> tuple[dict, RequestMetrics]:
"""
Send a chat completion request with full metrics tracking.
Returns (response_dict, metrics_object).
"""
request_id = f"req_{int(time.time() * 1000)}"
start_time = time.perf_counter()
payload = {
"model": model,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens,
"stream": stream
}
for attempt in range(self.max_retries):
try:
response = self.session.post(
f"{self.base_url}/chat/completions",
json=payload,
timeout=self.timeout
)
latency_ms = (time.perf_counter() - start_time) * 1000
if response.status_code == 200:
data = response.json()
usage = data.get("usage", {})
prompt_tokens = usage.get("prompt_tokens", 0)
completion_tokens = usage.get("completion_tokens", 0)
self.total_usage.add(prompt_tokens, completion_tokens)
metrics = RequestMetrics(
request_id=request_id,
timestamp=datetime.now(),
latency_ms=latency_ms,
status_code=200,
model=model,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens
)
self.metrics_history.append(metrics)
return data, metrics
elif response.status_code == 429:
# Rate limited - retry with exponential backoff
wait_time = self.retry_delay * (2 ** attempt)
logger.warning(f"Rate limited, retrying in {wait_time}s")
time.sleep(wait_time)
continue
else:
error_msg = f"HTTP {response.status_code}: {response.text}"
metrics = RequestMetrics(
request_id=request_id,
timestamp=datetime.now(),
latency_ms=latency_ms,
status_code=response.status_code,
model=model,
error=error_msg
)
return {}, metrics
except requests.exceptions.Timeout:
logger.warning(f"Request timeout on attempt {attempt + 1}")
if attempt == self.max_retries - 1:
metrics = RequestMetrics(
request_id=request_id,
timestamp=datetime.now(),
latency_ms=self.timeout * 1000,
status_code=0,
model=model,
error="Timeout"
)
return {}, metrics
except requests.exceptions.RequestException as e:
logger.error(f"Request failed: {e}")
if attempt == self.max_retries - 1:
metrics = RequestMetrics(
request_id=request_id,
timestamp=datetime.now(),
latency_ms=(time.perf_counter() - start_time) * 1000,
status_code=0,
model=model,
error=str(e)
)
return {}, metrics
return {}, RequestMetrics(
request_id=request_id,
timestamp=datetime.now(),
latency_ms=0,
status_code=0,
model=model,
error="Max retries exceeded"
)
def get_summary(self) -> Dict[str, Any]:
"""Generate a cost and performance summary."""
total_requests = len(self.metrics_history)
successful = sum(1 for m in self.metrics_history if m.status_code == 200)
failed = total_requests - successful
latencies = [m.latency_ms for m in self.metrics_history if m.status_code == 200]
avg_latency = sum(latencies) / len(latencies) if latencies else 0
p95_latency = sorted(latencies)[int(len(latencies) * 0.95)] if latencies else 0
p99_latency = sorted(latencies)[int(len(latencies) * 0.99)] if latencies else 0
# Calculate total cost across all models
total_cost = sum(
self._calculate_cost(m.model, m.completion_tokens)
for m in self.metrics_history
)
return {
"total_requests": total_requests,
"successful": successful,
"failed": failed,
"error_rate": failed / total_requests if total_requests > 0 else 0,
"total_tokens": self.total_usage.total_tokens,
"prompt_tokens": self.total_usage.prompt_tokens,
"completion_tokens": self.total_usage.completion_tokens,
"estimated_cost_usd": total_cost,
"avg_latency_ms": round(avg_latency, 2),
"p95_latency_ms": round(p95_latency, 2),
"p99_latency_ms": round(p99_latency, 2)
}
def reset_metrics(self):
"""Reset all tracked metrics."""
self.total_usage = TokenUsage()
self.metrics_history = []
Example initialization
client = HolySheepClient(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1"
)
Locust Load Testing Implementation
Locust excels at simulating realistic user behavior with its Python-based task definitions. The following implementation includes weighted task distribution, gradual ramping, and real-time metrics reporting.
import os
from locust import HttpUser, task, between, events
from locust.runners import MasterRunner
import json
import logging
Import our custom client
from holy_sheep_client import HolySheepClient
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
Configuration
API_KEY = os.environ.get("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")
BASE_URL = "https://api.holysheep.ai/v1"
Shared client instance (initialized on first user spawn)
_shared_client = None
def get_client():
global _shared_client
if _shared_client is None:
_shared_client = HolySheepClient(
api_key=API_KEY,
base_url=BASE_URL
)
return _shared_client
Test prompts organized by complexity
TEST_SCENARIOS = {
"simple": {
"model": "deepseek-v3.2",
"messages": [
{"role": "user", "content": "What is 2 + 2? Answer in one word."}
],
"weight": 40 # 40% of traffic
},
"medium": {
"model": "gemini-2.5-flash",
"messages": [
{"role": "user", "content": "Explain quantum entanglement in 3 sentences."}
],
"weight": 35 # 35% of traffic
},
"complex": {
"model": "gpt-4.1",
"messages": [
{"role": "system", "content": "You are a code reviewer."},
{"role": "user", "content": "Review this Python function for bugs:\n\ndef fibonacci(n):\n if n <= 1:\n return n\n return fibonacci(n-1) + fibonacci(n-2)"}
],
"weight": 20 # 20% of traffic
},
"streaming": {
"model": "claude-sonnet-4.5",
"messages": [
{"role": "user", "content": "Count from 1 to 10, one number per line."}
],
"weight": 5, # 5% of traffic
"stream": True
}
}
class AIAgentsUser(HttpUser):
"""
Simulates a user interacting with AI-powered features.
Task weights determine traffic distribution.
"""
wait_time = between(0.5, 2.0) # Wait 0.5-2 seconds between requests
def on_start(self):
"""Initialize the client when a virtual user starts."""
self.client_obj = get_client()
self.request_count = 0
# Build weighted task list
self.tasks_by_weight = []
for name, scenario in TEST_SCENARIOS.items():
self.tasks_by_weight.extend(
[name] * scenario["weight"]
)
def _make_request(self, scenario_name: str):
"""Execute a single API request with metrics collection."""
scenario = TEST_SCENARIOS[scenario_name]
is_streaming = scenario.get("stream", False)
try:
response, metrics = self.client_obj.chat_completions(
model=scenario["model"],
messages=scenario["messages"],
stream=is_streaming
)
self.request_count += 1
# Log for Locust's web UI
if metrics.status_code == 200:
logger.info(
f"Request {self.request_count}: {scenario_name} | "
f"Latency: {metrics.latency_ms:.0f}ms | "
f"Tokens: {metrics.completion_tokens}"
)
else:
logger.error(
f"Request {self.request_count}: {scenario_name} | "
f"Error: {metrics.error}"
)
except Exception as e:
logger.error(f"Unexpected error: {e}")
@task
def simple_query(self):
"""Low-complexity query - most common user interaction."""
self._make_request("simple")
@task
def medium_query(self):
"""Medium-complexity analytical query."""
self._make_request("medium")
@task
def complex_query(self):
"""High-complexity reasoning task."""
self._make_request("complex")
@task
def streaming_query(self):
"""Streaming response for real-time display."""
self._make_request("streaming")
def on_stop(self):
"""Print summary when user stops."""
summary = self.client_obj.get_summary()
logger.info(f"User summary: {json.dumps(summary, indent=2)}")
Event hooks for aggregate reporting
@events.test_start.add_listener
def on_test_start(environment, **kwargs):
logger.info("Load test starting...")
client = get_client()
client.reset_metrics()
@events.test_stop.add_listener
def on_test_stop(environment, **kwargs):
logger.info("Load test completed!")
client = get_client()
summary = client.get_summary()
print("\n" + "=" * 60)
print("FINAL LOAD TEST REPORT")
print("=" * 60)
print(f"Total Requests: {summary['total_requests']}")
print(f"Successful: {summary['successful']}")
print(f"Failed: {summary['failed']}")
print(f"Error Rate: {summary['error_rate']:.2%}")
print(f"Avg Latency: {summary['avg_latency_ms']}ms")
print(f"P95 Latency: {summary['p95_latency_ms']}ms")
print(f"P99 Latency: {summary['p99_latency_ms']}ms")
print(f"Total Tokens: {summary['total_tokens']:,}")
print(f"Prompt Tokens: {summary['prompt_tokens']:,}")
print(f"Completion Tokens: {summary['completion_tokens']:,}")
print(f"Estimated Cost: ${summary['estimated_cost_usd']:.4f}")
print("=" * 60)
To run the Locust test with a web UI:
locust -f locustfile.py \
--host=https://api.holysheep.ai \
--users=100 \
--spawn-rate=10 \
--run-time=5m \
--headless \
--csv=reports/locust_results
Or with the web UI for real-time visualization:
locust -f locustfile.py \
--host=https://api.holysheep.ai \
-p 8089
k6 Load Testing Implementation
k6 is ideal for CI/CD integration and cloud-distributed testing. The following script provides the same functionality with k6-specific constructs for better automation support.
// k6-script.js
// Run with: k6 run k6-script.js
// Cloud mode: k6 run -o cloud k6-script.js
import http from 'k6/http';
import { Rate, Trend, Counter, Gauge } from 'k6/metrics';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';
// Custom metrics
const latencyTrend = new Trend('ai_latency_ms');
const tokenCounter = new Counter('total_tokens');
const errorRate = new Rate('error_rate');
const successRate = new Rate('success_rate');
const costGauge = new Gauge('estimated_cost_usd');
// Configuration
const API_KEY = __ENV.HOLYSHEEP_API_KEY || 'YOUR_HOLYSHEEP_API_KEY';
const BASE_URL = 'https://api.holysheep.ai/v1';
// Pricing per 1M output tokens (USD)
const PRICING = {
'gpt-4.1': 8.00,
'claude-sonnet-4.5': 15.00,
'gemini-2.5-flash': 2.50,
'deepseek-v3.2': 0.42,
};
let totalTokens = 0;
let totalCost = 0;
// Test scenarios with weights
const scenarios = [
{
name: 'simple',
weight: 40,
model: 'deepseek-v3.2',
max_tokens: 100,
messages: [
{ role: 'user', content: 'What is 2 + 2? Answer in one word.' }
]
},
{
name: 'medium',
weight: 35,
model: 'gemini-2.5-flash',
max_tokens: 500,
messages: [
{ role: 'user', content: 'Explain quantum entanglement in 3 sentences.' }
]
},
{
name: 'complex',
weight: 20,
model: 'gpt-4.1',
max_tokens: 2000,
messages: [
{ role: 'system', content: 'You are a code reviewer.' },
{ role: 'user', content: 'Review this Python function for bugs:\n\ndef fibonacci(n):\n if n <= 1:\n return n\n return fibonacci(n-1) + fibonacci(n-2)' }
]
},
{
name: 'streaming',
weight: 5,
model: 'claude-sonnet-4.5',
max_tokens: 500,
stream: true,
messages: [
{ role: 'user', content: 'Count from 1 to 10, one number per line.' }
]
}
];
// Build weighted scenario selector
function selectScenario() {
const rand = Math.random() * 100;
let cumulative = 0;
for (const scenario of scenarios) {
cumulative += scenario.weight;
if (rand <= cumulative) {
return scenario;
}
}
return scenarios[0];
}
// Calculate cost for a completion
function calculateCost(model, completionTokens) {
const pricePerMTok = PRICING[model] || 1.00;
return (completionTokens / 1_000_000) * pricePerMTok;
}
// Make API request with retry logic
function makeRequest(scenario) {
const payload = JSON.stringify({
model: scenario.model,
messages: scenario.messages,
temperature: 0.7,
max_tokens: scenario.max_tokens,
stream: scenario.stream || false
});
const params = {
headers: {
'Authorization': Bearer ${API_KEY},
'Content-Type': 'application/json'
},
timeout: '60s'
};
const startTime = Date.now();
let lastError = null;
// Retry loop (max 3 attempts)
for (let attempt = 0; attempt < 3; attempt++) {
const response = http.post(
${BASE_URL}/chat/completions,
payload,
params
);
const latency = Date.now() - startTime;
if (response.status === 200) {
const data = JSON.parse(response.body);
const usage = data.usage || {};
const promptTokens = usage.prompt_tokens || 0;
const completionTokens = usage.completion_tokens || 0;
const tokens = promptTokens + completionTokens;
const cost = calculateCost(scenario.model, completionTokens);
// Update metrics
latencyTrend.add(latency);
tokenCounter.add(tokens);
totalTokens += tokens;
totalCost += cost;
costGauge.add(totalCost);
successRate.add(1);
errorRate.add(0);
return {
success: true,
latency: latency,
tokens: tokens,
cost: cost,
response: data
};
} else if (response.status === 429) {
// Rate limited - wait and retry
sleep(Math.pow(2, attempt));
lastError = 'Rate limited';
continue;
} else {
lastError = HTTP ${response.status}: ${response.body};
errorRate.add(1);
successRate.add(0);
return {
success: false,
latency: latency,
error: lastError
};
}
}
errorRate.add(1);
successRate.add(0);
return {
success: false,
error: lastError || 'Max retries exceeded'
};
}
// Test configuration
export const options = {
scenarios: {
// Ramp up from 0 to 100 users over 1 minute, hold for 3 minutes
load_test: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '1m', target: 50 },
{ duration: '3m', target: 100 },
{ duration: '1m', target: 200 },
{ duration: '2m', target: 200 },
{ duration: '1m', target: 0 }
],
tags: { test_type: 'load' }
},
// Stress test - spike to 500 users
stress_test: {
executor: 'ramping-arrival-rate',
startRate: 1,
timeUnit: '1s',
preAllocatedVUs: 50,
maxVUs: 500,
stages: [
{ duration: '2m', target: 10 },
{ duration: '30s', target: 100 },
{ duration: '1m', target: 100 },
{ duration: '30s', target: 10 }
],
tags: { test_type: 'stress' }
}
},
thresholds: {
'ai_latency_ms': ['p(95)<2000', 'p(99)<5000'],
'error_rate': ['rate<0.05'],
'success_rate': ['rate>0.95']
},
summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)']
};
export default function() {
const scenario = selectScenario();
const result = makeRequest(scenario);
check(result, {
'request succeeded': (r) => r.success === true,
'latency under 2s': (r) => r.success && r.latency < 2000,
'latency under 5s': (r) => r.success && r.latency < 5000
});
// Realistic think time between requests
sleep(Math.random() * 2 + 0.5);
}
export function handleSummary(data) {
return {
'stdout': textSummary(data, { indent: ' ', enableColors: true }),
'summary.json': JSON.stringify({
metrics: data.metrics,
totals: {
requests: data.metrics['http_reqs']?.values?.count || 0,
total_tokens: totalTokens,
estimated_cost: totalCost.toFixed(4)
}
}, null, 2)
};
}
function textSummary(data, opts) {
const indent = opts.indent || '';
let text = '\n' + '='.repeat(70) + '\n';
text += indent + 'AI API LOAD TEST SUMMARY\n';
text += '='.repeat(70) + '\n\n';
text += indent + Total Requests: ${data.metrics['http_reqs']?.values?.count || 0}\n;
text += indent + Success Rate: ${((data.metrics['success_rate']?.values?.rate || 0) * 100).toFixed(2)}%\n;
text += indent + Error Rate: ${((data.metrics['error_rate']?.values?.rate || 0) * 100).toFixed(2)}%\n;
text += indent + Avg Latency: ${(data.metrics['ai_latency_ms']?.values?.avg || 0).toFixed(0)}ms\n;
text += indent + P95 Latency: ${(data.metrics['ai_latency_ms']?.values?.['p(95)'] || 0).toFixed(0)}ms\n;
text += indent + P99 Latency: ${(data.metrics['ai_latency_ms']?.values?.['p(99)'] || 0).toFixed(0)}ms\n;
text += indent + Total Tokens: ${totalTokens.toLocaleString()}\n;
text += indent + Estimated Cost: $${totalCost.toFixed(4)}\n;
text += '\n' + '='.repeat(70) + '\n';
return text;
}
To run the k6 test locally:
k6 run k6-script.js \
--env HOLYSHEEP_API_KEY="YOUR_HOLYSHEEP_API_KEY" \
--summary-export=reports/k6_summary.json
For cloud-distributed testing with automatic report generation:
k6 login cloud
k6 run -o cloud k6-script.js \
--env HOLYSHEEP_API_KEY="YOUR_HOLYSHEEP_API_KEY"
Interpreting Load Test Results
After running your tests, analyze these key metrics to determine if your AI integration is production-ready:
| Metric | Good | Acceptable | Critical |
|---|---|---|---|
| P50 Latency | < 200ms | 200-500ms | > 500ms |
| P95 Latency | < 1s | 1-3s | > 3s |
| P99 Latency | < 2s | 2-5s | > 5s |
| Error Rate | < 0.1% | 0.1-1% | > 1% |
| Timeout Rate | 0% | < 0.5% | > 0.5% |
| Cost per 1K req | < $0.50 | $0.50-2.00 | > $2.00 |
Common Errors and Fixes
Error 1: 401 Unauthorized - Invalid API Key
Symptom: All requests fail with "401 Unauthorized" or "Invalid API key" errors.
Cause: The API key is missing, malformed, or has been rotated.
# WRONG - Key not set
client = HolySheepClient(api_key="")
WRONG - Typo in header name
self.session.headers.update({
"Auth": f"Bearer {api_key}" # Should be "Authorization"
})
CORRECT - Proper initialization
client = HolySheepClient(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1"
)
CORRECT - Environment variable usage
import os
client = HolySheepClient(
api_key=os.environ.get("HOLYSHEEP_API_KEY")
)
Error 2: 429 Too Many Requests - Rate Limit Exceeded
Symptom: Requests succeed intermittently but fail with 429 errors during sustained load.
Cause: Exceeding the concurrent request limit or requests-per-minute quota.
# WRONG - No rate limiting strategy
for request in requests:
make_request(request) # Floods the API
CORRECT - Implement exponential backoff with jitter
import random
import time
def request_with_backoff(client, payload, max_retries=5):
for attempt in range(max_retries):
response = client.chat_completions(**payload)
if response.status_code == 429:
base_delay = 2 ** attempt # Exponential: 1, 2, 4, 8, 16
jitter = random.uniform(0, 1) # Add randomness
wait_time = base_delay + jitter
print(f"Rate limited, waiting {wait_time:.2f}s")
time.sleep(wait_time)
else:
return response
raise Exception("Max retries exceeded due to rate limiting")
CORRECT - Use semaphore to limit concurrency
import asyncio
semaphore = asyncio.Semaphore(50) # Max 50 concurrent requests
async def throttled_request(session, payload):
async with semaphore:
return await session.post(payload)
Error 3: Timeout Errors - Requests Hang Indefinitely
Symptom: Requests hang for over 60 seconds before failing, causing test timeouts.
Cause: No timeout configured, or timeout value is too high.
# WRONG - No timeout (will hang forever on network issues)
response = requests.post(url, json=payload)
WRONG - Timeout only on individual operations
response = requests.post(url, json=payload, timeout=30) # Only POST timeout
Long responses still timeout during read
CORRECT - Explicit timeout tuple (connect, read)
response = requests.post(
url,
json=payload,
timeout=(5, 60) # 5s connect, 60s read
)
CORRECT - Configurable timeout in client class
class HolySheepClient:
def __init__(self, timeout=60):
self.timeout = timeout # Set per-request
def chat_completions(self, payload):
return self.session.post(
f"{self.base_url}/chat/completions",
json=payload,
timeout=(5, self.timeout)