Files
wehub-resource-sync 97e91a83f3
Ruff / Ruff (push) Waiting to run
Test / Core Tests (push) Waiting to run
Test / Offline Coverage Tests (Python 3.10) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.11) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.12) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.13) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.9) (push) Waiting to run
Test / Full Coverage (Python 3.11) (push) Waiting to run
Test / Core Provider Tests (OpenAI) (push) Blocked by required conditions
Test / Core Provider Tests (Anthropic) (push) Blocked by required conditions
Test / Core Provider Tests (Google) (push) Blocked by required conditions
Test / Core Provider Tests (Other) (push) Blocked by required conditions
Test / Anthropic Tests (push) Blocked by required conditions
Test / Gemini Tests (push) Blocked by required conditions
Test / Google GenAI Tests (push) Blocked by required conditions
Test / Vertex AI Tests (push) Blocked by required conditions
Test / OpenAI Tests (push) Blocked by required conditions
Test / Writer Tests (push) Blocked by required conditions
Test / Auto Client Tests (push) Blocked by required conditions
ty / type-check (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:38 +08:00

181 lines
6.8 KiB
Python

from typing import Any, Union
from collections.abc import Awaitable
from datetime import datetime, timedelta
import typer
import os
import aiohttp
import asyncio
from builtins import list as List
from collections import defaultdict
from rich.console import Console
from rich.table import Table
from rich.progress import Progress
from instructor._types._alias import ModelNames
app = typer.Typer()
console = Console()
api_key = os.environ.get("OPENAI_API_KEY")
async def fetch_usage(date: str) -> dict[str, Any]:
headers = {"Authorization": f"Bearer {api_key}"}
url = f"https://api.openai.com/v1/usage?date={date}"
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers) as resp:
return await resp.json()
async def get_usage_for_past_n_days(
n_days: int,
) -> List[dict[str, Any]]: # noqa: UP006 - conflicting with the fn name
tasks: List[Awaitable[dict[str, Any]]] = [] # noqa: UP006 - conflicting with the fn name
all_data: List[dict[str, Any]] = [] # noqa: UP006 - conflicting with the fn name
with Progress() as progress:
if n_days > 1:
task = progress.add_task("[green]Fetching usage data...", total=n_days)
for i in range(n_days):
date = (datetime.now() - timedelta(days=i)).strftime("%Y-%m-%d")
tasks.append(fetch_usage(date))
progress.update(task, advance=1)
else:
tasks.append(fetch_usage(datetime.now().strftime("%Y-%m-%d")))
fetched_data = await asyncio.gather(*tasks)
for data in fetched_data:
all_data.extend(data.get("data", []))
return all_data
# Define the cost per unit for each model
MODEL_COSTS = {
"gpt-4o": {"prompt": 0.005 / 1000, "completion": 0.015 / 1000},
"gpt-4o-2024-05-13": {"prompt": 0.005 / 1000, "completion": 0.015 / 1000},
"gpt-4-turbo": {"prompt": 0.01 / 1000, "completion": 0.03 / 1000},
"gpt-4-turbo-2024-04-09": {"prompt": 0.01 / 1000, "completion": 0.03 / 1000},
"gpt-4-0125-preview": {"prompt": 0.01 / 1000, "completion": 0.03 / 1000},
"gpt-4-turbo-preview": {"prompt": 0.01 / 1000, "completion": 0.03 / 1000},
"gpt-4-1106-preview": {"prompt": 0.01 / 1000, "completion": 0.03 / 1000},
"gpt-4-vision-preview": {"prompt": 0.01 / 1000, "completion": 0.03 / 1000},
"gpt-4": {"prompt": 0.03 / 1000, "completion": 0.06 / 1000},
"gpt-4-0314": {"prompt": 0.03 / 1000, "completion": 0.06 / 1000},
"gpt-4-0613": {"prompt": 0.03 / 1000, "completion": 0.06 / 1000},
"gpt-4-32k": {"prompt": 0.06 / 1000, "completion": 0.12 / 1000},
"gpt-4-32k-0314": {"prompt": 0.06 / 1000, "completion": 0.12 / 1000},
"gpt-4-32k-0613": {"prompt": 0.06 / 1000, "completion": 0.12 / 1000},
"gpt-3.5-turbo": {"prompt": 0.0005 / 1000, "completion": 0.0015 / 1000},
"gpt-3.5-turbo-16k": {"prompt": 0.0030 / 1000, "completion": 0.0040 / 1000},
"gpt-3.5-turbo-0301": {"prompt": 0.0015 / 1000, "completion": 0.0020 / 1000},
"gpt-3.5-turbo-0613": {"prompt": 0.0015 / 1000, "completion": 0.0020 / 1000},
"gpt-3.5-turbo-1106": {"prompt": 0.0010 / 1000, "completion": 0.0020 / 1000},
"gpt-3.5-turbo-0125": {"prompt": 0.0005 / 1000, "completion": 0.0015 / 1000},
"gpt-3.5-turbo-16k-0613": {"prompt": 0.0030 / 1000, "completion": 0.0040 / 1000},
"gpt-3.5-turbo-instruct": {"prompt": 0.0015 / 1000, "completion": 0.0020 / 1000},
"text-embedding-3-small": 0.00002 / 1000,
"text-embedding-3-large": 0.00013 / 1000,
"text-embedding-ada-002": 0.00010 / 1000,
}
def get_model_cost(
model: ModelNames,
) -> Union[dict[str, float], float]:
"""Get the cost details for a given model."""
if model in MODEL_COSTS:
return MODEL_COSTS[model]
if model.startswith("gpt-3.5-turbo-16k"):
return MODEL_COSTS["gpt-3.5-turbo-16k"]
if model.startswith("gpt-3.5-turbo"):
return MODEL_COSTS["gpt-3.5-turbo"]
if model.startswith("gpt-4-turbo"):
return MODEL_COSTS["gpt-4-turbo-preview"]
if model.startswith("gpt-4-32k"):
return MODEL_COSTS["gpt-4-32k"]
if model.startswith("gpt-4o"):
return MODEL_COSTS["gpt-4o"]
if model.startswith("gpt-4"):
return MODEL_COSTS["gpt-4"]
raise ValueError(f"Cost for model {model} not found")
def calculate_cost(
snapshot_id: ModelNames,
n_context_tokens: int,
n_generated_tokens: int,
) -> float:
"""Calculate the cost based on the snapshot ID and number of tokens."""
cost = get_model_cost(snapshot_id)
if isinstance(cost, (float, int)):
return cost * (n_context_tokens + n_generated_tokens)
prompt_cost = cost["prompt"] * n_context_tokens
completion_cost = cost["completion"] * n_generated_tokens
return prompt_cost + completion_cost
def group_and_sum_by_date_and_snapshot(
usage_data: List[dict[str, Any]], # noqa: UP006 - conflicting with the fn name
) -> Table:
"""Group and sum the usage data by date and snapshot, including costs."""
summary: defaultdict[str, defaultdict[str, dict[str, Union[int, float]]]] = (
defaultdict(
lambda: defaultdict(
lambda: {"total_requests": 0, "total_tokens": 0, "total_cost": 0.0}
)
)
)
for usage in usage_data:
snapshot_id = usage["snapshot_id"]
date = datetime.fromtimestamp(usage["aggregation_timestamp"]).strftime(
"%Y-%m-%d"
)
summary[date][snapshot_id]["total_requests"] += usage["n_requests"]
summary[date][snapshot_id]["total_tokens"] += usage["n_generated_tokens_total"]
# Calculate and add the cost
cost = calculate_cost(
snapshot_id,
usage["n_context_tokens_total"],
usage["n_generated_tokens_total"],
)
summary[date][snapshot_id]["total_cost"] += cost
table = Table(title="Usage Summary by Date, Snapshot, and Cost")
table.add_column("Date", style="dim")
table.add_column("Model", style="dim")
table.add_column("Total Requests", justify="right")
table.add_column("Total Cost ($)", justify="right")
# Sort dates and snapshots in descending order
sorted_dates = sorted(summary.keys(), reverse=True)
for date in sorted_dates:
sorted_snapshots = sorted(summary[date].keys(), reverse=True)
for snapshot_id in sorted_snapshots:
data = summary[date][snapshot_id]
table.add_row(
date,
snapshot_id,
str(data["total_requests"]),
"{:.2f}".format(data["total_cost"]),
)
return table
@app.command(help="Displays OpenAI API usage data for the past N days.")
def list(
n: int = typer.Option(0, help="Number of days."),
) -> None:
all_data = asyncio.run(get_usage_for_past_n_days(n))
table = group_and_sum_by_date_and_snapshot(all_data)
console.print(table)
if __name__ == "__main__":
app()