txaion-model-pricing 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- txaion_model_pricing/__init__.py +28 -0
- txaion_model_pricing/core.py +151 -0
- txaion_model_pricing/data_source.json +8 -0
- txaion_model_pricing/model_prices_and_context_window.json +43262 -0
- txaion_model_pricing/py.typed +1 -0
- txaion_model_pricing-0.1.0.dist-info/METADATA +180 -0
- txaion_model_pricing-0.1.0.dist-info/RECORD +11 -0
- txaion_model_pricing-0.1.0.dist-info/WHEEL +5 -0
- txaion_model_pricing-0.1.0.dist-info/licenses/LICENSE +9 -0
- txaion_model_pricing-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +18 -0
- txaion_model_pricing-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Txaion Model Pricing 的公開 Python API。"""
|
|
2
|
+
|
|
3
|
+
from .core import (
|
|
4
|
+
InvalidTokenCountError,
|
|
5
|
+
InvalidTokenTypeError,
|
|
6
|
+
ModelPriceError,
|
|
7
|
+
NotFound,
|
|
8
|
+
PriceUnavailableError,
|
|
9
|
+
TokenType,
|
|
10
|
+
calculate_cost,
|
|
11
|
+
count_models,
|
|
12
|
+
get_model_details,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__version__ = "0.1.0"
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"InvalidTokenCountError",
|
|
19
|
+
"InvalidTokenTypeError",
|
|
20
|
+
"ModelPriceError",
|
|
21
|
+
"NotFound",
|
|
22
|
+
"PriceUnavailableError",
|
|
23
|
+
"TokenType",
|
|
24
|
+
"__version__",
|
|
25
|
+
"calculate_cost",
|
|
26
|
+
"count_models",
|
|
27
|
+
"get_model_details",
|
|
28
|
+
]
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""模型價格查詢與 token 成本計算。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from decimal import Decimal, InvalidOperation
|
|
8
|
+
from importlib.resources import files
|
|
9
|
+
from threading import RLock
|
|
10
|
+
from types import MappingProxyType
|
|
11
|
+
from typing import Literal, TypeAlias
|
|
12
|
+
|
|
13
|
+
import orjson
|
|
14
|
+
|
|
15
|
+
TokenType: TypeAlias = Literal["input", "output", "cached"]
|
|
16
|
+
|
|
17
|
+
_PRICE_FIELDS: Mapping[TokenType, str] = MappingProxyType(
|
|
18
|
+
{
|
|
19
|
+
"input": "input_cost_per_token",
|
|
20
|
+
"output": "output_cost_per_token",
|
|
21
|
+
"cached": "cache_read_input_token_cost",
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
_METADATA_KEYS = frozenset({"sample_spec"})
|
|
25
|
+
_PRICES_RESOURCE = "model_prices_and_context_window.json"
|
|
26
|
+
_PRICES: Mapping[str, Mapping[str, object]] | None = None
|
|
27
|
+
_PRICES_LOCK = RLock()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ModelPriceError(Exception):
|
|
31
|
+
"""所有 Txaion Model Pricing 領域錯誤的基底類別。"""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class NotFound(ModelPriceError):
|
|
35
|
+
"""找不到指定模型。"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, model: str) -> None:
|
|
38
|
+
super().__init__(f"Model price not found for model: {model}.")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class InvalidTokenTypeError(ModelPriceError):
|
|
42
|
+
"""token 類型不是 input、output 或 cached。"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, token_type: object) -> None:
|
|
45
|
+
super().__init__(
|
|
46
|
+
f"Invalid token type: {token_type!r}. "
|
|
47
|
+
"Expected one of: input, output, cached."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class InvalidTokenCountError(ModelPriceError):
|
|
52
|
+
"""token 數量不是非負整數。"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, tokens: object) -> None:
|
|
55
|
+
super().__init__(
|
|
56
|
+
f"Invalid token count: {tokens!r}. Expected a non-negative integer."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class PriceUnavailableError(ModelPriceError):
|
|
61
|
+
"""模型存在, 但沒有指定 token 類型的價格。"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, model: str, token_type: TokenType) -> None:
|
|
64
|
+
super().__init__(
|
|
65
|
+
f"Price unavailable for model {model!r} and token type {token_type!r}."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _load_prices() -> Mapping[str, Mapping[str, object]]:
|
|
70
|
+
"""延遲載入價格資料, 並以唯讀 mapping 快取。"""
|
|
71
|
+
global _PRICES
|
|
72
|
+
|
|
73
|
+
if _PRICES is not None:
|
|
74
|
+
return _PRICES
|
|
75
|
+
|
|
76
|
+
with _PRICES_LOCK:
|
|
77
|
+
if _PRICES is not None:
|
|
78
|
+
return _PRICES
|
|
79
|
+
|
|
80
|
+
raw_data = orjson.loads(
|
|
81
|
+
files("txaion_model_pricing").joinpath(_PRICES_RESOURCE).read_bytes()
|
|
82
|
+
)
|
|
83
|
+
if not isinstance(raw_data, dict):
|
|
84
|
+
raise ModelPriceError("The bundled model price data must be a JSON object.")
|
|
85
|
+
|
|
86
|
+
prices: dict[str, Mapping[str, object]] = {}
|
|
87
|
+
for model, details in raw_data.items():
|
|
88
|
+
if model in _METADATA_KEYS:
|
|
89
|
+
continue
|
|
90
|
+
if not isinstance(model, str) or not isinstance(details, dict):
|
|
91
|
+
raise ModelPriceError(
|
|
92
|
+
"The bundled model price data contains an invalid model entry."
|
|
93
|
+
)
|
|
94
|
+
prices[model] = MappingProxyType(details)
|
|
95
|
+
|
|
96
|
+
_PRICES = MappingProxyType(prices)
|
|
97
|
+
return _PRICES
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def count_models() -> int:
|
|
101
|
+
"""回傳資料集中真正的模型數量, 不包含 schema 範例。"""
|
|
102
|
+
return len(_load_prices())
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def calculate_cost(
|
|
106
|
+
model: str,
|
|
107
|
+
tokens: int,
|
|
108
|
+
token_type: TokenType | str,
|
|
109
|
+
) -> Decimal:
|
|
110
|
+
"""計算指定模型與 token 類型的美元成本。
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
model: 價格資料中的模型識別名稱。
|
|
114
|
+
tokens: 非負整數 token 數量。
|
|
115
|
+
token_type: ``input``、``output`` 或 ``cached``。
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
未量化、未四捨五入的美元成本。
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
InvalidTokenCountError: ``tokens`` 不是非負整數。
|
|
122
|
+
InvalidTokenTypeError: ``token_type`` 不在支援範圍。
|
|
123
|
+
NotFound: 找不到模型。
|
|
124
|
+
PriceUnavailableError: 模型沒有指定類型的 token 價格。
|
|
125
|
+
"""
|
|
126
|
+
if type(tokens) is not int or tokens < 0:
|
|
127
|
+
raise InvalidTokenCountError(tokens)
|
|
128
|
+
if token_type not in _PRICE_FIELDS:
|
|
129
|
+
raise InvalidTokenTypeError(token_type)
|
|
130
|
+
|
|
131
|
+
prices = _load_prices()
|
|
132
|
+
if model not in prices:
|
|
133
|
+
raise NotFound(model)
|
|
134
|
+
|
|
135
|
+
price_field = _PRICE_FIELDS[token_type]
|
|
136
|
+
price = prices[model].get(price_field)
|
|
137
|
+
if price is None:
|
|
138
|
+
raise PriceUnavailableError(model, token_type)
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
return Decimal(str(price)) * tokens
|
|
142
|
+
except (InvalidOperation, TypeError, ValueError) as exc:
|
|
143
|
+
raise PriceUnavailableError(model, token_type) from exc
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_model_details(model: str) -> dict[str, object]:
|
|
147
|
+
"""回傳指定模型資料的獨立深層副本。"""
|
|
148
|
+
prices = _load_prices()
|
|
149
|
+
if model not in prices:
|
|
150
|
+
raise NotFound(model)
|
|
151
|
+
return deepcopy(dict(prices[model]))
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": "LiteLLM model price and context-window data",
|
|
3
|
+
"repository": "https://github.com/BerriAI/litellm",
|
|
4
|
+
"upstream_file": "model_prices_and_context_window.json",
|
|
5
|
+
"upstream_ref": "ae81625ee6659c96abf87e84e74840f9c0b3164a",
|
|
6
|
+
"retrieved_at": "2026-07-25T23:33:21Z",
|
|
7
|
+
"sha256": "012fbba3de82c3cf38bc601cf834a4dc175173ab2e71ff727a5e7f22b6c15255"
|
|
8
|
+
}
|