콘텐츠로 이동

06. 흐름 제어

일직선으로 흐르는 자동화만으로 부족할 때 씁니다.

하고 싶은 것 쓸 노드
값에 따라 다른 길로 가기 CONDITIONAL
갈라진 길을 다시 합치기 JOINT
배열을 하나씩 처리하기 LOOP_START / LOOP_END

조건 분기 — CONDITIONAL

값을 보고 갈 길을 고릅니다.

- id: route
  name: 금액별 분기
  type: CONDITIONAL
  execution-info:
    conditions:
      - label: vip
        expression: "amount >= 100000"
      - label: normal
        expression: "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

핵심 규칙 세 가지입니다.

  1. 조건은 위에서부터 검사합니다. 먼저 맞는 것 하나만 실행됩니다.
  2. label마다 나가는 엣지가 하나씩 있어야 합니다. 없으면 저장이 거부됩니다.
  3. 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}"

이렇게 넘긴 뒤 조건식에서는 이름만 씁니다.

expression: "amount >= 100000"    # ✅ ${} 없이 이름만
expression: "${input.amount} >= 100000"   # ❌

쓸 수 있는 연산자

연산자
== 같다 status == 'paid'
!= 다르다 status != 'cancelled'
> >= < <= 크기 비교 amount >= 50000
contains 포함한다 message contains '환불'
startsWith 로 시작한다 orderId startsWith 'ORD-'
endsWith 로 끝난다 email endsWith '@company.com'
  • 문자열은 작은따옴표로 감쌉니다: 'paid'
  • 양쪽 다 숫자로 바뀌면 숫자 비교, 아니면 사전순 문자열 비교입니다.

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: "category contains '환불'"
      - label: delivery
        expression: "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

갈라진 길이 다시 만나는 자리입니다. 여러 선이 들어올 수 있는 유일한 노드입니다.

- id: join
  name: 합류
  type: JOINT

설정할 게 아무것도 없습니다. type만 적으면 끝입니다.

하는 일

들어오는 모든 가지가 끝날 때까지 기다립니다. 전부 끝나면 다음 노드로 넘어갑니다. 두 API를 동시에 부르고 둘 다 온 다음에 처리하고 싶을 때 씁니다.

            ┌─→ [주문 조회] ─┐
[트리거] ─→ ┤                ├─→ [JOINT] ─→ [둘 다 써서 요약]
            └─→ [재고 조회] ─┘
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

규칙

  1. LOOP_STARTLOOP_END는 반드시 짝입니다. 하나만 있으면 저장이 거부됩니다.
  2. items에는 | raw 를 꼭 붙이세요. 없으면 배열이 문자열이 되어 반복이 안 됩니다.
  3. 반복 본문의 모든 길은 LOOP_END로 모여야 합니다.
  4. 반복 안에는 CONDITIONAL을 넣을 수 없습니다. (아래 참고)

루프 안에서 쓰는 값

표현식
${item} 현재 원소 전체
${item.필드} 현재 원소의 특정 필드
${index} 몇 번째인지 (0부터)

이 값들은 LOOP_STARTLOOP_END 사이에서만 유효합니다.

반복 안에서 분기하고 싶다면

CONDITIONAL은 루프 안에 못 넣습니다. 가지가 하나 잘리면 LOOP_END로 모일 길이 없어지기 때문입니다.

대안 두 가지입니다.

대안 1 — 반복 전에 미리 걸러내기 (대부분 이걸로 충분합니다)

- id: filter
  name: 처리 대상만 추리기
  type: CALL
  integration: transform_jmespath
  input:
    expression: "{targets: orders[?status == `paid`]}"
    data: "${nodes.fetch-orders.response.body | raw}"

- id: each
  type: LOOP_START
  execution-info:
    items: "${nodes.filter.response.body.targets | raw}"

대안 2 — 반복 밖에서 분기하기

집계 결과를 LOOP_END 다음에 CONDITIONAL로 판단합니다.


그래프 규칙 정리

저장할 때 검사되는 규칙입니다. 어기면 저장이 거부되고 무엇이 문제인지 알려줍니다.

규칙 내용
시작점 ENTRYPOINT정확히 하나, 들어오는 선 0개
다중 입력 선이 2개 이상 들어올 수 있는 건 JOINT
순환 금지 화살표를 따라가다 제자리로 돌아오면 안 됨
노드 id 워크플로우 안에서 고유해야
엣지 참조 from / to가 실제 존재하는 노드를 가리켜야 함
조건 분기 모든 label에 나가는 엣지가 있어야 함
루프 LOOP_STARTLOOP_END가 짝을 이루고, 본문이 LOOP_END로 수렴해야 함
루프 안 분기 루프 본문에 CONDITIONAL 금지
크기 노드 최대 200개, YAML 최대 256,000자

다음07. 연결 관리