MiniMax Function Calling: Tool Loops, Interleaved Thinking, and Errors

Last verified: July 18, 2026.

Verification scope: We reviewed MiniMax’s official M3 tool-use guide, OpenAI-compatible request schema, server-tool documentation, and error-code reference. The syntax and state-handling rules below were checked against those sources. We did not execute a live API call because no MiniMax credentials were available. Test the code with a restricted key and harmless tools before connecting it to production systems.

MiniMax function calling lets a model request a function, but your application remains responsible for deciding whether that function is allowed, validating the arguments, executing it, and returning a result. MiniMax M3 can reason between tool rounds through interleaved thinking. That makes one state rule especially important: append the complete assistant message to history, including tool_calls and reasoning_details, before adding tool results.

This page focuses on safe client-executed tool loops. For keys, base URLs, and model selection, read the MiniMax API guide and OpenAI-compatible API guide. For model characteristics rather than integration logic, use the MiniMax M3 guide.

Independent website notice: MiniMax-AI.chat is an independent website. It is not owned, operated, sponsored, endorsed by, or affiliated with MiniMax. MiniMax names, model names, logos, and related trademarks belong to their respective owners.

How the tool loop works

  1. Your application sends messages plus a list of tool definitions.
  2. M3 either produces a normal answer or returns one or more tool_calls.
  3. Your code appends the complete assistant message to history without deleting its content, calls, or reasoning data.
  4. Your dispatcher checks the requested tool name against an allowlist and validates the JSON arguments.
  5. Your application executes the approved function and appends one role: "tool" message for each call ID.
  6. The expanded history is sent back to M3. The cycle continues until there are no tool calls or a configured round limit is reached.

A tool definition is not an instruction to run arbitrary code. It is a contract that describes a small set of operations your application has chosen to expose. The model can propose a name and JSON arguments, but that output must be treated as untrusted input.

The state rule that prevents broken interleaved thinking

With the OpenAI-compatible interface, MiniMax documents two response formats. Setting reasoning_split: true separates thinking into reasoning_details. In that format, append the returned assistant object as a whole. Do not rebuild it from only content and tool_calls, because that would omit the reasoning state.

With the native format, thinking remains inside <think> tags in the assistant content. MiniMax says that content must not be modified before the next round. Pick one format and preserve it consistently. The examples below use the split format and keep the complete SDK message object in messages.

Preserving reasoning for the API round trip does not mean displaying it to end users or placing it in routine logs. Render the assistant’s answer content, log operational identifiers and outcomes, and protect the full message history according to your application’s data policy.

Node.js: a complete, restricted tool loop

This example exposes one read-only function backed by sample data. It has an exact-name allowlist, rejects extra fields, validates the order-ID format, caps tool-result size, handles every returned tool call, and stops after six rounds. Install the SDK with npm install openai and keep MINIMAX_API_KEY on the server.

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.MINIMAX_API_KEY,
  baseURL: "https://api.minimax.io/v1",
});

const ORDER_ID = /^ORD-[A-Z0-9]{8}$/;
const orderStore = new Map([
  ["ORD-A1B2C3D4", { status: "shipped", carrier: "Example Parcel" }],
]);

const tools = [
  {
    type: "function",
    function: {
      name: "lookup_order_status",
      description: "Look up the fulfillment status of one order ID.",
      parameters: {
        type: "object",
        properties: {
          order_id: {
            type: "string",
            description: "Order ID in the form ORD- plus eight uppercase letters or digits.",
          },
        },
        required: ["order_id"],
        additionalProperties: false,
      },
    },
  },
];

function validateOrderArgs(raw) {
  let value;
  try {
    value = JSON.parse(raw);
  } catch {
    throw new Error("Arguments are not valid JSON");
  }

  if (value === null || Array.isArray(value) || typeof value !== "object") {
    throw new Error("Arguments must be an object");
  }
  if (Object.keys(value).some((key) => key !== "order_id")) {
    throw new Error("Unexpected argument field");
  }
  if (typeof value.order_id !== "string" || !ORDER_ID.test(value.order_id)) {
    throw new Error("order_id has an invalid format");
  }
  return { order_id: value.order_id };
}

const executors = Object.freeze({
  lookup_order_status: async ({ order_id }) => {
    const record = orderStore.get(order_id);
    return record
      ? { ok: true, order_id, ...record }
      : { ok: true, order_id, found: false };
  },
});

async function executeToolCall(call) {
  const name = call.function?.name;
  if (!name || !Object.prototype.hasOwnProperty.call(executors, name)) {
    return JSON.stringify({ ok: false, error: { code: "TOOL_NOT_ALLOWED" } });
  }

  try {
    const args = validateOrderArgs(call.function.arguments);
    const result = await executors[name](args);
    const serialized = JSON.stringify(result);
    return serialized.length <= 4000
      ? serialized
      : JSON.stringify({ ok: false, error: { code: "RESULT_TOO_LARGE" } });
  } catch {
    return JSON.stringify({ ok: false, error: { code: "INVALID_TOOL_ARGUMENTS" } });
  }
}

const messages = [
  {
    role: "system",
    content: "Use tools only when required. Never invent an order status.",
  },
  { role: "user", content: "Check order ORD-A1B2C3D4." },
];

const MAX_TOOL_ROUNDS = 6;
let finalText;

for (let round = 0; round < MAX_TOOL_ROUNDS; round += 1) {
  const response = await client.chat.completions.create({
    model: "MiniMax-M3",
    messages,
    tools,
    // MiniMax extension: return thinking separately from visible text.
    reasoning_split: true,
  });

  const assistant = response.choices[0].message;

  // Preserve content, tool_calls, and reasoning_details as returned.
  messages.push(assistant);

  const calls = assistant.tool_calls ?? [];
  if (calls.length === 0) {
    finalText = assistant.content ?? "";
    break;
  }

  for (const call of calls) {
    if (!call.id) throw new Error("Tool call ID is missing");
    messages.push({
      role: "tool",
      tool_call_id: call.id,
      content: await executeToolCall(call),
    });
  }
}

if (finalText === undefined) {
  throw new Error("Tool round limit reached");
}

console.log(finalText);

The JSON Schema helps the model form arguments, while validateOrderArgs enforces the contract in application code. The object of executors is the allowlist; there is no eval, shell command, dynamic import, or name-to-method reflection. Tool failures are returned as small machine-readable results rather than raw stack traces.

Python: the same safety pattern

Install the SDK with pip install openai and set MINIMAX_OPENAI_BASE_URL to the international base URL shown in the Node.js example. This version also preserves the complete assistant object, executes all calls, rejects unknown fields, and uses a read-only in-memory store.

import json
import os
import re
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["MINIMAX_API_KEY"],
    base_url=os.environ["MINIMAX_OPENAI_BASE_URL"],
)

ORDER_ID = re.compile(r"^ORD-[A-Z0-9]{8}$")
ORDER_STORE = {
    "ORD-A1B2C3D4": {"status": "shipped", "carrier": "Example Parcel"}
}

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "lookup_order_status",
            "description": "Look up the fulfillment status of one order ID.",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "string",
                        "description": "ORD- plus eight uppercase letters or digits",
                    }
                },
                "required": ["order_id"],
                "additionalProperties": False,
            },
        },
    }
]

def validate_order_args(raw: str) -> dict:
    try:
        value = json.loads(raw)
    except json.JSONDecodeError as exc:
        raise ValueError("Arguments are not valid JSON") from exc

    if not isinstance(value, dict) or set(value) != {"order_id"}:
        raise ValueError("Arguments must contain only order_id")
    if not isinstance(value["order_id"], str) or not ORDER_ID.fullmatch(value["order_id"]):
        raise ValueError("order_id has an invalid format")
    return {"order_id": value["order_id"]}

def lookup_order_status(order_id: str) -> dict:
    record = ORDER_STORE.get(order_id)
    if record is None:
        return {"ok": True, "order_id": order_id, "found": False}
    return {"ok": True, "order_id": order_id, **record}

EXECUTORS = {"lookup_order_status": lookup_order_status}

def execute_tool_call(call) -> str:
    name = call.function.name
    if name not in EXECUTORS:
        return json.dumps({"ok": False, "error": {"code": "TOOL_NOT_ALLOWED"}})
    try:
        args = validate_order_args(call.function.arguments)
        result = json.dumps(EXECUTORS[name](**args))
        if len(result) > 4000:
            return json.dumps({"ok": False, "error": {"code": "RESULT_TOO_LARGE"}})
        return result
    except (TypeError, ValueError):
        return json.dumps({"ok": False, "error": {"code": "INVALID_TOOL_ARGUMENTS"}})

messages = [
    {
        "role": "system",
        "content": "Use tools only when required. Never invent an order status.",
    },
    {"role": "user", "content": "Check order ORD-A1B2C3D4."},
]

MAX_TOOL_ROUNDS = 6

for _ in range(MAX_TOOL_ROUNDS):
    response = client.chat.completions.create(
        model="MiniMax-M3",
        messages=messages,
        tools=TOOLS,
        extra_body={"reasoning_split": True},
    )
    assistant = response.choices[0].message

    # Preserve content, tool_calls, and reasoning_details as returned.
    messages.append(assistant)

    calls = assistant.tool_calls or []
    if not calls:
        print(assistant.content or "")
        break

    for call in calls:
        if not call.id:
            raise RuntimeError("Tool call ID is missing")
        messages.append(
            {
                "role": "tool",
                "tool_call_id": call.id,
                "content": execute_tool_call(call),
            }
        )
else:
    raise RuntimeError("Tool round limit reached")

Client functions and MiniMax server tools are different

PropertyClient-executed functionMiniMax server tool
Who executes it?Your applicationMiniMax’s service
Round tripsYour code returns a tool result in a later requestExecution and continuation occur within one API request
Allowlist and argument validationYour responsibilityExecution is managed by the service; your app still decides whether to enable the tool
Documented interfaceOpenAI- and Anthropic-compatible tool flowsAnthropic Messages endpoint
Documented built-in toolYour own functionsweb_search

MiniMax labels server tools as Beta on the verification date. Its server-side web_search uses the Anthropic Messages route and returns server-tool content blocks. Do not copy that declaration into an OpenAI client loop or pretend it is a locally executed function. Our Anthropic-compatible API guide explains the separate message format.

Error handling without unsafe execution

  • Unknown tool name: return a structured TOOL_NOT_ALLOWED result. Never resolve a model-provided name through eval, a shell, a file path, or arbitrary object traversal.
  • Malformed JSON: catch the parser error and return a generic invalid-arguments result. Do not guess missing values.
  • Schema violation: reject extra properties, wrong types, invalid enums, oversized strings, and identifiers outside the expected pattern.
  • Dependency timeout: enforce a per-tool deadline, cancel the operation where supported, and return a small retriable error. Do not send connection strings or stack traces to the model.
  • Oversized result: return a bounded summary or an opaque reference that the same authorized application can retrieve. Huge tool responses increase cost and can crowd out useful context.
  • Repeated call: use idempotency controls for tools that can mutate data. Record processed call IDs or an application transaction key so a retry cannot duplicate an action.
  • Round-limit breach: stop and surface a controlled application error. An unbounded agent loop can create cost and operational risk.
  • API error: retry only transient failures with capped exponential backoff and jitter. Do not retry invalid credentials, insufficient balance, unsafe input, or invalid parameters as if they were network faults.

MiniMax error codes are separate from tool-result errors. Keep the two layers distinct in logs and metrics. See our MiniMax API error-codes guide for rate limits, timeouts, authentication, balance, token limits, and service failures.

Rules for tools that change data

The examples are read-only by design. A payment, deletion, message send, account change, or external publication needs stronger controls. Require explicit user confirmation at the point of action, check authorization in the executor rather than the prompt, show the exact target and effect, use least-privilege credentials, and preserve an audit record. The model’s argument is never proof that the user authorized an operation.

Separate planning tools from committing tools. For example, draft_refund may calculate an amount without changing data, while submit_refund requires a validated approval token generated by your application. Avoid a broad tool such as run_sql or execute_command; expose narrowly scoped operations with server-side policy checks.

Tool definitions that produce better calls

  • Use a precise function name that describes one operation.
  • State when the tool should be used and what it does not do.
  • Use enums for closed choices and strict patterns for identifiers.
  • Mark required properties and set additionalProperties: false.
  • Keep descriptions stable across requests so MiniMax prompt caching can reuse the tools-first prefix.
  • Return compact JSON with an explicit ok field and stable error codes.
  • Do not include secrets, internal credentials, or privileged implementation details in descriptions or tool results.

Test checklist before production

  1. Test a request that needs no tool and confirm the loop returns the assistant text directly.
  2. Test one valid call, multiple calls in one assistant message, and several sequential rounds.
  3. Inject malformed JSON, an unknown name, missing fields, extra fields, excessive lengths, and invalid enum values.
  4. Confirm the complete assistant object is present before every matching tool message and that each tool result uses the correct call ID.
  5. Simulate dependency timeouts, rate limits, and an unavailable downstream service.
  6. Verify the six-round guard and tool-result size cap.
  7. Confirm that reasoning data is preserved for the API but excluded from user-facing output and ordinary logs.
  8. For any write tool, test authorization failure, confirmation expiry, duplicate execution, rollback, and audit events.
  9. Measure tool-call accuracy with a labeled evaluation set: correct tool, correct arguments, unnecessary calls, missed calls, and final-answer grounding.

If you use a different orchestration interface, such as the MiniMax Responses API, follow that interface’s state model rather than transplanting Chat Completions messages unchanged.

Frequently asked questions

Does MiniMax execute my function automatically?

No. In client-side function calling, the model proposes a call and your code executes an allowed function. MiniMax’s server tools are a separate feature executed by its service.

Why must I preserve reasoning_details?

M3 uses interleaved thinking between tool rounds. MiniMax instructs developers to return the complete assistant response, including the separated reasoning field, so the reasoning chain remains continuous.

Should I trust JSON Schema validation by the model?

No. The schema guides generation. Your executor must parse and validate the arguments independently before any operation.

Can the model call several tools in one response?

Your loop should be prepared for multiple entries in tool_calls. Execute each approved call and return a tool message paired with its exact ID before the next model request.

Is a tool-call error the same as a MiniMax API error?

No. A tool error comes from your dispatcher or dependency. An API error comes from the model service, authentication, limits, billing, or request validation. Handle and measure them separately.

Official sources