mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-07 15:52:52 +03:00
chatCore.ts fed applyCompressionAsync's supportsVision option from isVisionModelId() — the deliberately-conservative model-id fragment heuristic in src/shared/constants/visionModels.ts — instead of the authoritative getResolvedModelCapabilities().supportsVision used by every other vision-aware path (e.g. the vision-bridge guardrail). gpt-5.5 is registered with supportsVision:true in modelSpecs.ts, but the fragment list has no gpt-5.x entry, so the heuristic wrongly returned false. That false reached lite.ts's replaceImageUrls(), whose gate is `supportsVision !== false`, silently stripping every image_url block before the request ever reached the executor. getResolvedModelCapabilities().supportsVision resolves to null (not false) for genuinely unknown models, which the same !== false gate already treats as preserve-by-default — matching the conservative semantics used elsewhere and avoiding the #4071/#4012 class of bug (blinding a model that can actually see).
This commit is contained in:
committed by
GitHub
parent
851582a88a
commit
f3d92aec5c
@@ -0,0 +1 @@
|
||||
- fix(sse): feed the compression pipeline the authoritative vision capability instead of the conservative model-id heuristic, so vision models absent from the fragment list (e.g. gpt-5.5) no longer have their image_url blocks silently stripped (#7237)
|
||||
@@ -112,9 +112,8 @@ import { normalizeClaudeHaikuConstraints } from "../services/claudeHaikuConstrai
|
||||
import { echoModelInObject } from "../services/responseModelEcho.ts";
|
||||
import { stripGpt5SamplingWhenReasoning } from "../services/gpt5SamplingGuard.ts";
|
||||
import { getUnsupportedParams, REGISTRY } from "../config/providerRegistry.ts";
|
||||
import { supportsMaxTokens } from "@/lib/modelCapabilities.ts";
|
||||
import { supportsMaxTokens, getResolvedModelCapabilities } from "@/lib/modelCapabilities.ts";
|
||||
import { normalizeThinkingForModel } from "@/shared/constants/modelSpecs.ts";
|
||||
import { isVisionModelId } from "@/shared/constants/visionModels.ts";
|
||||
import {
|
||||
buildErrorBody,
|
||||
createErrorResult,
|
||||
@@ -1327,7 +1326,16 @@ export async function handleChatCore({
|
||||
const compressionConfig = resolveCacheAwareConfig(config, compressionInputBody, cacheCtx);
|
||||
const result = await applyCompressionAsync(compressionInputBody, mode, {
|
||||
model: effectiveModel,
|
||||
supportsVision: isVisionModelId(effectiveModel),
|
||||
// #7237: feed the AUTHORITATIVE capability (model spec / models.dev sync / DB
|
||||
// override, with the conservative model-id fragment heuristic only as its
|
||||
// last-resort fallback) instead of calling the heuristic directly here. The
|
||||
// heuristic alone wrongly returned false for e.g. gpt-5.5 (registered
|
||||
// supportsVision:true in modelSpecs but absent from the deliberately-conservative
|
||||
// fragment list), and lite.ts's gate (`supportsVision !== false`) treated that
|
||||
// false as "strip every image_url block". Resolves to `null` for genuinely unknown
|
||||
// models, which is intentionally NOT `false` so the gate still preserves images.
|
||||
supportsVision: getResolvedModelCapabilities({ provider, model: effectiveModel })
|
||||
.supportsVision,
|
||||
// Rota direta oficial ('anthropic') vs agregadores: o engine omniglyph
|
||||
// exige 'direct' — agregadores redimensionam imagens (medido 2026-07-06).
|
||||
providerTransport: provider === "anthropic" ? "direct" : "aggregator",
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* #7237 — vision-capable models lose image_url blocks under compression.
|
||||
*
|
||||
* `open-sse/handlers/chatCore.ts` fed `applyCompressionAsync`'s `supportsVision` option
|
||||
* from `isVisionModelId(effectiveModel)` — the deliberately-conservative model-id
|
||||
* fragment heuristic in `src/shared/constants/visionModels.ts` — instead of the
|
||||
* authoritative `getResolvedModelCapabilities().supportsVision` that every other
|
||||
* vision-aware code path (e.g. the vision-bridge guardrail) uses.
|
||||
*
|
||||
* `gpt-5.5` is registered with `supportsVision: true` in `src/shared/constants/modelSpecs.ts`
|
||||
* but has no gpt-5.x entry in the fragment list, so the heuristic wrongly returned `false`.
|
||||
* `open-sse/services/compression/lite.ts::replaceImageUrls()` gates on
|
||||
* `supportsVision !== false`, so that spurious `false` made it silently strip every
|
||||
* `image_url` block from the request before it ever reached the executor.
|
||||
*
|
||||
* This test asserts the CORRECT, authoritative-capability-driven behavior: gpt-5.5
|
||||
* keeps its images through the lite-compression path.
|
||||
*/
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { isVisionModelId } from "../../src/shared/constants/visionModels.ts";
|
||||
import { getResolvedModelCapabilities } from "../../src/lib/modelCapabilities.ts";
|
||||
import { replaceImageUrls } from "../../open-sse/services/compression/lite.ts";
|
||||
import { applyCompressionAsync } from "../../open-sse/services/compression/strategySelector.ts";
|
||||
|
||||
function imageBody() {
|
||||
return {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "image_url", image_url: { url: "data:image/png;base64,iVBOR" } }],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
describe("#7237 vision-capable models keep their images through compression", () => {
|
||||
it("documents the drift: the conservative id-fragment heuristic disagrees with the authoritative spec for gpt-5.5", () => {
|
||||
assert.equal(
|
||||
isVisionModelId("gpt-5.5"),
|
||||
false,
|
||||
"the fragment-list heuristic has no gpt-5.x entry — it is a deliberately conservative fallback, not the source of truth"
|
||||
);
|
||||
assert.equal(
|
||||
getResolvedModelCapabilities({ model: "gpt-5.5" }).supportsVision,
|
||||
true,
|
||||
"modelSpecs.ts registers gpt-5.5 with supportsVision:true — this is the authoritative source chatCore must use"
|
||||
);
|
||||
});
|
||||
|
||||
it("replaceImageUrls preserves the image when fed the authoritative capability (the fixed chatCore.ts:1330 behavior)", () => {
|
||||
const authoritativeSupportsVision = getResolvedModelCapabilities({
|
||||
model: "gpt-5.5",
|
||||
}).supportsVision;
|
||||
const result = replaceImageUrls(imageBody(), { supportsVision: authoritativeSupportsVision });
|
||||
assert.equal(result.applied, false, "the image must be KEPT, not stripped to a placeholder");
|
||||
const content = result.body.messages?.[0]?.content as Array<Record<string, unknown>>;
|
||||
assert.equal(content[0].type, "image_url", "the block must remain a real image_url block");
|
||||
});
|
||||
|
||||
it("regresses the pre-fix bug: feeding the raw heuristic value strips the image for gpt-5.5", () => {
|
||||
const buggyValue = isVisionModelId("gpt-5.5"); // false — the pre-fix chatCore.ts:1330 input
|
||||
const result = replaceImageUrls(imageBody(), { supportsVision: buggyValue });
|
||||
assert.equal(
|
||||
result.applied,
|
||||
true,
|
||||
"sanity check: this reproduces the bug shape when fed the wrong (heuristic) value"
|
||||
);
|
||||
});
|
||||
|
||||
it("applyCompressionAsync end-to-end (lite mode) keeps image_url blocks for gpt-5.5 when fed the authoritative capability", async () => {
|
||||
const model = "gpt-5.5";
|
||||
const supportsVision = getResolvedModelCapabilities({ model }).supportsVision;
|
||||
const result = await applyCompressionAsync(imageBody(), "lite", { model, supportsVision });
|
||||
const content = (result.body as { messages: Array<{ content: unknown }> }).messages[0]
|
||||
.content as Array<Record<string, unknown>>;
|
||||
assert.equal(content[0].type, "image_url", "gpt-5.5 must keep its image_url block intact");
|
||||
assert.equal(
|
||||
(content[0].image_url as Record<string, unknown>)?.url,
|
||||
"data:image/png;base64,iVBOR",
|
||||
"the original data URL must survive unchanged"
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user