October 22, 2026
Four jobs decide how a multilingual support assistant performs: answering in many languages, keeping a brand voice in each, handing a case to a person at the right moment, and writing fields a ticketing system can read. A trial of Qwen3.8 Max 0902 against DeepSeek-V4-Pro-0813 should score both models on those four. On GMI Cloud MaaS, both models have cards in the model library (DeepSeek-V4-Pro-0813 currently carries a 20% off badge) and answer on the same OpenAI-compatible endpoint. The key, client code, prompts, ticket set, and usage report are shared, and the model ID is the only switch between the two arms.
On the developers page, the migration rule is to leave your code alone and swap the model ID. The host is https://api.gmi-serving.com, the OpenAI SDK's base_url adds /v1, chat completions accepts the OpenAI request shape, and GET /v1/models lists the models your key can call. Create a key in the console (the API keys documentation covers creation and revocation), then copy the two IDs.
The console's public billing listing names the two models Qwen/Qwen3.8-Max-0902 and deepseek-ai/DeepSeek-V4-Pro-0813. The library also holds Qwen3.8 Max, Qwen3.8 Flash, Qwen3.8 27B, and a DeepSeek-V4-Pro card without the 0813 suffix, so copy the IDs from the two dated cards under test.
# pip install openai
from openai import OpenAI
# one client per language key if you split spend by language (see the cost section)
client = OpenAI(base_url="https://api.gmi-serving.com/v1", api_key=GMI_API_KEY)
# Model IDs of the two dated cards in the GMI model library
ARMS = {"qwen": "Qwen/Qwen3.8-Max-0902",
"deepseek": "deepseek-ai/DeepSeek-V4-Pro-0813"}
def reply(arm, system_prompt, history):
return client.chat.completions.create(
model=ARMS[arm],
messages=[{"role": "system", "content": system_prompt}] + history,
temperature=0.2,
max_tokens=MAX_TOKENS, # one shared output budget for both arms
stream=True, # time the first token from your own client
)
Use a dedicated key for the trial. The Serverless Usage page in the usage documentation filters by API key and model name, so trial spend stays separate from other traffic and splits by model without extra tooling.
Where each arm's trial spend is read: set the filter to the trial key, then switch between the two model IDs.
Each language is a separate product surface with its own customers, tone expectations, and reviewers. Rows of the matrix are the languages you serve or plan to serve, with their share of ticket volume. Columns are the ticket types in your queue, and six types make a workable first set:
| Ticket type | What the assistant must do | What counts as a failure |
|---|---|---|
| Policy question (returns, shipping, plans) | Answer from the supplied policy excerpt | Facts outside the excerpt, or a commitment the policy does not make |
| Account or order status | Ask for missing details, then use the data the system passes in | Guessing an order state, or asking for data already in the ticket |
| Complaint or frustrated customer | Acknowledge the problem and keep the brand's register | Over-apologizing, cold phrasing, or a register that does not suit the language |
| Out-of-scope request | Decline briefly and route correctly | Improvised advice on legal, medical, or financial matters |
| Escalation trigger (chargeback, safety, legal threat, repeated failure) | Hand off to a human with a usable summary | Continuing to answer, or escalating with a summary an agent cannot act on |
| Mixed-language or switching customer | Follow the language-reply rule you set | Replying in the wrong language, or answering half in each |
Choose the languages that carry the most traffic or the most risk, and give long-tail languages a smaller block of their own, so the trial shows whether they are workable.
Pull historical conversations per language and mask personal data before anything leaves your systems. Include the awkward tickets on purpose:
Attach an expected outcome to each ticket before any model runs: the reply language, the action (answer, ask a clarifying question, decline, escalate), and the policy points the reply must or must not state. Keep policy excerpts in the language of your knowledge base, so some tickets ask in a different language from the excerpt. Keep a held-out portion that nobody tunes prompts against.
Native speakers should own the review for each language.
Write a single system prompt per language, or a shared one with a language-specific style block, and use it unchanged for both arms. Spell out:
The LLM API reference lists response_format for JSON output and tools for function calling. Give both arms the same output contract, validate the returned fields against your schema in the application, and score structured output like any other dimension: a reply with unparseable fields fails that ticket. If your ticketing integration needs tool calls rather than JSON, run that path the same way.
The Playground exposes the model selector, sampling controls, and the system prompt, so ten tickets per language can go through both models there before any script exists. It is a quick way to catch a prompt that confuses the reply-language rule.
max_tokens for both arms, using only values both models accept. Choose a low temperature for support replies, and record it. Set the shared max_tokens from the language that needs the most tokens, and tag replies that stop at the limit as truncated, since tokenizers can split the same sentence differently. Truncated replies are still scored for completeness and required fields, and they count against acceptance and cost per accepted reply.context_length_exceeded_behavior is truncate. During the trial, pass extra_body={"context_length_exceeded_behavior": "error"} so a thread that is too long for a model fails visibly instead of being cut. Build the main comparison from threads that fit in both models' context, and report any over-limit thread as its own sample rather than folding it into the averages.Keep every raw reply so reviewers can read the failures, and shuffle outputs so reviewers cannot see which model wrote them.
| Measure | How to get it |
|---|---|
| Reply-language adherence | Share of replies in the language your rule requires, checked by a language-detection pass and spot-read by natives |
| Resolution quality | Blind rubric score by a native reviewer: correct answer, complete, appropriate for the ticket |
| Tone and register | Reviewer score against the per-language style guide and glossary |
| Policy grounding | Count of claims or commitments not in the supplied excerpt |
| Escalation precision | Of the tickets the model escalated, the share that should have been |
| Escalation recall | Of the tickets that should have been escalated, the share the model caught |
| Hand-off usefulness | Reviewer score on whether a human agent could act on the summary without re-reading the thread |
| Structured-output validity | Share of replies whose fields parse and contain the required values |
| Tokens per ticket | prompt_tokens and completion_tokens from the response usage field |
| Latency | Time to first token and total time per reply, measured from the same machine and network location with the same concurrency for both arms |
| Failures | Rate-limit responses, timeouts, empty or truncated replies |
For escalation, report the automation rate, the share of tickets the model handles without a hand-off, next to resolution quality: a model that never escalates and a model that escalates everything fail in opposite ways. If a model never escalates, its precision is undefined: report the number of escalations it made and the full confusion matrix instead of a score. Weigh recall higher for categories where a missed hand-off costs most, such as legal or safety tickets.
On cost, compare inference spend per accepted reply rather than list rates. Use tokens per ticket from the usage field, then compute that figure: the billed API spend for each arm from the trial key's usage view, divided by the number of replies that reviewers accepted under a fixed rule, with repeats counted under the rule you set up front. Review labor and tool calls are separate cost lines. The usage view filters by key and model, so for per-language spend give each language its own trial key. A model with no accepted reply in a language has no usable unit cost there: mark that language as failed for the model rather than ranking it on cost.
A single average hides the cases where a model is strong on your highest-volume language and weak on a long-tail one. Report each language separately, then by ticket type inside it.
Set an acceptance gate per language before looking at results: for example, a minimum resolution score, a minimum escalation recall for the high-risk ticket types, and a maximum rate of out-of-excerpt commitments. A model that fails the gate in a language does not take that language into production, whatever its overall mean.
The outcome can be a split. If one model passes in some languages and the other passes in the rest, route by detected language in your own application; both models stay on the same endpoint, with usage still separated by model name. If both pass, choose on the measures the business weighs most, such as latency, escalation recall, or cost per accepted reply. If neither passes in a language, that language starts with human agents and an assistant that drafts replies for review. Record the model IDs, the date, prompt versions, sampling settings, sample counts per language, and the rubric, so the same harness can be rerun when a new checkpoint appears.
Ten tickets per language through the Playground make the first pass: open the model library in the GMI Cloud console, select the Qwen3.8 Max 0902 and DeepSeek-V4-Pro-0813 cards, and send the same tickets to each. Then create the dedicated trial key and script the full set with the settings above. To discuss volume pricing, zero-retention setup, or reserved capacity for whichever model wins, reach out to GMI.
Colin Mo
GMI Cloud helps you architect, deploy, optimize, and scale your AI strategies
