Créer un pipeline de traduction IA avec garde-fous à l'aide des API IA natives de ColdFusion, partie 1 : traduire un seul champ

Avant de construire un pipeline de traduction, il nous faut un appel de modèle fiable, et un endroit sensé où le mettre.

Pour ceux qui ne le savent pas, je suis Canadien. Le Canada a deux langues officielles - l'anglais et le français - et, pour promouvoir notre culture, nous avons des lois sur les langues. Nous avons aussi la remarquable capacité de transformer un libellé de bouton de trois mots en une réunion avec six intervenants et un avocat. Alors, quand je dis qu'une application a besoin d'une traduction, je ne veux pas dire changer « Welcome » en « Bienvenue » et laisser le reste en anglais. J'ai essayé cette approche. Il s'avère que les gens peuvent lire au-delà du premier mot.

ColdFusion peut maintenant appeler un modèle d'IA sans que nous assemblions des requêtes web à la main. Nous donnons à ChatModel() un fournisseur, un nom de modèle et des identifiants, puis nous appelons .chat() avec une invite. ColdFusion retourne une réponse contenant le message du modèle. Le modèle lui-même est sans état - il ne se souvient pas de l'appel précédent et ne maintient pas une conversation pour nous. Cela convient plutôt bien à la traduction. Je n'ai pas besoin que le modèle développe une relation avec les titres de nos pages. Consultez la documentation ChatModel d'Adobe pour en savoir plus.

Dans cette série de six parties, je vais créer un pipeline qui trouve le contenu ayant besoin d'une traduction, l'envoie par morceaux gérables, vérifie ce qui revient et enregistre le résultat pour révision humaine. La génération de texte et la publication de texte resteront des opérations distinctes. Cette séparation est importante quand la machine traduit avec assurance une page de contact en ce qui ressemble à une note de rançon.

L'objectif d'aujourd'hui est plus modeste - traduire un seul champ de texte brut par l'interface native d'IA de ColdFusion. Je vais placer l'appel au fournisseur derrière un composant, garder les identifiants hors du code et tester le service de traduction sans payer pour une requête de modèle.

Préparer ColdFusion

Cet exemple nécessite Adobe ColdFusion 2025 Update 8 ou une version ultérieure ainsi que le package ai. Il peut être nécessaire d'installer le package séparément après la mise à jour de ColdFusion. Vous pouvez l'installer au moyen du Package Manager d'Adobe ColdFusion Administrator ou avec le ColdFusion Package Manager :

cfpm install ai

Exécutez cette commande sur l'installation de ColdFusion qui sert votre application, et non sur celle qui est simplement la plus facile à trouver. Les ordinateurs sont remarquablement disposés à nous laisser corriger la mauvaise. Consultez le guide de configuration de l'IA d'Adobe et la documentation du ColdFusion Package Manager.

Nous utiliserons OpenAI comme fournisseur dans l'exemple, mais nous garderons ce choix à l'intérieur d'un seul composant. Définissez ces variables d'environnement pour le processus ColdFusion :

TRANSLATION_AI_ENABLED=true
TRANSLATION_AI_API_KEY=your-provider-key
TRANSLATION_AI_MODEL=your-supported-model-name

Le nom du modèle doit être celui que votre compte fournisseur peut utiliser. Ne placez pas la clé dans un modèle ColdFusion, un fichier JavaScript ou une capture d'écran utile de vos paramètres de serveur.

Mon exemple comporte trois fichiers :

components/
    NativeTranslationModel.cfc
    TranslationService.cfc
translation_smoke.cfm

Mettre l'appel au modèle derrière un composant

Créez components/NativeTranslationModel.cfc :

component {
    public string function generate(required string prompt) {
        var system = createObject( "java", "java.lang.System" );
        var enabled = system.getenv( "TRANSLATION_AI_ENABLED" );

        if ( isNull( enabled ) || compareNoCase( trim( toString( enabled ) ), "true" ) != 0 ) {
            throw(
                type="TranslationAI.Disabled",
                message="Translation is disabled."
            );
        }

        var apiKey = system.getenv( "TRANSLATION_AI_API_KEY" );
        var modelName = system.getenv( "TRANSLATION_AI_MODEL" );

        if (
            isNull( apiKey ) || !len( trim( toString( apiKey ) ) ) ||
            isNull( modelName ) || !len( trim( toString( modelName ) ) )
        ) {
            throw(
                type="TranslationAI.NotConfigured",
                message="Translation is not configured."
            );
        }

        var response = {};

        try {
            var model = ChatModel( {
                provider: "openAi",
                apiKey: toString(apiKey),
                modelName: toString(modelName),
                timeout: 20,
                logRequests: false,
                logResponses: false
            } );

            response = model.chat( arguments.prompt );
        } catch ( any error ) {
            throw(
                type="TranslationAI.Unavailable",
                message="The translation model is unavailable."
            );
        }

        if (
            !isStruct( response ) ||
            !structKeyExists( response, "message" ) ||
            !isSimpleValue( response.message ) ||
            !len(trim(toString( response.message ) ) )
        ) {
            throw(
                type="TranslationAI.EmptyResponse",
                message="The translation model returned no text."
            );
        }

        return trim( toString( response.message ) );
    }
}

Quelques-uns des choix ici sont délibérés.

Le composant lit la clé lorsqu'il effectue l'appel. Il ne stocke pas la clé dans la portée de l'application et ne la retourne pas à son appelant. L'interrupteur de fonctionnalité est désactivé par défaut : toute valeur autre que la valeur littérale true désactive la traduction.

logRequests et logResponses sont tous les deux définis à false. Adobe les documente comme des options de configuration du modèle; nous ne voulons pas qu'un paramètre de diagnostic bien intentionné écrive le contenu des pages ou les traductions générées dans les journaux du fournisseur. Assurez-vous de lire la référence de configuration du modèle d'Adobe.

Enfin, les échecs de construction et de requête deviennent une erreur d'application générique. Je peux ajouter plus tard des diagnostics sûrs et utiles. Transmettre une exception brute du fournisseur au navigateur est un moyen efficace d'en dire plus à des inconnus sur mon infrastructure qu'ils ne me l'avaient demandé.

Donner à l'application un service de traduction

Le composant de modèle sait comment générer du texte. Il ne devrait pas décider entre quelles langues je traduis ni construire l'invite de chaque fonctionnalité. Créez components/TranslationService.cfc :

component {
    public TranslationService function init( required any model ) {
        variables.model = arguments.model;
        return this;
    }

    public string function translate(
        required string sourceText,
        required string sourceLocale,
        required string targetLocale
    ) {
        var text = trim( arguments.sourceText );

        if ( !len( text ) || len( text ) > 1000 ) {
            throw(
                type="Translation.InvalidSource",
                message="Source text must contain between 1 and 1000 characters."
            );
        }

        if (
            !reFind( "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$", arguments.sourceLocale ) ||
            !reFind( "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$", arguments.targetLocale ) ||
            compareNoCase( arguments.sourceLocale, arguments.targetLocale ) == 0
        ) {
            throw(
                type="Translation.InvalidLocale",
                message="Choose different, valid source and target locales."
            );
        }

        var prompt =
            "Translate the source_text value from " &
            arguments.sourceLocale & " to " &
            arguments.targetLocale & ". " &
            "Return only the translation, with no explanation." &
            chr(10) &
            serializeJSON({source_text: text});

        var translated = variables.model.generate( prompt );

        if (!isSimpleValue( translated ) || !len( trim( toString( translated ) ) ) ) {
            throw(
                type="Translation.InvalidResult",
                message="The translation was empty."
            );
        }

        return trim( toString( translated ) );
    }
}

Je limite cette première version à 1 000 caractères. Ce n'est pas ma limite finale de pipeline; c'est une protection contre le fait d'alimenter accidentellement un article entier dans une démonstration conçue pour un seul champ. Le contenu plus long aura son propre traitement plus tard dans la série.

Le texte source est sérialisé comme une valeur JSON afin qu'il soit visiblement séparé de l'instruction qui l'entoure. Cela ne rend pas le contenu source hostile inoffensif. Je traiterai du filtrage des entrées et d'une validation de sortie plus robuste dans la troisième partie. Pour l'instant, utilisez ce service avec du contenu que vous contrôlez.

Tester sans appeler le fournisseur

Le service de traduction accepte un objet modèle quand je le crée. Cela me permet de remplacer le modèle payant par un petit faux :

<cfscript>
    fakeModel = {
        generate: function( required string prompt ) {
            if (
                !find( "Hello", arguments.prompt ) ||
                !find( "fr-CA", arguments.prompt )
            ) {
                throw( message="The prompt is missing expected content." );
            }

            return "Bonjour";
        }
    };

    service = new components.TranslationService( fakeModel );

    result = service.translate(
        sourceText="Hello",
        sourceLocale="en-CA",
        targetLocale="fr-CA"
    );

    if (result != "Bonjour") {
        throw( message="Translation test failed." );
    }

    writeOutput(" Translation service test passed." );
</cfscript>

Ce test prouve que ma validation, la construction de l'invite et la frontière du modèle fonctionnent sans compte, sans requête réseau ni facture. Le faux modèle ne peut pas me dire si le vrai fournisseur produira une bonne traduction. Il peut me dire si mon propre code est correctement branché, ce qui est une indignité utile à établir avant de dépenser de l'argent.

Faire un appel en direct

Une fois le paquet et les variables d'environnement configurés, créez translation_smoke.cfm :

<cfscript>
    service = new components.TranslationService( new components.NativeTranslationModel() );

    translated = service.translate(
        sourceText="Welcome to our website",
        sourceLocale="en-CA",
        targetLocale="fr-CA"
    );

    writeOutput( encodeForHTML( translated ) );
</cfscript>

Exécutez cela dans un environnement local ou protégé, puis supprimez ou protégez la page. Un point de terminaison de démonstration non authentifié relié à un modèle payant est moins un tutoriel qu'une contribution charitable à quiconque le trouve en premier.

J'ai maintenant un appel de traduction ColdFusion, une frontière propre autour du modèle natif et un test qui n'a pas besoin du fournisseur. Je n'ai pas de pipeline de publication sûr. La traduction n'a pas été vérifiée pour des noms modifiés, des espaces réservés manquants, des faits inventés ou des interprétations créatives du texte source. Elle n'a pas été enregistrée ni approuvée.

Dans la deuxième partie, je vais créer l'inventaire qui indique à ce service quoi doit être traduit. Sinon, mon pipeline sophistiqué n'est qu'une fonction qui dit « Bonjour » quand on la pousse.