Skip to content

Commit e5f8f2b

Browse files
authored
fix: update MiniMax model metadata and endpoints (#1103)
* fix: update MiniMax model metadata and endpoints Keep context limits, tiered costs, and regional endpoint configuration aligned with current provider documentation. * Remove documentation file per review --------- Co-authored-by: octo-patch <266937838+octo-patch@users.noreply.github.com>
1 parent 85b3d1b commit e5f8f2b

7 files changed

Lines changed: 362 additions & 21 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ There are other pipelines that can be used to extract information from multiple
155155

156156
For each of these graphs there is the multi version. It allows to make calls of the LLM in parallel.
157157

158-
It is possible to use different LLM through APIs, such as **OpenAI**, **Groq**, **Azure**, **Gemini**, **MiniMax** and more, or local models using **Ollama**.
158+
It is possible to use different LLM through APIs, such as **OpenAI**, **Groq**, **Azure**, **Gemini**, **[MiniMax](docs/minimax.md)** and more, or local models using **Ollama**.
159159

160160
Remember to have [Ollama](https://ollama.com/) installed and download the models using the **ollama pull** command, if you want to use local models.
161161

scrapegraphai/helpers/models_tokens.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -412,8 +412,8 @@
412412
"grok-beta": 128000,
413413
},
414414
"minimax": {
415-
"MiniMax-M3": 524288,
416-
"MiniMax-M2.7": 204000,
417-
"MiniMax-M2.7-highspeed": 204000,
415+
"MiniMax-M3": 1000000,
416+
"MiniMax-M2.7": 204800,
417+
"MiniMax-M2.7-highspeed": 204800,
418418
},
419419
}

scrapegraphai/models/minimax.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
from langchain_openai import ChatOpenAI
66

77

8+
DEFAULT_MINIMAX_OPENAI_BASE_URL = "https://api.minimax.io/v1"
9+
10+
811
class MiniMax(ChatOpenAI):
912
"""
1013
A wrapper for the ChatOpenAI class (MiniMax uses an OpenAI-compatible API) that
@@ -18,6 +21,7 @@ class MiniMax(ChatOpenAI):
1821
def __init__(self, **llm_config):
1922
if "api_key" in llm_config:
2023
llm_config["openai_api_key"] = llm_config.pop("api_key")
21-
llm_config["openai_api_base"] = "https://api.minimax.io/v1"
24+
if "base_url" not in llm_config and "openai_api_base" not in llm_config:
25+
llm_config["openai_api_base"] = DEFAULT_MINIMAX_OPENAI_BASE_URL
2226

2327
super().__init__(**llm_config)

scrapegraphai/utils/custom_callback.py

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,19 @@
1515
from langchain_core.outputs import ChatGeneration, LLMResult
1616
from langchain_core.tracers.context import register_configure_hook
1717

18-
from .model_costs import MODEL_COST_PER_1K_TOKENS_INPUT, MODEL_COST_PER_1K_TOKENS_OUTPUT
18+
from .model_costs import (
19+
MODEL_COST_PER_1K_TOKENS_INPUT,
20+
MODEL_COST_TIERS_PER_1K_TOKENS,
21+
get_model_cost_per_1k_tokens,
22+
)
1923

2024

2125
def get_token_cost_for_model(
22-
model_name: str, num_tokens: int, is_completion: bool = False
26+
model_name: str,
27+
num_tokens: int,
28+
is_completion: bool = False,
29+
input_tokens: Optional[int] = None,
30+
service_tier: str = "standard",
2331
) -> float:
2432
"""
2533
Get the cost in USD for a given model and number of tokens.
@@ -29,15 +37,30 @@ def get_token_cost_for_model(
2937
num_tokens: Number of tokens.
3038
is_completion: Whether the model is used for completion or not.
3139
Defaults to False.
40+
input_tokens: Number of input tokens used to select a pricing tier.
41+
service_tier: Provider service tier. Defaults to standard.
3242
3343
Returns:
3444
Cost in USD.
3545
"""
36-
if model_name not in MODEL_COST_PER_1K_TOKENS_INPUT:
46+
if (
47+
model_name not in MODEL_COST_PER_1K_TOKENS_INPUT
48+
and model_name not in MODEL_COST_TIERS_PER_1K_TOKENS
49+
):
3750
return 0.0
38-
if is_completion:
39-
return MODEL_COST_PER_1K_TOKENS_OUTPUT[model_name] * (num_tokens / 1000)
40-
return MODEL_COST_PER_1K_TOKENS_INPUT[model_name] * (num_tokens / 1000)
51+
if input_tokens is None:
52+
if is_completion and model_name in MODEL_COST_TIERS_PER_1K_TOKENS:
53+
raise ValueError(
54+
"input_tokens is required for completion costs with tiered pricing"
55+
)
56+
input_tokens = num_tokens
57+
rate = get_model_cost_per_1k_tokens(
58+
model_name,
59+
input_tokens,
60+
is_completion=is_completion,
61+
service_tier=service_tier,
62+
)
63+
return rate * (num_tokens / 1000)
4164

4265

4366
class CustomCallbackHandler(BaseCallbackHandler):
@@ -49,10 +72,11 @@ class CustomCallbackHandler(BaseCallbackHandler):
4972
successful_requests: int = 0
5073
total_cost: float = 0.0
5174

52-
def __init__(self, llm_model_name: str) -> None:
75+
def __init__(self, llm_model_name: str, service_tier: str = "standard") -> None:
5376
super().__init__()
5477
self._lock = threading.Lock()
5578
self.model_name = llm_model_name if llm_model_name else "unknown"
79+
self.service_tier = service_tier
5680

5781
def __repr__(self) -> str:
5882
return (
@@ -114,11 +138,23 @@ def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
114138
token_usage = response.llm_output["token_usage"]
115139
completion_tokens = token_usage.get("completion_tokens", 0)
116140
prompt_tokens = token_usage.get("prompt_tokens", 0)
117-
if self.model_name in MODEL_COST_PER_1K_TOKENS_INPUT:
141+
if (
142+
self.model_name in MODEL_COST_PER_1K_TOKENS_INPUT
143+
or self.model_name in MODEL_COST_TIERS_PER_1K_TOKENS
144+
):
118145
completion_cost = get_token_cost_for_model(
119-
self.model_name, completion_tokens, is_completion=True
146+
self.model_name,
147+
completion_tokens,
148+
is_completion=True,
149+
input_tokens=prompt_tokens,
150+
service_tier=self.service_tier,
151+
)
152+
prompt_cost = get_token_cost_for_model(
153+
self.model_name,
154+
prompt_tokens,
155+
input_tokens=prompt_tokens,
156+
service_tier=self.service_tier,
120157
)
121-
prompt_cost = get_token_cost_for_model(self.model_name, prompt_tokens)
122158
else:
123159
completion_cost = 0
124160
prompt_cost = 0
@@ -147,11 +183,11 @@ def __deepcopy__(self, memo: Any) -> "CustomCallbackHandler":
147183

148184

149185
@contextmanager
150-
def get_custom_callback(llm_model_name: str):
186+
def get_custom_callback(llm_model_name: str, service_tier: str = "standard"):
151187
"""
152188
Function to get custom callback for LLM token usage statistics.
153189
"""
154-
cb = CustomCallbackHandler(llm_model_name)
190+
cb = CustomCallbackHandler(llm_model_name, service_tier=service_tier)
155191
custom_callback.set(cb)
156192
yield cb
157193
custom_callback.set(None)

scrapegraphai/utils/llm_callback_manager.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,15 @@ def exclusive_get_callback(self, llm_model, llm_model_name):
4747
"""
4848
if CustomLLMCallbackManager._lock.acquire(blocking=False):
4949
try:
50-
if isinstance(llm_model, ChatOpenAI) or isinstance(
50+
from ..models.minimax import MiniMax
51+
52+
if isinstance(llm_model, MiniMax):
53+
service_tier = llm_model.service_tier or "standard"
54+
with get_custom_callback(
55+
llm_model_name, service_tier=service_tier
56+
) as cb:
57+
yield cb
58+
elif isinstance(llm_model, ChatOpenAI) or isinstance(
5159
llm_model, AzureChatOpenAI
5260
):
5361
with get_openai_callback() as cb:

scrapegraphai/utils/model_costs.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"amazon.titan-text-express-v1": 0.0002,
5151
"amazon.titan-text-lite-v1": 0.00015,
5252
"amazon.titan-text-premier-v1:0": 0.0005,
53+
"MiniMax-M2.7": 0.0003,
5354
}
5455

5556
"""
@@ -102,4 +103,84 @@
102103
"amazon.titan-text-express-v1": 0.0006,
103104
"amazon.titan-text-lite-v1": 0.0002,
104105
"amazon.titan-text-premier-v1:0": 0.0015,
106+
"MiniMax-M2.7": 0.0012,
105107
}
108+
109+
110+
MODEL_CACHE_COST_PER_1K_TOKENS = {
111+
"MiniMax-M2.7": {"read": 0.00006, "write": 0.000375},
112+
}
113+
114+
115+
MODEL_COST_TIERS_PER_1K_TOKENS = {
116+
"MiniMax-M3": {
117+
"standard": (
118+
{
119+
"input_tokens_lte": 512000,
120+
"input": 0.0003,
121+
"output": 0.0012,
122+
"cache_read": 0.00006,
123+
"cache_write": None,
124+
},
125+
{
126+
"input_tokens_gt": 512000,
127+
"input": 0.0006,
128+
"output": 0.0024,
129+
"cache_read": 0.00012,
130+
"cache_write": None,
131+
},
132+
),
133+
"priority": (
134+
{
135+
"input_tokens_lte": 512000,
136+
"input": 0.00045,
137+
"output": 0.0018,
138+
"cache_read": 0.00009,
139+
"cache_write": None,
140+
},
141+
{
142+
"input_tokens_gt": 512000,
143+
"input": 0.0009,
144+
"output": 0.0036,
145+
"cache_read": 0.00018,
146+
"cache_write": None,
147+
},
148+
),
149+
}
150+
}
151+
152+
153+
def get_model_cost_per_1k_tokens(
154+
model_name: str,
155+
input_tokens: int,
156+
is_completion: bool = False,
157+
service_tier: str = "standard",
158+
) -> float:
159+
"""Return the applicable input or output rate for a model."""
160+
if input_tokens < 0:
161+
raise ValueError("input_tokens must not be negative")
162+
163+
if model_name in MODEL_COST_TIERS_PER_1K_TOKENS:
164+
try:
165+
pricing_tiers = MODEL_COST_TIERS_PER_1K_TOKENS[model_name][service_tier]
166+
except KeyError as exc:
167+
raise ValueError(
168+
f"Unsupported service tier {service_tier!r} for {model_name}"
169+
) from exc
170+
171+
rate_key = "output" if is_completion else "input"
172+
for pricing in pricing_tiers:
173+
upper_bound = pricing.get("input_tokens_lte")
174+
lower_bound = pricing.get("input_tokens_gt")
175+
if upper_bound is not None and input_tokens <= upper_bound:
176+
return float(pricing[rate_key])
177+
if lower_bound is not None and input_tokens > lower_bound:
178+
return float(pricing[rate_key])
179+
raise ValueError(f"No pricing tier matches {input_tokens} input tokens")
180+
181+
costs = (
182+
MODEL_COST_PER_1K_TOKENS_OUTPUT
183+
if is_completion
184+
else MODEL_COST_PER_1K_TOKENS_INPUT
185+
)
186+
return costs[model_name]

0 commit comments

Comments
 (0)