Modell-Metadaten und Kostenberechnung
models.ts ist der Modellkatalog-Eingang von pi-ai — getModel sucht in der Registrierung nach Model, calculateCost rechnet Kosten aus Usage, getSupportedThinkingLevels listet die Thinking-Stufen eines Modells, und clampThinkingLevel zieht eine angefragte Stufe auf den nächsten gültigen Wert. Die Modelldaten selbst liegen nicht in dieser Datei, sondern in models.generated.ts (automatisch generiert; hier nicht vertieft); models.ts macht nur Lookup und Berechnung.
Verantwortung
- Katalog-Initialisierung: beim Modulladen wird
MODELS(ausmodels.generated.ts) zu einemMap<provider, Map<id, Model>>flachgemacht; siehepackages/ai/src/models.ts:4-13. - Modell-Lookup:
getModel(provider, modelId)liefert anhand von Provider + id dasModelund bewahrt über einen generischen Parameter denapi-Typ. Siehepackages/ai/src/models.ts:20-26. - Provider / Modelle auflisten:
getProviders()undgetModels(provider)für UI-Listen. Siehepackages/ai/src/models.ts:28-37. - Kostenberechnung:
calculateCost(model, usage)rechnet Millionen-Token-Preis × Verbrauch und füllt die Felder inusage.cost. Siehepackages/ai/src/models.ts:39-46. - Thinking-Stufen:
getSupportedThinkingLevelsfiltertnull-Stufen ausmodel.thinkingLevelMap;clampThinkingLevelzieht eine angefragte Stufe auf einen gültigen Wert. Siehepackages/ai/src/models.ts:48-80. - Modell-Gleichheit:
modelsAreEqualvergleichtid+provider; genutzt beim UI-Wechsel. Siehepackages/ai/src/models.ts:86-92.
Entwurfsmotivation
Warum models.generated.ts und models.ts trennen? Weil der Modellkatalog (Preise, Kontextlänge, Thinking-Unterstützung) hochfrequent geänderte Daten sind — Anbieter ändern Preise, bringen neue Modelle, passen API-Felder an. Die Trennung von der Lookup-/Berechnungslogik in models.ts erlaubt dem Generator, einmalig statische Daten zu produzieren, während die Lookup-Logik stabil bleibt. Diese Seite behandelt nicht den Generator, sondern nur die Lookup-Seite.
Warum verwendet ModelThinkingLevel sechs Stufen off | minimal | low | medium | high | xhigh? Weil die Thinking-Steuerung der Provider unterschiedlich granular ist — Anthropic gibt Budget-Tokens, OpenAI Responses gibt effort, Google gibt ein Thinking-Budget. Die Abstraktion auf 6 Stufen erfolgt über thinkingLevelMap, das jede Stufe auf provider-spezifische Parameter abbildet; null heißt „diese Stufe wird von diesem Modell nicht unterstützt". clampThinkingLevel behandelt Fälle wie „angefragt high, Modell geht nur bis medium", indem es den nächstgelegenen gültigen Wert nach unten sucht, damit der Request nicht fehlschlägt.
Wichtige Dateien
packages/ai/src/models.ts:4-13—modelRegistry-Initialisierung, zweistöckige Map.packages/ai/src/models.ts:15-18— bedingter TypModelApi, leitet dasapi-Literal ausMODELSab.packages/ai/src/models.ts:20-26—getModel, generisch und bewahrtapi.packages/ai/src/models.ts:39-46—calculateCost, Summe über vier Token-Preisstufen.packages/ai/src/models.ts:48-48— KonstanteEXTENDED_THINKING_LEVELS, Reihenfolge der 6 Stufen.packages/ai/src/models.ts:50-59—getSupportedThinkingLevels, filtertnull.packages/ai/src/models.ts:61-80—clampThinkingLevel, nach unten klammern.packages/ai/src/models.ts:86-92—modelsAreEqual, doppelter Vergleich id + provider.
calculateCost ist reine Arithmetik — vier Preise durch eine Million mal Verbrauch:
// packages/ai/src/models.ts:39-46
export function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage): Usage["cost"] {
usage.cost.input = (model.cost.input / 1000000) * usage.input;
usage.cost.output = (model.cost.output / 1000000) * usage.output;
usage.cost.cacheRead = (model.cost.cacheRead / 1000000) * usage.cacheRead;
usage.cost.cacheWrite = (model.cost.cacheWrite / 1000000) * usage.cacheWrite;
usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
return usage.cost;
}clampThinkingLevel sucht erst nach unten, dann nach oben, und liefert immer einen gültigen Wert:
// packages/ai/src/models.ts:71-79
for (let i = requestedIndex; i < EXTENDED_THINKING_LEVELS.length; i++) {
const candidate = EXTENDED_THINKING_LEVELS[i];
if (availableLevels.includes(candidate)) return candidate;
}
for (let i = requestedIndex - 1; i >= 0; i--) {
const candidate = EXTENDED_THINKING_LEVELS[i];
if (availableLevels.includes(candidate)) return candidate;
}
return availableLevels[0] ?? "off";Datenfluss
Modell-Lookup und Auflösung der Thinking-Stufe:
Grenzen und Fehler
- Modell nicht vorhanden:
getModelgibtundefinedzurück; der Aufrufer muss selbst behandeln; siehepackages/ai/src/models.ts:24-25. - Kein Reasoning:
getSupportedThinkingLevelsgibt bei!model.reasoningdirekt["off"]zurück; siehepackages/ai/src/models.ts:51-51. xhigh-Sonderfall: nur unterstützt, wennthinkingLevelMap.xhigh !== undefined; anders als dasnull-Filtern der übrigen Stufen; siehepackages/ai/src/models.ts:56-57.- Angefragte Stufe außerhalb der Liste: wenn in
clampThinkingLevelrequestedIndex === -1, wirdavailableLevels[0] ?? "off"zurückgegeben; siehepackages/ai/src/models.ts:68-69. calculateCostmutiert vor Ort:usage.costwird direkt geschrieben und dasselbe Objekt zurückgegeben; der Aufrufer sollte keine Unveränderlichkeit erwarten.
Zusammenfassung
models.ts ist die Lookup-/Berechnungsschicht; die Katalogdaten liegen in models.generated.ts. getModel sucht, calculateCost rechnet Kosten, getSupportedThinkingLevels / clampThinkingLevel behandeln die Thinking-Stufen-Abbildung. Wie die Model-Instanz nach dem Lookup an den Provider geht, steht in stream/complete-Fassade; wie Anthropic thinkingLevelMap nutzt, in Anthropic SSE-Implementierung.