06. 흐름 제어¶
일직선으로 흐르는 자동화만으로 부족할 때 씁니다.
| 하고 싶은 것 | 쓸 노드 |
|---|---|
| 값에 따라 다른 길로 가기 | CONDITIONAL |
| 갈라진 길을 다시 합치기 | JOINT |
| 배열을 하나씩 처리하기 | LOOP_START / LOOP_END |
조건 분기 — CONDITIONAL¶
값을 보고 갈 길을 고릅니다.
- id: route
name: 금액별 분기
type: CONDITIONAL
execution-info:
conditions:
- label: vip
expression: "${input.amount} >= 100000"
- label: normal
expression: "${input.amount} >= 10000"
- label: small
otherwise: true # 위 조건에 다 안 맞으면 여기로
edges:
- from: route
to: vip-handler
label: vip # ← label로 어느 가지인지 지정
- from: route
to: normal-handler
label: normal
- from: route
to: small-handler
label: small
핵심 규칙 세 가지입니다.
- 조건은 위에서부터 검사합니다. 먼저 맞는 것 하나만 실행됩니다.
label마다 나가는 엣지가 하나씩 있어야 합니다. 없으면 저장이 거부됩니다.otherwise: true는 "위에 아무것도 안 맞을 때" 가는 기본 가지입니다. 넣는 걸 권합니다.- 피연산자는
${...}표현식이거나 리터럴입니다. 맨 이름은 값이 아니라 글자 그대로의 문자열입니다 (아래).
조건이 볼 값은 엣지로 넘겨야 합니다¶
⚠️ 조건식은 ${input.*} 범위에서 평가됩니다. 들어오는 엣지에 request.data가 없으면 조건은 아무 값도 못 봅니다.
edges:
- from: fetch-order
to: route
request:
data:
amount: "${nodes.fetch-order.response.body.totalAmount}" # ← 이게 있어야
status: "${nodes.fetch-order.response.body.status}"
⚠️ 조건식에서도 ${input.*} 를 그대로 씁니다¶
넘겨준 뒤에도 조건식에서 이름만 적으면 안 됩니다. 이음새는 조건식을 먼저 연산자로 자르고,
그다음 양쪽 피연산자를 각각 표현식으로 해석합니다. ${...} 가 없는 맨 이름은 해석할 표현식이
아니라서 "amount" 라는 글자 그대로의 문자열이 됩니다.
expression: "${input.amount} >= 100000" # ✅ 엣지가 넘긴 값을 읽습니다
expression: "amount >= 100000" # ❌ 문자열 "amount" 와 100000 을 비교합니다
❌ 쪽이 특히 위험한 이유: 에러가 나지 않습니다. 조건이 조용히 항상 false 가 되어 모든 실행이
otherwise 가지로 흘러갑니다. "조건 분기가 늘 같은 쪽으로만 간다"면 여기부터 확인하세요.
쓸 수 있는 연산자¶
| 연산자 | 뜻 | 예 |
|---|---|---|
== |
같다 | ${input.status} == 'paid' |
!= |
다르다 | ${input.status} != 'cancelled' |
> >= < <= |
크기 비교 | ${input.amount} >= 50000 |
contains |
포함한다 | ${input.message} contains '환불' |
startsWith |
로 시작한다 | ${input.orderId} startsWith 'ORD-' |
endsWith |
로 끝난다 | ${input.email} endsWith '@company.com' |
- 문자열 리터럴은 작은따옴표로 감쌉니다:
'paid' - 양쪽 다 숫자로 바뀌면 숫자 비교, 아니면 사전순 문자열 비교입니다.
- 값이 없을 수도 있는 자리에는 fallback 을 붙일 수 있습니다:
expression: "${input.status | \"대기\"} == '대기'"→ 05. 표현식으로 데이터 잇기
AI 답변으로 분기하기¶
contains는 AI 응답을 보고 갈라질 때 특히 유용합니다.
- id: classify
name: 문의 분류
type: CALL
integration: llm_chat
input:
apiContract: OPENAI_CHAT
model: gpt-4o-mini
apiKey: "${secrets.OPENAI_API_KEY}"
systemPrompt: "고객 문의를 딱 한 단어로 분류해라: 환불 / 배송 / 상품문의 / 기타. 다른 말은 하지 마라."
userPrompt: "${nodes.trigger.response.body.message}"
- id: route
name: 분류별 분기
type: CONDITIONAL
execution-info:
conditions:
- label: refund
expression: "${input.category} contains '환불'"
- label: delivery
expression: "${input.category} contains '배송'"
- label: other
otherwise: true
edges:
- from: classify
to: route
request:
data:
category: "${nodes.classify.response.body.content}"
- from: route
to: refund-flow
label: refund
- from: route
to: delivery-flow
label: delivery
- from: route
to: default-flow
label: other
AI가 딱 한 단어만 답하도록
systemPrompt에서 못 박아두는 게 요령입니다. 그래도 완벽하진 않으니otherwise가지는 꼭 두세요.
합류 — JOINT¶
갈라진 길이 다시 만나는 자리입니다. 여러 선이 들어올 수 있는 유일한 노드입니다.
설정할 게 아무것도 없습니다. type만 적으면 끝입니다.
하는 일¶
들어오는 모든 가지가 끝날 때까지 기다립니다. 전부 끝나면 다음 노드로 넘어갑니다. 두 API를 동시에 부르고 둘 다 온 다음에 처리하고 싶을 때 씁니다.
edges:
- from: trigger
to: fetch-orders
- from: trigger
to: fetch-stock
- from: fetch-orders
to: join
- from: fetch-stock
to: join
- from: join
to: summarize
⚠️ JOINT는 자기 출력이 없습니다¶
JOINT는 기다리기만 합니다. 값을 만들지 않습니다. 그 뒤 노드에서는 JOINT 앞의 노드를 직접 참조하세요.
# ❌
userPrompt: "${nodes.join.response.body.data}"
# ✅
userPrompt: |
주문: ${nodes.fetch-orders.response.body.data}
재고: ${nodes.fetch-stock.response.body.data}
반복 — LOOP_START / LOOP_END¶
배열을 받아 원소마다 같은 처리를 반복합니다.
- id: each-order
name: 주문별 반복 시작
type: LOOP_START
execution-info:
items: "${nodes.fetch-orders.response.body.data | raw}" # ← 반복할 배열
# ─── 여기부터 반복 본문 ───
- id: notify-each
name: 주문별 알림
type: CALL
integration: http_request
input:
uri: "https://api.example.com/notify"
method: POST
body:
orderId: "${item.orderId}" # ← 현재 원소
순번: "${index}" # ← 0부터 시작하는 회차
# ─── 반복 본문 끝 ───
- id: collect
name: 반복 집계
type: LOOP_END
execution-info:
loop-start: each-order # ← 짝이 되는 LOOP_START의 id
규칙¶
LOOP_START와LOOP_END는 반드시 짝입니다. 하나만 있으면 저장이 거부됩니다.items에는| raw를 꼭 붙이세요. 없으면 배열이 문자열이 되어 반복이 안 됩니다.- 반복 본문의 모든 길은
LOOP_END로 모여야 합니다. - 반복 안에
CONDITIONAL을 넣을 수 있습니다. 단LOOP_END앞에서JOINT로 다시 모아야 합니다. (아래 참고)
루프 안에서 쓰는 값¶
| 표현식 | 뜻 |
|---|---|
${item} |
현재 원소 전체 |
${item.필드} |
현재 원소의 특정 필드 |
${index} |
몇 번째인지 (0부터) |
이 값들은 LOOP_START와 LOOP_END 사이에서만 유효합니다.
반복이 겹쳐 있을 때 — ${loops.*}¶
${item} / ${index} 는 가장 안쪽 반복을 가리킵니다. 반복 안에 반복이 또 있을 때
바깥쪽 원소를 쓰려면 LOOP_START의 id로 층을 지목합니다.
${loops.each-order.item} # 바깥 루프 each-order 의 현재 원소
${loops.each-order.index} # 바깥 루프의 회차
${item} # 안쪽 루프의 현재 원소 (= ${loops.<안쪽>.item})
지금 위치가 그 루프 안이 아니면 실행 중 에러가 납니다.
반복 안에서 분기하기¶
CONDITIONAL을 루프 본문에 넣을 수 있습니다. 다만 갈라진 가지는 LOOP_END 앞에서 JOINT로
다시 모아야 합니다 — LOOP_END로 들어오는 선은 여전히 하나뿐입니다.
- id: each-order
type: LOOP_START
execution-info:
items: "${nodes.fetch-orders.response.body.data | raw}"
- id: route
name: 금액별 분기
type: CONDITIONAL
execution-info:
conditions:
- label: vip
expression: "${input.amount} >= 100000"
- label: normal
otherwise: true
- id: vip-handle
type: CALL
integration: http_request
input: { uri: "https://api.example.com/vip", method: POST, body: { id: "${item.orderId}" } }
- id: normal-handle
type: CALL
integration: http_request
input: { uri: "https://api.example.com/normal", method: POST, body: { id: "${item.orderId}" } }
- id: rejoin
name: 가지 합류
type: JOINT
- id: collect
type: LOOP_END
execution-info:
loop-start: each-order
edges:
- from: each-order
to: route
request:
data:
amount: "${item.amount}" # ← 조건이 볼 값을 엣지로 넘깁니다
- from: route
to: vip-handle
label: vip
- from: route
to: normal-handle
label: normal
- from: vip-handle
to: rejoin
- from: normal-handle
to: rejoin
- from: rejoin
to: collect
선택되지 않은 가지의 노드는 실행되지 않고 SKIPPED 상태로 실행 이력에 남습니다.
JOINT와 LOOP_END는 SKIPPED를 "끝난 것"으로 인정하므로 그 회차도 정상 종결됩니다.
분기가 꼭 필요한 게 아니라면 반복 전에 걸러내는 쪽이 더 간단합니다.
그래프 규칙 정리¶
저장할 때 검사되는 규칙입니다. 어기면 저장이 거부되고 무엇이 문제인지 알려줍니다.
| 규칙 | 내용 |
|---|---|
| 시작점 | ENTRYPOINT는 정확히 하나, 들어오는 선 0개 |
| 다중 입력 | 선이 2개 이상 들어올 수 있는 건 JOINT뿐 |
| 순환 금지 | 화살표를 따라가다 제자리로 돌아오면 안 됨 |
| 노드 id | 워크플로우 안에서 고유해야 함 |
| 엣지 참조 | from / to가 실제 존재하는 노드를 가리켜야 함 |
| 조건 분기 | 모든 label에 나가는 엣지가 있어야 함 |
| 루프 | LOOP_START와 LOOP_END가 짝을 이루고, 본문이 LOOP_END로 수렴해야 함 |
| 루프 안 분기 | CONDITIONAL 가능. 단 LOOP_END 앞에서 JOINT로 재수렴해야 함 |
| 크기 | 노드 최대 200개, YAML 최대 256,000자 |
다음 → 07. 연결 관리