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
181 lines
6.8 KiB
Python
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()
|