81 lines
3.2 KiB
TypeScript
81 lines
3.2 KiB
TypeScript
/**
|
|
* #4072 — one shared vision-detection source.
|
|
*
|
|
* Three code paths kept independent vision-model lists and drifted apart, giving
|
|
* the same model id up to three different verdicts:
|
|
* - `src/lib/modelCapabilities.ts` (`modelIdLikelyVision`) — routing fallback (#4071)
|
|
* - `src/app/api/v1/models/catalog.ts` (`isVisionModelId`) — /v1/models listing
|
|
* - `open-sse/services/compression/lite.ts` (`replaceImageUrls`) — lite image strip
|
|
*
|
|
* The two concrete bugs:
|
|
* - lite stripped images for real vision models it didn't know (pixtral, llava,
|
|
* qwen-vl, glm-4v, kimi-vl, mistral-medium-3) → blinded them;
|
|
* - catalog flagged text models as vision (`gemma`, bare `kimi` like `kimi-k2`).
|
|
*
|
|
* After unification all three delegate to `@/shared/constants/visionModels`.
|
|
*/
|
|
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { isVisionModelId } from "../../src/shared/constants/visionModels.ts";
|
|
import { isVisionModelId as catalogIsVisionModelId } from "../../src/app/api/v1/models/catalog.ts";
|
|
import { modelIdLikelyVision } from "../../src/lib/modelCapabilities.ts";
|
|
import { replaceImageUrls } from "../../open-sse/services/compression/lite.ts";
|
|
|
|
const VISION = [
|
|
"mistral/pixtral-12b-latest",
|
|
"llava-1.5-7b",
|
|
"qwen-vl-max",
|
|
"gpt-4o",
|
|
"glm-4v",
|
|
"kimi-vl-a3b",
|
|
"mistral-medium-3",
|
|
];
|
|
const NOT_VISION = ["ministral-14b-latest", "mistral-large-latest", "gemma-2-9b", "kimi-k2"];
|
|
|
|
function imageBody() {
|
|
return {
|
|
messages: [
|
|
{
|
|
role: "user",
|
|
content: [
|
|
{ type: "image_url", image_url: { url: "data:image/png;base64,iVBOR" } },
|
|
],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
// `replaceImageUrls` strips the image (applied=true) only when the model is NOT a
|
|
// vision model. So `applied === !isVision`.
|
|
function liteStripsImage(modelId: string): boolean {
|
|
return replaceImageUrls(imageBody(), modelId).applied;
|
|
}
|
|
|
|
describe("#4072 vision detection is consistent across all three sources", () => {
|
|
for (const id of VISION) {
|
|
it(`treats ${id} as vision everywhere`, () => {
|
|
assert.equal(isVisionModelId(id), true, `shared isVisionModelId(${id})`);
|
|
assert.equal(catalogIsVisionModelId(id), true, `catalog isVisionModelId(${id})`);
|
|
assert.equal(modelIdLikelyVision(id), true, `modelCapabilities modelIdLikelyVision(${id})`);
|
|
assert.equal(liteStripsImage(id), false, `lite must KEEP the image for ${id}`);
|
|
});
|
|
}
|
|
|
|
for (const id of NOT_VISION) {
|
|
it(`treats ${id} as non-vision everywhere`, () => {
|
|
assert.equal(isVisionModelId(id), false, `shared isVisionModelId(${id})`);
|
|
assert.equal(catalogIsVisionModelId(id), false, `catalog isVisionModelId(${id})`);
|
|
assert.equal(modelIdLikelyVision(id), false, `modelCapabilities modelIdLikelyVision(${id})`);
|
|
assert.equal(liteStripsImage(id), true, `lite must STRIP the image for ${id}`);
|
|
});
|
|
}
|
|
|
|
it("preserves the MiniMax M3 #3328 carve-out and Gemini 3 multimodal", () => {
|
|
for (const id of ["minimax-m3", "minimax-m3-free", "oc/minimax-m3-free", "gemini-3-pro"]) {
|
|
assert.equal(isVisionModelId(id), true, `${id} should be vision`);
|
|
assert.equal(liteStripsImage(id), false, `lite must keep the image for ${id}`);
|
|
}
|
|
});
|
|
});
|