Passer à la navigation

Workflows en tant que code

Concevez, modifiez et versionnez les workflows d'agents avec les SDK Python et JavaScript

Présentation

Un workflow est stocké sous forme de graphe de nœuds et d’arêtes. Écrire ce graphe à la main implique de maintenir vous-même la cohérence des identifiants d’arête, des listes edge_order et des objets de condition. Le générateur Workflow des SDK Python et JavaScript vous permet de décrire le même graphe avec des appels de méthodes chaînés. Il produit ensuite la valeur workflow qu’acceptent déjà agents.create() et agents.update().

Utilisez-le pour :

  • Concevoir un workflow en code et le réviser comme toute autre modification de code.
  • Charger le workflow d’un agent, modifier un nœud et le renvoyer sans perturber le reste du graphe.
  • Stocker les workflows au format JSON dans le contrôle de version et les valider dans votre CI.

Le générateur s’exécute entièrement dans le SDK. Il n’appelle aucun nouveau point de terminaison d’API, et tout ce qu’il génère peut être ouvert et modifié dans l’éditeur visuel du Dashboard.

Prérequis

  • Python : le package elevenlabs. Le générateur est importé depuis elevenlabs.agent_workflows.
  • JavaScript : le package @elevenlabs/elevenlabs-js. Le générateur est exporté depuis la racine du package.

Concevoir un workflow

L’objet Workflow représente le nœud de départ. Chaque verbe ajoute un nœud et le renvoie, ce qui vous permet d’enchaîner l’étape suivante à partir de celui-ci :

PythonJavaScriptAjoute
converse(id, prompt, **options)converse(id, prompt, options?)Un nœud de sous-agent. L’agent mène une conversation avec prompt ajouté à ses instructions.
tool(id, tool_ids, **options)tool(id, toolIds, options?)Un nœud d’outil de répartition qui exécute les outils fournis.
transfer(id, destination, **options)transfer(id, destination, options?)Un transfert vers un numéro de téléphone, un URI SIP ou un autre agent.
end(id="end")end(options?)Un nœud de fin. Chaque appel à end() sans identifiant partage un même nœud "end".

Les méthodes de routage déterminent quelle arête mène au nœud suivant :

PythonJavaScriptCrée
when(condition)when(condition)Une arête conditionnelle. Une chaîne est une condition LLM ; un Expr est une expression.
on_success() / on_failure()onSuccess() / onFailure()Une arête depuis un nœud d’outil, empruntée lorsque l’appel d’outil réussit ou échoue.
on_result(success=..., failure=...)onResult({ success, failure })Les deux arêtes de résultat d’outil en une fois.
go_to(node)goTo(node)Une arête vers un nœud qui existe déjà, pour les fusions et les boucles.
up()up()Aucune arête. Renvoie le nœud depuis lequel le nœud actuel a été chaîné, afin de poursuivre le chaînage depuis celui-ci. Consultez Écrire un workflow en une seule chaîne.

Les verbes appelés directement sur le workflow créent une branche depuis le nœud de départ avec une arête inconditionnelle. Cet exemple dirige les appelants vers une recherche de commande, relance l’identification si la recherche échoue et transfère les appelants VIP ou frustrés :

from elevenlabs import ElevenLabs
from elevenlabs.agent_workflows import Destination, Expr, Workflow
elevenlabs = ElevenLabs()
wf = Workflow()
triage = wf.converse("triage", "Greet the caller and find out why they're calling")
identify = triage.when("Caller wants to check an order").converse(
"identify", "Ask for the order number and the email on the account"
)
lookup = identify.when("Caller gave an order number").tool("lookup_order", ["tool_get_order"])
lookup.on_result(
success=lambda ok: ok.converse("resolve", "Explain the order status")
.when("Caller is satisfied")
.end(),
failure=lambda failed: failed.go_to(identify),
)
triage.when(Expr.variable("tier").eq("gold")).transfer("vip", Destination.agent("agent_vip"))
triage.when("Caller wants a human").transfer("human", Destination.phone("+15551234567"))
elevenlabs.conversational_ai.agents.update("agent_id", workflow=wf.build())

build() valide l’ensemble du graphe et renvoie le modèle de requête. Il :

  • Attribue au nœud de départ l’identifiant start_node, requis par l’API.
  • Génère des identifiants d’arête sous la forme source__target.
  • Renseigne le edge_order de chaque nœud dans l’ordre où vous avez ajouté les arêtes.
  • Positionne tout nœud sans position, afin que le graphe s’affiche de gauche à droite dans l’éditeur visuel.

Écrire un workflow en une seule chaîne

up() renvoie le nœud depuis lequel le nœud actuel a été chaîné. Vous pouvez ainsi décrire un workflow entier dans une seule expression plutôt que de conserver chaque nœud dans une variable. Dans une seule chaîne, utilisez go_to() / goTo() avec un identifiant de nœud, car aucune variable n’est disponible à transmettre :

wf = Workflow()
(
wf.converse("triage", "Find out why the customer is calling")
.when("Caller gave an order number")
.tool("lookup_order", ["tool_get_order"])
.on_result(
success=lambda ok: ok.converse("resolve", "Explain the order status").when("Customer is satisfied").end(),
failure=lambda failed: failed.go_to("triage"),
)
.up()
.when(Expr.variable("tier").eq("gold"))
.transfer("vip", Destination.agent("agent_vip_desk"))
)
elevenlabs.conversational_ai.agents.update("agent_id", workflow=wf.build())

Pour un handle de nœud issu de wf.node(id), up() renvoie le nœud dont provient la première arête qui y entre. Pour le nœud de départ, il renvoie le nœud de départ.

Options de nœud

Les paramètres facultatifs sont transmis comme arguments nommés en Python et dans un objet d’options final en JavaScript. Leurs noms correspondent aux champs du nœud dans l’API :

NœudPythonJavaScript
converselabel, tool_ids, knowledge_base, conversation_config, entry_behaviorlabel, toolIds, knowledgeBase, conversationConfig, entryBehavior
toolschema_overridesschemaOverrides
transfertransfer_type, custom_sip_headers, post_dial_digits, delay_ms, transfer_message, …transferType, customSipHeaders, postDialDigits, delayMs, transferMessage, …
endidid

Chaque verbe accepte également position si vous souhaitez positionner le nœud vous-même. Workflow(prevent_subagent_loops=True) en Python, ou new Workflow({ preventSubagentLoops: true }) en JavaScript, empêche la conversation de réintégrer un sous-agent déjà visité.

triage = wf.converse(
"triage",
"Greet the caller and find out why they're calling",
label="Triage",
tool_ids=["tool_crm_search"],
entry_behavior="generate_immediately",
)
triage.when("Caller is a VIP").transfer(
"vip_desk",
Destination.agent("agent_vip_concierge"),
transfer_message="Connecting you to our VIP team",
delay_ms=500,
)
identify.when("Caller refuses to share details").end(id="abandoned")

Destinations de transfert

PythonJavaScriptTransfère vers
Destination.phone(number)Destination.phone(number)Un numéro de téléphone
Destination.phone_variable(name)Destination.phoneVariable(name)Un numéro de téléphone stocké dans une variable dynamique
Destination.sip(uri)Destination.sip(uri)Un URI SIP
Destination.sip_variable(name)Destination.sipVariable(name)Un URI SIP stocké dans une variable dynamique
Destination.agent(agent_id)Destination.agent(agentId)Un autre agent

Conditions

when() accepte une chaîne simple, une Condition ou un Expr :

  • Une chaîne est une condition LLM : le LLM détermine si elle est remplie.
  • Condition.llm(text, label=...) est identique, avec un libellé affiché sur l’arête dans l’éditeur visuel. Utilisez Condition.expr(expr, label=...) pour libeller une expression.
  • Un Expr est évalué par rapport aux variables dynamiques, sans appeler le LLM.

Construisez des expressions à partir de Expr.variable(name) et Expr.literal(value). Comparez-les avec eq, neq, gt, gte, lt et lte, et effectuez des opérations arithmétiques avec add, sub, mul et div. Combinez-les avec all_of / allOf, any_of / anyOf et if_else / ifElse. Les valeurs simples sont encapsulées pour vous dans Expr.literal(). Expr.llm(description) demande au LLM d’extraire une valeur de la conversation ; transmettez type et enum pour obtenir une chaîne, un nombre ou l’une d’un ensemble fixe plutôt qu’un booléen.

from elevenlabs.agent_workflows import Condition, Expr
is_vip = Expr.any_of(
Expr.variable("tier").eq("gold"),
Expr.all_of(Expr.variable("orders").gte(10), Expr.variable("churn_risk").eq(True)),
)
triage.when(Condition.expr(is_vip, label="VIP")).transfer("vip_desk", Destination.agent("agent_vip"))
big_refund = Expr.variable("unit_price").mul(Expr.variable("quantity")).gt(500)
resolve.when(big_refund).transfer("finance", Destination.phone("+15551234567"))
score = Expr.llm("How satisfied the caller sounds, from 1 to 5", type="integer")
resolve.when(score.lte(2)).converse("recovery", "Apologize and offer a discount code")

Boucles et arêtes inversées

L’API autorise une arête entre deux nœuds, dans l’un ou l’autre sens. Pour permettre à la conversation de revenir sur son chemin, attribuez à l’arête une condition inversée avec back :

faq = triage.when("Caller has a general question", back="Question answered").converse(
"faq", "Answer using the FAQ knowledge base"
)

go_to() / goTo() gère le cas courant pour vous. Lorsque le nœud cible possède déjà une arête menant ici, la nouvelle arête devient la condition inversée de cette arête. Dans le premier exemple, failed.go_to(identify) est envoyé comme condition inversée de l’arête identify → lookup_order. Toute autre seconde arête entre les deux mêmes nœuds, ainsi que toute arête d’un nœud vers lui-même, échoue à la validation.

Modifier un agent existant

Workflow.from_agent() / Workflow.fromAgent() charge le workflow d’un agent renvoyé par agents.get(). Les identifiants de nœud, les identifiants d’arête, les positions dans l’éditeur et l’ordre des arêtes sont conservés. Le renvoi ne modifie donc que ce que vous avez changé.

PythonJavaScriptFonction
wf.node(id)wf.node(id)Renvoie un handle vers un nœud existant, pour le modifier ou enchaîner à partir de celui-ci.
node.update(**fields)node.update(fields)Modifie le prompt, les outils, la destination ou les options d’un nœud.
wf.insert_between(source, target, fn)wf.insertBetween(source, target, fn)Insère de nouveaux nœuds dans une arête existante, en conservant sa condition.
wf.remove(id)wf.remove(id)Supprime un nœud et chaque arête qui y entre ou en sort.

insert_between() / insertBetween() transmet à votre fonction une branche qui porte la condition de l’arête remplacée. La fonction renvoie le dernier nœud ajouté, qui est ensuite connecté à la cible d’origine. La méthode renvoie ce nœud, ou None / undefined s’il n’existe aucune arête entre les deux nœuds.

agent = elevenlabs.conversational_ai.agents.get("agent_id")
wf = Workflow.from_agent(agent)
wf.node("resolve").update(prompt="Explain the status, then offer a discount code")
verify = wf.insert_between(
"identify",
"lookup_order",
lambda start: start.converse("verify", "Verify the caller's identity with a one-time code"),
)
if verify is not None:
verify.when("Caller can't verify").end(id="unverified")
wf.remove("vip")
elevenlabs.conversational_ai.agents.update("agent_id", workflow=wf.build())

Stocker les workflows dans le contrôle de version

to_json_string() / toJSONString() écrit le workflow au format de transmission de l’API : des clés en snake_case, triées à tous les niveaux, avec une indentation de 2 espaces. Il s’agit du même format que celui utilisé par l’API REST et la CLI, et les deux SDK écrivent des fichiers identiques octet par octet pour un même workflow, afin de limiter les différences. Workflow.from_json() / Workflow.fromJSON() le relit et accepte également une configuration complète d’agent avec une clé workflow, comme un fichier du répertoire agent_configs/ de la CLI.

from pathlib import Path
from elevenlabs.agent_workflows import Workflow
Path("workflows/support.json").write_text(wf.to_json_string())
stored = Workflow.from_json(Path("workflows/support.json").read_text())
from_cli = Workflow.from_json(Path("agent_configs/support.json").read_text())

Configuration type :

1

Initialiser le fichier

Chargez l’agent avec from_agent() / fromAgent() et écrivez-le dans un fichier, ou récupérez-le avec elevenlabs agents pull.

2

Modifier

Modifiez directement le JSON, ou chargez-le avec from_json() / fromJSON() et modifiez-le avec le générateur dans un script.

3

Valider dans la CI

Exécutez Workflow.from_json(file).build() / Workflow.fromJSON(file).build() sur chaque pull request, puis publiez elevenlabs agents workflow diagram <file> --format mermaid-md dans un commentaire afin que les réviseurs puissent voir le graphe.

4

Déployer lors de la fusion

Envoyez le workflow généré avec agents.update().

Visualiser un workflow

La CLI représente un fichier de workflow sous forme d’organigramme. Vous pouvez ainsi vérifier ce que votre code a généré sans ouvrir le Dashboard. Elle accepte un fichier de workflow seul écrit par to_json_string() / toJSONString(), ou une configuration d’agent avec une clé workflow :

elevenlabs agents workflow diagram workflows/support.json

Par défaut, elle dessine des boîtes et des flèches dans le terminal, chaque condition d’arête étant numérotée sur son arête et détaillée dans une légende en dessous. Utilisez --format mermaid pour afficher à la place le code source Mermaid, ou --format mermaid-md pour l’encapsuler dans un bloc de code clôturé rendu par GitHub, par exemple dans un commentaire de pull request :

elevenlabs agents workflow diagram workflows/support.json --format mermaid-md

La commande lit uniquement des fichiers locaux. Elle ne nécessite donc aucune clé API et peut s’exécuter dans la CI.

Validation

Les appels chaînés n’échouent jamais seuls. build(), to_json() / toJSON() et to_json_string() / toJSONString() vérifient l’ensemble du graphe et lèvent ou génèrent une unique WorkflowValidationError, dont la liste issues répertorie chaque problème avec son code, son message et le node_id / nodeId concerné.

from elevenlabs.agent_workflows import WorkflowValidationError
try:
workflow = wf.build(on_warning=lambda issue: print("warning:", issue.message))
except WorkflowValidationError as err:
for issue in err.issues:
print(issue.code, issue.message)

Les erreurs incluent :

  • Deux arêtes entre la même paire de nœuds, ou une arête d’un nœud vers lui-même.
  • Une arête qui entre dans le nœud de départ ou qui sort d’un nœud de fin.
  • Une arête depuis un nœud d’outil qui n’est pas une arête de réussite ou d’échec, ou une arête de réussite ou d’échec depuis tout autre type de nœud.
  • Deux nœuds avec le même identifiant, ou une arête vers un nœud qui n’existe pas.

Les nœuds inaccessibles depuis le nœud de départ sont signalés comme avertissements, via on_warning / onWarning, et n’empêchent pas la génération.

Types de nœuds plus récents que votre SDK

Un workflow chargé depuis l’API ou depuis un fichier peut contenir des types de nœuds que votre version du SDK ne connaît pas encore. Ils effectuent un aller-retour inchangés via from_json() / fromJSON() et to_json_string() / toJSONString(), et build() signale chacun d’eux comme un avertissement. Le modèle de requête typé ne peut pas les transporter. Mettez donc à niveau le SDK ou envoyez directement le format de transmission :

elevenlabs.conversational_ai.agents.update(
"agent_id",
request_options={"additional_body_parameters": {"workflow": wf.to_json()}},
)