mirror of
https://github.com/Mintplex-Labs/langchain-python.git
synced 2026-07-19 21:33:31 -04:00
efe0d39c6a
Added parentheses to ensure the division operation is performed before multiplication. This now correctly calculates the cost by dividing the number of tokens by 1000 first (to get the cost per token), and then multiplies it with the model's cost per 1k tokens @agola11
163 lines
5.2 KiB
Python
163 lines
5.2 KiB
Python
"""Callback Handler that prints to std out."""
|
|
from typing import Any, Dict, List
|
|
|
|
from langchain.callbacks.base import BaseCallbackHandler
|
|
from langchain.schema import LLMResult
|
|
|
|
MODEL_COST_PER_1K_TOKENS = {
|
|
# GPT-4 input
|
|
"gpt-4": 0.03,
|
|
"gpt-4-0314": 0.03,
|
|
"gpt-4-0613": 0.03,
|
|
"gpt-4-32k": 0.06,
|
|
"gpt-4-32k-0314": 0.06,
|
|
"gpt-4-32k-0613": 0.06,
|
|
# GPT-4 output
|
|
"gpt-4-completion": 0.06,
|
|
"gpt-4-0314-completion": 0.06,
|
|
"gpt-4-0613-completion": 0.06,
|
|
"gpt-4-32k-completion": 0.12,
|
|
"gpt-4-32k-0314-completion": 0.12,
|
|
"gpt-4-32k-0613-completion": 0.12,
|
|
# GPT-3.5 input
|
|
"gpt-3.5-turbo": 0.0015,
|
|
"gpt-3.5-turbo-0301": 0.0015,
|
|
"gpt-3.5-turbo-0613": 0.0015,
|
|
"gpt-3.5-turbo-16k": 0.003,
|
|
"gpt-3.5-turbo-16k-0613": 0.003,
|
|
# GPT-3.5 output
|
|
"gpt-3.5-turbo-completion": 0.002,
|
|
"gpt-3.5-turbo-0301-completion": 0.002,
|
|
"gpt-3.5-turbo-0613-completion": 0.002,
|
|
"gpt-3.5-turbo-16k-completion": 0.004,
|
|
"gpt-3.5-turbo-16k-0613-completion": 0.004,
|
|
# Others
|
|
"gpt-35-turbo": 0.002, # Azure OpenAI version of ChatGPT
|
|
"text-ada-001": 0.0004,
|
|
"ada": 0.0004,
|
|
"text-babbage-001": 0.0005,
|
|
"babbage": 0.0005,
|
|
"text-curie-001": 0.002,
|
|
"curie": 0.002,
|
|
"text-davinci-003": 0.02,
|
|
"text-davinci-002": 0.02,
|
|
"code-davinci-002": 0.02,
|
|
"ada-finetuned": 0.0016,
|
|
"babbage-finetuned": 0.0024,
|
|
"curie-finetuned": 0.012,
|
|
"davinci-finetuned": 0.12,
|
|
}
|
|
|
|
|
|
def standardize_model_name(
|
|
model_name: str,
|
|
is_completion: bool = False,
|
|
) -> str:
|
|
"""
|
|
Standardize the model name to a format that can be used in the OpenAI API.
|
|
Args:
|
|
model_name: Model name to standardize.
|
|
is_completion: Whether the model is used for completion or not.
|
|
Defaults to False.
|
|
|
|
Returns:
|
|
Standardized model name.
|
|
|
|
"""
|
|
model_name = model_name.lower()
|
|
if "ft-" in model_name:
|
|
return model_name.split(":")[0] + "-finetuned"
|
|
elif is_completion and (
|
|
model_name.startswith("gpt-4") or model_name.startswith("gpt-3.5")
|
|
):
|
|
return model_name + "-completion"
|
|
else:
|
|
return model_name
|
|
|
|
|
|
def get_openai_token_cost_for_model(
|
|
model_name: str, num_tokens: int, is_completion: bool = False
|
|
) -> float:
|
|
"""
|
|
Get the cost in USD for a given model and number of tokens.
|
|
|
|
Args:
|
|
model_name: Name of the model
|
|
num_tokens: Number of tokens.
|
|
is_completion: Whether the model is used for completion or not.
|
|
Defaults to False.
|
|
|
|
Returns:
|
|
Cost in USD.
|
|
"""
|
|
model_name = standardize_model_name(model_name, is_completion=is_completion)
|
|
if model_name not in MODEL_COST_PER_1K_TOKENS:
|
|
raise ValueError(
|
|
f"Unknown model: {model_name}. Please provide a valid OpenAI model name."
|
|
"Known models are: " + ", ".join(MODEL_COST_PER_1K_TOKENS.keys())
|
|
)
|
|
return MODEL_COST_PER_1K_TOKENS[model_name] * (num_tokens / 1000)
|
|
|
|
|
|
class OpenAICallbackHandler(BaseCallbackHandler):
|
|
"""Callback Handler that tracks OpenAI info."""
|
|
|
|
total_tokens: int = 0
|
|
prompt_tokens: int = 0
|
|
completion_tokens: int = 0
|
|
successful_requests: int = 0
|
|
total_cost: float = 0.0
|
|
|
|
def __repr__(self) -> str:
|
|
return (
|
|
f"Tokens Used: {self.total_tokens}\n"
|
|
f"\tPrompt Tokens: {self.prompt_tokens}\n"
|
|
f"\tCompletion Tokens: {self.completion_tokens}\n"
|
|
f"Successful Requests: {self.successful_requests}\n"
|
|
f"Total Cost (USD): ${self.total_cost}"
|
|
)
|
|
|
|
@property
|
|
def always_verbose(self) -> bool:
|
|
"""Whether to call verbose callbacks even if verbose is False."""
|
|
return True
|
|
|
|
def on_llm_start(
|
|
self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
|
|
) -> None:
|
|
"""Print out the prompts."""
|
|
pass
|
|
|
|
def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
|
|
"""Print out the token."""
|
|
pass
|
|
|
|
def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
|
|
"""Collect token usage."""
|
|
if response.llm_output is None:
|
|
return None
|
|
self.successful_requests += 1
|
|
if "token_usage" not in response.llm_output:
|
|
return None
|
|
token_usage = response.llm_output["token_usage"]
|
|
completion_tokens = token_usage.get("completion_tokens", 0)
|
|
prompt_tokens = token_usage.get("prompt_tokens", 0)
|
|
model_name = standardize_model_name(response.llm_output.get("model_name", ""))
|
|
if model_name in MODEL_COST_PER_1K_TOKENS:
|
|
completion_cost = get_openai_token_cost_for_model(
|
|
model_name, completion_tokens, is_completion=True
|
|
)
|
|
prompt_cost = get_openai_token_cost_for_model(model_name, prompt_tokens)
|
|
self.total_cost += prompt_cost + completion_cost
|
|
self.total_tokens += token_usage.get("total_tokens", 0)
|
|
self.prompt_tokens += prompt_tokens
|
|
self.completion_tokens += completion_tokens
|
|
|
|
def __copy__(self) -> "OpenAICallbackHandler":
|
|
"""Return a copy of the callback handler."""
|
|
return self
|
|
|
|
def __deepcopy__(self, memo: Any) -> "OpenAICallbackHandler":
|
|
"""Return a deep copy of the callback handler."""
|
|
return self
|