mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(ai): send only thinking on/off toggle for Xiaomi MiMo models
MiMo Chat Completions rejects reasoning_effort "minimal" with a 400. Xiaomi documents only thinking.type enabled/disabled and treats all effort levels identically, so stop sending reasoning_effort and expose only the off and high thinking levels. fixes #10818
This commit is contained in:
parent
1692bba634
commit
6374c2baf4
3 changed files with 53 additions and 2 deletions
|
|
@ -15,6 +15,7 @@
|
|||
- Fixed automatic retry not triggering for gateway `Unable to forward upstream stream` errors ([#10920](https://github.com/earendil-works/pi/issues/10920))
|
||||
- Fixed OpenAI-compatible responses showing thinking after the answer when a proxy such as LiteLLM sends reasoning and content in the same stream chunk ([#10926](https://github.com/earendil-works/pi/issues/10926))
|
||||
- Fixed Gemini 4 Argon requests on Google Vertex failing with "Thinking budget is not supported", and its thinking level being ignored on the Gemini API, by sending `thinkingLevel` for Gemini 4 and later models ([#10890](https://github.com/earendil-works/pi/issues/10890))
|
||||
- Fixed Xiaomi MiMo models failing with a 400 at the `minimal` thinking level by sending only the documented thinking on/off toggle and exposing only the `off` and `high` levels ([#10818](https://github.com/earendil-works/pi/issues/10818))
|
||||
|
||||
## [1.2.0] - 2026-10-12
|
||||
|
||||
|
|
|
|||
|
|
@ -2462,10 +2462,23 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
|||
// Built-in `xiaomi` targets the API billing endpoint (single stable URL,
|
||||
// keys from platform.xiaomimimo.com). The three `xiaomi-token-plan-*`
|
||||
// providers cover prepaid Token Plan endpoints in cn / ams / sgp.
|
||||
// MiMo Chat Completions only documents thinking.type enabled/disabled and
|
||||
// rejects some reasoning_effort values (e.g. "minimal"). Xiaomi documents
|
||||
// all effort levels as identical, so expose only off and high.
|
||||
const xiaomiCompat: OpenAICompletionsCompat = {
|
||||
supportsReasoningEffort: false,
|
||||
requiresReasoningContentOnAssistantMessages: true,
|
||||
thinkingFormat: "deepseek",
|
||||
};
|
||||
const xiaomiThinkingLevelMap = {
|
||||
off: "off",
|
||||
minimal: null,
|
||||
low: null,
|
||||
medium: null,
|
||||
high: "high",
|
||||
xhigh: null,
|
||||
max: null,
|
||||
} as const;
|
||||
const xiaomiVariants = [
|
||||
{ source: "xiaomi", provider: "xiaomi", baseUrl: "https://api.xiaomimimo.com/v1" },
|
||||
{
|
||||
|
|
@ -2502,12 +2515,12 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
|||
baseUrl,
|
||||
compat: xiaomiCompat,
|
||||
reasoning: m.reasoning === true,
|
||||
...(m.reasoning === true ? { thinkingLevelMap: xiaomiThinkingLevelMap } : {}),
|
||||
input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
|
||||
cost: getModelsDevCost(m.cost),
|
||||
contextWindow: m.limit?.context || 4096,
|
||||
maxTokens: m.limit?.output || 4096,
|
||||
});
|
||||
recordModelsDevReasoningOptions(provider, modelId, m);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { getModels } from "../src/compat.ts";
|
||||
import { getModels, streamSimple } from "../src/compat.ts";
|
||||
import { getSupportedThinkingLevels } from "../src/models.ts";
|
||||
import type { ThinkingLevel } from "../src/types.ts";
|
||||
|
||||
const XIAOMI_PROVIDERS = ["xiaomi", "xiaomi-token-plan-cn", "xiaomi-token-plan-ams", "xiaomi-token-plan-sgp"] as const;
|
||||
const DEPRECATED_MODEL_IDS = ["mimo-v2-flash", "mimo-v2-omni", "mimo-v2-pro"] as const;
|
||||
|
|
@ -15,4 +17,39 @@ describe("Xiaomi MiMo models", () => {
|
|||
const modelIds = getModels(provider).map((model) => model.id);
|
||||
for (const modelId of REPLACEMENT_MODEL_IDS) expect(modelIds).toContain(modelId);
|
||||
});
|
||||
|
||||
// #10818: MiMo Chat Completions only supports a thinking on/off toggle.
|
||||
it.each(XIAOMI_PROVIDERS)("models %s reasoning as an off/on toggle", async (provider) => {
|
||||
const models = getModels(provider).filter((model) => model.reasoning);
|
||||
expect(models.length).toBeGreaterThan(0);
|
||||
|
||||
for (const model of models) {
|
||||
expect(getSupportedThinkingLevels(model)).toEqual(["off", "high"]);
|
||||
|
||||
const capturePayload = async (reasoning: ThinkingLevel | undefined) => {
|
||||
let payload: Record<string, unknown> | undefined;
|
||||
await streamSimple(
|
||||
model,
|
||||
{ messages: [{ role: "user", content: "test", timestamp: 0 }] },
|
||||
{
|
||||
apiKey: "test-xiaomi-key",
|
||||
reasoning,
|
||||
onPayload: (value) => {
|
||||
payload = value as Record<string, unknown>;
|
||||
throw new Error("payload captured");
|
||||
},
|
||||
},
|
||||
).result();
|
||||
return payload;
|
||||
};
|
||||
|
||||
const enabled = await capturePayload("high");
|
||||
expect(enabled?.thinking).toEqual({ type: "enabled" });
|
||||
expect(enabled?.reasoning_effort).toBeUndefined();
|
||||
|
||||
const disabled = await capturePayload(undefined);
|
||||
expect(disabled?.thinking).toEqual({ type: "disabled" });
|
||||
expect(disabled?.reasoning_effort).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue