mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-07-31 07:46:15 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f160384e14 | ||
|
|
4ee6801413 | ||
|
|
e4153920a9 | ||
|
|
1b4e092242 | ||
|
|
5416cefd87 |
@@ -5,6 +5,7 @@ from pydantic.v1 import BaseModel
|
||||
from ..core import get_logger
|
||||
from ..core.db import AsyncSession
|
||||
from ..core.settings import settings
|
||||
from .price import sats_usd_price
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -64,13 +65,48 @@ async def calculate_cost( # todo: can be sync
|
||||
)
|
||||
return cost_data
|
||||
|
||||
usage_data = response_data["usage"]
|
||||
|
||||
if "cost" in usage_data and usage_data["cost"] is not None:
|
||||
try:
|
||||
usd_cost = float(usage_data["cost"])
|
||||
sats_per_usd = 1.0 / sats_usd_price()
|
||||
cost_in_sats = usd_cost * sats_per_usd
|
||||
cost_in_msats = math.ceil(cost_in_sats * 1000)
|
||||
|
||||
logger.info(
|
||||
"Using cost field from usage data",
|
||||
extra={
|
||||
"usd_cost": usd_cost,
|
||||
"cost_in_sats": cost_in_sats,
|
||||
"cost_in_msats": cost_in_msats,
|
||||
"model": response_data.get("model", "unknown"),
|
||||
},
|
||||
)
|
||||
|
||||
return CostData(
|
||||
base_msats=0,
|
||||
input_msats=0, # Cost field doesn't break down by token type
|
||||
output_msats=0,
|
||||
total_msats=cost_in_msats,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Error using cost field, falling back to token-based calculation",
|
||||
extra={
|
||||
"error": str(e),
|
||||
"cost_value": usage_data.get("cost"),
|
||||
"model": response_data.get("model", "unknown"),
|
||||
},
|
||||
)
|
||||
# Fall through to token-based calculation
|
||||
|
||||
MSATS_PER_1K_INPUT_TOKENS: float = (
|
||||
float(settings.fixed_per_1k_input_tokens) * 1000.0
|
||||
)
|
||||
MSATS_PER_1K_OUTPUT_TOKENS: float = (
|
||||
float(settings.fixed_per_1k_output_tokens) * 1000.0
|
||||
)
|
||||
MSATS_PER_1K_IMAGE_COMPLETION_TOKENS: float = 0.0
|
||||
|
||||
if not settings.fixed_pricing:
|
||||
response_model = response_data.get("model", "")
|
||||
@@ -105,13 +141,11 @@ async def calculate_cost( # todo: can be sync
|
||||
try:
|
||||
mspp = float(model_obj.sats_pricing.prompt)
|
||||
mspc = float(model_obj.sats_pricing.completion)
|
||||
mspci = float(getattr(model_obj.sats_pricing, "completion_image", 0.0))
|
||||
except Exception:
|
||||
return CostDataError(message="Invalid pricing data", code="pricing_invalid")
|
||||
|
||||
MSATS_PER_1K_INPUT_TOKENS = mspp * 1_000_000.0
|
||||
MSATS_PER_1K_OUTPUT_TOKENS = mspc * 1_000_000.0
|
||||
MSATS_PER_1K_IMAGE_COMPLETION_TOKENS = mspci * 1_000_000.0
|
||||
|
||||
logger.info(
|
||||
"Applied model-specific pricing",
|
||||
@@ -119,7 +153,6 @@ async def calculate_cost( # todo: can be sync
|
||||
"model": response_model,
|
||||
"input_price_msats_per_1k": MSATS_PER_1K_INPUT_TOKENS,
|
||||
"output_price_msats_per_1k": MSATS_PER_1K_OUTPUT_TOKENS,
|
||||
"image_completion_price_msats_per_1k": MSATS_PER_1K_IMAGE_COMPLETION_TOKENS,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -132,44 +165,18 @@ async def calculate_cost( # todo: can be sync
|
||||
},
|
||||
)
|
||||
return cost_data
|
||||
usage_data = response_data["usage"]
|
||||
|
||||
input_tokens = usage_data.get("prompt_tokens", 0)
|
||||
output_tokens = usage_data.get("completion_tokens", 0)
|
||||
|
||||
# added for response api
|
||||
input_tokens = (
|
||||
input_tokens if input_tokens != 0 else usage_data.get("input_tokens", 0)
|
||||
)
|
||||
output_tokens = (
|
||||
output_tokens if output_tokens != 0 else usage_data.get("output_tokens", 0)
|
||||
)
|
||||
|
||||
# Calculate image completion cost
|
||||
image_completion_msats = 0.0
|
||||
if MSATS_PER_1K_IMAGE_COMPLETION_TOKENS > 0:
|
||||
completion_details = usage_data.get("completion_tokens_details", {})
|
||||
image_tokens = completion_details.get("image_tokens", 0)
|
||||
|
||||
if image_tokens > 0:
|
||||
if output_tokens >= image_tokens:
|
||||
output_tokens -= image_tokens
|
||||
|
||||
image_completion_msats = round(
|
||||
image_tokens / 1000 * MSATS_PER_1K_IMAGE_COMPLETION_TOKENS, 3
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Calculated image completion cost",
|
||||
extra={
|
||||
"image_tokens": image_tokens,
|
||||
"image_completion_msats": image_completion_msats,
|
||||
},
|
||||
)
|
||||
input_tokens = input_tokens if input_tokens != 0 else usage_data.get("input_tokens", 0)
|
||||
output_tokens = output_tokens if output_tokens != 0 else usage_data.get("output_tokens", 0)
|
||||
|
||||
input_msats = round(input_tokens / 1000 * MSATS_PER_1K_INPUT_TOKENS, 3)
|
||||
|
||||
output_msats = round(output_tokens / 1000 * MSATS_PER_1K_OUTPUT_TOKENS, 3)
|
||||
token_based_cost = math.ceil(input_msats + output_msats + image_completion_msats)
|
||||
token_based_cost = math.ceil(input_msats + output_msats)
|
||||
|
||||
logger.info(
|
||||
"Calculated token-based cost",
|
||||
@@ -178,7 +185,6 @@ async def calculate_cost( # todo: can be sync
|
||||
"output_tokens": output_tokens,
|
||||
"input_cost_msats": input_msats,
|
||||
"output_cost_msats": output_msats,
|
||||
"image_completion_msats": image_completion_msats,
|
||||
"total_cost_msats": token_based_cost,
|
||||
"model": response_data.get("model", "unknown"),
|
||||
},
|
||||
|
||||
@@ -31,7 +31,6 @@ class Pricing(BaseModel):
|
||||
completion: float
|
||||
request: float = 0.0
|
||||
image: float = 0.0
|
||||
completion_image: float = 0.0
|
||||
web_search: float = 0.0
|
||||
internal_reasoning: float = 0.0
|
||||
input_cache_read: float = 0.0
|
||||
@@ -41,13 +40,6 @@ class Pricing(BaseModel):
|
||||
max_cost: float = 0.0 # in sats not msats
|
||||
|
||||
|
||||
PRICING_OVERRIDES = {
|
||||
"gemini-3-pro-image-preview": {"completion_image": 0.00012},
|
||||
"gemini-2.5-flash-image": {"completion_image": 0.00003},
|
||||
"gemini-2.0-flash": {"completion_image": 0.00003},
|
||||
}
|
||||
|
||||
|
||||
class TopProvider(BaseModel):
|
||||
context_length: int | None = None
|
||||
max_completion_tokens: int | None = None
|
||||
@@ -124,16 +116,6 @@ async def async_fetch_openrouter_models(source_filter: str | None = None) -> lis
|
||||
if not _has_valid_pricing(model):
|
||||
continue
|
||||
|
||||
# Apply manual pricing overrides
|
||||
if model_id in PRICING_OVERRIDES:
|
||||
pricing = model.get("pricing", {})
|
||||
if pricing:
|
||||
for k, v in PRICING_OVERRIDES[model_id].items():
|
||||
pricing[k] = str(
|
||||
v
|
||||
) # OpenRouter API returns strings for pricing
|
||||
model["pricing"] = pricing
|
||||
|
||||
models_data.append(model)
|
||||
|
||||
return models_data
|
||||
@@ -166,12 +148,6 @@ def _row_to_model(
|
||||
if isinstance(pricing, dict) and float(pricing.get("request", 0.0)) <= 0.0:
|
||||
pricing["request"] = max(pricing.get("request", 0.0), 0.0)
|
||||
|
||||
# Apply defaults for missing fields from manual overrides
|
||||
if row.id in PRICING_OVERRIDES and isinstance(pricing, dict):
|
||||
for k, v in PRICING_OVERRIDES[row.id].items():
|
||||
if k not in pricing:
|
||||
pricing[k] = v
|
||||
|
||||
parsed_pricing = Pricing.parse_obj(pricing)
|
||||
model = Model(
|
||||
id=row.id,
|
||||
@@ -531,7 +507,6 @@ def _pricing_matches(
|
||||
"completion",
|
||||
"request",
|
||||
"image",
|
||||
"completion_image",
|
||||
"web_search",
|
||||
"internal_reasoning",
|
||||
]
|
||||
@@ -553,4 +528,5 @@ async def models(session: AsyncSession = Depends(get_session)) -> dict:
|
||||
from ..proxy import get_unique_models
|
||||
|
||||
items = get_unique_models()
|
||||
print(items)
|
||||
return {"data": items}
|
||||
|
||||
@@ -85,6 +85,7 @@ async def _fetch_btc_usd_price() -> float:
|
||||
_binance_btc_usdt(client),
|
||||
)
|
||||
valid_prices = [price for price in prices if price is not None]
|
||||
print(valid_prices)
|
||||
if not valid_prices:
|
||||
logger.error("No valid BTC prices obtained from any exchange")
|
||||
raise ValueError("Unable to fetch BTC price from any exchange")
|
||||
@@ -102,14 +103,26 @@ async def _update_prices() -> None:
|
||||
global BTC_USD_PRICE, SATS_USD_PRICE
|
||||
try:
|
||||
btc_price = await _fetch_btc_usd_price()
|
||||
BTC_USD_PRICE = btc_price
|
||||
SATS_USD_PRICE = btc_price / 100_000_000
|
||||
logger.info(
|
||||
"Updated BTC/SATS prices successfully",
|
||||
extra={"btc_usd": btc_price, "sats_usd": SATS_USD_PRICE},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Skipping price update; unable to fetch BTC price",
|
||||
"Failed to fetch live prices, using fallback if available",
|
||||
extra={"error": str(e), "error_type": type(e).__name__},
|
||||
)
|
||||
return
|
||||
BTC_USD_PRICE = btc_price
|
||||
SATS_USD_PRICE = btc_price / 100_000_000
|
||||
# Use fallback price if no price is set
|
||||
if BTC_USD_PRICE is None or SATS_USD_PRICE is None:
|
||||
fallback_btc_price = getattr(settings, "fallback_btc_usd_price", 50000.0)
|
||||
BTC_USD_PRICE = fallback_btc_price
|
||||
SATS_USD_PRICE = fallback_btc_price / 100_000_000
|
||||
logger.info(
|
||||
"Using fallback BTC price",
|
||||
extra={"fallback_btc_usd": fallback_btc_price, "sats_usd": SATS_USD_PRICE},
|
||||
)
|
||||
|
||||
|
||||
def btc_usd_price() -> float:
|
||||
|
||||
Reference in New Issue
Block a user