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

9.9 KiB

Lab 02 · LLM 답을 데이터로 바꾸기 (입력 → LLM → 파싱)

  • 관련 강의: 2단계 · 데이터 파싱과 변환 노드
  • 소요 시간: 약 55분 (예제 1 15분 · 예제 2 15분 · 과제 25분)
  • 준비: 65일차 n8n 실행 (cd ~/aio-01d4-stu/Last3Mon/65/n8n && docker compose start), Windows 브라우저에서 http://localhost:5678
  • 목표: LLM의 답은 글자라는 것을 눈으로 확인하고, Code 노드로 목록으로 바꾼다. 그다음 뽑을 정보를 하나 늘려 본다.
flowchart LR
  E1["예제 1<br/>LLM 없이<br/>글자 → 목록"] --> E2["예제 2<br/>진짜 LLM이 붙은<br/>1부 실행"] --> T["과제<br/>참석자도 뽑기"]

노드 이름은 뒤의 표현식에서 그대로 쓰므로 안내와 똑같이 지어 주세요. 노드 이름은 노드를 열고 맨 위 제목을 눌러 바꿉니다.

예제 1 · LLM 없이 "글자 → 목록" (15분)

LLM을 부르기 전에, LLM이 돌려줄 법한 답을 직접 적어 넣고 파싱부터 연습합니다. 돈도 시간도 들지 않고 결과가 항상 같습니다.

flowchart LR
  T(["Start<br/>Manual Trigger"]) --> F["Fake LLM answer<br/>Edit Fields"] --> P["Parse actions<br/>Code"]
  1. 왼쪽 위 + (Create) → Workflow. 이름을 66 Practice 1 · parse text로 바꿉니다.
  2. Add first step에서 Manual Trigger(Trigger manually)를 추가하고 이름을 Start로 바꿉니다.
  3. 오른쪽 + → Edit Fields (Set), 이름 Fake LLM answer. Add Field로 필드 하나를 만듭니다.
Name Type Value (Fixed 모드 그대로)
content String 아래 한 줄
{"actions": [{"task": "신규 고객 문의 응답 템플릿 정리", "owner": "이준호", "due": "금요일"}, {"task": "회의실 예약 규칙 개선", "owner": "", "due": ""}]}
  1. 오른쪽 + → Code, 이름 Parse actions. Language JavaScript, Mode Run Once for All Items. 원래 있던 코드를 지우고 아래를 붙여 넣습니다.
// Parse the answer text (JSON written as text) into a clean list of action items.
const raw = String($input.first().json.content ?? '').trim().replace(/^```(json)?\s*|```$/g, '');
let data;
try {
  data = JSON.parse(raw);
} catch (error) {
  return [{ json: { actions: [], count: 0, error: 'answer is not valid JSON' } }];
}
const items = Array.isArray(data) ? data : (data.actions ?? []);
const actions = items
  .filter(item => item && String(item.task ?? '').trim())
  .map(item => ({
    task: String(item.task).trim(),
    owner: String(item.owner || '미정').trim() || '미정',
    due: String(item.due || '미정').trim() || '미정',
  }));
return [{ json: { actions, count: actions.length } }];
  1. 아래쪽 Execute workflow를 누르고 두 노드의 OUTPUT을 비교합니다. (오른쪽 위 Table / JSON / Schema 보기를 바꿔 가며 봅니다.)
노드 보이는 것
Fake LLM answer content 한 칸에 긴 글자 한 줄
Parse actions actions가 목록 2개로 펼쳐짐, count: 2, 두 번째 할 일의 owner·due가 미정
  1. 답을 일부러 망가뜨려 봅니다. Fake LLM answer의 값에서 맨 끝 } 하나를 지우고 다시 실행합니다.
결과 뜻
{"actions": [], "count": 0, "error": "answer is not valid JSON"} 망가진 답이 와도 같은 모양으로 끝나고, 이유(error)가 남음

확인한 뒤 지운 }를 다시 넣고 Save 합니다.

예제 2 · 진짜 LLM이 붙은 1부 실행하기 (15분)

이번에는 같은 파싱 앞에 Webhook과 LLM이 붙은 워크플로우를 가져와 실행합니다.

flowchart LR
  W(["Meeting webhook"]) --> S["Settings"] --> H{"Has notes?"}
  H -->|false| R0["Reject empty<br/>(400)"]
  H -->|true| L{{"LLM extract actions"}} --> P["Parse actions"] --> R["Send result"]
  1. + (Create) → Workflow → 오른쪽 위 ⋯ → Import from File... → Last3Mon/66/n8n/workflows/lab02-example2-meeting-part1.json
    • Windows 파일 선택 창 주소창: \\wsl$\Ubuntu\home\<사용자>\aio-01d4-stu\Last3Mon\66\n8n\workflows
  2. 이름이 66 Meeting notes (my)인지 확인하고 Save 합니다. (Lab 03에서 이 워크플로우를 이어서 완성합니다.)
  3. 노드 세 개를 열어 어디서 무엇을 하는지 확인합니다.
노드 볼 곳
Settings title, notes가 Webhook 입력($json.body)에서 꺼낸 값
LLM extract actions 아래로 내려 JSON 칸: ① schema(답의 모양) ② role: 'system'(지시문) ③ role: 'user'(회의록)
Parse actions 예제 1과 같은 코드. 첫 줄만 choices[0].message.content(LLM 답이 있는 곳)를 읽음
  1. Meeting webhook 노드를 열고 Listen for test event를 누른 뒤, WSL 터미널에서 보냅니다.
cd ~/aio-01d4-stu/Last3Mon/66
curl -s -m 300 -X POST http://localhost:5678/webhook-test/my-meeting-actions \
  -H 'Content-Type: application/json' -d @n8n/requests/weekly.json
  1. 결과와 캔버스의 OUTPUT을 확인합니다. (수업 준비 때 gpt-4.1-mini로 실제 실행한 결과)
{"actions":[{"task":"신규 고객 문의 응답 템플릿 정리","owner":"이준호","due":"금요일"},
 {"task":"VPN 장애 보고서 작성","owner":"박서연","due":"다음 주 월요일"},
 {"task":"서버 점검 일정 공지","owner":"김민지","due":"오늘"},
 {"task":"회의실 예약 규칙 개선 담당자 선정","owner":"미정","due":"10월 23일"}],"count":4}
노드 확인할 것
LLM extract actions choices[0].message.content가 따옴표로 둘러싸인 글자 (예제 1의 Fake LLM answer와 같은 모양)
Parse actions actions가 목록 4개, 4번째 owner가 미정 (due는 LLM이 "다음 회의는 10월 23일"을 보고 10월 23일로 채우는 경우가 많음)
  1. Listen을 다시 누르고 empty.json → HTTP 400 {"error":"notes is required"}, no-action.json → {"actions":[],"count":0} 도 확인합니다. (Test URL은 한 번에 한 요청만 받습니다.)

과제 · 참석자도 뽑기 (25분)

팀장이 요청했습니다.

"할 일과 함께 회의 참석자 명단도 받아 보고 싶어요."

예제 2의 66 Meeting notes (my)를 고쳐서, 결과에 attendees(참석자 이름 목록)가 들어가게 만드세요.

요청 기대 결과
weekly.json 할 일 4건과 함께 "attendees":["김민지","이준호","박서연"]
no-action.json "attendees":[] (참석자가 적혀 있지 않음)
empty.json 그대로 HTTP 400

먼저 생각해 보기 (3분): LLM에게 무엇을 더 알려 줘야 하나요? LLM 답이 바뀌면 그다음 노드는 무엇을 더 꺼내야 하나요? 고칠 곳은 세 군데입니다. 활동지나 메모장에 세 곳을 먼저 적은 뒤 고칩니다.

힌트 1 · 고칠 세 곳
곳 노드 무엇을
① 답의 모양 LLM extract actions의 schema properties에 attendees(글자 목록) 추가, required에도 추가
② 지시문 LLM extract actions의 role: 'system' 문장 참석자 이름도 attendees에 넣으라고 한 문장 추가
③ 꺼내기 Parse actions의 마지막 return attendees도 돌려주기 (없으면 [])
힌트 2 · ① schema에 넣을 한 줄

actions: { ... } }, 줄 바로 아래에 넣습니다.

      attendees: { type: 'array', items: { type: 'string' } },

그리고 required: ['actions'], 를 required: ['actions', 'attendees'], 로 바꿉니다.

힌트 3 · ② 지시문과 ③ 코드

② "... when there is no action item. 뒤, 닫는 따옴표 " 앞에 붙입니다.

 Also list the attendee names in \"attendees\" (use [] when not stated).

③ 마지막 return 줄을 아래 두 줄로 바꿉니다.

const attendees = Array.isArray(data.attendees) ? data.attendees.map(name => String(name).trim()).filter(Boolean) : [];
return [{ json: { actions, count: actions.length, attendees } }];

테스트: Listen for test event → weekly.json, 다시 Listen → no-action.json, 다시 Listen → empty.json. 세 결과를 위 표와 비교합니다.

참석자를 넣었더니 할 일이 3건으로 줄었다면 한 번 더 보내 보세요. 수업 준비 때 8번 중 1번 이런 일이 있었습니다. 뽑을 것을 늘리면 원래 뽑던 결과도 흔들릴 수 있으니, 고친 뒤에는 원래 기대 결과(할 일 4건)도 다시 확인합니다.

더 생각해 보기 (빨리 끝난 사람)

  • ①만 고치고 ②를 안 고치면? ③을 안 고치면? 하나씩 되돌려 실행해 보고, 어느 노드의 OUTPUT에서 attendees가 사라지는지 찾아보세요.
  • 참석자 명단은 LLM이 찾는 일인가요, 규칙이 하는 일인가요?

막혔을 때

  • invalid syntax: JSON 칸에서 }와 }가 붙은 곳이 있는지 봅니다. 표현식 {{ }} 안에서 }}가 붙으면 표현식이 거기서 끝납니다. } }처럼 띄어 씁니다.
  • 401: LLM extract actions의 Credential이 OpenAI (.env)인지 확인합니다. 없다면 65일차 SETUP 6단계(register-credentials.sh)를 다시 실행합니다.
  • 고치다가 망가졌다면: 워크플로우를 지우고 예제 2의 파일을 다시 가져옵니다.
  • Test URL 404: Listen for test event를 다시 누릅니다.

확인

  • 예제 1: Fake LLM answer(글자)와 Parse actions(목록)의 차이를 OUTPUT으로 확인했다. 망가진 답에서 error를 봤다.
  • 예제 2: weekly 4건, empty 400, no-action 0건을 확인했다.
  • 과제: weekly에서 attendees 3명, no-action에서 []를 받았다.