Files
aio-01d4-stu/Last3Mon/66/LAB-03-BUILD-LOOP-PARALLEL.md
T
2026-10-15 22:45:16 +09:00

14 KiB

Lab 03 · 반복·병렬로 회의록 워크플로우 완성하기

  • 관련 강의: 3단계 · 반복·병렬 제어 구조와 완성
  • 소요 시간: 약 65분 (예제 1 12분 · 예제 2 10분 · 과제 40분)
  • 목표: 연습용 데이터로 펼치기 → 항목마다 처리 → 모으기와 병렬 → 합치기를 먼저 익히고, 이것을 Lab 02의 내 워크플로우에 옮겨 완성한다.
flowchart LR
  E1["예제 1<br/>펼치고 모으기<br/>(1 → 3 → 1)"] --> E2["예제 2<br/>두 길을 합치기<br/>(Merge)"] --> T["과제 40분<br/>내 회의록 워크플로우 완성<br/>+ 확인 필요 표시"]

예제 1 · 펼치고 모으기 (12분)

LLM 없이, 할 일 3건이 든 목록을 직접 만들어 연습합니다.

flowchart LR
  T(["Start"]) --> S["Sample actions<br/>Edit Fields"] --> SO["One item per action<br/>Split Out"] --> N["Normalize action<br/>Edit Fields"] --> AG["Collect actions<br/>Aggregate"]
  1. 새 워크플로우 66 Practice 2 · loop and merge, 첫 노드는 Manual Trigger(이름 Start).
  2. Edit Fields (Set) Sample actions: 필드 하나
Name Type Value (Expression 모드)
actions Array 아래 한 줄
{{ [ { task: '신규 고객 문의 응답 템플릿 정리', owner: '이준호', due: '금요일' }, { task: '서버 점검 일정 공지', owner: '김민지', due: '오늘' }, { task: '회의실 예약 규칙 개선', owner: '미정', due: '미정' } ] }}
  1. Split Out One item per action: Fields To Split Out actions
  2. Edit Fields (Set) Normalize action: 필드 네 개
Name Type Value (Expression 모드)
task String {{ $json.task }}
owner String {{ $json.owner }}
due String {{ $json.due }}
priority String {{ ['오늘', '내일', '긴급'].some(w => $json.due.includes(w)) ? '높음' : '보통' }}
  1. Aggregate Collect actions: Aggregate All Item Data (Into a Single List), Put Output in Field actions
  2. Execute workflow 후, 캔버스의 노드 사이 연결선 위에 표시되는 항목 수를 읽습니다.
노드 항목 수 보이는 것
Sample actions 1 actions 목록 하나 (3건)
One item per action 3 할 일 하나씩
Normalize action 3 + priority (서버 점검 일정 공지만 높음)
Collect actions 1 다시 actions 목록 하나

펼쳤으면(1 → 3) 반드시 모읍니다(3 → 1). 모으지 않으면 뒤의 노드가 할 일마다 따로 실행됩니다.

예제 2 · 두 길을 합치기 (10분)

같은 워크플로우에 두 번째 길을 하나 더 만들고 Merge로 합칩니다.

flowchart LR
  T(["Start"]) --> S["Sample actions"] --> X["... Collect actions"] --> J["Join results<br/>Merge"]
  T --> SS["Sample summary<br/>Edit Fields"] --> J
  1. Start 노드의 오른쪽 점에서 선을 하나 더 끌어 Edit Fields (Set) Sample summary 추가
Name Type Value
summary String 주간 회의에서 할 일 3건을 정했습니다. 서버 점검 공지는 오늘 안에 합니다.
  1. Merge Join results: Mode Combine, Combine By Position
    • Collect actions → Join results의 Input 1
    • Sample summary → Join results의 Input 2
  2. Execute workflow → Join results의 OUTPUT이 항목 1개이고 actions(3건)와 summary가 함께 들어 있는지 확인합니다.
{"actions":[ ...3건, priority 포함 ... ], "summary":"주간 회의에서 할 일 3건을 정했습니다. 서버 점검 공지는 오늘 안에 합니다."}
  1. Save 합니다. 이 워크플로우의 노드는 과제에서 복사해 붙여 씁니다.

막혔다면 n8n/workflows/lab03-examples-loop-merge-practice.json을 가져와 비교합니다.

과제 · 내 회의록 워크플로우 완성하기 (40분)

상황

팀장의 요청을 모두 담은 회의록 워크플로우를 완성합니다. 예제 1·2는 가짜 데이터였습니다. 이제 그 자리에 Lab 02의 진짜 LLM 결과를 넣습니다.

"회의록을 보내면 2문장 요약, 우선순위가 붙은 할 일 목록, 공유용 정리문을 한 번에 받고 싶어요. 할 일이 없는 회의면 할 일 없음으로 알려 주세요. 그리고 담당자나 기한이 정해지지 않은 할 일은 확인 필요로 표시하고 몇 건인지 알려 주세요." (Lab 01 과제에서 설계한 내용)

완성 그림

66 Meeting notes (my)(Lab 02)를 아래 모양으로 만듭니다. 굵은 테두리가 새로 넣을 노드입니다.

flowchart LR
  W(["Meeting webhook"]) --> S["Settings"] --> H{"Has notes?"}
  H -->|false| R0["Reject empty"]
  H -->|true| L1{{"LLM extract actions"}} --> P["Parse actions"] --> J["Join results"]
  H -->|true| L2{{"LLM summarize"}} --> K["Keep summary"] --> J
  J --> A{"Any actions?"}
  A -->|true| SO["One item per action"] --> N["Normalize action"] --> AG["Collect actions"] --> B["Build report"] --> R["Send result"]
  A -->|false| NA["No actions"] --> R
  classDef new stroke-width:3px
  class L2,K,J,A,SO,N,AG,B,NA new

기대 결과 (이 표가 정답 기준입니다)

요청 기대 결과
weekly.json route: 할 일 정리, count: 4, need_check: 1, summary(2문장 안팎), 3번째 할 일 priority: 높음, 4번째 할 일 check: true, report_text 4번째 줄 끝에 ※ 확인 필요
no-action.json route: 할 일 없음, count: 0, need_check: 0, summary, actions: []
empty.json HTTP 400 {"error":"notes is required"}

진행 순서 (시간은 기준)

각 단계를 마칠 때마다 Listen for test event → weekly.json 으로 실행해 OUTPUT을 확인하고 다음으로 갑니다.

1단계 · 요약 길을 만들고 합치기 (10분)

  • Parse actions → Send result 연결선을 지웁니다. (선 위에 마우스 → 휴지통)
  • LLM extract actions를 오른쪽 클릭 → Duplicate → 이름 LLM summarize. Has notes?의 true에서 선을 하나 더 끌어 연결하고, JSON 칸을 아래로 통째로 바꿉니다.
{{ JSON.stringify({ model: $('Settings').first().json.model, temperature: 0, messages: [ { role: 'system', content: "You summarize meeting notes. Reply only with the summary in Korean, at most 2 sentences. No English, no comments." }, { role: 'user', content: '다음 회의록을 요약하세요.\n\n회의록:\n' + $('Settings').first().json.notes } ] }) }}
  • 생각할 것: 요약은 LLM 답(choices[0].message.content)에서 꺼내야 합니다. 예제 2의 Sample summary처럼 summary 필드 하나를 만드는 Keep summary를 직접 만들어 보세요. 그다음 Join results(예제 2에서 복사)의 Input 1·2에 어느 노드를 연결하나요?
힌트
  • Keep summary: Edit Fields, summary(String) = {{ $json.choices[0].message.content.trim() }}
  • Parse actions → Input 1, Keep summary → Input 2. 합친 결과에 actions, count, summary가 함께 있으면 성공.

2단계 · 할 일이 있을 때와 없을 때 (5분)

  • Join results 다음에 If Any actions?를 넣습니다.
  • 생각할 것: 할 일이 있는지 무엇으로 알 수 있나요? (Parse actions가 만든 필드를 떠올려 보세요.)
힌트

{{ $json.count }} · Number → is greater than · 0

3단계 · 펼치고 모아서 정리문 만들기 (12분)

  • 예제 1·2의 워크플로우에서 One item per action, Normalize action, Collect actions 세 노드를 드래그로 함께 선택 → Ctrl+C, 내 워크플로우에서 Ctrl+V 합니다. Any actions?의 true에 연결합니다.
  • Collect actions 다음에 Code Build report를 넣고 아래 코드를 붙여 넣은 뒤 Send result에 연결합니다.
// Build the final result from the collected actions.
const settings = $('Settings').first().json;
const joined = $('Join results').first().json;
const actions = $input.first().json.actions;
const lines = actions.map((a, i) => `${i + 1}. [${a.priority}] ${a.task} - ${a.owner} (${a.due})`);
return [{ json: {
  title: settings.title,
  route: '할 일 정리',
  count: actions.length,
  summary: joined.summary,
  actions,
  report_text: `${settings.title}\n${joined.summary}\n\n${lines.join('\n')}`,
} }];
  • Any actions?의 false에 Edit Fields No actions를 넣고 Send result에 연결합니다.
  • 생각할 것: No actions의 결과는 할 일이 있을 때와 같은 필드여야 받는 쪽이 편합니다. Build report가 돌려주는 필드를 보고 No actions의 필드를 정해 보세요. summary는 어디서 가져오나요?
힌트
Name Type Value
title String {{ $('Settings').first().json.title }}
route String 할 일 없음
count Number 0
summary String {{ $json.summary }}
actions Array {{ [] }}

Any actions?의 false로 들어온 항목은 Join results의 결과이므로 $json.summary가 있습니다.

여기까지 weekly.json으로 count: 4와 정리문(report_text)이 나오면, no-action.json으로 할 일 없음도 확인합니다.

4단계 · 확인 필요 표시 (Lab 01 과제 구현) (8분)

Lab 01 과제에서 적은 내 설계를 펼쳐 놓고 만듭니다.

  • 생각할 것 ①: 할 일 하나마다 check(참/거짓)를 붙일 노드는 어디인가요? priority를 만든 방식을 참고해, "담당자가 미정이거나 기한이 미정이면 참"을 표현식으로 써 보세요.
  • 생각할 것 ②: need_check(확인 필요 건수)는 할 일을 다 모은 뒤에 셀 수 있습니다. Build report 코드의 어디에 무엇을 더하나요? No actions에는요?
  • (선택) report_text에서 확인이 필요한 줄 끝에 ※ 확인 필요를 붙여 보세요.
힌트 1 · check 필드

Normalize action에 필드를 하나 더합니다. Name check, Type Boolean, Value (Expression 모드)

{{ $json.owner === '미정' || $json.due === '미정' }}
힌트 2 · need_check와 표시

Build report의 count: actions.length, 아래에 한 줄

  need_check: actions.filter(a => a.check).length,

lines를 만드는 줄 끝의 (${a.due})` 를 (${a.due})${a.check ? ' ※ 확인 필요' : ''}` 로 바꾸면 표시가 붙습니다. No actions에는 need_check(Number) 0을 더합니다.

5단계 · 세 요청 확인과 게시 (5분)

cd ~/aio-01d4-stu/Last3Mon/66
curl -s -m 300 -w "  [HTTP %{http_code}]\n" -X POST http://localhost:5678/webhook-test/my-meeting-actions \
  -H 'Content-Type: application/json' -d @n8n/requests/weekly.json

요청마다 Listen for test event를 다시 누르고 no-action.json, empty.json도 보냅니다. 위 기대 결과 표와 모두 같으면 오른쪽 위 Publish 합니다.

실행 기록으로 검증: weekly.json 실행에서 Join results 1 → One item per action 4 → Normalize action 4 → Collect actions 1 → Build report 1 인지 소리 내어 읽어 봅니다.

Lab 02 과제(참석자)를 한 사람: Build report의 결과에 attendees가 없습니다. 왜 사라졌을까요? (Build report는 결과를 새로 만듭니다.) attendees: joined.attendees ?? [], 한 줄로 살려 보세요.

막혔을 때

증상 볼 곳
응답에 첫 할 일 하나만 옴 Normalize action에서 Send result로 바로 연결하지 않았나? Collect actions를 거쳐야 함
Build report에서 오류 Collect actions를 빼먹었거나, 노드 이름(Settings, Join results)이 다름
summary가 비어 있음 Keep summary가 Join results의 Input 2에 연결됐는지
no-action에서 HTTP 200인데 내용이 비어 있음 Any actions?의 false → No actions → Send result가 연결됐는지 (수업 준비 때 실측: false 길이 끊기면 오류 없이 빈 응답)
need_check가 4 (할 일 수와 같음) check의 Type이 String이면 "false"라는 글자가 되어 모두 참으로 셈 (실측). Type을 Boolean으로
  • 구조가 헷갈리면 n8n/workflows/01-meeting-actions-solution.json(확인 필요 표시 전까지의 정답)을 새 워크플로우로 가져와 비교합니다.

마무리 · Python에서 부르기 (8교시에 함께)

게시한 뒤 workflow-lab/platforms.json의 meeting → n8n → webhook 값을 my-meeting-actions로 바꾸고 부릅니다.

cd ~/aio-01d4-stu/Last3Mon/66/workflow-lab
uv run miniflow call meeting -i title="주간 회의" \
  -i notes="$(python3 -c "import json;print(json.load(open('data/meeting-notes.json'))['weekly']['notes'])")"
git checkout platforms.json   # 끝나면 되돌리기

코드는 그대로 두고 설정 한 줄만 바꿔 호출 대상이 내 워크플로우로 바뀝니다. n8n을 멈추면 같은 업무를 miniflow의 flows/08-meeting-actions.json이 이어받습니다.

확인

  • 예제 1: 항목 수 1 → 3 → 3 → 1을 확인했다.
  • 예제 2: Join results에서 actions와 summary가 한 항목으로 합쳐졌다.
  • 과제: 세 요청의 기대 결과를 모두 확인했다 (need_check: 1 포함).
  • 과제: 실행 기록에서 1 → 4 → 4 → 1 → 1을 확인했다.