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

# 以代码构建工作流

## 概览

[工作流](/docs/zh/eleven-agents/customization/agent-workflows)以节点和边组成的图结构存储。手动编写该图意味着你需要自行确保边 ID、`edge_order` 列表和条件对象保持一致。Python 和 JavaScript SDK 中的 `Workflow` 构建器让你可以通过链式方法调用描述同一图结构，然后生成 `agents.create()` 和 `agents.update()` 已支持的 `workflow` 值。

可用于：

* 通过代码构建工作流，并像审查其他代码变更一样审查它。
* 加载智能体工作流，更改一个节点后推送回去，而不影响图中的其他部分。
* 将工作流以 JSON 格式存入版本控制，并在 CI 中验证。

> **Note**
>
> 构建器完全在 SDK 内运行。它不会调用新的 API 端点，构建的任何内容都可以
> 在控制台的可视化编辑器中打开和编辑。

## 要求

* Python：使用 `elevenlabs` 包。构建器从 `elevenlabs.agent_workflows` 导入。
* JavaScript：使用 `@elevenlabs/elevenlabs-js` 包。构建器从包根目录导出。

## 构建工作流

`Workflow` 对象代表起始节点。每个动词方法都会添加一个节点并返回该节点，因此你可以从该节点继续链式调用下一步：

| Python                                 | JavaScript                            | 添加内容                                           |
| -------------------------------------- | ------------------------------------- | ---------------------------------------------- |
| `converse(id, prompt, **options)`      | `converse(id, prompt, options?)`      | 一个子智能体节点。智能体会围绕添加到其指令中的 `prompt` 展开对话。         |
| `tool(id, tool_ids, **options)`        | `tool(id, toolIds, options?)`         | 一个运行指定工具的调度工具节点。                               |
| `transfer(id, destination, **options)` | `transfer(id, destination, options?)` | 转接至电话号码、SIP URI 或其他智能体。                        |
| `end(id="end")`                        | `end(options?)`                       | 一个结束节点。未指定 ID 的每次 `end()` 调用都会共用一个 `"end"` 节点。 |

路由方法决定哪条边通向下一个节点：

| Python                                | JavaScript                       | 创建内容                                                                              |
| ------------------------------------- | -------------------------------- | --------------------------------------------------------------------------------- |
| `when(condition)`                     | `when(condition)`                | 一条条件边。字符串是 LLM 条件；`Expr` 是表达式。                                                    |
| `on_success()` / `on_failure()`       | `onSuccess()` / `onFailure()`    | 一条来自工具节点的边，会在工具调用成功或失败时采用。                                                        |
| `on_result(success=..., failure=...)` | `onResult({ success, failure })` | 同时创建两条工具结果边。                                                                      |
| `go_to(node)`                         | `goTo(node)`                     | 一条通向已有节点的边，用于合并和循环。                                                               |
| `up()`                                | `up()`                           | 不创建边。返回当前节点链式调用所来自的节点，以便继续从该节点链式调用。参见[将工作流写成一条链](#write-a-workflow-as-one-chain)。 |

直接在工作流上调用的动词方法会通过无条件边从起始节点分支。以下示例将来电者路由至订单查询；如果查询失败，则重试身份识别；并转接 VIP 或感到沮丧的来电者：

```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()` 会验证整个图并返回请求模型。它还会：

* 为起始节点分配 API 所需的 ID `start_node`。
* 生成 `source__target` 格式的边 ID。
* 按添加边的顺序填充每个节点的 `edge_order`。
* 为所有未设置位置的节点定位，使图在可视化编辑器中从左到右排列。

### 将工作流写成一条链

`up()` 会返回当前节点链式调用所来自的节点，因此你可以在一个表达式中描述整个工作流，而无需将每个节点存入变量。在单条链中，请使用带节点 ID 的 `go_to()` / `goTo()`，因为没有可传入的变量：

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

对于通过 `wf.node(id)` 获取的节点句柄，`up()` 会返回第一条进入该节点的边所来自的节点。对于起始节点，它会返回起始节点。

### 节点选项

Python 中的可选设置通过关键字参数传入，JavaScript 中则通过末尾的选项对象传入。其名称与 API 中节点字段一致：

| 节点         | 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`                                                                                    |

如果要自行放置节点，每个动词方法还接受 `position`。Python 中的 `Workflow(prevent_subagent_loops=True)`，或 JavaScript 中的 `new Workflow({ preventSubagentLoops: true })`，会阻止对话再次进入已访问过的子智能体。

```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" });
```

### 转接目标

| Python                             | JavaScript                        | 转接至            |
| ---------------------------------- | --------------------------------- | -------------- |
| `Destination.phone(number)`        | `Destination.phone(number)`       | 电话号码           |
| `Destination.phone_variable(name)` | `Destination.phoneVariable(name)` | 动态变量中的电话号码     |
| `Destination.sip(uri)`             | `Destination.sip(uri)`            | SIP URI        |
| `Destination.sip_variable(name)`   | `Destination.sipVariable(name)`   | 动态变量中的 SIP URI |
| `Destination.agent(agent_id)`      | `Destination.agent(agentId)`      | 其他智能体          |

## 条件

`when()` 接受普通字符串、`Condition` 或 `Expr`：

* 字符串是 LLM 条件：由 LLM 决定其是否成立。
* `Condition.llm(text, label=...)` 的作用相同，但会在可视化编辑器的边上显示标签。使用 `Condition.expr(expr, label=...)` 为表达式添加标签。
* `Expr` 会根据[动态变量](/docs/zh/eleven-agents/customization/personalization/dynamic-variables)求值，无需调用 LLM。

使用 `Expr.variable(name)` 和 `Expr.literal(value)` 构建表达式。通过 `eq`、`neq`、`gt`、`gte`、`lt` 和 `lte` 比较，通过 `add`、`sub`、`mul` 和 `div` 进行算术运算。通过 `all_of` / `allOf`、`any_of` / `anyOf` 和 `if_else` / `ifElse` 组合表达式。普通值会自动包装为 `Expr.literal()`。`Expr.llm(description)` 会要求 LLM 从对话中提取一个值；传入 `type` 和 `enum` 可获取字符串、数字或固定集合中的一个值，而不是布尔值。

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

## 循环和反向边

API 允许任意两个节点之间存在一条边，方向不限。要让对话沿原路返回，请使用 `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()` 会为你处理常见情况。当目标节点已有一条边指向当前节点时，新边会成为该边的反向条件。在第一个示例中，`failed.go_to(identify)` 会作为 `identify` → `lookup_order` 边的反向条件发送。相同两个节点之间的任何其他第二条边，以及从节点指向自身的任何边，都会导致验证失败。

## 编辑现有智能体

`Workflow.from_agent()` / `Workflow.fromAgent()` 会加载由 `agents.get()` 返回的智能体工作流。节点 ID、边 ID、编辑器位置和边顺序都会保留，因此推送回去时只会更改你修改的内容。

| Python                                  | JavaScript                             | 作用                         |
| --------------------------------------- | -------------------------------------- | -------------------------- |
| `wf.node(id)`                           | `wf.node(id)`                          | 返回现有节点的句柄，用于编辑或从该节点继续链式调用。 |
| `node.update(**fields)`                 | `node.update(fields)`                  | 更改节点的提示词、工具、目标或选项。         |
| `wf.insert_between(source, target, fn)` | `wf.insertBetween(source, target, fn)` | 将新节点插入现有边中，并保留其条件。         |
| `wf.remove(id)`                         | `wf.remove(id)`                        | 删除节点及所有指向或来自该节点的边。         |

`insert_between()` / `insertBetween()` 会将一个携带被替换边条件的分支传入你的函数。函数返回其添加的最后一个节点，随后该节点会连接到原始目标。该方法返回该节点；若两个节点之间没有边，则返回 `None` / `undefined`。

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

## 在版本控制中存储工作流

`to_json_string()` / `toJSONString()` 会以 API 的传输格式写入工作流：使用 snake_case 键、每一级排序，并以 2 个空格缩进。它与 REST API 和 [CLI](/docs/zh/eleven-agents/operate/cli) 使用的结构相同，且两个 SDK 对相同工作流写出的文件逐字节一致，因此差异会保持很小。`Workflow.from_json()` / `Workflow.fromJSON()` 会将其读回，也接受包含 `workflow` 键的完整智能体配置，例如 CLI 的 `agent_configs/` 目录中的文件。

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

典型设置：

#### 初始化文件

使用 `from_agent()` / `fromAgent()` 加载智能体并写入文件，或通过 `elevenlabs
    agents pull` 拉取。

#### 编辑

直接更改 JSON，或使用 `from_json()` / `fromJSON()` 加载后，在脚本中通过构建器
编辑。

#### 在 CI 中验证

对每个拉取请求运行 `Workflow.from_json(file).build()` / `Workflow.fromJSON(file).build()`，
并将 `elevenlabs agents workflow diagram <file> --format mermaid-md` 作为评论发布，以便
审阅者查看图结构。

#### 合并时部署

使用 `agents.update()` 推送构建后的工作流。

## 可视化工作流

[CLI](/docs/zh/eleven-agents/operate/cli) 会将工作流文件绘制为流程图，因此无需打开控制台即可检查代码构建的内容。它接受由 `to_json_string()` / `toJSONString()` 写入的纯工作流文件，或带有 `workflow` 键的智能体配置：

```bash
elevenlabs agents workflow diagram workflows/support.json
```

默认情况下，它会在终端中绘制方框和箭头，每条边的条件都会在边上编号，并在下方图例中完整列出。使用 `--format mermaid` 可改为输出 [Mermaid](https://mermaid.js.org/) 源码，或使用 `--format mermaid-md` 将其包装在 GitHub 可渲染的围栏代码块中，例如用于拉取请求评论：

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

该命令仅读取本地文件，因此无需 API 密钥，也可在 CI 中运行。

## 验证

链式调用本身不会失败。`build()`、`to_json()` / `toJSON()` 和 `to_json_string()` / `toJSONString()` 会检查整个图，并引发或抛出一个 `WorkflowValidationError`。其 `issues` 列表会列出每个问题，每项都包含 `code`、`message` 以及相关的 `node_id` / `nodeId`。

```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;
  }
}
```

错误包括：

* 相同两个节点之间存在两条边，或存在从节点指向自身的边。
* 指向起始节点的边，或从结束节点发出的边。
* 从工具节点发出但并非成功或失败边的边，或从任何其他类型节点发出的成功或失败边。
* 两个节点具有相同 ID，或存在指向不存在节点的边。

无法从起始节点到达的节点会通过 `on_warning` / `onWarning` 报告为警告，但不会阻止构建。

## 比 SDK 更新的节点类型

从 API 或文件加载的工作流可能包含 SDK 版本尚未识别的节点类型。它们会通过 `from_json()` / `fromJSON()` 和 `to_json_string()` / `toJSONString()` 原样往返保留，且 `build()` 会将每个此类节点报告为警告。类型化请求模型无法携带它们，因此请升级 SDK，或直接发送传输格式：

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