# Notre Lambda se prend pour un téléphone

**Résumé:** Nous nous sommes imposé une règle : rien sur le chemin critique n'attend une autre machine. Résultat, un modèle conçu pour les téléphones tourne dans notre Lambda.

**En bref:** À l'endroit exact où se trouvait l'appel réseau, nous avons installé un modèle embarqué

**Publié:** 2026-08-26

## Nous n'avons pas le droit d'attendre

Nous sommes des techniciens qui fabriquent un produit d'IA. Et nous aimons les règles. Nous nous en sommes donc imposé une nouvelle cette année : rien sur le chemin critique d'une requête n'a le droit d'attendre une autre machine.

En 2026, l'énoncé est absurde. Chaque fonctionnalité intéressante arrive sous forme d'appel d'API, et le conseil dominant consiste à en appeler davantage plutôt que moins. Nous avons écrit la règle quand même.

Nous nous infligeons cet exercice environ une fois par décennie. Il y a dix ans, la règle nous interdisait le moindre serveur, et [l'architecture qui en est sortie](https://unless.com/en/blog/stories/we-were-only-ever-half-serious/ "Nous n'étions qu'à moitié sérieux") fait encore tourner UNLESS aujourd'hui. Une contrainte que l'on ne peut pas contourner par la discussion fait un travail de conception qu'aucune réunion ne fera jamais.

Mais cette nouvelle règle a cassé quelque chose immédiatement. La victime est l'étape la plus importante de notre chaîne.

## La première chose que la règle a cassée

Nous nous adressons à des secteurs réglementés de l'Union européenne, les services financiers par exemple. Chaque question posée à notre Agent de clientèle traverse donc d'abord un filtre. Les noms, les adresses e-mail et le reste sortent du texte avant qu'un modèle ne le voie. Tenir les données à caractère personnel à l'écart des fournisseurs de modèles est une exigence ferme chez Unless, pas une préférence.

Ce filtre tournait sur [AWS Bedrock Guardrails](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-sensitive-filters.html "Documentation AWS sur les filtres d'informations sensibles"). Service géré, fiable, ennuyeux au bon sens du terme. Mais il tourne ailleurs, et il faut donc l'attendre.

À chaque appel, la fonction s'arrête. Elle attend 200 à 400 ms. Puis elle reprend la rédaction de la réponse.

Vous pouvez ranger cela sous « le réseau est lent » et passer à autre chose. Sauf que cette attente précédait chaque question, et qu'elle se multipliait sur un échange de plusieurs tours. Nous ne nous en sommes pas contentés et nous avons réfléchi. Attendre plus vite n'existe malheureusement pas. Respecter notre propre règle imposait donc que le modèle vienne à nous.

## Un modèle embarqué face à un service géré

Heureusement, quelque chose avait bougé du côté des modèles pendant que nous regardions ailleurs. Les téléphones et les navigateurs ont créé une demande pour des modèles qui fonctionnent sans le moindre réseau. Petits, quantifiés pour que les poids soient stockés en précision réduite, natifs, et prévus pour du matériel qui appartient à quelqu'un d'autre.

Ce travail vise un appareil doté d'une batterie, d'une puce lente et d'aucune patience. Rien là-dedans ne cible une fonction cloud. Nous avons pourtant constaté que les écarts restent faibles. Une fonction AWS Lambda ressemble à un appareil, à l'écran près. Petite machine, seule, à durée de vie courte, sans voisin à appeler. Cela ressemble à un excellent terrain pour un modèle embarqué.

L'image est jolie et ne prouve rien. Nous avons donc opposé un spécialiste au format téléphone au service géré que nous payions déjà. Et devinez quoi ? Il en a détecté davantage. Il a coûté moins cher. Et il se livre à chaque fonction sous forme d'une seule couche.

## Ce que nous avons installé

Le modèle est [@desert-ant-labs/redact](https://www.npmjs.com/package/@desert-ant-labs/redact "Redact : masquage multilingue embarqué pour JavaScript") en version 3.0.0. Redact reconnaît 17 catégories relevant du RGPD, et absolument rien d'autre.

Dessous se trouve un noyau natif précompilé qui s'exécute sur [LiteRT](https://ai.google.dev/edge/litert "Présentation de LiteRT"), le moteur d'inférence embarquée de Google. Node l'appelle via l'interface [koffi](https://koffi.dev/ "Koffi : module FFI pour Node.js"). Les poids du modèle ne sont pas livrés dans le paquet npm, nous en figeons donc une version au moment de la construction.

Ces fichiers et le noyau natif partent dans une seule [couche Lambda](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html "Gérer les dépendances Lambda avec les couches") construite avec CDK, publiée à 47 Mo. Chaque fonction qui a besoin du filtrage y est rattachée. Un seul artefact à maintenir, aucune copie par fonction.

L'ensemble se comporte comme une application installée. Version figée, présente sur le disque, démarrage en même temps que la fonction. Rien ne téléphone à la maison.

## Nous nous attendions à le payer

Une règle que l'on s'impose coûte généralement quelque chose. Nous partions donc du principe qu'un modèle assez petit pour tenir dans une couche de 47 Mo perdrait face à un service géré d'AWS. Nous voulions mesurer l'écart avant de nous engager, ou peut-être même utiliser ce modèle comme contrôle supplémentaire.

Nous avons donc rédigé 102 questions, formulées comme les internautes s'adressent réellement à l'agent, dans un contexte européen. Toutes les données personnelles du jeu de test sont fictives. Rien de réel n'a été exposé pendant les essais.

Nous avons ensuite cherché à faire trébucher les deux détecteurs. À côté des questions contenant de véritables identifiants, nous avons planté des pièges : numéros de commande, références produit, dates, prix et noms d'entreprises cotées. Ils ressemblent à des données personnelles, c'est exactement ce qu'un détecteur trop zélé attrape, et c'est ainsi que le résultat se gâte. Retirez un numéro de commande d'une question et l'agent répond à une version mutilée de celle-ci. Le client reçoit une moins bonne réponse et personne ne voit jamais pourquoi.

Une réserve sur la notation, car elle change le sens des chiffres. Nous avons noté au niveau de la question : chacune ressort-elle correctement classée, avec ou sans données personnelles ? La production fait plus difficile, puisqu'elle masque chaque élément identifiant à l'intérieur de la question. Lisez donc ces chiffres comme des taux de détection.

## Le prix s'est révélé négatif

Sur les 102 questions, le modèle local n'a commis aucune erreur. Aucune. Guardrails en a commis huit. Résumer cela par 92 % contre 100 % masque l'essentiel, car il s'agit de deux erreurs distinctes qui échouent en sens inverse.

### Les identifiants passés au travers

Sur les 102 questions, 57 contenaient des données à caractère personnel. C'est ici que l'erreur compte le plus, puisque ces données atteignent alors le fournisseur du modèle, en général un hébergeur tiers situé en Europe. C'est précisément l'événement que le filtre existe pour empêcher, parce que nous n'aimons pas laisser de zones d'ombre. Nos clients non plus.

Guardrails en a repéré 53 et en a manqué 4, soit un taux de détection de 93,0 %. Redact a repéré les 57. Sur notre jeu de test, quatre fuites contre aucune.

### Les questions propres abîmées au passage

Les 45 autres questions étaient propres, pièges compris. Un faux positif coûte de la qualité et non de la conformité. L'agent répond alors à une version caviardée d'une question parfaitement innocente.

Guardrails en a signalé 4 sur 45, soit un taux de faux positifs de 8,9 %. Redact n'en a signalé aucune, et l'emporte donc sur les deux types d'erreur à la fois.

| Résultat sur 102 questions | AWS Bedrock Guardrails | Redact 3.0.0 (local, 17 catégories) |
|---|---|---|
| Correct | 94/102 (92,2 %) | 102/102 (100,0 %) |
| Détections justes | 53 | 57 |
| Détections manquées | 4 | 0 |
| Questions propres laissées intactes | 41 | 45 |
| Questions propres signalées à tort | 4 | 0 |

Avant que l'enthousiasme ne monte, rappelons que nous avons opposé un spécialiste à un généraliste. Guardrails est large et réglé prudemment pour de nombreux usages, là où Redact ne fait qu'une chose. Qu'un spécialiste batte un généraliste sur son propre terrain n'a rien d'une nouvelle ; l'écart et le coût d'exploitation, si.

Deux précisions supplémentaires pour rester honnête. La colonne AWS correspond à notre configuration de production réelle, pas à un épouvantail que nous aurions déréglé. Et ces 100 % désignent zéro erreur sur 102 questions fictives soigneusement construites, en une seule exécution. C'est un résultat solide sur ce jeu de test, pas une prévision de ce qui se passe à grande échelle.

## N'oubliez pas : une attente se paie deux fois

La précision a motivé le changement. La latence et le coût en ont été la récompense. L'appel géré prenait 200 à 400 ms par question, quand le modèle local en demande 30 à 50 sur le chemin chaud.

Ces deux mesures ne se prennent pas de la même façon. La première inclut l'aller-retour réseau vers Bedrock. La seconde correspond à une inférence dans une fonction déjà démarrée, chargement initial d'un démarrage à froid exclu.

Vient ensuite la facture. Guardrails est facturé [0,10 $ par tranche de 1 000 unités de texte](https://aws.amazon.com/bedrock/pricing/ "Tarification d'Amazon Bedrock"), et une unité de texte représente 1 000 caractères. Nos questions étant courtes, comptez une unité chacune, soit environ 10 cents pour mille.

Voici la lecture naïve de l'échange. Nous avons supprimé en partie une facture de 10 cents et ajouté 30 à 50 ms de calcul. Elle en oublie la moitié. Votre modèle embarqué n'est évidemment pas gratuit non plus, mais si vous l'hébergez vous-même, son coût ne représente probablement qu'une fraction du précédent. Ce n'est pourtant pas de cela que nous parlons.

L'appel était attendu, et la fonction restait bloquée sans rien d'autre à faire. [Lambda facture à la durée](https://aws.amazon.com/lambda/pricing/ "Tarification d'AWS Lambda"). L'attente inactive se facture exactement comme le travail : nous payions Amazon pour rester en ligne.

L'échange coupe donc des deux côtés. Les frais d'API disparaissent, et 200 à 400 ms d'attente facturée deviennent 30 à 50 ms de travail facturé. Comptez de 150 à 370 ms de durée facturée en moins par question.

Une contrepartie honnête appartient à votre propre calcul. Le modèle et son moteur réclamaient de la place, et la fonction est passée de 1 024 Mo à 1 536 Mo. Une mémoire plus élevée augmente le prix à la milliseconde de l'invocation entière, pas seulement de l'étape de filtrage.

Faites donc le vrai calcul sur votre fonction : (nouvelle mémoire × nouvelle durée) − (ancienne mémoire × ancienne durée). Chez nous, il penche nettement vers le modèle local. Chez vous, peut-être pas.

## Ce que la règle nous a coûté

Rien de tout cela n'est gratuit, et la facture arrive au démarrage à froid. Un conteneur froid charge le modèle une fois avant la première inférence, ce qui ajoute environ 240 à 330 ms.

Nous avons mesuré que 2 % des invocations tombent sur un démarrage à froid. Chaque invocation chaude ensuite tient les 30 à 50 ms. Les démarrages à froid ne sont jamais devenus un problème pour cette charge.

Un trafic irrégulier casse complètement ce calcul. Mesurez le temps d'initialisation sur votre propre configuration avant de croire les chiffres de latence de qui que ce soit, les nôtres compris.

## Ce que la règle n'achète pas

La règle a acheté de la latence et de l'argent. Elle n'a pas sorti le filtre de la chaîne de traitement, et nous tenons à cette distinction.

Un détecteur d'identifiants lit l'entrée brute par construction, puisque c'est ainsi qu'il trouve des données personnelles. Il reste donc un sous-traitant ultérieur déclaré, qu'un tiers l'exploite ou que nous l'exploitions nous-mêmes. Le fait de l'héberger dans notre propre fonction ne change rien à ce qu'il peut voir.

Ce que le filtre protège est plus étroit et vérifiable. Les identifiants bruts n'atteignent pas le modèle tiers qui formule la réponse, lequel reçoit des champs remplacés par des jetons qu'il ne peut pas inverser. Notre [Coffre-fort de confidentialité](https://unless.com/en/trust/ "Confiance : conforme au RGPD et au règlement européen sur l'IA dès la conception") repose sur ce fait, avant comme après ce changement.

Le filtrage est une obligation de moyens visant à réduire les identifiants détectables. Ce n'est jamais une anonymisation, et les quatre manques du tableau montrent pourquoi la distinction mérite sa place. Nous avons écrit ailleurs sur [le filtrage des données personnelles sous le RGPD](https://unless.com/en/blog/know-how/privacy-in-the-eu-and-llms/ "Vie privée dans l'UE et grands modèles de langage") et sur [la frontière de sécurité autour d'un agent](https://unless.com/en/blog/opinion/lethal-trifecta-customer-agents/ "Le trifecta mortel et les agents de clientèle").

## Choisissez une règle indiscutable

Choisir une règle dure et regarder jusqu'où elle nous mènerait nous a conduits vers cette nouvelle classe de micromodèles. Tout ce qui pourrait tourner dans une application mobile convient parfaitement à Lambda.

Et la liste des tâches qu'un petit modèle local peut reprendre s'allonge. Nous n'avons pas testé le reste, prenez donc ceci comme une direction et non comme un résultat. Résumer une interaction, lui donner un titre, ranger une question dans un thème. Chacune de ces tâches est aujourd'hui un await. Chacune est aussi assez petite et assez délimitée pour qu'un spécialiste s'en charge sur place.

Allez donc regarder les appels attendus sur votre propre chemin critique. Mesurez l'attente que vous payez déjà, puis demandez-vous si un modèle conçu pour une poche pourrait faire ce travail dans votre fonction.

Notre filtre n'a jamais vu un téléphone. Il lui fallait seulement une petite machine à durée de vie courte, et c'est exactement ce qu'est une Lambda.
