> 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/ko/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`는 LLM을 호출하지 않고 [동적 변수](/docs/ko/eleven-agents/customization/personalization/dynamic-variables)를 기준으로 평가됩니다.

`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/ko/eleven-agents/operate/cli)가 사용하는 것과 동일한 형식이며, 두 SDK 모두 동일한 워크플로에 대해 바이트 단위까지 같은 파일을 작성하므로 diff가 작게 유지됩니다. `Workflow.from_json()` / `Workflow.fromJSON()`은 이를 다시 읽으며, CLI의 `agent_configs/` 디렉터리에 있는 파일처럼 `workflow` 키가 포함된 전체 에이전트 구성도 받을 수 있습니다.

```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에서 검증

모든 pull request에서 `Workflow.from_json(file).build()` / `Workflow.fromJSON(file).build()`를
실행하고, 검토자가 그래프를 볼 수 있도록 `elevenlabs agents workflow diagram <file> --format mermaid-md`를 댓글로 게시합니다.

#### 병합 시 배포

`agents.update()`로 빌드한 워크플로를 푸시합니다.

## 워크플로 시각화

[CLI](/docs/ko/eleven-agents/operate/cli)는 워크플로 파일을 순서도로 그리므로 대시보드를 열지 않고도 코드가 구축한 내용을 확인할 수 있습니다. `to_json_string()` / `toJSONString()`으로 작성한 단독 워크플로 파일 또는 `workflow` 키가 있는 에이전트 구성을 받을 수 있습니다.

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

기본적으로 터미널에 상자와 화살표를 그리며, 각 엣지 조건은 해당 엣지에 번호로 표시되고 아래 범례에 전체 내용이 작성됩니다. 대신 [Mermaid](https://mermaid.js.org/) 소스를 출력하려면 `--format mermaid`를 사용하세요. 또는 pull request 댓글과 같이 GitHub에서 렌더링되는 fenced 코드 블록으로 감싸려면 `--format mermaid-md`를 사용하세요.

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

이 명령어는 로컬 파일만 읽으므로 API 키가 필요하지 않으며 CI에서 실행할 수 있습니다.

## 검증

연결된 호출은 자체적으로 실패하지 않습니다. `build()`, `to_json()` / `toJSON()`, `to_json_string()` / `toJSONString()`는 전체 그래프를 검사하고, 각 문제를 나열하는 단일 `WorkflowValidationError`를 발생시키거나 throw합니다. 각 문제에는 `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() } }
);
```