-
Notifications
You must be signed in to change notification settings - Fork 507
feat: add MiniMax M2.7 and M2.7-highspeed as LLM providers #184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| import { describe, it, expect } from "vitest"; | ||
|
|
||
| const API_KEY = process.env.MINIMAX_API_KEY; | ||
| const BASE_URL = "https://api.minimax.io/v1"; | ||
|
|
||
|
Comment on lines
+3
to
+5
|
||
| describe.skipIf(!API_KEY)("MiniMax Integration", () => { | ||
| it( | ||
| "completes a basic chat request with MiniMax-M2.7", | ||
| async () => { | ||
| const response = await fetch(`${BASE_URL}/chat/completions`, { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| Authorization: `Bearer ${API_KEY}`, | ||
| }, | ||
| body: JSON.stringify({ | ||
| model: "MiniMax-M2.7", | ||
| messages: [ | ||
| { | ||
| role: "user", | ||
| content: 'Respond with exactly: "MiniMax works"', | ||
| }, | ||
| ], | ||
| temperature: 0.01, | ||
| max_tokens: 20, | ||
| }), | ||
| }); | ||
|
|
||
| expect(response.ok).toBe(true); | ||
| const data = await response.json(); | ||
| expect(data.choices).toBeDefined(); | ||
| expect(data.choices.length).toBeGreaterThan(0); | ||
| expect(data.choices[0].message.content).toBeTruthy(); | ||
| }, | ||
| 30000 | ||
| ); | ||
|
|
||
| it( | ||
| "handles streaming response", | ||
| async () => { | ||
| const response = await fetch(`${BASE_URL}/chat/completions`, { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| Authorization: `Bearer ${API_KEY}`, | ||
| }, | ||
| body: JSON.stringify({ | ||
| model: "MiniMax-M2.7", | ||
| messages: [{ role: "user", content: "Count 1 to 3" }], | ||
| temperature: 0.01, | ||
| max_tokens: 50, | ||
| stream: true, | ||
| }), | ||
| }); | ||
|
|
||
| expect(response.ok).toBe(true); | ||
|
|
||
| const reader = response.body!.getReader(); | ||
| const decoder = new TextDecoder(); | ||
| let chunks = 0; | ||
| let fullText = ""; | ||
|
|
||
| while (true) { | ||
| const { done, value } = await reader.read(); | ||
| if (done) break; | ||
| const text = decoder.decode(value, { stream: true }); | ||
| const lines = text.split("\n").filter((l) => l.startsWith("data: ")); | ||
| for (const line of lines) { | ||
| const data = line.slice(6); | ||
| if (data === "[DONE]") continue; | ||
| try { | ||
| const json = JSON.parse(data); | ||
| const content = json.choices?.[0]?.delta?.content; | ||
| if (content) { | ||
| fullText += content; | ||
| chunks++; | ||
| } | ||
| } catch { | ||
| // skip incomplete JSON | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+63
to
+82
|
||
|
|
||
| expect(chunks).toBeGreaterThan(0); | ||
| expect(fullText.length).toBeGreaterThan(0); | ||
| }, | ||
| 30000 | ||
| ); | ||
|
|
||
| it( | ||
| "works with MiniMax-M2.7-highspeed model", | ||
| async () => { | ||
| const response = await fetch(`${BASE_URL}/chat/completions`, { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| Authorization: `Bearer ${API_KEY}`, | ||
| }, | ||
| body: JSON.stringify({ | ||
| model: "MiniMax-M2.7-highspeed", | ||
| messages: [{ role: "user", content: "Say hello" }], | ||
| temperature: 0.01, | ||
| max_tokens: 20, | ||
| }), | ||
| }); | ||
|
|
||
| expect(response.ok).toBe(true); | ||
| const data = await response.json(); | ||
| expect(data.choices[0].message.content).toBeTruthy(); | ||
| }, | ||
| 30000 | ||
| ); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import { describe, it, expect } from "vitest"; | ||
| import { models, getModel } from "@/utils/model"; | ||
|
|
||
| describe("Model configuration", () => { | ||
| describe("models array", () => { | ||
| it("contains MiniMax-M2.7", () => { | ||
| const minimax = models.find((m) => m.name === "MiniMax-M2.7"); | ||
| expect(minimax).toBeDefined(); | ||
| }); | ||
|
|
||
| it("contains MiniMax-M2.7-highspeed", () => { | ||
| const minimax = models.find((m) => m.name === "MiniMax-M2.7-highspeed"); | ||
| expect(minimax).toBeDefined(); | ||
| }); | ||
|
|
||
| it("MiniMax-M2.7 has correct config", () => { | ||
| const minimax = models.find((m) => m.name === "MiniMax-M2.7")!; | ||
| expect(minimax.temperature).toBeGreaterThan(0); | ||
| expect(minimax.temperature).toBeLessThanOrEqual(1); | ||
| expect(minimax.max_token).toBe(204800); | ||
| expect(minimax.cost_per_call).toBe(1); | ||
| }); | ||
|
|
||
| it("MiniMax-M2.7-highspeed has correct config", () => { | ||
| const minimax = models.find((m) => m.name === "MiniMax-M2.7-highspeed")!; | ||
| expect(minimax.temperature).toBeGreaterThan(0); | ||
| expect(minimax.temperature).toBeLessThanOrEqual(1); | ||
| expect(minimax.max_token).toBe(204800); | ||
| expect(minimax.cost_per_call).toBe(1); | ||
| }); | ||
|
|
||
| it("MiniMax temperature is within valid range (0, 1]", () => { | ||
| const minimaxModels = models.filter((m) => m.name.startsWith("MiniMax")); | ||
| expect(minimaxModels.length).toBe(2); | ||
| for (const model of minimaxModels) { | ||
| expect(model.temperature).toBeGreaterThan(0); | ||
| expect(model.temperature).toBeLessThanOrEqual(1); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| describe("getModel", () => { | ||
| it("returns MiniMax-M2.7 by name", () => { | ||
| const model = getModel("MiniMax-M2.7"); | ||
| expect(model.name).toBe("MiniMax-M2.7"); | ||
| }); | ||
|
|
||
| it("returns MiniMax-M2.7-highspeed by name", () => { | ||
| const model = getModel("MiniMax-M2.7-highspeed"); | ||
| expect(model.name).toBe("MiniMax-M2.7-highspeed"); | ||
| }); | ||
|
|
||
| it("returns default model for unknown name", () => { | ||
| const model = getModel("unknown-model"); | ||
| expect(model.name).toBe("gpt-3.5-turbo"); | ||
| }); | ||
|
|
||
| it("returns correct model for empty string", () => { | ||
| const model = getModel(""); | ||
| expect(model.name).toBe("gpt-3.5-turbo"); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { defineConfig } from "vitest/config"; | ||
| import path from "path"; | ||
|
|
||
| export default defineConfig({ | ||
| test: { | ||
| include: ["tests/**/*.test.ts"], | ||
| }, | ||
| resolve: { | ||
| alias: { | ||
| "@": path.resolve(__dirname, "src"), | ||
| }, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
package.jsonadds a new dev dependency (vitest), butpnpm-lock.yamlis not updated. Since this repo uses pnpm and commits the lockfile, installs/CI won’t pick up the new dependency deterministically until the lockfile is regenerated and committed.