October 22, 2026
Office automation is a chain (read a document, pick a tool, fill its arguments, return a table or JSON, hand the result to the next step), so comparing MiniMax M3 and Qwen3.8 Max 0902 means testing the whole chain, not a single prompt. Both models have a card in the GMI Cloud MaaS model library: "MiniMaxAI/MiniMax-M3", which carries a 20% off badge, and "Qwen3.8 Max 0902". One OpenAI-compatible endpoint and one API key serve both, so the same test harness sends each office workflow to each model and only the model ID changes. Usage is then split by model name in one dashboard.
The platform should meet four conditions.
tools parameter and a response_format parameter, and documents {"type": "json_object"} for JSON mode.Start by generating a key in the console (API keys documentation). Both model IDs come from the cards or the GET /v1/models response, and each result row should carry the exact ID that was used.
Requests go to https://api.gmi-serving.com in the OpenAI chat completions format. The sketch below sends one tool-calling step to either arm and records what comes back.
# pip install openai
import time
from openai import OpenAI
# max_retries=0: the SDK retries some errors by default, which would hide attempts from your log
client = OpenAI(base_url="https://api.gmi-serving.com/v1", api_key=GMI_API_KEY, max_retries=0)
# Model IDs for the two arms
ARMS = {"minimax": "MiniMaxAI/MiniMax-M3",
"qwen": "Qwen/Qwen3.8-Max-0902"}
MAX_TOKENS = ... # one output budget for both arms
TOOLS = [{"type": "function", "function": {
"name": "create_calendar_event",
"description": "Create a meeting on the shared calendar",
"parameters": {"type": "object",
"properties": {"title": {"type": "string"},
"start": {"type": "string"},
"attendees": {"type": "array", "items": {"type": "string"}}},
"required": ["title", "start"]}}}]
def step(arm, messages, **extra): # extra: e.g. response_format={"type": "json_object"} for the JSON smoke request
t0 = time.perf_counter()
try:
r = client.chat.completions.create(
model=ARMS[arm], messages=messages, tools=TOOLS,
temperature=0, max_tokens=MAX_TOKENS, **extra)
except Exception as err: # a rejected parameter or a 429 is a result, not a crash
return None, None, time.perf_counter() - t0, repr(err)
# log the model ID, tool calls, usage, and elapsed seconds for every row
return r.choices[0].message, r.usage, time.perf_counter() - t0, None
Run each arm with the same system prompt, tool definitions, temperature, and output budget, and change only the model. Before building the full set, send each model two smoke requests through step(): one with the dummy tool above, and one that also sets response_format to json_object, which checks that tools and JSON mode work together. A normal response means the request was accepted, and an error is a first result to record, not to patch around. The full set then compares pass rates under each configuration. The Playground is a quick place to try a prompt before you script it. Cap the number of concurrent workers and back off on 429 responses, so rate limits do not end a run halfway.
Where the office comparison's cost is read afterward: set the key reserved for each workflow set, then switch between the two model names.
Tool calls are where the chain first touches your systems. For each test case, store the user request, the tools offered, and the tool call you expected. Include cases where the right answer is no tool call, such as a question that the document already answers.
| Outcome to record | What it tells you |
|---|---|
| Correct tool, valid arguments | The step works |
| Correct tool, wrong or missing argument | Schema, data-type, or format weakness |
| Wrong tool chosen | Tool descriptions may be ambiguous, or the model misreads the request |
| Tool called when none was needed | Over-eager behavior that costs real actions |
| No call when one was needed | Silent drops, the hardest to spot in production |
Count outcomes per category instead of a single accuracy figure, because a wrong calendar entry is a different problem from a missing one. Validate arguments against your own schema in code, and keep the tool descriptions identical for both arms. If one model needs a rewritten description to behave, record that as a cost of adopting it.
Spreadsheet cleanup, invoice extraction, and report filling end in JSON or a table that another system reads. Score three things separately: the output parses, it matches the schema (required fields, types, allowed values), and the values are correct against a labeled answer. A model can pass the first two and fail the third.
Run each structured task in two modes. In the first, ask for JSON in the prompt only. In the second, add response_format with json_object. Do this for each model, since a mode that helps one arm may do nothing for the other. Keep the raw text of parse failures, since a trailing sentence after the JSON and a missing bracket call for different fixes.
For table output, pick one format (Markdown, CSV, or JSON rows) and use it for both arms.
Office inputs include contracts, meeting transcripts, and spreadsheets exported to text.
Set context_length_exceeded_behavior to error for the comparison; with the OpenAI Python SDK, pass it through extra_body, since it is a GMI request parameter. The API reference lists truncate as the default; with error, an input that exceeds the model's context returns an error that shows up in your results instead of being shortened on the server. Add this setting to the long-input cases for each model, for example step("qwen", messages, extra_body={"context_length_exceeded_behavior": "error"}).
Build the cases in two sizes from the same source: one that clearly fits, and one near the longest input you expect. Ask questions whose answers sit at the start, middle, and end of the document. Record prompt_tokens from the usage field for every request that returns it, and log over-limit errors as their own result.
A meeting-notes workflow might extract action items, look up owners in a directory tool, create tasks, and draft a summary email. Score the whole chain and each step, because a step-two failure shows up in step four.
usage.usage tokens per attempt alongside it. If a model completes no workflow, report that result instead of a cost.Run each workflow several times per model, since one run says little about stability, and keep every trace so the failing step can be opened.
Tool functions are code you own. The model proposes a call, your code executes it against the calendar, spreadsheet service, mail system, or ticket queue, and returns the result in the next message. For the comparison, stub these tools with fixtures first, so both models face identical, repeatable responses, and connect the real systems afterward.
If your team builds workflows in Dify, the GMI Cloud plugin appears as a model provider there. The Dify guide in the GMI docs says chat, streaming, tool calling, and custom endpoints work the way Dify expects. In the comparison, swap only the model in a workflow node and keep the rest of the graph fixed as the control.
Put the outcomes in one table per workflow: tool-call outcome counts, JSON validity, long-input results at both sizes, first failing step, retry recovery, step and workflow latency, and cost per finished run.
Cost comes from the Serverless Usage view. Give each workflow set a dedicated key so other traffic stays out of the numbers. For a running token count while a test is still going, sum the usage tokens that step() returns; spend itself comes from the usage view.
Plan the request volume too. On the rate limits page, usage tiers upgrade automatically once your total purchases reach the next tier's threshold (voucher redemptions do not count), applied within 24 hours, so run heavy repeated passes after your tier is settled.
The winner moves to production with the same request code the comparison used. The MaaS page describes centralized billing across all models, zero-retention configurations for sensitive workloads, and guaranteed SLAs with uptime and performance commitments. Office documents can carry sensitive content, so settle the zero-retention configuration and SLA terms with GMI's team before real files go through the API.
If production traffic needs fixed latency, the developers page lists switching to a dedicated endpoint, and the MaaS FAQ says GMI lets teams move between deployment models without changing APIs. Dedicated endpoints are set up per model in the console, and GMI's team can advise on dedicated capacity for the winning model.
The same base URL and key also serve POST /v1/responses (OpenAI Responses API) and POST /v1/messages (Anthropic Messages API). If your agent already uses one of those routes, keep both arms on it, in that route's own request and tool format rather than the chat completions schema in the sketch above, so the comparison stays like for like. Send the two smoke requests on that route first, since parameter support varies by model.
Limits are enforced at the organization level, as tokens per minute for LLMs, so keys under it share the organization's tier. Separate keys keep each workflow set's usage report apart but do not add capacity. When several workflow sets run at once, cap workers across the whole organization, stagger the runs, and back off on 429 responses.
Copy the two model IDs from the cards in the GMI model library, drop them into the harness above under a dedicated key, and send the tool-call and json_object smoke requests to both.
Colin Mo
GMI Cloud helps you architect, deploy, optimize, and scale your AI strategies
