Skip to content

Métadonnées des modèles et calcul de coût

源码版本v0.73.1

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

  1. Initialisation du catalogue : au chargement du module, MODELS (venant de models.generated.ts) est aplati en Map<provider, Map<id, Model>>, voir packages/ai/src/models.ts:4-13.
  2. Recherche de modèle : getModel(provider, modelId) récupère le Model par provider + id, avec une générique qui préserve le type api. Voir packages/ai/src/models.ts:20-26.
  3. Liste provider / modèles : getProviders(), getModels(provider) pour les listes UI. Voir packages/ai/src/models.ts:28-37.
  4. Calcul de coût : calculateCost(model, usage) applique le prix par million de tokens × la consommation, remplit les champs de usage.cost puis renvoie. Voir packages/ai/src/models.ts:39-46.
  5. Niveaux de réflexion (thinking) : getSupportedThinkingLevels filtre les niveaux null dans model.thinkingLevelMap; clampThinkingLevel plaque le niveau demandé sur la valeur légale la plus proche. Voir packages/ai/src/models.ts:48-80.
  6. Égalité de modèles : modelsAreEqual compare id + provider, utilisé par l'UI lors d'un switch. Voir packages/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

calculateCost est de l'arithmétique pure : prix unitaire sur 4 paliers divisé par un million × quantité :

typescript
// 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 :

typescript
// 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 : getModel renvoie undefined, l'appelant doit le gérer, voir packages/ai/src/models.ts:24-25.
  • Pas de reasoning : getSupportedThinkingLevels renvoie directement ["off"] quand !model.reasoning, voir packages/ai/src/models.ts:51-51.
  • Cas particulier xhigh : supporté seulement si thinkingLevelMap.xhigh !== undefined, à la différence du filtre null des autres niveaux, voir packages/ai/src/models.ts:56-57.
  • Niveau demandé hors liste : clampThinkingLevel quand requestedIndex === -1 renvoie availableLevels[0] ?? "off", voir packages/ai/src/models.ts:68-69.
  • calculateCost mute sur place : usage.cost est é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.