Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions packages/create-12-factor-agent/template/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,18 +111,29 @@ If you want to try swapping out the model, you can change the `client` line.

[Docs on baml clients can be found here](https://docs.boundaryml.com/guide/baml-basics/switching-llms)

For example, you can configure [gemini](https://docs.boundaryml.com/ref/llm-client-providers/google-ai-gemini)
For example, you can configure [gemini](https://docs.boundaryml.com/ref/llm-client-providers/google-ai-gemini)
or [anthropic](https://docs.boundaryml.com/ref/llm-client-providers/anthropic) as your model provider.

For example, to use openai with an OPENAI_API_KEY, you can do:

client "openai/gpt-4o"

To use [MiniMax](https://www.minimaxi.com/) with a MINIMAX_API_KEY, you can use the pre-configured clients:

client MiniMaxM27 // MiniMax-M2.7 (latest, recommended)
client MiniMaxM27Highspeed // MiniMax-M2.7-highspeed (faster)
client MiniMaxM25 // MiniMax-M2.5 (previous generation)
client MiniMaxM25Highspeed // MiniMax-M2.5-highspeed (previous gen, faster)


Set your env vars

export BASETEN_API_KEY=...
export BASETEN_BASE_URL=...
export BASETEN_BASE_URL=...

For MiniMax (optional):

export MINIMAX_API_KEY=... # Get your key at https://www.minimaxi.com/

Try it out

Expand Down
25 changes: 25 additions & 0 deletions packages/create-12-factor-agent/template/baml_src/agent.baml
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,29 @@ test MathOperationPostClarification {
@@assert(b, {{this.a == 3}})
@@assert(a, {{this.b == 12}})
}

// Test using MiniMax M2.5 as the LLM provider
test HelloWorldMiniMax {
functions [DetermineNextStep]
args {
thread #"
<user_input>
hello!
</user_input>
"#
}
@@assert(intent, {{this.intent == "request_more_information"}})
}

test MathOperationMiniMax {
functions [DetermineNextStep]
args {
thread #"
<user_input>
can you add 5 and 7?
</user_input>
"#
}
@@assert(intent, {{this.intent == "add"}})
}

47 changes: 45 additions & 2 deletions packages/create-12-factor-agent/template/baml_src/clients.baml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,55 @@ client<llm> CustomHaiku {
}
}

// MiniMax M2.7 - latest high-capability model (recommended)
// Uses OpenAI-compatible API: https://www.minimaxi.com/
client<llm> MiniMaxM27 {
provider openai
options {
model "MiniMax-M2.7"
api_key env.MINIMAX_API_KEY
base_url "https://api.minimax.io/v1"
}
}

// MiniMax M2.7-highspeed - faster variant optimized for low-latency tasks
client<llm> MiniMaxM27Highspeed {
provider openai
retry_policy Exponential
options {
model "MiniMax-M2.7-highspeed"
api_key env.MINIMAX_API_KEY
base_url "https://api.minimax.io/v1"
}
}

// MiniMax M2.5 - previous generation model with 204K context window
client<llm> MiniMaxM25 {
provider openai
options {
model "MiniMax-M2.5"
api_key env.MINIMAX_API_KEY
base_url "https://api.minimax.io/v1"
}
}

// MiniMax M2.5-highspeed - previous generation faster variant
client<llm> MiniMaxM25Highspeed {
provider openai
retry_policy Exponential
options {
model "MiniMax-M2.5-highspeed"
api_key env.MINIMAX_API_KEY
base_url "https://api.minimax.io/v1"
}
}

// https://docs.boundaryml.com/docs/snippets/clients/round-robin
client<llm> CustomFast {
provider round-robin
options {
// This will alternate between the two clients
strategy [CustomGPT4oMini, CustomHaiku]
strategy [CustomGPT4oMini, CustomHaiku, MiniMaxM27Highspeed]
}
}

Expand All @@ -49,7 +92,7 @@ client<llm> OpenaiFallback {
provider fallback
options {
// This will try the clients in order until one succeeds
strategy [CustomGPT4oMini, CustomGPT4oMini]
strategy [CustomGPT4oMini, CustomGPT4oMini, MiniMaxM27]
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/**
* Integration tests for MiniMax provider via OpenAI-compatible API.
*
* These tests verify that the MiniMax API is reachable and produces
* valid responses using the same endpoint and model configuration
* defined in clients.baml.
*
* Requires MINIMAX_API_KEY environment variable to be set.
* Run with: MINIMAX_API_KEY=sk-... npx tsx test/minimax-integration.test.ts
*/

import * as assert from "assert";

const MINIMAX_API_KEY = process.env.MINIMAX_API_KEY;
const BASE_URL = "https://api.minimax.io/v1";

let passed = 0;
let failed = 0;
let skipped = 0;

async function test(name: string, fn: () => Promise<void>) {
if (!MINIMAX_API_KEY) {
skipped++;
console.log(` SKIP: ${name} (MINIMAX_API_KEY not set)`);
return;
}
try {
await fn();
passed++;
console.log(` PASS: ${name}`);
} catch (err: any) {
failed++;
console.log(` FAIL: ${name}`);
console.log(` ${err.message}`);
}
}

async function callMiniMax(model: string, prompt: string, temperature = 0.7): Promise<any> {
const response = await fetch(`${BASE_URL}/chat/completions`, {
method: "POST",
headers: {
Authorization: `Bearer ${MINIMAX_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model,
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: prompt },
],
temperature,
max_tokens: 100,
}),
});

if (!response.ok) {
const errorText = await response.text();
throw new Error(`API error ${response.status}: ${errorText}`);
}

return response.json();
}

async function main() {
console.log("MiniMax Integration Tests\n");

// --- M2.7 model tests (latest) ---

await test("MiniMax-M2.7 responds to a simple prompt", async () => {
const result = await callMiniMax("MiniMax-M2.7", "Reply with exactly: hello");
assert.ok(result.choices, "Response should have choices array");
assert.ok(result.choices.length > 0, "Should have at least one choice");
assert.ok(
result.choices[0].message?.content,
"First choice should have message content"
);
});

await test("MiniMax-M2.7 returns valid chat completion format", async () => {
const result = await callMiniMax("MiniMax-M2.7", "What is 2+2?");
assert.ok(result.id, "Response should have an id");
assert.ok(result.model, "Response should have a model field");
assert.ok(result.choices[0].message.role === "assistant", "Role should be assistant");
assert.ok(result.usage, "Response should include usage info");
assert.ok(typeof result.usage.total_tokens === "number", "total_tokens should be a number");
});

await test("MiniMax-M2.7 respects temperature constraint", async () => {
const result = await callMiniMax("MiniMax-M2.7", "Say hi", 0.5);
assert.ok(result.choices, "Should respond with valid choices at temperature 0.5");
});

// --- M2.7-highspeed model tests ---

await test("MiniMax-M2.7-highspeed responds to a simple prompt", async () => {
const result = await callMiniMax("MiniMax-M2.7-highspeed", "Reply with exactly: world");
assert.ok(result.choices, "Response should have choices array");
assert.ok(result.choices.length > 0, "Should have at least one choice");
assert.ok(
result.choices[0].message?.content,
"First choice should have message content"
);
});

await test("MiniMax-M2.7-highspeed returns valid usage metrics", async () => {
const result = await callMiniMax("MiniMax-M2.7-highspeed", "Count to 3");
assert.ok(result.usage, "Response should include usage");
assert.ok(result.usage.prompt_tokens > 0, "prompt_tokens should be positive");
assert.ok(result.usage.completion_tokens > 0, "completion_tokens should be positive");
});

// --- M2.5 model tests (previous generation) ---

await test("MiniMax-M2.5 responds to a simple prompt", async () => {
const result = await callMiniMax("MiniMax-M2.5", "Reply with exactly: hello");
assert.ok(result.choices, "Response should have choices array");
assert.ok(result.choices.length > 0, "Should have at least one choice");
assert.ok(
result.choices[0].message?.content,
"First choice should have message content"
);
});

await test("MiniMax-M2.5 returns valid chat completion format", async () => {
const result = await callMiniMax("MiniMax-M2.5", "What is 2+2?");
assert.ok(result.id, "Response should have an id");
assert.ok(result.model, "Response should have a model field");
assert.ok(result.choices[0].message.role === "assistant", "Role should be assistant");
assert.ok(result.usage, "Response should include usage info");
assert.ok(typeof result.usage.total_tokens === "number", "total_tokens should be a number");
});

await test("MiniMax-M2.5 respects temperature constraint (non-zero)", async () => {
// MiniMax requires temperature in (0.0, 1.0]
const result = await callMiniMax("MiniMax-M2.5", "Say hi", 0.5);
assert.ok(result.choices, "Should respond with valid choices at temperature 0.5");
});

// --- M2.5-highspeed model tests ---

await test("MiniMax-M2.5-highspeed responds to a simple prompt", async () => {
const result = await callMiniMax("MiniMax-M2.5-highspeed", "Reply with exactly: world");
assert.ok(result.choices, "Response should have choices array");
assert.ok(result.choices.length > 0, "Should have at least one choice");
assert.ok(
result.choices[0].message?.content,
"First choice should have message content"
);
});

await test("MiniMax-M2.5-highspeed returns valid usage metrics", async () => {
const result = await callMiniMax("MiniMax-M2.5-highspeed", "Count to 3");
assert.ok(result.usage, "Response should include usage");
assert.ok(result.usage.prompt_tokens > 0, "prompt_tokens should be positive");
assert.ok(result.usage.completion_tokens > 0, "completion_tokens should be positive");
});

// --- Summary ---

console.log(
`\nResults: ${passed} passed, ${failed} failed, ${skipped} skipped, ${passed + failed + skipped} total`
);
process.exit(failed > 0 ? 1 : 0);
}

main().catch((err) => {
console.error("Test runner error:", err);
process.exit(1);
});
Loading