Files
OmniRoute/tests/unit/vision-bridge-task-aware.test.ts
Praveen K Palaniswamy 65e81158ab fix(ollama): route models by advertised capability (#11088)
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host.

Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean.

Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
2026-08-23 11:45:01 -03:00

116 lines
4.3 KiB
TypeScript

/**
* Task-aware vision description prompt (codex-vision-proxy pattern) —
* Modality Bridge PR-1. The describe path appends the user's last question as
* a focus hint so the vision model describes what is relevant to answering it
* instead of producing a generic caption. Default ON; disabled via
* `modalityBridgeVisionTaskAware: false`.
*
* Guardrail-level cases use `model: "auto/..."` + `mode: "describe"` so the
* whole flow is DB-free (the auto prefix skips the capability/combo lookups
* that open SQLite, and the forced describe mode skips the reroute block).
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import { composeVisionPrompt } from "../../src/lib/guardrails/visionBridgeHelpers.ts";
import { VisionBridgeGuardrail } from "../../src/lib/guardrails/visionBridge.ts";
import type { VisionModelConfig } from "../../src/lib/guardrails/visionBridgeHelpers.ts";
// ── composeVisionPrompt (pure) ──────────────────────────────────────────────
test("appends user focus hint when taskAware", () => {
const p = composeVisionPrompt("Describe the image.", "qual o erro no screenshot?", true);
assert.ok(p.startsWith("Describe the image."));
assert.ok(p.includes("qual o erro no screenshot?"));
});
test("no hint when disabled or no user text", () => {
assert.equal(composeVisionPrompt("Base.", "pergunta", false), "Base.");
assert.equal(composeVisionPrompt("Base.", undefined, true), "Base.");
assert.equal(composeVisionPrompt("Base.", " ", true), "Base.");
});
test("hint truncated to 500 chars", () => {
const p = composeVisionPrompt("Base.", "x".repeat(2000), true);
assert.ok(p.length < 700, `expected truncated prompt, got length ${p.length}`);
assert.ok(p.includes("x".repeat(500)));
assert.ok(!p.includes("x".repeat(501)));
});
// ── Guardrail describe path wiring ──────────────────────────────────────────
function describeGuardrail(
settings: Record<string, unknown>,
capturedPrompts: string[]
): InstanceType<typeof VisionBridgeGuardrail> {
return new VisionBridgeGuardrail({
deps: {
getSettings: async () => ({ modalityBridgeVisionMode: "describe", ...settings }),
callVisionModel: async (_imageDataUri: string, config: VisionModelConfig) => {
capturedPrompts.push(config.prompt);
return "descrição";
},
hasUsableCredentials: async () => null,
},
});
}
/**
* Unique per-test payload: the describe path caches by image+prompt+model
* (Task 8), so reusing the same data URI across tests would make a later
* describe a cache hit and hide the upstream call whose prompt is asserted.
*/
function autoImageBody(uniqueRef: string, userText: string): Record<string, unknown> {
return {
model: "auto/task-aware",
messages: [
{
role: "user",
content: [
{ type: "text", text: userText },
{
type: "image_url",
image_url: {
url: `data:image/png;base64,${Buffer.from(uniqueRef).toString("base64")}`,
},
},
],
},
],
};
}
test("describe call prompt contains the last user question (taskAware default on)", async () => {
const prompts: string[] = [];
const guardrail = describeGuardrail({}, prompts);
const result = await guardrail.preCall(
autoImageBody("task-aware-default-on-test", "qual o erro no screenshot?"),
{ model: "auto/task-aware", log: console }
);
assert.equal((result.meta ?? {}).imagesProcessed, 1);
assert.equal(prompts.length, 1);
assert.ok(
prompts[0].includes("qual o erro no screenshot?"),
`prompt should carry the user question, got: ${prompts[0]}`
);
});
test("modalityBridgeVisionTaskAware=false keeps the base prompt untouched", async () => {
const prompts: string[] = [];
const guardrail = describeGuardrail(
{ modalityBridgeVisionTaskAware: false, modalityBridgeVisionPrompt: "Base prompt." },
prompts
);
const result = await guardrail.preCall(
autoImageBody("task-aware-disabled-test", "pergunta que não deve vazar"),
{ model: "auto/task-aware", log: console }
);
assert.equal((result.meta ?? {}).imagesProcessed, 1);
assert.equal(prompts.length, 1);
assert.equal(prompts[0], "Base prompt.");
});