update models.json shape

This commit is contained in:
Shroominic
2025-04-29 14:05:50 +08:00
parent cbd581cd8e
commit 0fa3829e14
6 changed files with 7864 additions and 252 deletions
+7787 -204
View File
File diff suppressed because it is too large Load Diff
+9 -36
View File
@@ -1,14 +1,11 @@
import hashlib
import json
import os
from typing import Literal
from fastapi import HTTPException
from pydantic import BaseModel, Field
from .cashu import credit_balance, pay_out
from .db import ApiKey, AsyncSession
from .price import btc_usd_ask_price
from .models import MODELS
COST_PER_REQUEST = (
int(os.environ.get("COST_PER_REQUEST", "1")) * 1000
@@ -79,14 +76,17 @@ async def adjust_payment_for_tokens(
"output_msats": 0,
"total_msats": COST_PER_REQUEST,
}
if MODEL_BASED_PRICING and os.path.exists("models.json"):
models = read_models()
response_model = response_data.get("model", "")
if response_model not in [model.name for model in models]:
if response_model not in [model.id for model in MODELS]:
raise HTTPException(status_code=400, detail="Invalid model")
model = next(model for model in models if model.name == response_model)
MSATS_PER_1K_INPUT_TOKENS = await model.msats_per_1k_input_tokens()
MSATS_PER_1K_OUTPUT_TOKENS = await model.msats_per_1k_output_tokens()
model = next(model for model in MODELS if model.id == response_model)
if model.sats_pricing is None:
raise HTTPException(status_code=400, detail="Model pricing not defined")
MSATS_PER_1K_INPUT_TOKENS = model.sats_pricing.prompt * 1_000_000
MSATS_PER_1K_OUTPUT_TOKENS = model.sats_pricing.completion * 1_000_000
if not (MSATS_PER_1K_OUTPUT_TOKENS and MSATS_PER_1K_INPUT_TOKENS):
raise HTTPException(status_code=400, detail="Model pricing not defined")
@@ -134,30 +134,3 @@ async def adjust_payment_for_tokens(
await pay_out(session)
return cost_data
class LLModel(BaseModel):
name: str
cost_per_1m_input_tokens: float = Field(alias="cost_per_1m_prompt_tokens")
cost_per_1m_output_tokens: float = Field(alias="cost_per_1m_completion_tokens")
currency: Literal["btc", "usd"]
async def msats_per_1k_input_tokens(self) -> float:
if self.currency == "btc":
return self.cost_per_1m_input_tokens * 100_000
btc_price = await btc_usd_ask_price()
return (self.cost_per_1m_input_tokens / 1000) * (100_000_000_000 / btc_price)
async def msats_per_1k_output_tokens(self) -> float:
if self.currency == "btc":
return self.cost_per_1m_output_tokens * 100_000
btc_price = await btc_usd_ask_price()
return (self.cost_per_1m_output_tokens / 1000) * (100_000_000_000 / btc_price)
def read_models() -> list[LLModel]:
if not os.path.exists("models.json"):
raise HTTPException(status_code=400, detail="Models not defined")
with open("models.json", "r") as f:
models = json.load(f)["models"]
return [LLModel(**model) for model in models]
+1 -1
View File
@@ -96,7 +96,7 @@ async def pay_out(session: AsyncSession) -> None:
wallet = await _initialize_wallet()
wallet_balance = wallet.available_balance
assert wallet_balance >= user_balance, "Something went deeply wrong."
assert wallet_balance <= user_balance, "Something went deeply wrong."
if (revenue := wallet_balance - user_balance) <= MINIMUM_PAYOUT:
return
+4 -2
View File
@@ -1,3 +1,4 @@
import asyncio
import os
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
@@ -5,8 +6,8 @@ from fastapi.middleware.cors import CORSMiddleware
from .db import init_db
from .proxy import proxy_router
from .account import account_router
from .auth import read_models
from .cashu import _initialize_wallet
from .models import MODELS, update_sats_pricing
__version__ = "0.0.1"
@@ -37,7 +38,7 @@ async def info():
"mint": os.environ.get("MINT", ""),
"http_url": os.environ.get("HTTP_URL", ""),
"onion_url": os.environ.get("ONION_URL", ""),
"models": read_models(),
"models": MODELS,
}
@@ -49,3 +50,4 @@ app.include_router(proxy_router)
async def startup_event():
await init_db()
await _initialize_wallet()
asyncio.create_task(update_sats_pricing())
+50
View File
@@ -0,0 +1,50 @@
import asyncio
import json
from pydantic.v1 import BaseModel
from .price import sats_usd_ask_price
class Architecture(BaseModel):
modality: str
input_modalities: list[str]
output_modalities: list[str]
tokenizer: str
instruct_type: str | None
class Pricing(BaseModel):
prompt: float
completion: float
request: float
image: float
web_search: float
internal_reasoning: float
class Model(BaseModel):
id: str
name: str
created: int
description: str
context_length: int
architecture: Architecture
pricing: Pricing
sats_pricing: Pricing | None
per_request_limits: dict | None
MODELS: list[Model] = []
with open("models.json", "r") as f:
MODELS = [Model(**model) for model in json.load(f)["models"]]
async def update_sats_pricing() -> None:
while True:
sats_to_usd = await sats_usd_ask_price()
for model in MODELS:
model.sats_pricing = Pricing(
**{k: v / sats_to_usd for k, v in model.pricing.dict().items()}
)
await asyncio.sleep(10)
+13 -9
View File
@@ -3,27 +3,27 @@ import httpx
import asyncio
# artifical spread to cover conversion fees
EXCHANGE_FEE = os.environ.get("EXCHANGE_FEE", 1.005) # 0.5% default
EXCHANGE_FEE = float(os.environ.get("EXCHANGE_FEE", "1.005")) # 0.5% default
async def kraken_btc_usd(client: httpx.AsyncClient) -> int:
async def kraken_btc_usd(client: httpx.AsyncClient) -> float:
api = "https://api.kraken.com/0/public/Ticker?pair=XBTUSD"
return int(float((await client.get(api)).json()["result"]["XXBTZUSD"]["c"][0]))
return float((await client.get(api)).json()["result"]["XXBTZUSD"]["c"][0])
async def coinbase_btc_usd(client: httpx.AsyncClient) -> int:
async def coinbase_btc_usd(client: httpx.AsyncClient) -> float:
api = "https://api.coinbase.com/v2/prices/BTC-USD/spot"
return int(float((await client.get(api)).json()["data"]["amount"]))
return float((await client.get(api)).json()["data"]["amount"])
async def binance_btc_usdt(client: httpx.AsyncClient) -> int:
async def binance_btc_usdt(client: httpx.AsyncClient) -> float:
api = "https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT"
return int(float((await client.get(api)).json()["price"]))
return float((await client.get(api)).json()["price"])
async def btc_usd_ask_price() -> int:
async def btc_usd_ask_price() -> float:
async with httpx.AsyncClient() as client:
return int(
return (
max(
await asyncio.gather(
kraken_btc_usd(client),
@@ -33,3 +33,7 @@ async def btc_usd_ask_price() -> int:
)
* EXCHANGE_FEE
)
async def sats_usd_ask_price() -> float:
return (await btc_usd_ask_price()) / 100_000_000