Au cœur du moteur de workflow de HappyRobot

Une IA pilotée uniquement par des prompts laisse les décisions au raisonnement du modèle. Voici comment nous avons construit la couche déterministe qui sous-tend chaque agent HappyRobot.

karan
Karan Pahawa
Staff Engineer
Mis à jour le 22 sept. 20266 min de lecture
Exemple de workflow sur la plateforme HappyRobot

La plateforme HappyRobot permet de mettre des agents au travail dans des environnements d'entreprise complexes. Au cœur de la plateforme se trouve notre moteur de workflow, qui évalue une logique de workflow complexe. Tandis que les agents mènent la conversation et le raisonnement, la logique de workflow définit les étapes, les conditions et le comportement des agents qui maintiennent chaque interaction sur les rails, quelle que soit la tournure de la conversation. Dans cet article de blog, nous explorons en profondeur notre orchestrateur de workflows et le moteur sous-jacent qui pilote tous les workflows de notre plateforme.

Éditeur de workflows

L'éditeur de workflows HappyRobot est une approche low-code/no-code permettant à nos clients et aux Forward Deployed Engineers (FDE) qui les accompagnent d'exprimer des solutions pour des cas d'usage métier complexes. L'éditeur d'interface est le principal moyen par lequel nos utilisateurs créent des workflows. L'interface leur permet également d'inspecter les exécutions, d'auditer les résultats des agents, de réaliser des tests A/B, et plus encore.

Un exemple de workflow

Si l'éditeur est le principal moyen de construire, ce n'est pas le seul. Chaque opération de l'interface repose sur notre API publique, avec un SDK TypeScript typé (@happyrobot-ai/sdk) par-dessus : vous pouvez créer des workflows, ajouter et configurer des nœuds, gérer des variables, publier des versions et déclencher des exécutions entièrement depuis le code. L'expérience low-code est une couche au-dessus des mêmes primitives : les équipes qui préfèrent traiter les workflows comme du code peuvent les construire, les versionner et les déployer de façon programmatique, et le moteur décrit ci-dessous les exécute de manière identique dans les deux cas.

Représentation des workflows

Grâce à l'expérience no-code extrêmement simple que nous proposons, nos utilisateurs peuvent créer des dizaines de milliers de workflows uniques au sein de notre plateforme. Comme il est d'usage, nous utilisons des tables relationnelles de nœuds et d'arêtes pour représenter le graphe du workflow. Notre moteur construit une représentation interne à partir de ces tables, entre autres, avant le début de l'exécution.

Tableau des nœuds

Ce modèle de données nous permet de conserver une copie « blueprint » stable du graphe, ainsi qu'un modèle d'exécution correspondant à ce même blueprint. Dans certains cas, la contrepartie d'exécution est quasiment identique au blueprint, mais comme nous prenons en charge des décisions d'exécution proches du code — conditions, boucles (et interruptions) et exécutions parallèles —, un graphe d'exécution peut différer radicalement de son blueprint d'origine.

Présentation de l'exécuteur de workflows

Le cœur de notre exécuteur de workflows est notre moteur d'orchestration. Chaque fois que nos utilisateurs exécutent un workflow, c'est lui qui tourne en arrière-plan. Voici un aperçu simplifié de la façon dont notre pile d'exécution traite un workflow, de bout en bout :

exécuteur de workflow

Principe de conception du moteur

Un principe fondamental de notre moteur de workflow consiste à établir une séparation stricte des responsabilités entre l'orchestrateur et les exécutables (les nœuds du graphe). Les objets exécutables sont autonomes et l'orchestrateur ignore totalement leur fonctionnement interne. Cela n'a rien de nouveau : on parle couramment du modèle nœud-acteur. L'orchestrateur et les acteurs-nœuds entretiennent une relation coordinateur / worker.

L'orchestrateur détient la vue globale du graphe du workflow. Il décide quels nœuds sont autorisés à s'exécuter, en fonction de la disponibilité des dépendances. L'acteur-nœud détient le cycle de vie local de l'exécution d'un nœud.

Un acteur-nœud détient en propre son ensemble d'opérations de graphe et le moteur d'orchestration attend le retour de l'acteur lorsque celui-ci a terminé. Voici un modèle simplifié de l'acteur-nœud :

flux node actor

Une décision de conception clé est que l'acteur-nœud est responsable de la modification du graphe d'exécution pendant qu'il s'exécute. C'est précisément ce comportement qui nous permet de déployer les itérations de boucle pendant l'exécution du workflow, sans que l'orchestrateur en connaisse les détails. Une fois une boucle déployée, nous continuons simplement d'exécuter le graphe comme n'importe quel autre nœud : un nœud à la fois.

Throttling des workflows et choc système

Imaginez un workflow qui charge une liste de 10 000 éléments, parcourt chaque élément et effectue une série d'actions. En code, c'est évidemment trivial :

1for item in items:
2 output = do_foo(item)
3 output2 = do_bar(output)
4 output_3 = do_voice_call(output2)

Mais pour notre moteur et notre infrastructure, les workflows volumineux peuvent être vraiment nuisibles si nous n'y prenons pas garde. Notre infrastructure peut être mise à l'échelle horizontalement dans une certaine mesure, mais un défi majeur qu'aucune montée en charge responsable ne résoudra est le choc système. 10 000 itérations prises isolément, ce n'est pas énorme, mais cela pourrait être excessif si nous tentons de planifier et d'orchestrer chaque itération quasi simultanément, parmi tous les autres workflows en cours d'exécution.

Compte tenu de notre objectif de limiter le choc, notre limitation de débit/throttling doit tenir compte de deux façons dont un workflow peut malmener notre infrastructure :

  1. Les grandes boucles comportant un travail important dans chaque corps : en reprenant l'exemple ci-dessus, nous avons intégré des limiteurs de débit au sein du moteur lui-même, qui throttlent l'exécution de chaque itération ; lors de nos expérimentations de mise à l'échelle, cette approche s'est révélée simple mais extrêmement efficace pour réduire le choc système. La mise à l'échelle horizontale peut ainsi absorber un débit plus élevé sans que notre infrastructure implose
  2. Un grand nombre d'exécutions de workflows indépendantes : nos utilisateurs disposent d'un accès API pour lancer des workflows. Notre limiteur d'API comporte deux paramètres : le nombre de workflows qu'un utilisateur peut lancer par seconde, et le nombre de workflows simultanés qu'il peut exécuter à un instant donné. Le limiteur d'appels par seconde étale le choc système et le limiteur de concurrence garantit qu'aucun client ne monopolise les ressources de la plateforme

Appels vocaux

Le schéma de haut niveau montre comment chaque nœud de notre graphe est exécuté séquentiellement. Cela signifie aussi qu'une fois un nœud exécuté, notre moteur tente immédiatement d'exécuter le nœud suivant — mais comment prendre en charge les appels vocaux dans nos workflows dans ce cas ? Supposons que nous voulions terminer un appel avant de passer au nœud suivant.

Nos pods de service vocal sont des services à état, chaque pod pouvant gérer jusqu'à 200 appels vocaux simultanés. Chaque appel est géré comme une goroutine au sein du pod. Nos agents vocaux peuvent parfois mener des appels téléphoniques de 30 minutes à 1 heure avec des personnes, prendre en charge les transferts entre interlocuteurs et permettre aux agents qui répondent à l'appel d'exécuter des outils personnalisés créés par l'auteur du workflow. Nous prenons également en charge des nœuds placés après les agents vocaux dans nos workflows (par exemple : analyser l'appel, effectuer des appels API, stocker des données, lancer des tâches ultérieures). Tout cela pour dire qu'il nous faut un moyen de « mettre en pause » le workflow jusqu'à la fin d'un appel vocal et de garantir que l'agent vocal puisse toujours accéder à l'état du workflow pour savoir ce qui a été exécuté et quels outils sont à sa disposition.

Comment procédons-nous ? En bref, nous utilisons des signaux : une couche de communication entre le pod orchestrateur et un pod d'agent vocal. Lorsque nous démarrons un appel vocal dans un pod, la nature à état de l'appel nous permet de stocker les informations propres au workflow dans la session vocale. Le gestionnaire de session du pod vocal utilise ces informations de workflow pour relayer des signaux vers le worker du moteur d'orchestration concernant l'appel d'outils, la fin de session, les transferts, le statut de l'appel, et plus encore. Voici un schéma d'interaction simplifié :

Schéma d'interaction de l'orchestrateur

Conclusion

Le moteur de workflow HappyRobot a été créé en combinant une architecture événementielle (par exemple, les signaux) et une séparation stricte des responsabilités du système. Cela nous a permis de représenter une logique métier extrêmement complexe dans une plateforme no-code, tout en l'exécutant comme du code pour accomplir des tâches et des actions à grande échelle. Le moteur continue d'évoluer et nous ajoutons en permanence de nouvelles fonctionnalités et capacités pour faire passer la plateforme d'un exécuteur de workflows à un véritable système d'exploitation distribué des workflows.

cds
Blogs Engineering

Pourquoi l'IA est la couche de traduction entre l'humain et la machine, pourquoi le rôle de l'ingénieur logiciel va évoluer plutôt que disparaître, et pourquoi j'ai rejoint HappyRobot.

6878a32c21d86b93f8a566c9 technical overview
Lancements de produits,  Blogs Engineering

Cet article présente en détail l'architecture sous-jacente, les modèles d'IA intégrés et l'audit.