mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-08-09 11:04:36 +00:00
568 lines
14 KiB
Markdown
568 lines
14 KiB
Markdown
# Custom Pricing
|
|
|
|
This guide covers advanced pricing strategies and customization options for Routstr Core.
|
|
|
|
## Pricing Models Overview
|
|
|
|
Routstr supports three pricing models:
|
|
|
|
1. **Fixed Pricing** - Simple per-request fee
|
|
2. **Token-Based Pricing** - Charge per input/output token
|
|
3. **Model-Based Pricing** - Dynamic pricing from models.json
|
|
|
|
## Model-Based Pricing
|
|
|
|
### Configuration
|
|
|
|
Enable model-based pricing (default behavior):
|
|
|
|
```bash
|
|
# .env
|
|
FIXED_PRICING=false
|
|
MODELS_PATH=/app/config/models.json
|
|
EXCHANGE_FEE=1.005 # 0.5% exchange fee
|
|
UPSTREAM_PROVIDER_FEE=1.05 # 5% provider margin
|
|
```
|
|
|
|
### Custom Models File
|
|
|
|
Create a `models.json` with your pricing:
|
|
|
|
```json
|
|
{
|
|
"models": [
|
|
{
|
|
"id": "gpt-4",
|
|
"name": "GPT-4",
|
|
"description": "Advanced reasoning model",
|
|
"context_length": 8192,
|
|
"pricing": {
|
|
"prompt": "0.03", // USD per 1K tokens
|
|
"completion": "0.06", // USD per 1K tokens
|
|
"request": "0.0001", // Fixed per-request fee
|
|
"image": "0", // For multimodal models
|
|
"web_search": "0.005", // Additional features
|
|
"internal_reasoning": "0.01"
|
|
},
|
|
"supported_features": [
|
|
"function_calling",
|
|
"vision",
|
|
"json_mode"
|
|
],
|
|
"deprecation_date": null,
|
|
"replacement_model": null
|
|
},
|
|
{
|
|
"id": "custom-model",
|
|
"name": "Custom Fine-tuned Model",
|
|
"pricing": {
|
|
"prompt": "0.001",
|
|
"completion": "0.002",
|
|
"request": "0.00005"
|
|
},
|
|
"minimum_charge": "0.0001" // Minimum charge per request
|
|
}
|
|
],
|
|
"default_pricing": {
|
|
"prompt": "0.002",
|
|
"completion": "0.002",
|
|
"request": "0"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Dynamic Price Updates
|
|
|
|
Automatically fetch prices from providers:
|
|
|
|
```python
|
|
# scripts/update_prices.py
|
|
import asyncio
|
|
import httpx
|
|
import json
|
|
|
|
async def fetch_openrouter_models():
|
|
"""Fetch current model pricing from OpenRouter."""
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(
|
|
"https://openrouter.ai/api/v1/models"
|
|
)
|
|
return response.json()
|
|
|
|
async def update_models_json():
|
|
"""Update local models.json with latest prices."""
|
|
data = await fetch_openrouter_models()
|
|
|
|
models = []
|
|
for model in data['data']:
|
|
models.append({
|
|
"id": model['id'],
|
|
"name": model['name'],
|
|
"pricing": {
|
|
"prompt": model['pricing']['prompt'],
|
|
"completion": model['pricing']['completion'],
|
|
"request": model['pricing'].get('request', '0')
|
|
},
|
|
"context_length": model.get('context_length', 4096)
|
|
})
|
|
|
|
with open('models.json', 'w') as f:
|
|
json.dump({"models": models}, f, indent=2)
|
|
|
|
# Run periodically
|
|
if __name__ == "__main__":
|
|
asyncio.run(update_models_json())
|
|
```
|
|
|
|
## Token-Based Pricing
|
|
|
|
### Configuration
|
|
|
|
Set up token-based pricing overrides:
|
|
|
|
```bash
|
|
# .env
|
|
FIXED_PRICING=false # use model pricing
|
|
FIXED_COST_PER_REQUEST=1 # optional base fee
|
|
FIXED_PER_1K_INPUT_TOKENS=5 # optional override
|
|
FIXED_PER_1K_OUTPUT_TOKENS=15 # optional override
|
|
```
|
|
|
|
### Custom Token Counting
|
|
|
|
Override default token counting:
|
|
|
|
```python
|
|
from tiktoken import encoding_for_model
|
|
|
|
class CustomTokenCounter:
|
|
def __init__(self):
|
|
self.encodings = {}
|
|
|
|
def count_tokens(
|
|
self,
|
|
text: str,
|
|
model: str
|
|
) -> int:
|
|
"""Custom token counting logic."""
|
|
# Cache encodings
|
|
if model not in self.encodings:
|
|
try:
|
|
self.encodings[model] = encoding_for_model(model)
|
|
except:
|
|
# Fallback encoding
|
|
self.encodings[model] = encoding_for_model("gpt-3.5-turbo")
|
|
|
|
encoding = self.encodings[model]
|
|
|
|
# Special handling for certain content
|
|
if text.startswith("```"):
|
|
# Code blocks might need special handling
|
|
tokens = encoding.encode(text)
|
|
return len(tokens) * 1.1 # 10% markup for code
|
|
|
|
return len(encoding.encode(text))
|
|
```
|
|
|
|
## Advanced Pricing Strategies
|
|
|
|
### Time-Based Pricing
|
|
|
|
Implement peak/off-peak pricing:
|
|
|
|
```python
|
|
from datetime import datetime
|
|
import pytz
|
|
|
|
class TimeBased PricingStrategy:
|
|
def __init__(self):
|
|
self.timezone = pytz.timezone('US/Eastern')
|
|
self.peak_hours = [(9, 17)] # 9 AM - 5 PM
|
|
self.peak_multiplier = 1.5
|
|
self.weekend_discount = 0.8
|
|
|
|
def get_price_multiplier(self) -> float:
|
|
"""Calculate price multiplier based on time."""
|
|
now = datetime.now(self.timezone)
|
|
|
|
# Weekend discount
|
|
if now.weekday() >= 5: # Saturday or Sunday
|
|
return self.weekend_discount
|
|
|
|
# Peak hours surcharge
|
|
hour = now.hour
|
|
for start, end in self.peak_hours:
|
|
if start <= hour < end:
|
|
return self.peak_multiplier
|
|
|
|
# Off-peak standard pricing
|
|
return 1.0
|
|
|
|
def apply_to_cost(self, base_cost: int) -> int:
|
|
"""Apply time-based pricing to cost."""
|
|
multiplier = self.get_price_multiplier()
|
|
return int(base_cost * multiplier)
|
|
```
|
|
|
|
### Model-Specific Surcharges
|
|
|
|
Add custom fees for specific models:
|
|
|
|
```python
|
|
class ModelSurchargeStrategy:
|
|
def __init__(self):
|
|
self.surcharges = {
|
|
"gpt-4-turbo": 1.1, # 10% premium
|
|
"claude-3-opus": 1.15, # 15% premium
|
|
"dall-e-3-hd": 1.25, # 25% premium for HD
|
|
}
|
|
|
|
self.discounts = {
|
|
"gpt-3.5-turbo": 0.95, # 5% discount
|
|
"deprecated-model": 0.8, # 20% discount
|
|
}
|
|
|
|
def get_model_multiplier(self, model: str) -> float:
|
|
"""Get price multiplier for model."""
|
|
if model in self.surcharges:
|
|
return self.surcharges[model]
|
|
elif model in self.discounts:
|
|
return self.discounts[model]
|
|
return 1.0
|
|
```
|
|
|
|
### Geographic Pricing
|
|
|
|
Adjust pricing based on client location:
|
|
|
|
```python
|
|
import geoip2.database
|
|
|
|
class GeographicPricingStrategy:
|
|
def __init__(self):
|
|
self.reader = geoip2.database.Reader('GeoLite2-Country.mmdb')
|
|
self.country_multipliers = {
|
|
'US': 1.0,
|
|
'GB': 1.0,
|
|
'DE': 1.0,
|
|
'IN': 0.7, # 30% discount
|
|
'BR': 0.8, # 20% discount
|
|
'NG': 0.6, # 40% discount
|
|
}
|
|
self.default_multiplier = 0.9
|
|
|
|
def get_country_multiplier(self, ip_address: str) -> float:
|
|
"""Get price multiplier based on country."""
|
|
try:
|
|
response = self.reader.country(ip_address)
|
|
country_code = response.country.iso_code
|
|
return self.country_multipliers.get(
|
|
country_code,
|
|
self.default_multiplier
|
|
)
|
|
except:
|
|
return 1.0 # Default pricing if lookup fails
|
|
```
|
|
|
|
## Cost Calculation Pipeline
|
|
|
|
### Implementing Custom Calculator
|
|
|
|
```python
|
|
from abc import ABC, abstractmethod
|
|
|
|
class CostCalculator(ABC):
|
|
@abstractmethod
|
|
async def calculate(
|
|
self,
|
|
request_data: dict,
|
|
usage_data: dict,
|
|
context: dict
|
|
) -> CostResult:
|
|
pass
|
|
|
|
class CompositeCostCalculator(CostCalculator):
|
|
"""Combine multiple pricing strategies."""
|
|
|
|
def __init__(self):
|
|
self.strategies = [
|
|
BaseCostCalculator(),
|
|
TimeBasedPricingStrategy(),
|
|
ModelSurchargeStrategy(),
|
|
GeographicPricingStrategy()
|
|
]
|
|
|
|
async def calculate(
|
|
self,
|
|
request_data: dict,
|
|
usage_data: dict,
|
|
context: dict
|
|
) -> CostResult:
|
|
# Start with base cost
|
|
base_cost = await self.strategies[0].calculate(
|
|
request_data, usage_data, context
|
|
)
|
|
|
|
# Apply each strategy
|
|
final_cost = base_cost.total_msats
|
|
breakdown = {"base": base_cost.total_msats}
|
|
|
|
for strategy in self.strategies[1:]:
|
|
multiplier = await strategy.get_multiplier(context)
|
|
adjustment = final_cost * (multiplier - 1)
|
|
final_cost += adjustment
|
|
breakdown[strategy.__class__.__name__] = adjustment
|
|
|
|
return CostResult(
|
|
total_msats=int(final_cost),
|
|
breakdown=breakdown
|
|
)
|
|
```
|
|
|
|
### Integration with Routstr
|
|
|
|
```python
|
|
# In routstr/payment/cost_calculation.py
|
|
async def calculate_request_cost(
|
|
model: str,
|
|
prompt_tokens: int,
|
|
completion_tokens: int,
|
|
request_type: str,
|
|
context: dict
|
|
) -> CostData:
|
|
"""Enhanced cost calculation with custom strategies."""
|
|
|
|
# Use custom calculator if configured
|
|
if os.getenv("USE_CUSTOM_PRICING", "false").lower() == "true":
|
|
calculator = CompositeCostCalculator()
|
|
result = await calculator.calculate(
|
|
request_data={
|
|
"model": model,
|
|
"type": request_type
|
|
},
|
|
usage_data={
|
|
"prompt_tokens": prompt_tokens,
|
|
"completion_tokens": completion_tokens
|
|
},
|
|
context=context
|
|
)
|
|
return result
|
|
|
|
# Fall back to standard calculation
|
|
return standard_calculate_cost(...)
|
|
```
|
|
|
|
## Monitoring Pricing
|
|
|
|
### Price Analytics
|
|
|
|
Track pricing effectiveness:
|
|
|
|
```python
|
|
class PricingAnalytics:
|
|
async def analyze_pricing(
|
|
self,
|
|
start_date: datetime,
|
|
end_date: datetime
|
|
):
|
|
"""Analyze pricing performance."""
|
|
# Average cost per request by model
|
|
model_costs = await self.get_average_costs_by_model(
|
|
start_date, end_date
|
|
)
|
|
|
|
# Revenue by pricing strategy
|
|
strategy_revenue = await self.get_revenue_by_strategy(
|
|
start_date, end_date
|
|
)
|
|
|
|
# Price elasticity
|
|
elasticity = await self.calculate_price_elasticity()
|
|
|
|
return {
|
|
"model_costs": model_costs,
|
|
"strategy_revenue": strategy_revenue,
|
|
"price_elasticity": elasticity,
|
|
"recommendations": self.generate_recommendations(
|
|
model_costs, elasticity
|
|
)
|
|
}
|
|
```
|
|
|
|
### A/B Testing Prices
|
|
|
|
Test different pricing strategies:
|
|
|
|
```python
|
|
class PricingExperiment:
|
|
def __init__(self):
|
|
self.experiments = {
|
|
"exp_001": {
|
|
"name": "10% discount test",
|
|
"group_a": {"multiplier": 1.0},
|
|
"group_b": {"multiplier": 0.9},
|
|
"allocation": 0.5 # 50/50 split
|
|
}
|
|
}
|
|
|
|
def assign_group(self, api_key_id: int) -> str:
|
|
"""Assign API key to experiment group."""
|
|
# Consistent assignment based on key ID
|
|
import hashlib
|
|
hash_value = int(hashlib.md5(
|
|
str(api_key_id).encode()
|
|
).hexdigest()[:8], 16)
|
|
|
|
return "group_b" if (hash_value % 100) < 50 else "group_a"
|
|
|
|
def get_experiment_multiplier(
|
|
self,
|
|
api_key_id: int,
|
|
experiment_id: str
|
|
) -> float:
|
|
"""Get price multiplier for experiment."""
|
|
experiment = self.experiments.get(experiment_id)
|
|
if not experiment:
|
|
return 1.0
|
|
|
|
group = self.assign_group(api_key_id)
|
|
return experiment[group]["multiplier"]
|
|
```
|
|
|
|
## Configuration Examples
|
|
|
|
### Enterprise Pricing
|
|
|
|
```json
|
|
{
|
|
"models": [
|
|
{
|
|
"id": "gpt-4-enterprise",
|
|
"name": "GPT-4 Enterprise",
|
|
"pricing": {
|
|
"prompt": "0.02",
|
|
"completion": "0.04"
|
|
},
|
|
"minimum_commitment": "1000", // $1000/month minimum
|
|
"sla": {
|
|
"uptime": "99.9%",
|
|
"support_response": "1 hour",
|
|
"dedicated_capacity": true
|
|
}
|
|
}
|
|
],
|
|
"enterprise_features": {
|
|
"priority_queue": true,
|
|
"custom_models": true,
|
|
"audit_logs": true,
|
|
"sso": true
|
|
}
|
|
}
|
|
```
|
|
|
|
### Budget-Friendly Options
|
|
|
|
```json
|
|
{
|
|
"models": [
|
|
{
|
|
"id": "gpt-3.5-turbo-budget",
|
|
"name": "GPT-3.5 Turbo Budget",
|
|
"pricing": {
|
|
"prompt": "0.0005",
|
|
"completion": "0.001"
|
|
},
|
|
"restrictions": {
|
|
"max_tokens_per_request": 1000,
|
|
"requests_per_minute": 10,
|
|
"peak_hours_blocked": true
|
|
}
|
|
}
|
|
],
|
|
"prepaid_packages": [
|
|
{
|
|
"name": "Starter Pack",
|
|
"price_usd": 10,
|
|
"tokens_included": 10000000,
|
|
"expires_days": 30
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Price Calculation Issues
|
|
|
|
```python
|
|
# Debug pricing
|
|
async def debug_price_calculation(
|
|
model: str,
|
|
tokens: dict,
|
|
api_key_id: int
|
|
):
|
|
"""Debug price calculation step by step."""
|
|
print(f"Model: {model}")
|
|
print(f"Tokens: {tokens}")
|
|
|
|
# Base price
|
|
base_price = get_model_price(model)
|
|
print(f"Base price: {base_price}")
|
|
|
|
# Token cost
|
|
token_cost = calculate_token_cost(base_price, tokens)
|
|
print(f"Token cost: {token_cost}")
|
|
|
|
# Strategies
|
|
strategies = get_active_strategies()
|
|
for strategy in strategies:
|
|
multiplier = await strategy.get_multiplier(api_key_id)
|
|
print(f"{strategy.name}: {multiplier}x")
|
|
|
|
# Final cost
|
|
final_cost = apply_all_strategies(token_cost, api_key_id)
|
|
print(f"Final cost: {final_cost} msats")
|
|
|
|
return final_cost
|
|
```
|
|
|
|
### Common Issues
|
|
|
|
1. **Prices Not Updating**
|
|
- Check `MODELS_PATH` is correct
|
|
- Verify file permissions
|
|
- Check background task logs
|
|
|
|
2. **Wrong Currency Conversion**
|
|
- Verify BTC/USD rate source
|
|
- Check `EXCHANGE_FEE` setting
|
|
- Monitor rate update frequency
|
|
|
|
3. **Discounts Not Applied**
|
|
- Verify strategy configuration
|
|
- Check API key metadata
|
|
- Review transaction history
|
|
|
|
## Best Practices
|
|
|
|
1. **Transparent Pricing**
|
|
- Publish pricing clearly
|
|
- Show cost breakdowns
|
|
- Notify of price changes
|
|
|
|
2. **Fair Pricing**
|
|
- Regular competitive analysis
|
|
- Consider user feedback
|
|
- Offer budget options
|
|
|
|
3. **Performance**
|
|
- Cache price calculations
|
|
- Optimize database queries
|
|
- Monitor calculation time
|
|
|
|
## Next Steps
|
|
|
|
- [Migrations](migrations.md) - Database migration guide
|
|
- [API Endpoints](../api/endpoints.md) - Pricing endpoints
|
|
- [Monitoring](../user-guide/admin-dashboard.md) - Track pricing metrics
|