Métadonnées des modèles et calcul de coût
models.ts est l'entrée du catalogue de modèles de pi-ai—getModel consulte le registre (registry) pour récupérer un Model, calculateCost calcule le coût à partir de l'usage, getSupportedThinkingLevels liste les niveaux de réflexion (thinking) supportés par un modèle, et clampThinkingLevel plaque le niveau demandé sur la valeur légale la plus proche. Les données modèles elles-mêmes ne sont pas dans ce fichier, mais dans models.generated.ts (auto-généré, non développé ici); models.ts ne fait que requêter et calculer.
Responsabilités
- Initialisation du catalogue : au chargement du module,
MODELS(venant demodels.generated.ts) est aplati enMap<provider, Map<id, Model>>, voirpackages/ai/src/models.ts:4-13. - Recherche de modèle :
getModel(provider, modelId)récupère leModelpar provider + id, avec une générique qui préserve le typeapi. Voirpackages/ai/src/models.ts:20-26. - Liste provider / modèles :
getProviders(),getModels(provider)pour les listes UI. Voirpackages/ai/src/models.ts:28-37. - Calcul de coût :
calculateCost(model, usage)applique le prix par million de tokens × la consommation, remplit les champs deusage.costpuis renvoie. Voirpackages/ai/src/models.ts:39-46. - Niveaux de réflexion (thinking) :
getSupportedThinkingLevelsfiltre les niveauxnulldansmodel.thinkingLevelMap;clampThinkingLevelplaque le niveau demandé sur la valeur légale la plus proche. Voirpackages/ai/src/models.ts:48-80. - Égalité de modèles :
modelsAreEqualcompareid+provider, utilisé par l'UI lors d'un switch. Voirpackages/ai/src/models.ts:86-92.
Motivation de design
Pourquoi séparer models.generated.ts et models.ts ? Parce que le catalogue de modèles (prix, longueur de contexte, support de thinking) est une donnée à fort taux de changement—un fournisseur change ses prix, sort un nouveau modèle, ajuste un champ d'API, et il faut modifier. En l'isolant de la logique de requête/calcul de models.ts, le générateur tourne une fois pour produire des données statiques, et la logique de requête reste stable. Cette page ne couvre pas le générateur, uniquement la face requête.
Pourquoi ModelThinkingLevel utilise-t-il six niveaux off | minimal | low | medium | high | xhigh ? Parce que la granularité de contrôle du thinking varie beaucoup selon les provider—Anthropic donne des budget tokens, OpenAI Responses donne effort, Google donne thinking budget. L'abstraction unifiée en 6 niveaux mappe chacun via thinkingLevelMap vers les paramètres spécifiques au provider; null signifie « ce modèle ne supporte pas ce niveau ». clampThinkingLevel gère le cas « on demande high mais le modèle ne monte que jusqu'à medium » : il cherche d'abord vers le bas la valeur légale la plus proche, garantissant que la requête ne plante pas.
Fichiers clés
packages/ai/src/models.ts:4-13— initialisation demodelRegistry, Map double.packages/ai/src/models.ts:15-18— type conditionnelModelApi, infère le littéralapidepuisMODELS.packages/ai/src/models.ts:20-26—getModel, générique qui préserveapi.packages/ai/src/models.ts:39-46—calculateCost, cumul sur 4 paliers de prix de tokens.packages/ai/src/models.ts:48-48— constanteEXTENDED_THINKING_LEVELS, l'ordre des 6 niveaux.packages/ai/src/models.ts:50-59—getSupportedThinkingLevels, filtre lesnull.packages/ai/src/models.ts:61-80—clampThinkingLevel, plaque vers la valeur légale la plus proche vers le bas.packages/ai/src/models.ts:86-92—modelsAreEqual, double comparaison id + provider.
calculateCost est de l'arithmétique pure : prix unitaire sur 4 paliers divisé par un million × quantité :
// 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 cherche d'abord vers le bas, puis remonte, pour toujours renvoyer une valeur légale :
// 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";Flux de données
Requête de modèle et résolution du niveau de thinking :
Frontières et échecs
- Modèle inexistant :
getModelrenvoieundefined, l'appelant doit le gérer, voirpackages/ai/src/models.ts:24-25. - Pas de reasoning :
getSupportedThinkingLevelsrenvoie directement["off"]quand!model.reasoning, voirpackages/ai/src/models.ts:51-51. - Cas particulier
xhigh: supporté seulement sithinkingLevelMap.xhigh !== undefined, à la différence du filtrenulldes autres niveaux, voirpackages/ai/src/models.ts:56-57. - Niveau demandé hors liste :
clampThinkingLevelquandrequestedIndex === -1renvoieavailableLevels[0] ?? "off", voirpackages/ai/src/models.ts:68-69. calculateCostmute sur place :usage.costest écrit directement, le même objet est renvoyé; l'appelant ne doit pas s'attendre à de l'immutabilité.
Récapitulatif
models.ts est la couche requête/calcul; les données du catalogue sont dans models.generated.ts. getModel cherche, calculateCost calcule le coût, getSupportedThinkingLevels / clampThinkingLevel gèrent le mappage des niveaux de thinking. Une fois l'instance Model récupérée, la façon de la passer au provider se lit dans Façade stream/complete, et la façon dont Anthropic utilise thinkingLevelMap dans Implémentation Anthropic SSE.