> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# Workflows en tant que code

## Présentation

Un [workflow](/docs/fr/eleven-agents/customization/agent-workflows) 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.

> **Note**
>
> 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 :

| Python                                 | JavaScript                            | Ajoute                                                                                        |
| -------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------- |
| `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 :

| Python                                | JavaScript                       | Cré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](#write-a-workflow-as-one-chain). |

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 :

```python
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())

```

```typescript
import { Destination, ElevenLabsClient, Expr, Workflow } from "@elevenlabs/elevenlabs-js";

const elevenlabs = new ElevenLabsClient();

const wf = new Workflow();

const triage = wf.converse("triage", "Greet the caller and find out why they're calling");
const identify = triage
  .when("Caller wants to check an order")
  .converse("identify", "Ask for the order number and the email on the account");

const lookup = identify
  .when("Caller gave an order number")
  .tool("lookup_order", ["tool_get_order"]);
lookup.onResult({
  success: (ok) =>
    ok.converse("resolve", "Explain the order status").when("Caller is satisfied").end(),
  failure: (failed) => failed.goTo(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"));

await elevenlabs.conversationalAi.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 :

```python

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())

```

```typescript
const wf = new Workflow();
wf.converse("triage", "Find out why the customer is calling")
  .when("Caller gave an order number")
  .tool("lookup_order", ["tool_get_order"])
  .onResult({
    success: (ok) =>
      ok.converse("resolve", "Explain the order status").when("Customer is satisfied").end(),
    failure: (failed) => failed.goTo("triage"),
  })
  .up()
  .when(Expr.variable("tier").eq("gold"))
  .transfer("vip", Destination.agent("agent_vip_desk"));

await elevenlabs.conversationalAi.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œud       | Python                                                                                         | JavaScript                                                                              |
| ---------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `converse` | `label`, `tool_ids`, `knowledge_base`, `conversation_config`, `entry_behavior`                 | `label`, `toolIds`, `knowledgeBase`, `conversationConfig`, `entryBehavior`              |
| `tool`     | `schema_overrides`                                                                             | `schemaOverrides`                                                                       |
| `transfer` | `transfer_type`, `custom_sip_headers`, `post_dial_digits`, `delay_ms`, `transfer_message`, ... | `transferType`, `customSipHeaders`, `postDialDigits`, `delayMs`, `transferMessage`, ... |
| `end`      | `id`                                                                                           | `id`                                                                                    |

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é.

```python
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")
```

```typescript
const triage = wf.converse("triage", "Greet the caller and find out why they're calling", {
  label: "Triage",
  toolIds: ["tool_crm_search"],
  entryBehavior: "generate_immediately",
});

triage.when("Caller is a VIP").transfer("vip_desk", Destination.agent("agent_vip_concierge"), {
  transferMessage: "Connecting you to our VIP team",
  delayMs: 500,
});

identify.when("Caller refuses to share details").end({ id: "abandoned" });
```

### Destinations de transfert

| Python                             | JavaScript                        | Transfè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](/docs/fr/eleven-agents/customization/personalization/dynamic-variables), 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.

```python
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")
```

```typescript
import { Condition, Expr } from "@elevenlabs/elevenlabs-js";

const isVip = Expr.anyOf(
  Expr.variable("tier").eq("gold"),
  Expr.allOf(Expr.variable("orders").gte(10), Expr.variable("churn_risk").eq(true))
);

triage
  .when(Condition.expr(isVip, { label: "VIP" }))
  .transfer("vip_desk", Destination.agent("agent_vip"));

const bigRefund = Expr.variable("unit_price").mul(Expr.variable("quantity")).gt(500);
resolve.when(bigRefund).transfer("finance", Destination.phone("+15551234567"));

const 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` :

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

```typescript
const 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é.

| Python                                  | JavaScript                             | Fonction                                                                                     |
| --------------------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------- |
| `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.

```python

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())
```

```typescript
const agent = await elevenlabs.conversationalAi.agents.get("agent_id");
const wf = Workflow.fromAgent(agent);

wf.node("resolve").update({ prompt: "Explain the status, then offer a discount code" });

const verify = wf.insertBetween("identify", "lookup_order", (start) =>
  start.converse("verify", "Verify the caller's identity with a one-time code")
);

verify?.when("Caller can't verify").end({ id: "unverified" });

wf.remove("vip");

await elevenlabs.conversationalAi.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](/docs/fr/eleven-agents/operate/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.

```python
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())
```

```typescript
import { Workflow } from "@elevenlabs/elevenlabs-js";
import { readFile, writeFile } from "node:fs/promises";

await writeFile("workflows/support.json", wf.toJSONString());

const stored = Workflow.fromJSON(await readFile("workflows/support.json", "utf8"));
const fromCli = Workflow.fromJSON(await readFile("agent_configs/support.json", "utf8"));
```

Configuration type :

#### 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`.

#### Modifier

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

#### 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.

#### Déployer lors de la fusion

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

## Visualiser un workflow

La [CLI](/docs/fr/eleven-agents/operate/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` :

```bash
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](https://mermaid.js.org/), 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 :

```bash
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é.

```python
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)
```

```typescript
import { WorkflowValidationError } from "@elevenlabs/elevenlabs-js";

try {
  const workflow = wf.build({ onWarning: (issue) => console.warn(issue.message) });
} catch (err) {
  if (err instanceof WorkflowValidationError) {
    for (const issue of err.issues) console.error(issue.code, issue.message);
  } else {
    throw err;
  }
}
```

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 :

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

```typescript
await elevenlabs.conversationalAi.agents.update(
  "agent_id",
  {},
  { additionalBodyParameters: { workflow: wf.toJSON() } }
);
```