Das 64K-Output-Gate war global und trieb auto/mass:fast auf solar-pro4 (0,42 statt 0,27 $/1M), weil ling-3.0-flash mit 32768 Output-Token durchfiel. Gemessen 09.09.: 1.415 Masse-Requests, 1.146 davon unter 100 Completion-Token — das Gate verfehlte dort seinen Zweck. Jetzt kann je Schwelle-Profil minOutput gesetzt werden (Default 65536); auto/mass:fast laeuft mit 32768, max_output unbekannt bleibt abgelehnt. Dazu role/coder -> auto/coding:junior (ACL fuer persona-spark), damit Code-Arbeit nicht ueber role/worker in die Masse faellt; der engineer-Agent der Mac-opencode.json wechselt auf role/coder. Refs #1283
154 lines
5.3 KiB
TypeScript
154 lines
5.3 KiB
TypeScript
process.env.OPENROUTER_API_URL = "http://127.0.0.1:1";
|
|
|
|
import { describe, it, expect } from "bun:test";
|
|
import {
|
|
MIN_OUTPUT,
|
|
kandidatenBauen,
|
|
billigsterUeberSchwelle,
|
|
type Model,
|
|
} from "../src/openrouter-auswahl.ts";
|
|
|
|
const modelle: Model[] = [
|
|
{
|
|
id: "anthropic/claude-haiku-4.5",
|
|
context_length: 131072,
|
|
pricing: { prompt: "0.01", completion: "0.01" },
|
|
supported_parameters: ["tools"],
|
|
benchmarks: {
|
|
artificial_analysis: { intelligence_index: 90 },
|
|
},
|
|
top_provider: { max_completion_tokens: 131072 },
|
|
},
|
|
{
|
|
id: "inclusionai/ling-3.0-flash",
|
|
context_length: 32768,
|
|
pricing: { prompt: "0.001", completion: "0.001" },
|
|
supported_parameters: ["tools"],
|
|
benchmarks: {
|
|
artificial_analysis: { intelligence_index: 50 },
|
|
},
|
|
top_provider: { max_completion_tokens: 32768 },
|
|
},
|
|
{
|
|
id: "z-ai/glm-5.3-flash",
|
|
context_length: 131072,
|
|
pricing: { prompt: "0.002", completion: "0.002" },
|
|
supported_parameters: ["tools"],
|
|
benchmarks: {
|
|
artificial_analysis: { intelligence_index: 55 },
|
|
},
|
|
top_provider: { max_completion_tokens: 131072 },
|
|
},
|
|
{
|
|
id: "foo/ohne-angabe",
|
|
context_length: 131072,
|
|
pricing: { prompt: "0.0015", completion: "0.0015" },
|
|
supported_parameters: ["tools"],
|
|
benchmarks: {
|
|
artificial_analysis: { intelligence_index: 60 },
|
|
},
|
|
},
|
|
];
|
|
|
|
describe("MIN_OUTPUT", () => {
|
|
it("ist genau 65536", () => {
|
|
expect(MIN_OUTPUT).toBe(65536);
|
|
});
|
|
});
|
|
|
|
describe("kandidatenBauen", () => {
|
|
it("fuellt maxOutput", () => {
|
|
const { alle } = kandidatenBauen({ voll: modelle, konto: null });
|
|
expect(alle.get("inclusionai/ling-3.0-flash")?.maxOutput).toBe(32768);
|
|
expect(alle.get("anthropic/claude-haiku-4.5")?.maxOutput).toBe(131072);
|
|
expect(alle.get("foo/ohne-angabe")?.maxOutput).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("billigsterUeberSchwelle", () => {
|
|
it("wahlt glm-5.3-flash und lehnt ling sowie foo ab", async () => {
|
|
const { alle } = kandidatenBauen({ voll: modelle, konto: null });
|
|
const abgelehnt: Array<{ id: string; grund: string }> = [];
|
|
const ablehnen = (id: string, grund: string) => {
|
|
abgelehnt.push({ id, grund });
|
|
};
|
|
const result = await billigsterUeberSchwelle(
|
|
40,
|
|
0,
|
|
alle,
|
|
undefined,
|
|
ablehnen,
|
|
new Map(),
|
|
{ varianten: [] },
|
|
);
|
|
expect(result.auswahl?.candidate.id).toBe("z-ai/glm-5.3-flash");
|
|
const ling = abgelehnt.find((e) => e.id === "inclusionai/ling-3.0-flash");
|
|
expect(ling).toBeDefined();
|
|
expect(ling!.grund).toContain("max_output");
|
|
expect(ling!.grund).toContain("32768");
|
|
const foo = abgelehnt.find((e) => e.id === "foo/ohne-angabe");
|
|
expect(foo).toBeDefined();
|
|
expect(foo!.grund).toBe("max_output unbekannt");
|
|
});
|
|
});
|
|
|
|
describe("Grenze maxOutput", () => {
|
|
it("ein Kandidat mit maxOutput genau 65536 bleibt infrage", async () => {
|
|
const grenzwert: Model[] = [
|
|
{
|
|
id: "bar/grenzwertig",
|
|
context_length: 131072,
|
|
pricing: { prompt: "0.001", completion: "0.001" },
|
|
supported_parameters: ["tools"],
|
|
benchmarks: {
|
|
artificial_analysis: { intelligence_index: 70 },
|
|
},
|
|
top_provider: { max_completion_tokens: 65536 },
|
|
},
|
|
];
|
|
const { alle } = kandidatenBauen({ voll: grenzwert, konto: null });
|
|
const abgelehnt: Array<{ id: string; grund: string }> = [];
|
|
const ablehnen = (id: string, grund: string) => {
|
|
abgelehnt.push({ id, grund });
|
|
};
|
|
const result = await billigsterUeberSchwelle(
|
|
0,
|
|
0,
|
|
alle,
|
|
undefined,
|
|
ablehnen,
|
|
new Map(),
|
|
{ varianten: [] },
|
|
);
|
|
expect(result.auswahl?.candidate.id).toBe("bar/grenzwertig");
|
|
expect(abgelehnt).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe("minOutput je Profil", () => {
|
|
it("32768 laesst ling als billigsten Kandidaten zu, Default tut es nicht", async () => {
|
|
const { alle } = kandidatenBauen({ voll: modelle, konto: null });
|
|
const mitGate = await billigsterUeberSchwelle(40, 0, alle, undefined, undefined, new Map(), { varianten: [] });
|
|
expect(mitGate.auswahl?.candidate.id).toBe("z-ai/glm-5.3-flash");
|
|
const ohneGate = await billigsterUeberSchwelle(40, 0, alle, undefined, undefined, new Map(), { varianten: [], minOutput: 32768 });
|
|
expect(ohneGate.auswahl?.candidate.id).toBe("inclusionai/ling-3.0-flash");
|
|
});
|
|
|
|
it("bekannte Ablehnung traegt den wirksamen Grenzwert", async () => {
|
|
const { alle } = kandidatenBauen({ voll: modelle, konto: null });
|
|
const abgelehnt: Array<{ id: string; grund: string }> = [];
|
|
await billigsterUeberSchwelle(40, 0, alle, undefined, (id, grund) => abgelehnt.push({ id, grund }), new Map(), { varianten: [], minOutput: 65536 });
|
|
const ling = abgelehnt.find((e) => e.id === "inclusionai/ling-3.0-flash");
|
|
expect(ling?.grund).toContain("65536");
|
|
});
|
|
|
|
it("max_output unbekannt bleibt abgelehnt, auch mit herabgesetztem minOutput", async () => {
|
|
const { alle } = kandidatenBauen({ voll: modelle, konto: null });
|
|
const abgelehnt: Array<{ id: string; grund: string }> = [];
|
|
await billigsterUeberSchwelle(40, 0, alle, undefined, (id, grund) => abgelehnt.push({ id, grund }), new Map(), { varianten: [], minOutput: 1 });
|
|
const foo = abgelehnt.find((e) => e.id === "foo/ohne-angabe");
|
|
expect(foo).toBeDefined();
|
|
expect(foo!.grund).toBe("max_output unbekannt");
|
|
});
|
|
});
|