Skip to content

Commit 915efe1

Browse files
committed
Add MiniMax hosted inference adapter
1 parent 2f22a9e commit 915efe1

5 files changed

Lines changed: 218 additions & 2 deletions

File tree

.github/scripts/spellcheck_conf/wordlist.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,8 @@ ShardingStrategy
11831183
hsdp
11841184
prem
11851185
Prem
1186+
Anthropic
1187+
MiniMax
11861188
OpenAI
11871189
Prem
11881190
TCP

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ scipy
1515
optimum
1616
matplotlib
1717
chardet
18+
anthropic>=0.72.0
1819
openai
1920
typing-extensions>=4.8.0
2021
tabulate

src/README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,31 @@ pip install -U pip setuptools
5555
pip install -e .[tests,auditnlg,vllm]
5656
```
5757

58+
### MiniMax hosted inference
59+
60+
The `MINIMAX` adapter supports `MiniMax-M3` and `MiniMax-M2.7` through both
61+
compatible API formats. It uses the global OpenAI-compatible endpoint by default.
62+
63+
```python
64+
import os
65+
66+
from llama_cookbook.inference.llm import MINIMAX
67+
68+
api_key = os.environ["MINIMAX_API_KEY"]
69+
70+
global_openai = MINIMAX("MiniMax-M3", api_key)
71+
china_openai = MINIMAX(
72+
"MiniMax-M2.7", api_key, base_url="https://api.minimaxi.com/v1"
73+
)
74+
global_anthropic = MINIMAX("MiniMax-M3", api_key, api_format="anthropic")
75+
china_anthropic = MINIMAX(
76+
"MiniMax-M2.7",
77+
api_key,
78+
api_format="anthropic",
79+
base_url="https://api.minimaxi.com/anthropic",
80+
)
81+
```
82+
5883

5984
### Getting the Llama models
6085
You can find Llama models on Hugging Face hub [here](https://huggingface.co/meta-llama), **where models with `hf` in the name are already converted to Hugging Face checkpoints so no further conversion is needed**. The conversion step below is only for original model weights from Meta that are hosted on Hugging Face model hub as well.
@@ -71,4 +96,4 @@ cd transformers
7196
pip install protobuf
7297
python src/transformers/models/llama/convert_llama_weights_to_hf.py \
7398
--input_dir /path/to/downloaded/llama/weights --model_size 3B --output_dir /output/path
74-
```
99+
```

src/llama_cookbook/inference/llm.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111

1212
import time
1313
from abc import ABC, abstractmethod
14-
from typing import Callable
14+
from typing import Callable, Literal
1515

16+
import anthropic
1617
import openai
1718
from typing_extensions import override
1819

@@ -21,6 +22,11 @@
2122
TEMPERATURE = 0.1
2223
TOP_P = 0.9
2324

25+
MINIMAX_DEFAULT_BASE_URLS = {
26+
"openai": "https://api.minimax.io/v1",
27+
"anthropic": "https://api.minimax.io/anthropic",
28+
}
29+
2430
LOG: logging.Logger = logging.getLogger(__name__)
2531

2632

@@ -157,3 +163,66 @@ def valid_models(self) -> list[str]:
157163
"mistralai/Mistral-7B-Instruct-v0.1",
158164
"HuggingFaceH4/zephyr-7b-beta",
159165
]
166+
167+
168+
class MINIMAX(LLM):
169+
"""Access MiniMax through its OpenAI- or Anthropic-compatible API.
170+
171+
Global endpoints are used by default. For China endpoints, pass
172+
``https://api.minimaxi.com/v1`` for the OpenAI format or
173+
``https://api.minimaxi.com/anthropic`` for the Anthropic format.
174+
"""
175+
176+
def __init__(
177+
self,
178+
model: str,
179+
api_key: str,
180+
api_format: Literal["openai", "anthropic"] = "openai",
181+
base_url: str | None = None,
182+
) -> None:
183+
super().__init__(model, api_key)
184+
if api_format not in MINIMAX_DEFAULT_BASE_URLS:
185+
raise ValueError(f"Unsupported MiniMax API format: {api_format}")
186+
187+
self.api_format = api_format
188+
resolved_base_url = base_url or MINIMAX_DEFAULT_BASE_URLS[api_format]
189+
self.openai_client: openai.OpenAI | None = None
190+
self.anthropic_client: anthropic.Anthropic | None = None
191+
if api_format == "openai":
192+
self.openai_client = openai.OpenAI(
193+
base_url=resolved_base_url, api_key=api_key
194+
)
195+
else:
196+
self.anthropic_client = anthropic.Anthropic(
197+
base_url=resolved_base_url, api_key=api_key
198+
)
199+
200+
@override
201+
def query(self, prompt: str) -> str:
202+
level = logging.getLogger().level
203+
logging.getLogger().setLevel(logging.WARNING)
204+
try:
205+
if self.openai_client is not None:
206+
response = self.openai_client.chat.completions.create(
207+
model=self.model,
208+
messages=[{"role": "user", "content": prompt}],
209+
max_tokens=MAX_TOKENS,
210+
)
211+
return response.choices[0].message.content or ""
212+
213+
if self.anthropic_client is None:
214+
raise RuntimeError("MiniMax client is not configured")
215+
message = self.anthropic_client.messages.create(
216+
model=self.model,
217+
messages=[{"role": "user", "content": prompt}],
218+
max_tokens=MAX_TOKENS,
219+
)
220+
return "".join(
221+
block.text for block in message.content if block.type == "text"
222+
)
223+
finally:
224+
logging.getLogger().setLevel(level)
225+
226+
@override
227+
def valid_models(self) -> list[str]:
228+
return ["MiniMax-M3", "MiniMax-M2.7"]

src/tests/test_llm.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
#
3+
# This source code is licensed under the MIT license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
import json
7+
import unittest
8+
from unittest.mock import patch
9+
10+
import anthropic
11+
import httpx
12+
import openai
13+
14+
from llama_cookbook.inference.llm import MINIMAX
15+
16+
OPENAI_CLIENT = openai.OpenAI
17+
ANTHROPIC_CLIENT = anthropic.Anthropic
18+
19+
20+
class MiniMaxTest(unittest.TestCase):
21+
def test_supported_models_and_endpoint_paths(self) -> None:
22+
cases = [
23+
(
24+
"openai",
25+
"MiniMax-M3",
26+
None,
27+
"https://api.minimax.io/v1/chat/completions",
28+
),
29+
(
30+
"openai",
31+
"MiniMax-M2.7",
32+
"https://api.minimaxi.com/v1",
33+
"https://api.minimaxi.com/v1/chat/completions",
34+
),
35+
(
36+
"anthropic",
37+
"MiniMax-M3",
38+
None,
39+
"https://api.minimax.io/anthropic/v1/messages",
40+
),
41+
(
42+
"anthropic",
43+
"MiniMax-M2.7",
44+
"https://api.minimaxi.com/anthropic",
45+
"https://api.minimaxi.com/anthropic/v1/messages",
46+
),
47+
]
48+
49+
for api_format, model, base_url, expected_url in cases:
50+
with self.subTest(api_format=api_format, base_url=base_url):
51+
requests = []
52+
53+
def handler(request: httpx.Request) -> httpx.Response:
54+
requests.append(request)
55+
if api_format == "openai":
56+
body = {
57+
"id": "chatcmpl-test",
58+
"object": "chat.completion",
59+
"created": 0,
60+
"model": model,
61+
"choices": [
62+
{
63+
"index": 0,
64+
"message": {
65+
"role": "assistant",
66+
"content": "Test response",
67+
},
68+
"finish_reason": "stop",
69+
}
70+
],
71+
}
72+
else:
73+
body = {
74+
"id": "msg_test",
75+
"type": "message",
76+
"role": "assistant",
77+
"content": [{"type": "text", "text": "Test response"}],
78+
"model": model,
79+
"stop_reason": "end_turn",
80+
"stop_sequence": None,
81+
"usage": {"input_tokens": 1, "output_tokens": 1},
82+
}
83+
return httpx.Response(200, json=body, request=request)
84+
85+
http_client = httpx.Client(transport=httpx.MockTransport(handler))
86+
kwargs = {"api_format": api_format}
87+
if base_url is not None:
88+
kwargs["base_url"] = base_url
89+
90+
if api_format == "openai":
91+
client = OPENAI_CLIENT(
92+
api_key="test-key",
93+
base_url=base_url or "https://api.minimax.io/v1",
94+
http_client=http_client,
95+
)
96+
patch_target = "llama_cookbook.inference.llm.openai.OpenAI"
97+
else:
98+
client = ANTHROPIC_CLIENT(
99+
api_key="test-key",
100+
base_url=base_url or "https://api.minimax.io/anthropic",
101+
http_client=http_client,
102+
)
103+
patch_target = "llama_cookbook.inference.llm.anthropic.Anthropic"
104+
105+
with patch(patch_target, return_value=client):
106+
provider = MINIMAX(model, "test-key", **kwargs)
107+
self.assertEqual(provider.query("Test prompt"), "Test response")
108+
self.assertEqual(
109+
provider.valid_models(), ["MiniMax-M3", "MiniMax-M2.7"]
110+
)
111+
112+
self.assertEqual(len(requests), 1)
113+
self.assertEqual(str(requests[0].url), expected_url)
114+
self.assertEqual(json.loads(requests[0].content)["model"], model)
115+
client.close()
116+
117+
118+
if __name__ == "__main__":
119+
unittest.main()

0 commit comments

Comments
 (0)