Aller au contenu

API

Fonctions serveur du module IA, runPrompt et sa réponse, codes d'erreur, propositions gardées, consommation en jetons, intégration ai pour les autres modules et routes HTTP d'admin.

Le module s'appelle de deux façons : le projet importe @fraza/module-ai/server, et un autre module lit l'intégration ai sans importer le paquet. Les deux donnent les mêmes fonctions. Les routes HTTP ne servent qu'aux écrans de l'admin.

Règle absolue : un appel au modèle passe toujours par runPrompt, jamais par une requête directe au fournisseur. C'est lui qui vérifie la réponse, compte les jetons et écrit l'historique, et lui seul suit le fournisseur que la configuration nomme.

Import

Sous-cheminRuntimeContenu
@fraza/module-aiserveurmanifest, register, createTables, dropTables, migrations.
@fraza/module-ai/serverserveurLes fonctions de cette page. Aucun React.
@fraza/module-ai/adminclientEnregistrement de la page IA, de la tuile du tableau de bord, et des composants AiActions et AiResults sous l'intégration ai. Aucun export.
import { runPrompt } from '@fraza/module-ai/server';

./server ne vérifie aucune permission : son appelant est le code du serveur, qui fait ses propres vérifications.

Fonctions serveur

FonctionRetourRôle
definePrompt(definition, { source }?)stringDéclare un prompt et rend son nom. Voir Prompts.
customizePrompt(name, changes)voidAjuste un prompt pour ce projet. Voir Prompts.
configureAi({ instructions })voidInstructions partagées par tous les prompts.
listPrompts()object[]Les prompts déclarés : name, label, target, mode, source, customized, enabled.
runPrompt(name, options?)Promise<object>Lance un prompt. Ne lève pas pour un échec d'usage normal.
saveResult({ target, id, prompt, data, model })Promise<object>Garde une réponse à côté d'un contenu.
getResults(target, id)Promise<object[]>Les réponses en attente d'un contenu, la plus récente en tête.
dismissResult(resultId)Promise<boolean>Marque une réponse utilisée ou ignorée.
deleteResults(target, id)Promise<number>Retire les réponses d'un contenu supprimé.
getUsage({ from, to })Promise<object>Les jetons consommés sur une période.
isAiConfigured()booleanUn appel peut partir. Ne lève jamais.
describeAiConfig()objectCe que la page IA montre de la configuration, sans la clé. Ne lève jamais.

runPrompt(name, options?)

const result = await runPrompt('site.summary', { context: { message } });

if (result.success) {
  console.log(result.data.summary, result.usage.outputTokens);
} else {
  console.warn(result.error.code, result.error.message);
}
OptionDéfautRôle
inputLes réponses d'un formulaire, vérifiées contre le schéma input du prompt.
contextnullCe que build() reçoit.
scope{}Ce que output(scope) reçoit.
userIdnullLe compte à qui attribuer l'appel dans l'historique.
trigger'code''button', 'code' ou 'test', l'origine notée dans l'historique. Une autre valeur compte comme 'code'.
signalUn AbortSignal qui arrête l'attente du fournisseur.

La réponse a l'une de ces deux formes :

{ success: true, data, model, usage: { inputTokens, outputTokens } }
{ success: false, error: { code, message, retryable } }

data est la réponse, déjà vérifiée contre le schéma du prompt. model est le modèle que le fournisseur dit avoir utilisé, sinon celui de la configuration. usage additionne les deux tentatives, et un compte vaut null quand le fournisseur ne le donne pas. message est en français, prêt pour l'admin. retryable dit qu'un essai plus tard peut réussir.

runPrompt ne lève que pour une erreur de programmation, jamais pour une panne, une configuration absente ou une réponse hors schéma. Le module n'a pas besoin d'être activé pour qu'un appel du code parte : FRAZA_MODULE_AI décide des routes, de l'admin et de l'intégration, pas de ./server.

Codes d'erreur

CodeQuandretryableStatut de POST /ai/run
unknown_promptAucun prompt de ce nom, ou le projet l'a désactivé.non404
not_configuredLe module est désactivé (FRAZA_MODULE_AI ne vaut pas true), ou une variable manque ou est illisible. Le message la nomme.non503
invalid_inputLa saisie ne correspond pas au schéma input.non400
not_applicableoutput(scope) a rendu null : le prompt ne s'applique pas à ce contenu. Aucun appel.non400
prompt_erroroutput ou build() a levé, ou a rendu une forme invalide. Le détail est dans le journal du serveur.non500
timeoutPas de réponse dans FRAZA_MODULE_AI_TIMEOUT_MS.oui502
unreachableLe fournisseur est injoignable : adresse, réseau.oui502
authLa clé est refusée (401, 403).non502
rate_limitedLe fournisseur demande de ralentir (429).oui429
rejectedLa demande est refusée (400, 404, 422) : modèle inconnu, schéma, image.non502
providerLe fournisseur a échoué (5xx), ou a répondu sans contenu lisible.oui502
refusedLe modèle a refusé de répondre. L'historique ne garde pas son explication, écrite par le modèle.non502
truncatedLa réponse a atteint maxTokens.non502
cancelledLe signal de l'appelant a annulé l'appel.non400
invalid_answerLa réponse était hors schéma deux fois de suite.oui502

Dans une réponse d'erreur de la route, le code est dans le champ code.

saveResult, getResults, dismissResult, deleteResults

const saved = await saveResult({ target: 'medias.media', id: media.id, prompt: 'medias.alt', data: result.data, model: result.model });
// { id: '17', target: 'medias.media', targetId: 'a1b2…', prompt: 'medias.alt', data: { altText: '…' }, model: '…', createdAt: '2026-11-07T…' }

target est un nom en mots minuscules séparés par des points, 120 caractères au plus. id est un entier ou une chaîne de 120 caractères au plus, gardé en texte. Une valeur hors de ces règles lève une TypeError. data est un objet, et createdAt la date de la dernière réponse.

saveResult remplace la réponse déjà gardée pour le même contenu et le même prompt, et la remontre si elle avait été ignorée. getResults ne rend que les réponses ni utilisées ni ignorées. dismissResult rend false pour un identifiant inconnu ou déjà marqué. deleteResults rend le nombre de réponses retirées. Voir Modèle de données.

getUsage({ from, to })

const to = new Date();
const from = new Date(to.getTime() - 30 * 24 * 60 * 60 * 1000);
const usage = await getUsage({ from, to });
// {
//   from, to,
//   totals: { calls, errors, inputTokens, outputTokens },
//   byDay: [{ day: '2026-11-06', calls, errors, inputTokens, outputTokens }, …],
//   byPrompt: [{ prompt, calls, errors, inputTokens, outputTokens }, …],
//   byModel: [{ provider, model, calls, errors, inputTokens, outputTokens }, …],
// }

from et to sont des Date, to exclue. Les jours sont coupés en UTC. byPrompt et byModel sont triés du plus gros consommateur au plus petit. Des dates invalides, ou from après to, lèvent une TypeError.

Des jetons seulement, jamais un prix : le module ne connaît pas le contrat passé avec le fournisseur. La fonction est publiée sous cette forme pour qu'un registre de consommation du core, à venir, la lise comme celle de tout module qui consomme un service payant.

describeAiConfig()

Rend { configured, provider, providerLabel, model, hasKey, baseUrl, problem }. hasKey dit si une clé est présente, jamais sa valeur. baseUrl n'est rendue que pour openai-compatible et ollama. problem est le message de la première variable en défaut, ou null.

L'intégration ai

Un module ne déclare pas @fraza/module-ai en dépendance : importer un paquet absent ferait échouer le build du site. Il lit l'intégration ai, publiée par le module quand il est activé.

import { getIntegration } from '@fraza/core/integrations';

const AI_CONTRACT = 1;

function aiOf() {
  const ai = getIntegration('ai');
  return ai && ai.contract === AI_CONTRACT ? ai : null;
}
CôtéContenu
Serveurcontract, definePrompt, runPrompt, saveResult, getResults, dismissResult, deleteResults, isAiConfigured, getUsage.
NavigateurAiActions et AiResults. Voir Boutons.

Lire le contrat. contract vaut 1. Il augmente quand la forme d'une de ces fonctions change, et un module ignore une intégration dont il ne parle pas le contrat : il perd ses boutons, il ne casse pas.

Déclarer dans onModulesReady. Le module IA peut s'enregistrer après le module qui l'utilise. Un module déclare donc ses prompts dans un callback de onModulesReady(), où toutes les intégrations sont publiées. Voir Enregistrement inter-modules.

// register-server.js d'un module, exemple
import { onModulesReady } from '@fraza/core/modules';

onModulesReady(() => {
  const ai = aiOf();
  if (!ai) return;
  ai.definePrompt({ name: 'acme.write', /* … */ }, { source: '@acme/module-x' });
});

Un module passe toujours son source à definePrompt : sans lui, le prompt est attribué au projet, et c'est la source qui distingue un rechargement d'un conflit de nom.

Routes HTTP

Toutes sont montées sous /za/api, et toutes demandent une session d'admin.

MéthodeCheminPermissionRôle
GET/ai/actions?target=&scope=ai.useLes boutons d'un écran que ce compte peut lancer, avec leur schéma d'entrée et de sortie. scope en JSON.
POST/ai/runai.useLance le prompt d'un bouton. Corps : { prompt, input, context, scope }. Préréglage ai_run.
GET/ai/results?target=&id=ai.useLes propositions en attente d'un contenu.
POST/ai/results/:id/dismissai.useMarque une proposition utilisée ou ignorée.
GET/ai/statusai.viewConfiguration, prompts déclarés, ajustements sans prompt.
POST/ai/testai.viewLe court appel du bouton « Tester ». Préréglage ai_run.
GET/ai/runs?page=&limit=&status=ai.viewL'historique, 50 lignes par page par défaut, 200 au plus. status : success ou error.
GET/ai/usage?days=ai.viewgetUsage() sur les days derniers jours, 30 par défaut, de 1 à 366.

POST /ai/run refuse un prompt sans target par un 404, comme un prompt inconnu, et un compte sans la permission du prompt par un 403. Il rend { success: true, data, model }, sans usage.

Notes

  • Les fonctions de ./server et celles de l'intégration sont les mêmes objets : un appel par l'une ou par l'autre écrit le même historique.
  • isAiConfigured() et describeAiConfig() tiennent aussi compte de FRAZA_MODULE_AI_RETENTION_DAYS : une rétention hors bornes les rend non configurés, alors que runPrompt n'en dépend pas et appelle le fournisseur.
  • Les propositions d'un contenu se lisent et se marquent avec ai.use seul : la route ne vérifie pas la permission du module qui possède le contenu.

Sur cette page