66일차 새 방식(CLI 코딩 에이전트로 n8n)으로 교체, 67일차 교안·실습 추가
This commit is contained in:
1 parent
d7dc9dd68a
commit
bf55eaf772
120 files changed
+7398
-5988
No files matched your search
+130
-241
@@ -1,305 +1,194 @@
|
||||
# 66일차 개요 · 단일 업무 자동화 워크플로우 구현
|
||||
# 66일차 개요 · 바이브 코딩으로 만드는 단일 업무 자동화 워크플로우
|
||||
|
||||
> 노코드 멀티 에이전트 워크플로우 설계 및 운영 · 2026-10-16
|
||||
> 15장 · 각 장은 가로줄(`---`)로 나뉩니다. 1교시에는 1~3장과 14장만 보고, 나머지 장은 각 단계를 설명할 때 함께 띄웁니다.
|
||||
> AI 워크플로우 설계 및 구현 · 2026-10-16
|
||||
> 13장 · 각 장은 가로줄(`---`)로 나뉩니다. 1교시에는 1~6장을 보고, 나머지 장은 각 단계를 설명할 때 함께 띄웁니다.
|
||||
|
||||
| 장 | 범위 |
|
||||
| --- | --- |
|
||||
| 1~3 | 표지 · 오늘의 목표 · 시간표 |
|
||||
| 4~7 | 1단계 · 업무 설계와 데이터 흐름 |
|
||||
| 8~10 | 2단계 · 파싱과 변환 노드 |
|
||||
| 11~13 | 3단계 · 반복·병렬과 완성 |
|
||||
| 14~15 | 실습 · 퀴즈와 예고 |
|
||||
| 1~6 | 표지 · 목표 · 시간표 · 오늘부터 바뀌는 것 · 일하는 순서 · 오늘의 그림 |
|
||||
| 7~8 | 1단계 · 결과 먼저 |
|
||||
| 9~10 | 2단계 · JSON과 파싱 |
|
||||
| 11 | 3단계 · 항목과 갈래 |
|
||||
| 12~13 | 7교시 바이브 코딩 · 정리와 예고 |
|
||||
|
||||
---
|
||||
|
||||
## 1 / 15 · 표지
|
||||
## 1 / 13 · 표지
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
M["회의록<br/>1건"] --> A1["할 일"] & A2["할 일"] & A3["할 일"] & A4["할 일"]
|
||||
A1 & A2 & A3 & A4 --> R["결과<br/>1건"]
|
||||
classDef d fill:#0F3D3E,stroke:#0F3D3E,color:#fff
|
||||
classDef o fill:#F2A541,stroke:#F2A541,color:#fff
|
||||
N(["회의록"]) --> S{{"요약"}} & E{{"할 일 뽑기"}}
|
||||
S & E --> P["읽기 · 하나씩 · 모으기"] --> R(["할 일 목록<br/>확인 필요"])
|
||||
classDef g fill:#8A9BA0,stroke:#8A9BA0,color:#fff
|
||||
classDef t fill:#0F7C80,stroke:#0F7C80,color:#fff
|
||||
class M d
|
||||
class A1,A2,A3,A4 o
|
||||
class R t
|
||||
classDef o fill:#F2A541,stroke:#F2A541,color:#fff
|
||||
classDef d fill:#0F3D3E,stroke:#0F3D3E,color:#fff
|
||||
class N g
|
||||
class S,E t
|
||||
class P o
|
||||
class R d
|
||||
```
|
||||
|
||||
**66일차**
|
||||
|
||||
# 단일 업무 자동화 워크플로우 구현
|
||||
# 바이브 코딩으로 만드는 단일 업무 자동화 워크플로우
|
||||
|
||||
쉬운 예제 2개 → 고쳐 보는 과제 · 데이터 파싱·변환·집계 · 반복과 병렬
|
||||
결과 먼저 · 요청문 · JSON과 파싱 · 1 → N → 1 · 두 갈래 · 테스트
|
||||
|
||||
<sub>노코드 멀티 에이전트 워크플로우 설계 및 운영 · 2026-10-16</sub>
|
||||
<sub>AI 워크플로우 설계 및 구현 · 2026-10-16</sub>
|
||||
|
||||
---
|
||||
|
||||
## 2 / 15 · 오늘 할 수 있게 될 세 가지
|
||||
## 2 / 13 · 오늘 할 수 있게 될 세 가지
|
||||
|
||||
| | 목표 | 내용 |
|
||||
| :-: | --- | --- |
|
||||
| **①** | **업무 하나를 노드와 데이터로 설계한다** | 목적·입출력·성공 기준 · TCA → 노드 목록 · 연결선마다 항목 수와 필드 · 제어 구조 고르기 |
|
||||
| **②** | **LLM의 답을 데이터로 바꾼다** | JSON 스키마 출력 · 문자열 파싱 · 검증과 기본값 · 실패 이유 남기기 · Edit Fields 변환 |
|
||||
| **③** | **반복과 병렬로 워크플로우를 완성한다** | Split Out → Aggregate · Fork-Join(Merge) · 같은 결과 형식 · Python 호출 |
|
||||
| | 오늘 할 수 있게 될 것 |
|
||||
| :-: | --- |
|
||||
| **①** | 결과를 먼저 정하고, 노드를 끌어다 놓지 않고 **요청문**으로 n8n 워크플로우를 만들어 **테스트**로 확인한다 |
|
||||
| **②** | LLM에게 **정해진 JSON**으로 받고, 코드로 읽고, 빈칸을 지어내지 않고 채운다 |
|
||||
| **③** | 목록을 **하나씩** 다루고 다시 모으며(1 → N → 1), 두 갈래로 나누고, 어려운 테스트로 완성한다 |
|
||||
|
||||
---
|
||||
|
||||
## 3 / 15 · 오늘의 시간표
|
||||
## 3 / 13 · 오늘의 시간표
|
||||
|
||||
| ① 1~2교시 | ② 3~4교시 | ③ 5~6교시 | ④ 7~8교시 |
|
||||
| --- | --- | --- | --- |
|
||||
| **1단계** | **2단계** | **3단계** | **정리** |
|
||||
| 업무 설계와 데이터 흐름 | 파싱과 변환 노드 | 반복·병렬과 완성 | 퀴즈 A·B·C · 오늘의 정리·예고 |
|
||||
| Lab 01 예제 2 + 과제 | Lab 02 예제 2 + 과제 | Lab 03 예제 2 + **과제 40분** | 퀴즈 20분 · Python 호출 |
|
||||
| **1단계** | **2단계** | **3단계** | **바이브 코딩** |
|
||||
| 바이브 코딩 n8n · 결과 먼저 | JSON · 파싱 · 변환 | 항목 · 갈래 · 검증 | `turl` |
|
||||
| Lab 01 요약 | Lab 02 할 일 · 참석자 | Lab 03 확인 필요 · 어려운 회의록 | 만들기 · 발표 · 정리 |
|
||||
|
||||
> 09:00 시작 · 50분 수업 10분 휴식 · 12:00~13:00 점심
|
||||
> 09:00 시작 · 50분 수업 10분 휴식 · 4교시 뒤 13:00~14:00 점심
|
||||
|
||||
---
|
||||
|
||||
## 4 / 15 · 오늘의 업무: 회의록 → 할 일 정리
|
||||
## 4 / 13 · 오늘부터 바뀌는 것
|
||||
|
||||
| 지금 방식 | 자동화 후 |
|
||||
| --- | --- |
|
||||
| 회의록을 읽고 할 일·담당자·기한을 직접 찾음 | 회의록을 보내면 요약·할 일 목록·정리문 |
|
||||
| 빠진 담당자·기한을 놓치기 쉬움 | 빠진 값은 `미정`으로 바로 보임 |
|
||||
| 급한 일이 묻힘 | 오늘·내일 마감은 우선순위 `높음` |
|
||||
| 일 | 64·65일차 | 오늘부터 |
|
||||
| --- | --- | --- |
|
||||
| 무엇을 만들지 정하기 | 나 | **나** (결과 · 요청문) |
|
||||
| 노드 고르기 · 설정 · 연결 | 나 (마우스) | CLI 코딩 에이전트 |
|
||||
| 형식 오류 찾기 | 실행해 보고 빨간 표시 | 에이전트 + `flow.py check` |
|
||||
| 잘 되는지 확인 | 눈으로 OUTPUT 보기 | **나** (`flow.py test`) |
|
||||
|
||||
| 범위를 좁히는 세 줄 | 오늘의 답 |
|
||||
| --- | --- |
|
||||
| **목적 한 문장** | 회의록을 받으면 할 일 목록과 공유용 정리문을 돌려준다 |
|
||||
| **입력과 출력** | `{title, notes}` → `{title, route, count, summary, actions, report_text}` |
|
||||
| **성공 기준** | `weekly`에서 할 일 4건, 빠진 값은 `미정`, 오늘 마감은 `높음` |
|
||||
|
||||
> **단일 업무로 좁히기** 오늘은 회의록 → 할 일 정리 → 응답까지. 알림·일정 등록은 나중에 확장
|
||||
> 손으로 하던 일은 에이전트에게, **무엇이 맞는 결과인지 정하는 일**은 나에게.
|
||||
|
||||
---
|
||||
|
||||
## 5 / 15 · 예시 데이터로 결과를 먼저 그려 보기
|
||||
## 5 / 13 · 일하는 순서 (터미널 두 개)
|
||||
|
||||
| # | 할 일 | 담당자 | 기한 | 우선순위 | 누가 정하나 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | 신규 고객 문의 응답 템플릿 정리 | 이준호 | 금요일 | 보통 | 찾기는 **LLM** |
|
||||
| 2 | VPN 장애 보고서 작성 | 박서연 | 다음 주 월요일 | 보통 | |
|
||||
| 3 | 서버 점검 일정 공지 | 김민지 | 오늘 | **높음** | 우선순위는 **규칙** |
|
||||
| 4 | 회의실 예약 규칙 개선 담당자 선정 | **미정** | 10월 23일 | 보통 | 빈 값 채우기는 **규칙** (기한은 LLM이 "다음 회의"에서 추론) |
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant 나
|
||||
participant 에이전트 as CLI 에이전트 (터미널 ①)
|
||||
participant 도구 as flow.py (터미널 ②)
|
||||
participant n8n
|
||||
나->>에이전트: 요청문
|
||||
에이전트->>에이전트: my-work/meeting.json 작성 · check
|
||||
에이전트-->>나: 바꾼 것 + 실행할 명령
|
||||
나->>도구: push
|
||||
도구->>n8n: 가져오기 · 게시
|
||||
나->>도구: test
|
||||
도구-->>나: ✔/✘ · Passed x/y · runs/보고서
|
||||
```
|
||||
|
||||
> **LLM은 찾기만, 채우기·우선순위·정리문은 규칙** · 결과 형식(JSON)은 노드를 만들기 전에 약속한다 · 할 일이 없어도 같은 필드(`route: 할 일 없음`, `count: 0`, `actions: []`)
|
||||
- 에이전트가 읽는 규칙 파일: `AGENTS.md` (Claude Code는 `CLAUDE.md` → `@AGENTS.md`)
|
||||
|
||||
---
|
||||
|
||||
## 6 / 15 · 상자(항목)가 몇 개 지나가나 · 1 → 4 → 1
|
||||
## 6 / 13 · 오늘의 그림
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["회의록<br/>1개"] -->|"LLM + 파싱"| B["1개<br/>안에 actions [4]"] -->|"Split Out<br/>1 → 4"| C["할 일<br/>4개"] -->|"항목마다 정리"| D["정리된 할 일<br/>4개"] -->|"Aggregate<br/>4 → 1"| E["결과<br/>1개"]
|
||||
classDef d fill:#0F3D3E,stroke:#0F3D3E,color:#fff
|
||||
classDef t fill:#0F7C80,stroke:#0F7C80,color:#fff
|
||||
classDef o fill:#F2A541,stroke:#F2A541,color:#fff
|
||||
classDef m fill:#5B8E7D,stroke:#5B8E7D,color:#fff
|
||||
class A d
|
||||
class B,E t
|
||||
class C o
|
||||
class D m
|
||||
W(["title · notes"]) --> H{"Has notes?"} --> S{{"Summarize"}} & E{{"Extract actions"}} --> J["Join"] --> P["Parse"] --> A{"Any?"}
|
||||
A -->|예| SO["하나씩"] --> M["확인 필요"] --> C["모으기"] --> R(["결과"])
|
||||
A -->|아니오| N["No actions"] --> R
|
||||
```
|
||||
|
||||
| 노드 | 상자 수 |
|
||||
| --- | --- |
|
||||
| Edit Fields · **If** · Switch | **그대로** (If는 한쪽 길로 보낼 뿐) |
|
||||
| **Split Out** | **1 → N** (목록을 펼침) |
|
||||
| **Aggregate** | **N → 1** (다시 모음) |
|
||||
| Merge | 두 길 → 하나 |
|
||||
|
||||
> **Lab 01 예제 1** 어제 흐름은 모두 1 · **예제 2** 파싱 직후는 아직 1 (목록이 상자 하나에 들어 있음) → Split Out 뒤에 4
|
||||
> 파일 하나(`my-meeting`)를 Lab 01 → 02 → 03으로 키워 갑니다.
|
||||
|
||||
---
|
||||
|
||||
## 7 / 15 · 제어 구조 고르기 · 요청이 바뀌면
|
||||
## 7 / 13 · 1단계 · 결과 먼저
|
||||
|
||||
| 조건 분기 | 반복 | 병렬 (Fork-Join) |
|
||||
| :-: | :-: | :-: |
|
||||
| **입력에 따라 다른 결과** | **목록의 항목마다 같은 처리** | **서로 결과가 필요 없는 일** |
|
||||
| 내용 없음 → 거절<br/>할 일 없음 → 다른 결과 | 할 일마다 우선순위 | 할 일 찾기와 요약 |
|
||||
| If · Switch | Split Out → … → Aggregate | 가지 나누기 + Merge |
|
||||
```json
|
||||
{"title": "10월 16일 주간 회의", "count": 4,
|
||||
"actions": [{"owner": "이준호", "task": "신규 고객 문의 응답 템플릿 정리", "due": "금요일까지", "needs_check": false},
|
||||
{"owner": "미정", "task": "회의실 예약 규칙 개선", "due": "미정", "needs_check": true}],
|
||||
"needs_check_count": 1}
|
||||
```
|
||||
|
||||
| 요청이 바뀌면 묻는 세 질문 | 고칠 곳 |
|
||||
| --- | --- |
|
||||
| 할 일 **하나마다** 정하는 값? | Split Out과 Aggregate **사이** (예: `priority`) |
|
||||
| **모두 모은 뒤**에야 아는 값? | Aggregate **뒤** (예: `count`) |
|
||||
| 결과 필드가 늘었나? | **모든 길**의 끝 |
|
||||
|
||||
> **Lab 01 과제** "담당자·기한이 없으면 확인 필요, 몇 건인지도" → 오후 Lab 03 과제에서 실제로 만든다
|
||||
> 내가 쓴 결과가 **테스트의 기대 결과**가 됩니다. 결과를 정하지 않으면 에이전트가 만든 것이 맞는지 판단할 수 없습니다.
|
||||
|
||||
---
|
||||
|
||||
## 8 / 15 · LLM의 답은 글자다 → 파싱
|
||||
## 8 / 13 · 1단계 · 요청문 다섯 가지 (수업 준비 측정)
|
||||
|
||||
| 넣을 것 | 예 |
|
||||
| --- | --- |
|
||||
| ① 파일 · ② 입력 · ③ 처리 · ④ 결과 모양 · ⑤ 기대 결과 | `my-work/meeting.json` · POST `my-meeting` · 한 문장 요약 · `{title, summary}` · `cases/lab01.json` |
|
||||
|
||||
| 빈 회의록 (`empty.json`) | 결과 |
|
||||
| --- | --- |
|
||||
| 입력 확인 없이 | **200** "회의 내용이 없으므로 …" (LLM을 불러 쓸모없는 답) |
|
||||
| LLM 앞에서 확인 | 400, 0.1초 |
|
||||
|
||||
---
|
||||
|
||||
## 9 / 13 · 2단계 · LLM의 답은 글자
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| 지시문 | "JSON만, 코드 블록 없이: `{"actions": [{"owner", "task", "due"}]}`" |
|
||||
| n8n이 받는 것 | **글자** (` ```json … ``` `가 붙기도) |
|
||||
| Code에서 | 앞뒤를 지우고 `JSON.parse`, 실패하면 빈 목록, 빈칸은 `미정` |
|
||||
|
||||
수업 준비: `lab02` 6/6 (할 일 4개, 할 일 없는 회의 0개)
|
||||
|
||||
---
|
||||
|
||||
## 10 / 13 · 2단계 · 하나 바꾸면 다른 것도 바뀐다 (수업 준비 때 실제로)
|
||||
|
||||
| 지시문 | 참석자 | 할 일 |
|
||||
| --- | --- | --- |
|
||||
| `attendees`만 더함 | ✔ 3명 | **3개** (담당 미정 할 일이 빠짐) |
|
||||
| "담당자가 아직 없는 할 일도 넣을 것" 추가 | ✔ | 4개 |
|
||||
|
||||
> 고친 곳과 상관없는 결과가 바뀌었습니다. **고칠 때마다 원래 테스트도 다시.**
|
||||
|
||||
---
|
||||
|
||||
## 11 / 13 · 3단계 · 1 → N → 1, 그리고 두 갈래
|
||||
|
||||
| | 결과 (수업 준비, 1회) |
|
||||
| --- | --- |
|
||||
| 펼치기 → 항목마다 확인 필요 → 모으기 | `lab03` 5/5, 확인 필요 1개 |
|
||||
| 할 일 0개면 펼치기 전에 따로 | 할 일 없는 회의도 끝까지 답 |
|
||||
| 요약 ∥ 할 일 두 갈래 + Merge | `lab03` 5/5, 주간 회의 4.0초 → 4.2초 (**빨라지지 않음**) |
|
||||
| 어려운 회의록 7개 | 예제 6/7 → 최종 7/7 |
|
||||
|
||||
> 두 갈래의 값은 속도가 아니라 **설계**입니다. n8n 기본 실행은 갈래를 하나씩 돕니다.
|
||||
|
||||
---
|
||||
|
||||
## 12 / 13 · 7교시 · 바이브 코딩 `turl`
|
||||
|
||||
```text
|
||||
Fake LLM answer content: "{\"actions\": [{\"task\": \"...\", \"owner\": \"\", ...}]}" ← 글자 한 줄
|
||||
|
||||
Parse actions { actions: [ {task, owner: "미정", due: "미정"}, ... ], count: 2 } ← 목록
|
||||
$ turl
|
||||
[POST] [http://localhost:5678/webhook/my-meeting] [보내기]
|
||||
Body: requests/weekly.json → 200 · x.xs · {"count": 4, …}
|
||||
```
|
||||
|
||||
| ① 꺼내기 | ② 글자 → 데이터 | ③ 빈 할 일 버리기 | ④ 빈 값은 `미정` |
|
||||
| :-: | :-: | :-: | :-: |
|
||||
| `content` (진짜 LLM은 `choices[0].message.content`) | 실패하면 빈 목록 + `error` | | |
|
||||
|
||||
> **Lab 02 예제 1** LLM 없이 가짜 답으로 파싱 연습 · 끝 `}`를 지우면 `error: answer is not valid JSON` → 실패해도 **같은 모양**, 이유는 남긴다
|
||||
- curl을 TUI로: 요청 만들기 · 보내기 · 응답 보기 · 기록 · curl 명령으로 복사
|
||||
- 오늘 만든 `my-meeting`을 이 도구로 불러 봅니다
|
||||
|
||||
---
|
||||
|
||||
## 9 / 15 · 답의 모양 알려 주기 · 하나 더 뽑기
|
||||
## 13 / 13 · 정리와 67일차 예고
|
||||
|
||||
| LLM 노드 JSON 칸 | 하는 일 | 비유 |
|
||||
| --- | --- | --- |
|
||||
| ① 스키마 `response_format` | 답의 **모양** | 빈칸이 정해진 답안지 |
|
||||
| ② 지시문 `system` | 무엇을 찾을지, 없으면 `미정` | 문제 설명 |
|
||||
| ③ 회의록 `user` | 읽을 글 | 지문 |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["① 스키마에 칸 추가"] --> B["② 지시문에 한 문장"] --> L{{"LLM 답<br/>(글자)"}} --> C["③ 파싱 코드 return"] --> R["결과"]
|
||||
classDef t fill:#0F7C80,stroke:#0F7C80,color:#fff
|
||||
classDef o fill:#F2A541,stroke:#F2A541,color:#fff
|
||||
class A,B,C t
|
||||
class L o
|
||||
```
|
||||
|
||||
> **스키마는 모양만, 내용은 보장 안 함** · **Lab 02 과제** 참석자도 뽑기 = 세 곳 고치기 (③을 빠뜨리면 LLM 답에는 있어도 결과에서 사라짐) · `}}`가 붙으면 `invalid syntax` → `} }`
|
||||
|
||||
---
|
||||
|
||||
## 10 / 15 · Edit Fields와 만들고 확인하는 순서
|
||||
|
||||
| 하고 싶은 일 | Edit Fields 표현식 |
|
||||
| 오늘 | 한 줄 |
|
||||
| --- | --- |
|
||||
| 웹훅 입력 꺼내기 | `{{ $json.body.notes }}` |
|
||||
| 비었으면 기본값 | `{{ $json.body.title \|\| '제목 없음' }}` |
|
||||
| 여러 단어 중 하나라도 포함 | `{{ ['오늘','내일','긴급'].some(w => $json.due.includes(w)) }}` |
|
||||
| 1단계 | 결과 먼저, 요청문 다섯 가지, LLM 앞에서 입력 확인 |
|
||||
| 2단계 | LLM의 답은 글자 → 코드로 읽기, 빈칸은 코드가, 고치면 다시 전부 재기 |
|
||||
| 3단계 | 1 → N → 1, 빈 목록 따로, 두 갈래는 설계, 어려운 테스트로 완성 |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["노드 하나 추가"] --> B["이름 짓기"] --> C["설정"] --> D["실행"] --> E["OUTPUT 확인"]
|
||||
E -->|"다음 노드"| A
|
||||
classDef t fill:#0F7C80,stroke:#0F7C80,color:#fff
|
||||
classDef o fill:#F2A541,stroke:#F2A541,color:#fff
|
||||
class A,B,C t
|
||||
class D,E o
|
||||
```
|
||||
|
||||
> `{{ }}` 값은 **Expression** 모드 · 같은 요청 파일(`weekly`·`no-action`·`empty`)로 매번 확인 · Test URL은 요청마다 Listen 다시
|
||||
|
||||
---
|
||||
|
||||
## 11 / 15 · 반복과 병렬 · Lab 03 예제 1·2
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
T(["Start"]) --> S["Sample actions<br/>[3]"] -->|"1 → 3"| SO["Split Out"] --> N["Normalize<br/>3번"] -->|"3 → 1"| AG["Aggregate"] -->|"Input 1"| J["Merge<br/>Position"]
|
||||
T --> SS["Sample summary"] -->|"Input 2"| J
|
||||
J --> O["{actions, summary}<br/>1개"]
|
||||
classDef o fill:#F2A541,stroke:#F2A541,color:#fff
|
||||
classDef m fill:#5B8E7D,stroke:#5B8E7D,color:#fff
|
||||
classDef p fill:#7A6FB0,stroke:#7A6FB0,color:#fff
|
||||
class S,SS o
|
||||
class SO,N,AG m
|
||||
class J p
|
||||
```
|
||||
|
||||
| 펼쳤으면 반드시 모은다 (수업 전 실측) | 결과 |
|
||||
| --- | --- |
|
||||
| 모으지 않고 바로 응답 | 200, **첫 할 일 하나만** (조용히 틀림) |
|
||||
| 모으지 않고 `Build report` | 500 |
|
||||
|
||||
> Merge는 **두 입력이 모두 와야** 합침 · 한 길이 실패하면 응답 없음 · 항목마다 LLM을 부르면 Loop Over Items, 시간·비용은 항목 수에 비례
|
||||
|
||||
---
|
||||
|
||||
## 12 / 15 · 가짜를 진짜로 · Lab 03 과제 40분
|
||||
|
||||
| 예제의 가짜 | 과제의 진짜 |
|
||||
| --- | --- |
|
||||
| `Sample actions` | `LLM extract actions` → `Parse actions` (Lab 02) |
|
||||
| `Sample summary` | `LLM summarize` → `Keep summary` |
|
||||
| Merge · Split Out · Normalize · Aggregate | **복사해 붙이기** |
|
||||
| (없음) | `Any actions?` · `Build report` · `No actions` · **확인 필요** |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
P["할 일 목록"] --> J["Merge"]
|
||||
K["요약"] --> J
|
||||
J --> A{"할 일<br/>있음?"} -->|"예"| L["펼치고 · 정리 · 모으기"] --> B["Build report"]
|
||||
A -->|"아니오"| NA["No actions"]
|
||||
classDef o fill:#F2A541,stroke:#F2A541,color:#fff
|
||||
classDef t fill:#0F7C80,stroke:#0F7C80,color:#fff
|
||||
classDef p fill:#7A6FB0,stroke:#7A6FB0,color:#fff
|
||||
class A o
|
||||
class P,K,L,B,NA t
|
||||
class J p
|
||||
```
|
||||
|
||||
> Merge를 **먼저** → 할 일 없음 길에도 요약이 있다 · 두 길 모두 `{title, route, count, summary, actions, need_check}`
|
||||
|
||||
---
|
||||
|
||||
## 13 / 15 · 기대 결과로 검증하고, 밖에서 부르기
|
||||
|
||||
| 요청 | 기대 결과 (수업 전 측정) |
|
||||
| --- | --- |
|
||||
| `weekly` | **할 일 정리** · `count: 4` · 오늘 마감 1건 `높음` · `need_check: 1` · 약 3~4초 |
|
||||
| `no-action` | **할 일 없음** · `count: 0` · `need_check: 0` · 약 2초 |
|
||||
| `empty` | **HTTP 400** · 1초 미만 |
|
||||
|
||||
| 오류 없이 틀리는 모양 (실측) | 원인 |
|
||||
| --- | --- |
|
||||
| 할 일 하나만 | Aggregate 빠짐 |
|
||||
| `no-action`이 빈 응답 | false 길 끊김 |
|
||||
| `need_check: 4` | `check`를 String으로 |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
APP["call('meeting')"] --> CFG["platforms.json<br/>웹훅 경로 한 줄"]
|
||||
CFG --> N8["n8n · 내 워크플로우"]
|
||||
CFG --> MF["miniflow · 08 flow"]
|
||||
classDef d fill:#0F3D3E,stroke:#0F3D3E,color:#fff
|
||||
classDef o fill:#F2A541,stroke:#F2A541,color:#fff
|
||||
classDef t fill:#0F7C80,stroke:#0F7C80,color:#fff
|
||||
classDef m fill:#5B8E7D,stroke:#5B8E7D,color:#fff
|
||||
class APP d
|
||||
class CFG o
|
||||
class N8 t
|
||||
class MF m
|
||||
```
|
||||
|
||||
> 실행 기록 항목 수 1 → 4 → 4 → 1 → 1 · 같은 입력·같은 결과 형식 → **설정 한 줄**로 대상 변경 (8교시 함께)
|
||||
|
||||
---
|
||||
|
||||
## 14 / 15 · 오늘의 실습
|
||||
|
||||
| | 실습 | 예제 1 | 예제 2 | 과제 (생각하기) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **1** | **Lab 01** · 설계 | 어제 흐름의 항목 수 | 오늘 흐름 빈칸 (1 → 4 → 1) | "확인 필요" 요청 → 어디를 고치나 |
|
||||
| **2** | **Lab 02** · 파싱 | LLM 없이 글자 → 목록 | LLM 붙은 1부 가져와 실행 | 참석자도 뽑기 (세 곳) |
|
||||
| **3** | **Lab 03** · 완성 | 펼치고 모으기 (1 → 3 → 1) | 두 길을 Merge로 | **40분** · 내 워크플로우 완성 + 확인 필요 |
|
||||
|
||||
<sub>예제는 따라 하기, 과제는 생각하기 · 환경: 65일차 n8n · Credential `OpenAI (.env)` 그대로 · 막히면 `n8n/workflows`의 파일과 비교</sub>
|
||||
|
||||
---
|
||||
|
||||
## 15 / 15 · 7교시 퀴즈 · 그리고 내일
|
||||
|
||||
| A · 기본 | B · 응용 | C · 심화 |
|
||||
| :-: | :-: | :-: |
|
||||
| 데이터 노드 · 제어 구조 용어 | 항목 수 · 표현식 · 노드 고르기 | 흐름 오류 찾기 · 설계 판단 |
|
||||
|
||||
- 단계별 10문항 · 20분 · 원하는 단계 선택
|
||||
- 마지막 주관식 '더 알고 싶은 것'은 선택 · 점수 없음
|
||||
|
||||
> **67일차 예고**
|
||||
> AI Agent가 Tool을 골라 쓰는 **Agent·Tool·데이터 연계**
|
||||
> 실행 결과 검증
|
||||
|
||||
### *오늘의 한 문장: 연결선마다 무엇이 몇 개 흐르는지 알면, 워크플로우는 틀리지 않는다*
|
||||
- 67일차: **Agent · Tool · 데이터 연계** — 정해진 흐름 대신 Agent가 Tool(FAQ 검색, 접수 현황)을 골라 답하고, 결과를 검증 · 바이브 코딩 `tman`
|
||||
+69
-49
@@ -1,80 +1,100 @@
|
||||
# 66일차 실습 준비 · 수업 전에 미리 해 두면 좋은 것
|
||||
|
||||
- 수업일: 2026-10-16 (66일차) · 단일 업무 자동화 워크플로우 직접 만들기
|
||||
- 준비 시간: 약 20분 (새로 받을 이미지·모델 없음)
|
||||
- 수업일: 2026-10-16 (66일차) · 바이브 코딩으로 만드는 단일 업무 자동화 워크플로우 (오늘부터 CLI 코딩 에이전트로 n8n을 만듭니다)
|
||||
- 준비 시간: 약 20분
|
||||
- 먼저 WSL에서 `cd ~/aio-01d4-stu && git pull`로 오늘 자료를 받습니다 (수강생 저장소: https://gl.isitsoft.kr/publicproj/aio-01d4-stu).
|
||||
- **수업 전날까지** 1~2절을 끝내 주세요. 3~4절은 시간이 될 때 읽어 오면 좋습니다.
|
||||
- **수업 전날까지** 1~3절을 끝내 주세요.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["1. 어제 환경 켜기<br/>n8n · .env 키"] --> B["2. 확인<br/>pytest · OpenAI · n8n 로그인"]
|
||||
B --> C["3. 미리 읽기<br/>JSON 객체·목록"]
|
||||
C --> D["4. 생각해 오기<br/>항목 수가 바뀌는 곳"]
|
||||
A["1. n8n 켜기"] --> B["2. CLI 코딩 에이전트<br/>설치 · 로그인"] --> C["3. doctor<br/>ready"] --> D["4. 미리 읽기"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. 어제 환경 다시 켜기 (전날 필수, 5분)
|
||||
## 1. n8n 켜기 (전날 필수, 3분)
|
||||
|
||||
오늘은 65일차에 만든 n8n(Credential `OpenAI (.env)`)과 `Last3Mon/.env`를 그대로 씁니다. Ollama는 대체용으로 함께 켜 둡니다.
|
||||
65일차에 설치한 n8n을 그대로 씁니다.
|
||||
|
||||
| 무엇 | 어디 |
|
||||
| --- | --- |
|
||||
| n8n 공식 설치 (compose.yml · .env) | `~/day65-n8n` (65일차 [SETUP-N8N](../../Last3Mon/65/SETUP-N8N.md) 5단계, 지우지 않음) |
|
||||
| 켜고 끄기 (n8n + Ollama) | `~/aio-01d4-stu/Last3Mon/65/n8n` (`~/day65-n8n/compose.yml`을 불러 씀) |
|
||||
|
||||
```bash
|
||||
sudo systemctl stop ollama # WSL의 Ollama가 켜져 있다면 멈춤
|
||||
cd ~/aio-01d4-stu/Last3Mon/65/n8n
|
||||
docker compose start
|
||||
docker compose ps # n8n, ollama 모두 running
|
||||
```
|
||||
|
||||
브라우저에서 http://localhost:5678 에 로그인합니다. 65일차 준비를 하지 못했다면 [n8n 준비 안내](../../Last3Mon/65/SETUP-N8N.md)를 먼저 끝내 주세요.
|
||||
- `~/day65-n8n`에서 `docker compose start`로 켰어도 n8n은 같은 것입니다 (Ollama만 빠짐).
|
||||
- `docker compose start`가 `.../day65-n8n/compose.yml: no such file or directory`이면 n8n을 홈이 아닌 곳에 설치한 것입니다. 65일차 SETUP-N8N 문제 해결 표대로 `Last3Mon/65/n8n/.env`에 `N8N_HOME=<설치 폴더>`를 적습니다 (`flow.py`도 이 값을 읽습니다).
|
||||
|
||||
## 2. 실습 엔진 확인 (전날 필수, 5분)
|
||||
## 2. CLI 코딩 에이전트 (전날 필수, 10분)
|
||||
|
||||
아래 중 **하나 이상**이 WSL 터미널에서 실행되고 로그인되어 있어야 합니다.
|
||||
|
||||
| 에이전트 | 실행 | 확인 |
|
||||
| --- | --- | --- |
|
||||
| Codex CLI | `codex` | `codex --version` |
|
||||
| Claude Code | `claude` | `claude --version` |
|
||||
| Antigravity CLI | `agy` | `agy --version` |
|
||||
|
||||
- 처음 실행하면 브라우저 로그인 안내가 나옵니다. 끝까지 진행한 뒤 아무 질문("안녕")이나 해서 답이 오는지 봅니다.
|
||||
- 설치가 안 되어 있으면 IT 문의 시간에 강사에게 알려 주세요.
|
||||
|
||||
## 3. 준비 확인 (전날 필수, 3분)
|
||||
|
||||
```bash
|
||||
cd ~/aio-01d4-stu/Last3Mon/66/workflow-lab
|
||||
uv sync
|
||||
uv run pytest -q # 43 passed, 1 skipped
|
||||
cd ~/aio-01d4-stu/Last3Mon/66
|
||||
uv run tools/flow.py doctor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 미리 읽어 오면 좋은 내용 (선택, 약 10분)
|
||||
|
||||
### 3.1 JSON의 객체와 목록
|
||||
|
||||
| 모양 | 이름 | 예 |
|
||||
| --- | --- | --- |
|
||||
| `{ }` | 객체 (이름: 값의 묶음) | `{"task": "보고서 작성", "owner": "박서연", "due": "월요일"}` |
|
||||
| `[ ]` | 목록 (값을 순서대로) | `[{...}, {...}, {...}]` |
|
||||
| `"..."` | 문자열 (글자) | `"{\"task\": \"보고서\"}"` ← JSON처럼 보이지만 **글자** |
|
||||
|
||||
오늘 LLM은 할 일 목록을 **글자**로 돌려줍니다. 이 글자를 실제 목록으로 바꾸는 작업을 **파싱**이라고 합니다. 64일차 준비 안내의 JSON 기초도 함께 보세요.
|
||||
|
||||
### 3.2 오늘 처음 쓰는 n8n 노드
|
||||
|
||||
| 노드 | 한 줄 설명 |
|
||||
| --- | --- |
|
||||
| Edit Fields (Set) | 필드를 만들고 값을 계산 |
|
||||
| Code | JavaScript로 데이터 가공 |
|
||||
| Split Out | 목록 하나를 항목 여러 개로 펼침 |
|
||||
| Aggregate | 항목 여러 개를 목록 하나로 모음 |
|
||||
| Merge | 두 갈래의 결과를 하나로 합침 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 생각해 오기 (선택, 5분)
|
||||
|
||||
회의록 1건에서 할 일 4건이 나왔습니다. 할 일마다 우선순위를 정한 뒤 다시 하나의 결과로 돌려주려면, 데이터의 개수가 어떻게 바뀌어야 할까요?
|
||||
|
||||
```text
|
||||
회의록 1건 → ? → ? → 결과 1건
|
||||
✔ n8n install folder /home/<사용자>/day65-n8n
|
||||
✔ n8n container day65-n8n-n8n-1
|
||||
✔ n8n http://localhost:5678
|
||||
! data API http://localhost:8800 (not used today)
|
||||
✔ credential 'OpenAI (.env)' (openAiCourse0001)
|
||||
✔ credential 'Ollama local' (ollamaLocal0001)
|
||||
✔ node catalog (for check)
|
||||
✔ CLI coding agent: codex, claude
|
||||
ready
|
||||
```
|
||||
|
||||
| ✘ 항목 | 할 일 |
|
||||
| --- | --- |
|
||||
| ! n8n install folder | `~/day65-n8n`에 `compose.yml`이 없음. 다른 곳에 설치했다면 `N8N_HOME=<폴더> uv run tools/flow.py doctor` (경고만, n8n이 켜져 있으면 실습 가능) |
|
||||
| n8n container | 1절 n8n 켜기. `flow.py`는 Docker 프로젝트 `day65-n8n`의 n8n 컨테이너를 자동으로 찾습니다. 그래도 못 찾으면 `N8N_CONTAINER=<이름>` (`docker ps`로 확인) |
|
||||
| credential | `cd ~/aio-01d4-stu/Last3Mon/65/n8n && bash register-credentials.sh` |
|
||||
| CLI coding agent | 2절 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 미리 읽어 오면 좋은 내용 (선택, 약 5분)
|
||||
|
||||
### 4.1 n8n 워크플로우는 JSON 파일이다
|
||||
|
||||
64·65일차에 화면에서 만든 워크플로우는 **Download**하면 JSON 파일입니다. 노드 목록(`nodes`)과 연결(`connections`)이 들어 있습니다. 오늘부터는 이 파일을 **CLI 코딩 에이전트가 씁니다.** 우리는 무엇을 만들지(요청문)와 무엇이 맞는 결과인지(테스트)를 정합니다.
|
||||
|
||||
### 4.2 오늘의 회의록
|
||||
|
||||
`cat ~/aio-01d4-stu/Last3Mon/66/requests/weekly.json` — 이 회의록에서 할 일이 몇 개인지, 담당자와 기한이 없는 할 일은 무엇인지 미리 세어 옵니다.
|
||||
|
||||
### 4.3 JSON 객체 · 목록
|
||||
|
||||
| 모양 | 예 |
|
||||
| --- | --- |
|
||||
| 객체 | `{"owner": "이준호", "due": "금요일"}` |
|
||||
| 목록 | `[{"owner": "이준호"}, {"owner": "박서연"}]` |
|
||||
| 글자 속의 JSON | `"{\"owner\": \"이준호\"}"` — LLM의 답은 이 모양 (읽어야 객체가 됨) |
|
||||
|
||||
## 5. 당일 준비물
|
||||
|
||||
- 충전된 노트북과 충전기, WSL의 Docker, Windows Terminal(WSL), 웹 브라우저
|
||||
- 충전된 노트북과 충전기, WSL의 Docker, Windows Terminal(WSL) 창 **두 개**, 웹 브라우저
|
||||
|
||||
## 확인
|
||||
|
||||
- [ ] `docker compose ps` → n8n, ollama running
|
||||
- [ ] `uv run pytest -q` → `43 passed, 1 skipped`
|
||||
- [ ] JSON 객체·목록·문자열의 차이를 읽었다
|
||||
- [ ] http://localhost:5678 이 열린다
|
||||
- [ ] `codex` / `claude` / `agy` 중 하나에 로그인하고 답을 받았다
|
||||
- [ ] `uv run tools/flow.py doctor` → 마지막 줄 `ready`
|
||||
@@ -1,260 +0,0 @@
|
||||
# 1단계 · 단일 업무 자동화 설계와 데이터 흐름
|
||||
|
||||
- 시간: 1~2교시 (1교시 앞부분은 [오늘의 개요](00-DAY66-OVERVIEW.md))
|
||||
- 학습 목표
|
||||
- 하나의 업무를 목적 하나로 좁히고, 결과를 먼저 그려 볼 수 있다.
|
||||
- 노드 사이를 지나가는 **항목(item)** 의 개수를 읽고 쓸 수 있다 (1 → 4 → 1).
|
||||
- LLM이 할 일과 규칙으로 할 일을 나눌 수 있다.
|
||||
- 조건 분기·반복·병렬 중 어떤 제어 구조가 필요한지 고를 수 있다.
|
||||
- 요청이 바뀌었을 때 **어느 노드를 고치면 되는지** 찾을 수 있다.
|
||||
- 실습: [Lab 01 · 회의록 자동 정리 워크플로우 설계하기 (예제 2개 + 과제)](../../Last3Mon/66/LAB-01-DESIGN-DATA-FLOW.md)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["1. 오늘의 업무"] --> B["2. 결과 먼저"] --> C["3. 항목 읽기<br/>(Lab 01 예제 1)"] --> D["4. 1 → 4 → 1<br/>(Lab 01 예제 2)"] --> E["5. 제어 구조"] --> F["6. 요청이 바뀌면<br/>(Lab 01 과제)"]
|
||||
```
|
||||
|
||||
오늘 수업은 모두 같은 순서로 갑니다. **설명 → 쉬운 예제 2개를 함께 → 그것을 고치는 과제를 혼자**. 이 교안의 절도 Lab의 예제·과제 순서에 맞춰 놓았습니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 오늘의 업무: 회의록 → 할 일 정리
|
||||
|
||||
64일차 시나리오 S1 "회의록 요약 후 할 일 정리"를 n8n으로 만듭니다.
|
||||
|
||||
| 지금 (AS-IS) | 자동화 후 (TO-BE) |
|
||||
| --- | --- |
|
||||
| 누군가 회의록을 읽고 할 일·담당자·기한을 찾아 정리 | 회의록을 보내면 요약·할 일 목록·정리문이 바로 나옴 |
|
||||
| 담당자나 기한이 빠진 일을 놓침 | 빠진 값은 `미정`으로 보임 |
|
||||
| 급한 일이 묻힘 | 오늘·내일 마감은 우선순위 `높음` |
|
||||
|
||||
### 1.1 목적 하나로 좁히기
|
||||
|
||||
처음 만드는 자동화는 **목적 하나**만 해결합니다. 메일 발송·일정 등록까지 한 번에 넣으면 어디가 틀렸는지 찾기 어렵습니다.
|
||||
|
||||
| 질문 | 오늘의 답 (한 문장씩) |
|
||||
| --- | --- |
|
||||
| 목적 | 회의록을 받으면 할 일 목록과 정리문을 돌려준다 |
|
||||
| 입력 → 출력 | `{title, notes}` → `{title, route, count, summary, actions, report_text}` |
|
||||
| 성공 기준 | 예시 회의록(`weekly`)에서 할 일 4건, 빠진 값은 `미정`, 오늘 마감은 `높음` |
|
||||
|
||||
세 줄이 한 문장씩으로 끝나지 않으면 아직 범위가 넓은 것입니다. 회의록이 **비어 있으면** LLM을 부르기 전에 맨 앞에서 거절(HTTP 400)합니다. 쓸데없는 호출과 비용을 막는 가장 싼 방법입니다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 결과를 먼저 그려 보기
|
||||
|
||||
노드를 생각하기 전에 **예시 입력 한 건과 기대하는 결과**를 손으로 적습니다.
|
||||
|
||||
> 참석: 김민지, 이준호, 박서연. 1) 신규 고객 문의 응답 템플릿을 이준호가 금요일까지 정리한다. 2) 박서연은 다음 주 월요일까지 VPN 장애 보고서를 작성한다. 3) 서버 점검 일정은 김민지가 오늘 중 공지한다. 4) 회의실 예약 규칙 개선은 담당자를 다음 회의에서 정한다. 다음 회의는 10월 23일.
|
||||
|
||||
| # | 할 일 | 담당자 | 기한 | 우선순위 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | 신규 고객 문의 응답 템플릿 정리 | 이준호 | 금요일 | 보통 |
|
||||
| 2 | VPN 장애 보고서 작성 | 박서연 | 다음 주 월요일 | 보통 |
|
||||
| 3 | 서버 점검 일정 공지 | 김민지 | 오늘 | **높음** |
|
||||
| 4 | 회의실 예약 규칙 개선 담당자 선정 | **미정** | 10월 23일 | 보통 |
|
||||
|
||||
이 표를 채우다 보면 일이 두 종류로 나뉩니다.
|
||||
|
||||
| 누가 | 하는 일 | 왜 |
|
||||
| --- | --- | --- |
|
||||
| **LLM** | 문장 속에서 할 일·담당자·기한 **찾기**, 요약 | 사람 말을 읽어야 함 |
|
||||
| **규칙** | 빈 값을 `미정`으로 **채우기**, 우선순위 **정하기**, 정리문 **만들기** | 항상 같은 답이어야 함 |
|
||||
|
||||
> 4번의 기한은 회의록에 직접 적혀 있지 않지만, 수업 전 측정(`gpt-4.1-mini`)에서 LLM은 매번 "다음 회의는 10월 23일"을 읽고 `10월 23일`로 채웠습니다. 담당자는 적혀 있지 않아 `미정`입니다. LLM이 **추론한 값**을 받아들일지도 설계에서 정할 일입니다.
|
||||
|
||||
### 2.1 결과 형식을 먼저 약속하기
|
||||
|
||||
결과를 받는 쪽(사람, 다른 시스템, 65일차의 플랫폼 전환 코드)이 이 모양에 맞춰 만들어지므로, 노드를 만들기 **전에** 정합니다.
|
||||
|
||||
```json
|
||||
{"title": "10월 16일 주간 회의", "route": "할 일 정리", "count": 4, "summary": "요약 2문장",
|
||||
"actions": [{"task": "...", "owner": "...", "due": "...", "priority": "높음 | 보통"}],
|
||||
"report_text": "공유용 정리문"}
|
||||
```
|
||||
|
||||
할 일이 없어도 **같은 필드**를 쓰고 `route: 할 일 없음`, `count: 0`, `actions: []`로 둡니다 (65일차 "모든 길이 같은 모양으로 끝나게").
|
||||
|
||||
---
|
||||
|
||||
## 3. 항목(item) 읽기 · Lab 01 예제 1
|
||||
|
||||
n8n에서 노드와 노드 사이를 지나가는 데이터 한 덩어리를 **항목(item)** 이라고 합니다. 택배 상자 하나라고 생각하면 됩니다. 상자 안에는 필드(`user`, `text` ...)가 들어 있습니다.
|
||||
|
||||
어제 Lab 02A에서 만든 흐름을 보면서 상자가 몇 개 지나가는지 셉니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
T(["Start"]) -->|"1"| S["Sample request<br/>Edit Fields"] -->|"1"| I{"Is urgent?<br/>If"}
|
||||
I -->|"true · 1"| U["Urgent route"]
|
||||
I -.->|"false · 0"| N["Normal route"]
|
||||
```
|
||||
|
||||
- 모든 연결선이 **1개**입니다. If는 상자를 **한쪽 길로 보낼 뿐** 개수를 바꾸지 않습니다.
|
||||
- 개수를 바꾸는 노드는 따로 있습니다. 오늘 새로 쓰는 노드입니다.
|
||||
|
||||
| 노드 | 하는 일 | 항목 수 |
|
||||
| --- | --- | --- |
|
||||
| Edit Fields (Set) | 필드 만들기·값 계산 | 그대로 |
|
||||
| If · Switch | 길 나누기 | 그대로 (한쪽으로) |
|
||||
| Code | 무엇이든 (파싱·검증) | 코드가 정함 |
|
||||
| **Split Out** | 상자 속 목록을 **상자 여러 개로 펼침** | **1 → N** |
|
||||
| **Aggregate** | 상자 여러 개를 **목록 하나로 모음** | **N → 1** |
|
||||
| **Merge** | 두 길에서 온 상자를 **합침** | 둘 → 하나 |
|
||||
| Filter | 조건에 맞는 상자만 남김 | 줄어듦 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 오늘 흐름의 항목 수 1 → 4 → 1 · Lab 01 예제 2
|
||||
|
||||
회의록 1건이 할 일 4건이 되었다가 다시 결과 1건이 됩니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["회의록<br/>상자 1개"] -->|"LLM + 파싱"| B["상자 1개<br/>안에 actions 목록 [4]"] -->|"Split Out<br/>1 → 4"| C["할 일<br/>상자 4개"] -->|"항목마다 정리"| D["정리된 할 일<br/>상자 4개"] -->|"Aggregate<br/>4 → 1"| E["결과<br/>상자 1개"]
|
||||
```
|
||||
|
||||
가장 자주 헷갈리는 곳은 **B**입니다. 할 일이 4건이라도 파싱 직후에는 **상자 1개 안에 목록으로** 들어 있습니다. Split Out을 지나야 비로소 상자 4개가 됩니다.
|
||||
|
||||
오늘 완성할 전체 그림과 각 노드의 역할입니다. (오후 Lab 03 과제의 완성 그림과 같습니다.)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
W(["Meeting webhook"]) --> S["Settings"] --> H{"Has notes?"}
|
||||
H -->|false| R0["Reject empty<br/>400"]
|
||||
H -->|true| L1{{"LLM extract actions"}} --> P["Parse actions"] --> J["Join results<br/>Merge"]
|
||||
H -->|true| L2{{"LLM summarize"}} --> K["Keep summary"] --> J
|
||||
J --> A{"Any actions?"}
|
||||
A -->|true| SO["One item per action<br/>Split Out"] --> N["Normalize action"] --> AG["Collect actions<br/>Aggregate"] --> B["Build report"] --> R["Send result"]
|
||||
A -->|false| NA["No actions"] --> R
|
||||
```
|
||||
|
||||
| 설계에서 | n8n 노드 | 언제 만드나 |
|
||||
| --- | --- | --- |
|
||||
| 회의록 받기, 설정 모으기, 비었으면 거절 | Webhook, Edit Fields, If, Respond | Lab 02 예제 2 (가져옴) |
|
||||
| 할 일 찾기 (LLM) → 글자를 목록으로 | HTTP Request, Code | Lab 02 예제 1·2 |
|
||||
| 요약 (LLM)과 합치기 | HTTP Request, Edit Fields, Merge | Lab 03 예제 2 → 과제 |
|
||||
| 할 일마다 우선순위 → 모으기 → 정리문 | Split Out, Edit Fields, Aggregate, Code | Lab 03 예제 1 → 과제 |
|
||||
| 할 일이 없을 때 | If, Edit Fields | Lab 03 과제 |
|
||||
|
||||
### 4.1 연결선 표에 적는 세 가지
|
||||
|
||||
| 적는 것 | 예 | 빠뜨리면 |
|
||||
| --- | --- | --- |
|
||||
| **항목 수** | Split Out 뒤 4 | 응답이 할 일 하나만 나가는 것을 늦게 알아챔 |
|
||||
| **필드 이름** | `actions`, `count`, `summary` | 표현식(`{{ $json.actions }}`)에 없는 이름 → 빈 값 |
|
||||
| **형식** | LLM 바로 뒤는 **글자** | LLM 답을 목록으로 착각 → 2단계의 파싱이 필요 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 제어 구조 고르기
|
||||
|
||||
질문 세 개로 고릅니다.
|
||||
|
||||
| 질문 | 예이면 | n8n | 오늘의 예 |
|
||||
| --- | --- | --- | --- |
|
||||
| 입력에 따라 **다른 결과**를 내야 하나? | 조건 분기 | If, Switch | 내용 없음 → 거절, 할 일 없음 → 다른 결과 |
|
||||
| 목록의 **항목마다** 같은 일을 하나? | 반복 | Split Out → … → Aggregate | 할 일마다 우선순위 |
|
||||
| 서로의 **결과가 필요 없는** 일이 둘 이상인가? | 병렬 (Fork-Join) | 가지 나누기 + Merge | 할 일 찾기와 요약 |
|
||||
|
||||
### 5.1 연습
|
||||
|
||||
| 업무 | 제어 구조 |
|
||||
| --- | --- |
|
||||
| 문의 메일 50통을 각각 분류 | 반복 |
|
||||
| 리뷰의 감정 분석과 키워드 추출 | 병렬 |
|
||||
| 감정이 부정이면 담당자에게, 아니면 자동 답글 | 조건 분기 |
|
||||
| 회의록 요약을 만든 뒤 그 요약으로 제목 짓기 | **순서** (제목에 요약 결과가 필요 → 병렬 불가) |
|
||||
| 출장자마다 날씨 조회 후 우산 필요 여부 판단 | 반복 + 조건 분기 |
|
||||
|
||||
실제 업무는 여러 구조가 섞이고, **앞 결과가 필요하면 병렬로 나눌 수 없습니다.**
|
||||
|
||||
---
|
||||
|
||||
## 6. 요청이 바뀌면 어디를 고치나 · Lab 01 과제
|
||||
|
||||
설계를 해 두면 요청이 바뀌었을 때 고칠 곳을 금방 찾습니다. 찾는 순서는 세 질문입니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Q1{"LLM이 할 일?<br/>규칙이 할 일?"} --> Q2{"할 일 하나마다?<br/>모두 모은 뒤?"} --> Q3{"모든 길이<br/>같은 필드로 끝나나?"}
|
||||
```
|
||||
|
||||
| 질문 | 고칠 곳 |
|
||||
| --- | --- |
|
||||
| 할 일 **하나마다** 정하는 값인가? | 상자가 하나씩 흐르는 곳 = Split Out과 Aggregate **사이** |
|
||||
| **모두 모은 뒤**에야 알 수 있는 값인가? (개수, 합계) | Aggregate **뒤** |
|
||||
| 결과에 필드가 늘었나? | **모든 길**의 끝 (할 일 없음 길 포함) |
|
||||
|
||||
연습 삼아 이미 있는 값으로 확인해 봅니다.
|
||||
|
||||
| 값 | 하나마다? 모은 뒤? | 그래서 만드는 곳 |
|
||||
| --- | --- | --- |
|
||||
| `priority` (오늘·내일이면 높음) | 하나마다 | `Normalize action` |
|
||||
| `count` (할 일 개수) | 모은 뒤 | `Build report` |
|
||||
| `route` | 길마다 | `Build report` / `No actions` |
|
||||
|
||||
Lab 01 과제는 이 방법으로 "확인 필요" 요청을 설계하고, 오후 Lab 03 과제에서 실제로 만듭니다.
|
||||
|
||||
---
|
||||
|
||||
## 7. Lab 01 안내
|
||||
|
||||
- 실습 안내: [Last3Mon/66/LAB-01-DESIGN-DATA-FLOW.md](../../Last3Mon/66/LAB-01-DESIGN-DATA-FLOW.md) (22분, 활동지 `templates/LAB-01-WORKSHEET.md`)
|
||||
|
||||
| 순서 | 시간 | 할 일 | 교안 |
|
||||
| --- | --- | --- | --- |
|
||||
| 예제 1 | 5분 | 어제 흐름의 연결선 항목 수 읽기 | 3절 |
|
||||
| 예제 2 | 7분 | 오늘 흐름의 빈칸 다섯 개 (1 → 4 → 1, 병렬 이유, LLM이 하는 일) | 2·4·5절 |
|
||||
| 과제 | 10분 | "담당자·기한이 없는 할 일은 **확인 필요**로, 몇 건인지도" → 어디를 고치나 | 6절 |
|
||||
|
||||
- 전체 설계서(`templates/DATA-FLOW-DESIGN-TEMPLATE.md`)는 오늘 복습 과제에서 **내 업무**로 써 봅니다.
|
||||
|
||||
---
|
||||
|
||||
## 8. 자주 하는 설계 실수
|
||||
|
||||
| 실수 | 증상 | 고치는 법 |
|
||||
| --- | --- | --- |
|
||||
| 한 번에 알림·일정까지 넣음 | 어디가 틀렸는지 찾기 어려움 | 목적 하나로, 나머지는 "나중에" |
|
||||
| LLM에게 우선순위·기본값까지 맡김 | 같은 회의록인데 결과가 매번 다름 | 찾기는 LLM, 채우기·계산은 규칙 |
|
||||
| 항목 수를 안 적음 | 응답에 할 일이 하나만 | 1 → N → 1 표시 |
|
||||
| 결과가 필요한 일을 병렬로 | 요약 전에 제목을 지으려 함 | "앞 결과가 필요한가?" |
|
||||
|
||||
---
|
||||
|
||||
## 9. 스스로 확인하기
|
||||
|
||||
<details><summary>Q1. If 노드를 지나면 항목 수는 어떻게 되나요?</summary>
|
||||
|
||||
그대로입니다. If는 항목을 true나 false 한쪽 길로 보낼 뿐 개수를 바꾸지 않습니다. 개수를 바꾸는 노드는 Split Out(1 → N), Aggregate(N → 1) 등입니다.
|
||||
</details>
|
||||
|
||||
<details><summary>Q2. 할 일이 4건인데 `Parse actions` 바로 뒤의 항목 수가 1인 이유는?</summary>
|
||||
|
||||
4건이 `actions`라는 **목록 하나**에 담겨 상자 1개로 지나가기 때문입니다. Split Out을 지나야 상자 4개가 됩니다.
|
||||
</details>
|
||||
|
||||
<details><summary>Q3. 우선순위를 LLM이 아니라 규칙으로 정하는 이유는?</summary>
|
||||
|
||||
"기한에 오늘·내일·긴급이 있으면 높음"은 규칙으로 항상 같게 정할 수 있습니다. LLM에 맡기면 결과가 흔들리고 시간·비용이 듭니다.
|
||||
</details>
|
||||
|
||||
<details><summary>Q4. "요약을 만든 뒤 그 요약으로 제목 짓기"를 병렬로 나눌 수 없는 이유는?</summary>
|
||||
|
||||
제목 짓기가 요약 결과를 필요로 하기 때문입니다. 병렬은 서로의 결과가 필요 없을 때만 가능합니다.
|
||||
</details>
|
||||
|
||||
<details><summary>Q5. 할 일 개수(`count`)를 `Normalize action`이 아니라 `Build report`에서 세는 이유는?</summary>
|
||||
|
||||
`Normalize action`은 할 일 하나마다 따로 실행되어 전체 개수를 모릅니다. 모두 모은(Aggregate) 뒤에야 셀 수 있습니다.
|
||||
</details>
|
||||
|
||||
## 정리
|
||||
|
||||
- 처음 만드는 자동화는 목적 하나로 좁히고, 예시 입력의 기대 결과를 먼저 그립니다.
|
||||
- LLM은 **찾기**, 규칙은 **채우기·계산·정리문**을 맡습니다. 결과 형식은 노드보다 먼저 약속합니다.
|
||||
- 노드 사이를 지나가는 상자(항목)의 개수를 셉니다. 오늘 흐름은 **1 → 4 → 1**입니다.
|
||||
- 결과가 필요 없는 일은 병렬, 항목마다 하는 일은 반복, 입력에 따라 다른 일은 조건 분기입니다.
|
||||
- 요청이 바뀌면 "하나마다? 모은 뒤? 모든 길?"로 고칠 곳을 찾습니다.
|
||||
@@ -0,0 +1,139 @@
|
||||
# 1단계 · 바이브 코딩으로 n8n 만들기, 결과 먼저
|
||||
|
||||
- 교시: 1~2교시 (09:00~10:50)
|
||||
- 실습: [Lab 01](../../Last3Mon/66/LAB-01-Q-RESULT-FIRST.md)
|
||||
- 이 단계가 끝나면: 결과를 먼저 정하고, 요청문으로 n8n 워크플로우를 만들어 push·test로 확인할 수 있다. 워크플로우 안에서 항목이 몇 개씩 지나가는지 읽을 수 있다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["1. 무엇이 바뀌나"] --> B["2. 워크플로우 = JSON"] --> C["3. AGENTS.md · flow.py"] --> D["4. 요청문 쓰기"] --> E["5. 결과 먼저"] --> F["6. 항목 흐름"]
|
||||
```
|
||||
|
||||
## 1. 무엇이 바뀌나
|
||||
|
||||
64·65일차에는 n8n 화면에서 노드를 끌어다 놓고, 설정 칸을 채우고, 선을 이었습니다. 오늘부터는 그 일을 **CLI 코딩 에이전트**(Codex · Claude Code · Antigravity)가 합니다.
|
||||
|
||||
| 일 | 전 | 오늘부터 |
|
||||
| --- | --- | --- |
|
||||
| 무엇을 만들지 정하기 | 나 | **나** (결과 · 요청문) |
|
||||
| 노드 고르기 · 설정 · 연결 | 나 (마우스) | 에이전트 |
|
||||
| 형식 오류 찾기 | 실행해 보고 빨간 표시 | 에이전트 + `flow.py check` |
|
||||
| 잘 되는지 확인 | 눈으로 OUTPUT 보기 | **나** (테스트 케이스, `flow.py test`) |
|
||||
| 실패 원인 판단 | 나 | **나** + 에이전트 |
|
||||
|
||||
> 손이 하던 일은 줄고, **"무엇이 맞는 결과인가"를 정하는 일**이 늘어납니다. 64·65일차에 배운 노드와 데이터 흐름은 요청문의 재료가 됩니다.
|
||||
|
||||
## 2. 워크플로우는 JSON 파일이다
|
||||
|
||||
n8n 화면의 워크플로우를 **Download**하면 JSON 파일이 됩니다. 에이전트는 이 파일을 씁니다. Lab 01 예제 2의 워크플로우를 줄이면 이렇습니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "66 Meeting summary (my)",
|
||||
"nodes": [
|
||||
{"name": "Meeting webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "parameters": {"path": "my-meeting", "...": "..."}},
|
||||
{"name": "Summarize", "type": "@n8n/n8n-nodes-langchain.agent", "typeVersion": 3.1, "parameters": {"...": "..."}},
|
||||
{"name": "Summary model", "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", "typeVersion": 1.2, "credentials": {"openAiApi": {"id": "openAiCourse0001"}}},
|
||||
{"name": "Send result", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "parameters": {"...": "..."}}
|
||||
],
|
||||
"connections": {
|
||||
"Meeting webhook": {"main": [[{"node": "Summarize", "type": "main", "index": 0}]]},
|
||||
"Summary model": {"ai_languageModel": [[{"node": "Summarize", "type": "ai_languageModel", "index": 0}]]}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| JSON | 화면에서 |
|
||||
| --- | --- |
|
||||
| `nodes[]` 하나 | 노드 상자 하나 |
|
||||
| `type` · `typeVersion` | 노드 종류와 버전 (버전이 틀리면 열리지 않음) |
|
||||
| `parameters` | 노드를 열었을 때의 설정 칸 |
|
||||
| `credentials.id` | 고른 Credential (키 자체는 n8n 안에만 있음) |
|
||||
| `connections` | 선. 보내는 노드 이름 → 받는 노드 이름 |
|
||||
|
||||
## 3. 에이전트에게 주는 규칙과 확인 도구
|
||||
|
||||
### 3.1 AGENTS.md: 에이전트가 모르는 것을 알려 준다
|
||||
|
||||
| 에이전트가 모르는 것 | AGENTS.md에 적은 것 |
|
||||
| --- | --- |
|
||||
| 우리 n8n 버전에서 되는 노드 버전 | 실행 확인한 노드 JSON (`typeVersion` 포함) |
|
||||
| Credential | `openAiCourse0001` · `ollamaLocal0001` id만 사용, 키는 쓰지 않음 |
|
||||
| 오늘 만들 것의 계약 | 웹훅 `my-meeting`, 입력 `title`·`notes`, Lab마다 결과 모양 |
|
||||
| 일하는 순서 | `my-work/`에 쓰기 → `check` → 사람이 push·test |
|
||||
| 하지 말 것 | 테스트 기대 결과 바꾸기, 도구 고치기, 키 출력 |
|
||||
|
||||
`codex`와 `agy`는 `AGENTS.md`를, `claude`는 `CLAUDE.md`(→ `@AGENTS.md`)를 읽습니다. 프로젝트마다 이런 파일을 두는 것이 바이브 코딩의 첫 단계입니다.
|
||||
|
||||
### 3.2 flow.py: 만든 것을 확인하는 도구
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
F["my-work/meeting.json"] --> C["check<br/>형식 · 버전 · 연결 · 키"] --> P["push<br/>가져오기 · 게시 · 재시작"] --> T["test<br/>요청 → 기대 결과 비교"]
|
||||
T --> RPT["runs/보고서.json"]
|
||||
```
|
||||
|
||||
| 명령 | 언제 |
|
||||
| --- | --- |
|
||||
| `check` | 에이전트가 파일을 쓸 때마다 (에이전트가 직접 실행) |
|
||||
| `push` | check 통과 후 (내가 실행). 화면의 Import → Publish를 대신함 |
|
||||
| `test` | push 후 (내가 실행). `Passed x/y`, 실패하면 보고서 경로 |
|
||||
|
||||
## 4. 요청문 쓰기
|
||||
|
||||
에이전트는 **적힌 것만** 압니다. 요청문에 다섯 가지를 넣습니다. Lab과 다른 업무(고객 문의 메일 분류)로 예를 듭니다.
|
||||
|
||||
| 넣을 것 | 고객 문의 메일을 분류하는 워크플로우라면 |
|
||||
| --- | --- |
|
||||
| ① 파일 | `my-work/mail-triage.json` (새로 만듦) |
|
||||
| ② 입력 | POST 웹훅 `my-mail-triage`, 본문의 `subject`·`body` |
|
||||
| ③ 처리 | LLM이 문의 종류를 고르고(배송 · 환불 · 기타), 한 문장 요약 |
|
||||
| ④ 결과 모양 | `{"category": "환불", "summary": "..."}` |
|
||||
| ⑤ 기대 결과 | 테스트 파일 (예: 환불 메일은 `환불`, 빈 메일은 400) |
|
||||
|
||||
| 나쁜 요청문 | 무엇이 빠졌나 |
|
||||
| --- | --- |
|
||||
| "메일 분류 워크플로우 만들어 줘" | 파일 · 입력 · 결과 모양 · 기대 결과 전부 |
|
||||
| "요약도 하게 해 줘" | 어느 파일을 고칠지, 결과에 무엇이 추가되는지 |
|
||||
| "테스트 통과하게 해 줘" | 무엇을 바꿔야 하는지 (에이전트가 테스트를 고치려 할 수 있음) |
|
||||
|
||||
> 요청문은 **한 번에 한 가지 변화**만 담습니다. 예제 1 → 예제 2 → 과제처럼 작게 나눠 요청하고, 매번 test로 확인합니다. Lab 문서의 `▶` 가이드는 무엇을 채워야 하는지만 알려 주고, 요청문을 대신 써 주지 않습니다.
|
||||
|
||||
## 5. 결과 먼저
|
||||
|
||||
만들기 전에 **결과 JSON을 손으로** 씁니다.
|
||||
|
||||
```json
|
||||
{"title": "10월 16일 주간 회의", "summary": "…", "count": 4,
|
||||
"actions": [{"owner": "이준호", "task": "신규 고객 문의 응답 템플릿 정리", "due": "금요일까지"}, "…",
|
||||
{"owner": "미정", "task": "회의실 예약 규칙 개선", "due": "미정"}]}
|
||||
```
|
||||
|
||||
| 결과를 먼저 쓰면 | |
|
||||
| --- | --- |
|
||||
| 요청문의 ④ 결과 모양이 정해진다 | 필드 이름을 정확히 적을 수 있음 |
|
||||
| 테스트가 된다 | `count`는 4, `actions.0.owner`는 이준호 |
|
||||
| 헷갈리는 곳이 먼저 보인다 | 4번 할 일의 담당자·기한은? → 회의록에 없으면 **미정** (지어내지 않음) |
|
||||
|
||||
수업 준비 때 빈 회의록을 입력 확인 없이 보내자 LLM이 **"회의 내용이 없으므로 논의 사항이나 결정된 내용이 없습니다"** 라는 답을 만들어 200으로 돌아왔습니다. "빈 회의록이면 400"을 결과로 먼저 정해 두었다면 바로 실패로 보였을 것입니다.
|
||||
|
||||
## 6. 항목 흐름: 1 → N → 1
|
||||
|
||||
n8n은 노드 사이로 **항목**(item)을 흘려보냅니다.
|
||||
|
||||
| 단계 | 들어오는 항목 | 나가는 항목 |
|
||||
| --- | --- | --- |
|
||||
| 웹훅 | — | 1 (회의록 하나) |
|
||||
| 할 일 뽑기 (LLM) | 1 | 1 (목록 하나) |
|
||||
| 할 일 하나씩 (Split Out) | 1 | 4 |
|
||||
| 확인 필요 표시 (Set) | 4 | 4 (항목마다 실행) |
|
||||
| 다시 모으기 (Aggregate) | 4 | 1 |
|
||||
|
||||
- 64·65일차에 화면에서 본 "items" 수가 이것입니다. 요청문에 **어디서 몇 개가 되는지** 적으면 에이전트가 덜 헤맵니다 (3단계).
|
||||
|
||||
## 정리
|
||||
|
||||
- [ ] 워크플로우는 JSON 파일이고, 오늘부터 에이전트가 쓴다. 나는 결과 · 요청문 · 기대 결과를 쓴다
|
||||
- [ ] AGENTS.md = 에이전트가 모르는 우리 교실의 사정, flow.py = check · push · test
|
||||
- [ ] 요청문 다섯 가지: 파일 · 입력 · 처리 · 결과 모양 · 기대 결과
|
||||
- [ ] 결과 먼저, 항목 흐름(1 → N → 1)
|
||||
@@ -0,0 +1,73 @@
|
||||
# 2단계 · LLM JSON 출력, 파싱과 변환
|
||||
|
||||
- 교시: 3~4교시 (11:00~12:50)
|
||||
- 실습: [Lab 02](../../Last3Mon/66/LAB-02-Q-JSON-AND-PARSE.md)
|
||||
- 이 단계가 끝나면: LLM에게 정해진 JSON 모양으로 답하게 하고, 그 글자를 코드로 읽어 목록으로 만들 수 있다. 빈칸은 지어내지 않고 코드로 채운다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["1. LLM의 답은 글자"] --> B["2. 모양 알려 주기"] --> C["3. 코드로 읽기"] --> D["4. 빈칸은 코드가"] --> E["5. 하나 바꾸면<br/>다른 것도"]
|
||||
```
|
||||
|
||||
## 1. LLM의 답은 글자
|
||||
|
||||
"할 일을 정리해 줘"라고 하면 LLM은 **사람이 읽기 좋은 글**로 답합니다. 다음 노드(할 일 하나씩, 확인 필요 표시)는 그 글을 쓸 수 없습니다. 프로그램이 읽을 수 있는 **모양**으로 받아야 합니다.
|
||||
|
||||
| 받은 것 | 다음 노드가 쓸 수 있나 |
|
||||
| --- | --- |
|
||||
| "1. 이준호 – 템플릿 정리(금요일) …" | ✘ 글자 |
|
||||
| `{"actions": [{"owner": "이준호", "task": "…", "due": "금요일까지"}]}` (글자) | 읽으면 ✔ |
|
||||
| 같은 JSON에 ```` ```json ```` 가 붙은 글자 | 지우고 읽으면 ✔ |
|
||||
|
||||
## 2. 모양 알려 주기
|
||||
|
||||
```text
|
||||
You read Korean meeting notes and list every action item. Return only JSON, no code fence:
|
||||
{"actions": [{"owner": "person in charge or empty", "task": "the task in Korean", "due": "due date as written or empty"}]}
|
||||
Keep the order of the notes. Use only what the notes say: never invent an owner or a date.
|
||||
If there is no action item, return {"actions": []}.
|
||||
```
|
||||
|
||||
| 지시 | 이유 |
|
||||
| --- | --- |
|
||||
| 필드 이름까지 정한 JSON 모양 | 코드가 `owner`, `task`, `due`로 읽음 |
|
||||
| 회의록 순서 | 테스트가 `actions.0`을 봄 |
|
||||
| 없으면 비워 둘 것, 지어내지 말 것 | 담당 · 기한이 없는 할 일 |
|
||||
| 할 일이 없을 때의 모양 | `{"actions": []}` — 0개도 같은 모양 |
|
||||
|
||||
## 3. 코드로 읽기
|
||||
|
||||
```javascript
|
||||
const raw = String($json.output ?? '').trim().replace(/^```(json)?\s*|```$/g, '');
|
||||
let data = {};
|
||||
try { data = JSON.parse(raw); } catch (error) { data = {}; } // 읽지 못하면 멈추지 말고 빈 목록
|
||||
```
|
||||
|
||||
- 읽기는 **실패할 수 있습니다.** 실패하면 워크플로우가 멈추지 않고 빈 목록으로 갑니다. 대신 `count: 0`이 나오면 Executions에서 LLM이 돌려준 글을 확인합니다.
|
||||
|
||||
## 4. 빈칸은 코드가
|
||||
|
||||
| 누가 "미정"을 쓰나 | 결과 |
|
||||
| --- | --- |
|
||||
| LLM에게 "없으면 미정이라고 써" | LLM이 지시를 다르게 따를 수 있음 |
|
||||
| LLM은 비워 두고 **코드가** `미정` | 매번 같은 답 |
|
||||
|
||||
수업 준비 때 정답 워크플로우 (1회): `cases/lab02.json` 6/6 — 할 일 4개, 4번 담당 `미정`, 할 일 없는 회의 0개 (1.7초), 빈 회의록 400.
|
||||
|
||||
## 5. 하나 바꾸면 다른 것도 바뀐다
|
||||
|
||||
Lab 02 과제(참석자도 뽑기)를 수업 준비 때 해 보니:
|
||||
|
||||
| 지시문 | 참석자 | 할 일 |
|
||||
| --- | --- | --- |
|
||||
| JSON 모양에 `attendees`만 더함 | ✔ 김민지 · 이준호 · 박서연 | **3개** — "담당자를 다음 회의에서 정한다"가 빠짐 |
|
||||
| "담당자가 아직 정해지지 않은 할 일도 넣을 것" 추가 | ✔ | 4개, `lab02` 6/6 |
|
||||
|
||||
- 고친 곳(참석자)과 **상관없는 결과**가 바뀌었습니다. LLM은 지시문 전체를 한 번에 읽기 때문입니다.
|
||||
- 그래서 과제 테스트에 "할 일은 그대로" 케이스를 넣고, 원래 테스트도 **함께** 돌립니다. 이것이 오늘 가장 중요한 습관입니다.
|
||||
|
||||
## 정리
|
||||
|
||||
- [ ] LLM의 답은 글자: 모양을 정해 주고, 코드로 읽는다
|
||||
- [ ] 읽지 못하면 멈추지 말고 빈 목록, 빈칸은 코드가 채운다
|
||||
- [ ] 지시문을 고치면 원래 테스트도 다시 돌린다
|
||||
@@ -1,272 +0,0 @@
|
||||
# 2단계 · 데이터 파싱과 변환 노드
|
||||
|
||||
- 시간: 3~4교시 (3교시 끝에 Lab 02 예제 1, 4교시에 예제 2와 과제)
|
||||
- 학습 목표
|
||||
- LLM의 답은 JSON처럼 **보이는 글자**라는 것을 OUTPUT에서 구별할 수 있다.
|
||||
- Code 노드로 글자를 목록으로 파싱하고, 빈 값을 채우고, 실패 이유를 남길 수 있다.
|
||||
- 지시문과 JSON 스키마로 LLM에게 답의 모양을 알려 줄 수 있다.
|
||||
- 뽑을 정보를 하나 늘릴 때 고칠 곳(알려 주기 · 받기 · 꺼내기)을 찾을 수 있다.
|
||||
- Edit Fields와 표현식으로 간단한 변환을 할 수 있다.
|
||||
- 실습: [Lab 02 · LLM 답을 데이터로 바꾸기 (예제 2개 + 과제)](../../Last3Mon/66/LAB-02-BUILD-PARSE-TRANSFORM.md)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["1. LLM 답은 글자<br/>(예제 1)"] --> B["2. 파싱<br/>(예제 1)"] --> C["3. 모양 알려 주기<br/>(예제 2)"] --> D["4. 하나 더 뽑기<br/>(과제)"] --> E["5. Edit Fields"] --> F["6. 만들고 확인하는 순서"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. LLM의 답은 "글자"다 · Lab 02 예제 1
|
||||
|
||||
LLM에게 "JSON으로 답해"라고 하면 이런 답이 옵니다.
|
||||
|
||||
```text
|
||||
{"actions": [{"task": "신규 고객 문의 응답 템플릿 정리", "owner": "이준호", "due": "금요일"}, {"task": "회의실 예약 규칙 개선", "owner": "", "due": ""}]}
|
||||
```
|
||||
|
||||
사람 눈에는 목록처럼 보이지만, n8n에게는 **따옴표로 둘러싸인 긴 글자 한 줄**입니다. 글자 상태로는 `actions`를 꺼낼 수도, 할 일 개수를 셀 수도 없습니다.
|
||||
|
||||
| 상태 | n8n OUTPUT에서 보이는 모습 | 할 수 있는 것 |
|
||||
| --- | --- | --- |
|
||||
| **글자** (문자열) | `content` 한 칸에 긴 글자 | 읽기만 |
|
||||
| **데이터** (객체·목록) | Table 보기에서 `actions`가 줄줄이 펼쳐짐 | `$json.actions`, `count`, Split Out |
|
||||
|
||||
글자를 데이터로 바꾸는 일을 **파싱(parsing)** 이라고 합니다. 27일차 Structured Output에서 Pydantic으로 하던 일을 오늘은 Code 노드가 합니다.
|
||||
|
||||
예제 1에서는 LLM을 부르지 않고 위 글자를 `Fake LLM answer` 노드에 **직접 적어 넣고** 파싱부터 연습합니다. 돈도 시간도 들지 않고 매번 같은 결과가 나와서, 파싱만 따로 이해하기 좋습니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
T(["Start"]) --> F["Fake LLM answer<br/>content = 글자"] --> P["Parse actions<br/>글자 → 목록"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 파싱: 글자를 목록으로 · Lab 02 예제 1
|
||||
|
||||
`Parse actions`의 코드는 네 단계입니다. 한 줄씩 외울 필요는 없고 **번호의 뜻**만 알면 됩니다.
|
||||
|
||||
```javascript
|
||||
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에서 확인 |
|
||||
| --- | --- | --- |
|
||||
| ① | 앞 노드에서 글자를 꺼냄 (모델이 ```json 으로 감싸면 벗김) | `content` |
|
||||
| ② | 글자 → 데이터. 실패하면 빈 목록 + `error` | 끝 `}`를 지우면 `error: answer is not valid JSON` |
|
||||
| ③ | 할 일이 빈 칸인 것은 버림 | |
|
||||
| ④ | 담당자·기한이 비면 `미정` | 두 번째 할 일의 `owner`·`due`가 `미정` |
|
||||
|
||||
예제 2에서는 ①만 바뀝니다. 가짜 답 대신 진짜 LLM 답이 있는 곳 `choices[0].message.content`를 읽습니다.
|
||||
|
||||
### 2.1 어떤 답이 와도 같은 모양으로
|
||||
|
||||
| LLM 답 | 그대로 `JSON.parse` | 오늘 코드의 결과 |
|
||||
| --- | --- | --- |
|
||||
| `{"actions": [...]}` | 성공 | 그대로 |
|
||||
| ```` ```json {"actions": [...]} ``` ```` | 실패 | ①에서 감싼 표시를 벗기고 성공 |
|
||||
| `[{"task": ...}]` (목록만) | 성공 | 목록 그대로 사용 |
|
||||
| `할 일은 다음과 같습니다 ...` (문장) | 실패 | `{actions: [], count: 0, error: ...}` |
|
||||
| `{"actions": [{"task": "", ...}]}` | 성공 | 빈 할 일 버림 |
|
||||
| `{"owner": null}` | 성공 | `owner: "미정"` |
|
||||
|
||||
마지막 열이 모두 `{actions, count}` 모양입니다. 다음 노드는 어떤 답이 와도 같은 필드를 받습니다.
|
||||
|
||||
### 2.2 실패를 숨기지 않기
|
||||
|
||||
| 결과 | `count` | `error` | 뜻 |
|
||||
| --- | --- | --- | --- |
|
||||
| 할 일 없는 회의 (`no-action`) | 0 | 없음 | 정상 |
|
||||
| LLM 답이 깨짐 | 0 | `answer is not valid JSON` | 문제 → 실행 기록 확인 |
|
||||
|
||||
`error` 한 칸이 "정말 할 일이 없는 회의"와 "답이 깨진 경우"를 구별합니다. 76일차(예외 대응)에서 이런 경우를 모아 알림을 보내는 Error Workflow를 만듭니다.
|
||||
|
||||
---
|
||||
|
||||
## 3. LLM에게 답의 모양 알려 주기 · Lab 02 예제 2
|
||||
|
||||
예제 2는 예제 1의 앞에 **Webhook과 진짜 LLM**이 붙은 워크플로우를 가져와 실행합니다.
|
||||
|
||||
```mermaid
|
||||
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"]
|
||||
```
|
||||
|
||||
`LLM extract actions` 노드의 JSON 칸은 세 부분입니다. (실습 파일은 읽기 쉽게 여러 줄로 적혀 있습니다.)
|
||||
|
||||
```text
|
||||
{{ JSON.stringify({
|
||||
model: $('Settings').first().json.model,
|
||||
temperature: 0,
|
||||
response_format: { type: 'json_schema', json_schema: { name: 'reply', schema: { ← ① 스키마: 답의 모양
|
||||
type: 'object',
|
||||
properties: {
|
||||
actions: { type: 'array', items: { type: 'object',
|
||||
properties: { task: { type: 'string' }, owner: { type: 'string' }, due: { type: 'string' } },
|
||||
required: ['task', 'owner', 'due'] } },
|
||||
},
|
||||
required: ['actions'],
|
||||
} } },
|
||||
messages: [
|
||||
{ role: 'system', content: "Extract action items ... Use \"미정\" when ..." }, ← ② 지시문: 무엇을 찾을지
|
||||
{ role: 'user', content: $('Settings').first().json.notes }, ← ③ 회의록 원문
|
||||
],
|
||||
}) }}
|
||||
```
|
||||
|
||||
| 부분 | 하는 일 | 비유 |
|
||||
| --- | --- | --- |
|
||||
| ① 스키마 (`response_format`) | "이 칸들로만 답해라" — 답의 **모양**을 정함 | 빈칸이 정해진 답안지 |
|
||||
| ② 지시문 (`system`) | 무엇을 찾고, 없으면 무엇을 쓸지 | 문제 설명 |
|
||||
| ③ 회의록 (`user`) | 읽을 글 | 지문 |
|
||||
|
||||
### 3.1 스키마는 모양만 지킨다
|
||||
|
||||
| | 지시문만 | 지시문 + 스키마 |
|
||||
| --- | --- | --- |
|
||||
| 모양 | 가끔 어긋남 (설명 문장, ```json) | 정한 모양으로만 |
|
||||
| 필드 이름 | 가끔 `assignee`, `deadline` | 스키마 이름 그대로 |
|
||||
| **내용이 맞나** | 보장 안 함 | **보장 안 함** |
|
||||
|
||||
스키마를 써도 담당자를 잘못 찾거나 할 일을 빠뜨리는 것은 막지 못합니다. 4번 할 일의 기한 `10월 23일`(회의록에 직접 적히지 않은 추론)이 그 예입니다. 그래서 스키마를 써도 2절의 파싱·검증은 그대로 둡니다. 그리고 답은 여전히 **글자**(`choices[0].message.content`)로 옵니다.
|
||||
|
||||
Anthropic·Gemini에도 비슷한 구조화 출력 기능이 있고, 대체용 Ollama도 같은 `response_format`을 받습니다.
|
||||
|
||||
> **표현식 주의**: `{{ }}` 안에서 `}`와 `}`가 **붙으면** 표현식이 거기서 끝나 `invalid syntax`가 납니다. 실습 파일에서 `} }`처럼 띄어 쓴 곳은 일부러 띄운 것입니다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 뽑을 것을 하나 늘리려면 · Lab 02 과제
|
||||
|
||||
새 정보(예: 참석자)가 결과까지 오려면 데이터가 지나가는 길을 따라 **세 곳**이 모두 알아야 합니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["① 알려 주기<br/>스키마에 칸 추가"] --> B["② 부탁하기<br/>지시문에 한 문장"] --> L{{"LLM 답<br/>(글자)"}} --> C["③ 꺼내기<br/>파싱 코드의 return"] --> R["결과"]
|
||||
```
|
||||
|
||||
| 곳 | 빠뜨리면 |
|
||||
| --- | --- |
|
||||
| ① 스키마 | LLM이 그 칸을 만들지 않을 수 있음 |
|
||||
| ② 지시문 | 무엇을 넣을지 몰라 비우거나 엉뚱한 값 |
|
||||
| ③ 파싱 코드 | LLM 답에는 있어도 **새 상자를 만들며 버려짐** → 결과에 없음 |
|
||||
|
||||
③이 가장 잘 빠집니다. 파싱 코드는 `return [{ json: { actions, count } }]`처럼 **필요한 필드만 골라 새 상자를 만들기** 때문입니다. 어디서 사라졌는지 모를 때는 노드를 하나씩 열어 OUTPUT에 그 필드가 있는지 봅니다.
|
||||
|
||||
뽑을 것을 늘린 뒤에는 **원래 기대 결과(할 일 4건)도 다시** 확인합니다. 요청이 바뀌면 원래 뽑던 결과도 흔들릴 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 변환: Edit Fields와 표현식
|
||||
|
||||
코드 없이 필드를 만들거나 값을 계산할 때는 Edit Fields(Set)를 씁니다. 예제 2의 `Settings`가 그 예입니다.
|
||||
|
||||
| 하고 싶은 일 | 설정 예 |
|
||||
| --- | --- |
|
||||
| 입력값 꺼내기 | `notes` = `{{ $json.body.notes }}` (웹훅 입력은 `body` 안에) |
|
||||
| 비었으면 기본값 | `title` = `{{ $json.body.title \|\| '제목 없음' }}` |
|
||||
| 공백 다듬기 | `summary` = `{{ $json.choices[0].message.content.trim() }}` |
|
||||
| 조건으로 값 정하기 | `priority` = `{{ ['오늘', '내일', '긴급'].some(w => $json.due.includes(w)) ? '높음' : '보통' }}` |
|
||||
| 빈 목록 | `actions` (Array) = `{{ [] }}` |
|
||||
|
||||
- `{{ }}`가 들어간 값은 입력칸이 **Expression** 모드(초록색)여야 합니다. Fixed 모드면 글자 그대로 들어갑니다.
|
||||
- 필드 형식(String·Number·Array·Boolean)을 맞춥니다. 예를 들어 `count`가 글자 `"0"`이면 숫자 비교가 원하는 대로 되지 않을 수 있습니다.
|
||||
|
||||
| Edit Fields | Code |
|
||||
| --- | --- |
|
||||
| 필드 몇 개를 만들거나 바꿀 때 | 파싱, 여러 줄 검증, 실패 처리 (`try / catch`) |
|
||||
| 누구나 읽기 쉬움 | 자유롭지만 실수하기 쉬움 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 만들고 확인하는 순서
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["노드 하나 추가"] --> B["이름 짓기<br/>(표현식이 이름을 씀)"] --> C["설정"] --> D["실행"] --> E["OUTPUT 확인"]
|
||||
E -->|"다음 노드"| A
|
||||
```
|
||||
|
||||
- **한 번에 다 만들지 않습니다.** 노드 하나를 붙일 때마다 실행해 OUTPUT을 봅니다.
|
||||
- 노드 이름을 안내와 똑같이 짓습니다. `$('Settings')`처럼 표현식이 이름을 씁니다.
|
||||
- 같은 요청 파일(`weekly`·`no-action`·`empty`)로 매번 확인해야 고친 것의 효과를 비교할 수 있습니다.
|
||||
- Test URL은 **한 번에 한 요청**만 받습니다. 요청마다 `Listen for test event`를 다시 누릅니다.
|
||||
|
||||
---
|
||||
|
||||
## 7. 막혔을 때
|
||||
|
||||
| 증상 | 원인 | 해결 |
|
||||
| --- | --- | --- |
|
||||
| `invalid syntax` | 표현식 안에서 `}}`가 붙음 | `} }`로 띄어 쓰기 |
|
||||
| `401` | Credential이 `OpenAI (.env)`가 아님 | Authentication 확인 |
|
||||
| 값에 `{{ $json.body.notes }}` 글자가 그대로 | Fixed 모드 | Expression 모드 |
|
||||
| `actions`가 비고 `error`가 있음 | LLM 답이 JSON이 아님 | 앞 노드 OUTPUT의 `content` 확인 |
|
||||
| 새 필드가 결과에 없음 | 파싱 코드의 `return`에 안 넣음 | 4절 ③ |
|
||||
| 고치다 망가짐 | | 예제 2 파일을 다시 가져오기 |
|
||||
|
||||
---
|
||||
|
||||
## 8. Lab 02 안내
|
||||
|
||||
- 실습 안내: [Last3Mon/66/LAB-02-BUILD-PARSE-TRANSFORM.md](../../Last3Mon/66/LAB-02-BUILD-PARSE-TRANSFORM.md)
|
||||
|
||||
| 순서 | 시간 | 할 일 | 교안 |
|
||||
| --- | --- | --- | --- |
|
||||
| 예제 1 | 3교시 끝 15분 (함께) | `Fake LLM answer`(글자) → `Parse actions`(목록), 끝 `}`를 지워 `error` 보기 | 1~2절 |
|
||||
| 예제 2 | 4교시 15분 | `lab02-example2-meeting-part1.json` 가져오기 → `weekly`·`empty`·`no-action`, JSON 칸의 ①②③ 찾기 | 3절 |
|
||||
| 과제 | 4교시 25분 | 결과에 **참석자(`attendees`)** 추가 | 4절 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 스스로 확인하기
|
||||
|
||||
<details><summary>Q1. `Fake LLM answer`의 `content`와 `Parse actions`의 `actions`는 무엇이 다른가요?</summary>
|
||||
|
||||
`content`는 글자 한 줄이고, `actions`는 파싱해서 만든 목록입니다. 목록이어야 개수를 세고 Split Out으로 펼칠 수 있습니다.
|
||||
</details>
|
||||
|
||||
<details><summary>Q2. 스키마(`response_format`)를 지정했는데도 파싱·검증이 필요한 이유는?</summary>
|
||||
|
||||
스키마는 모양만 지켜 줍니다. 답은 여전히 글자로 오므로 파싱이 필요하고, 빈 값이나 잘못 찾은 내용은 검증으로 걸러야 합니다.
|
||||
</details>
|
||||
|
||||
<details><summary>Q3. 파싱에 실패하면 빈 목록을 돌려주면서도 `error`를 남기는 이유는?</summary>
|
||||
|
||||
흐름이 멈추지 않게 하면서도, "정말 할 일이 없는 회의"와 "LLM 답이 깨진 경우"를 구별하기 위해서입니다.
|
||||
</details>
|
||||
|
||||
<details><summary>Q4. 스키마와 지시문에 `attendees`를 넣었는데 결과에 없습니다. 어디를 보나요?</summary>
|
||||
|
||||
`LLM extract actions`의 OUTPUT에는 있는데 `Parse actions`의 OUTPUT에 없다면, 파싱 코드의 `return`이 `attendees`를 넣지 않은 것입니다. 노드를 앞에서부터 하나씩 열어 어디서 사라졌는지 찾습니다.
|
||||
</details>
|
||||
|
||||
<details><summary>Q5. 기한을 스키마에서 날짜 형식이 아니라 글자(`string`)로 둔 이유는?</summary>
|
||||
|
||||
회의록에는 "금요일", "다음 주 월요일"처럼 날짜가 아닌 표현이 많습니다. 날짜로 바꾸려면 회의 날짜를 아는 계산이 필요하므로, 필요하면 규칙으로 따로 합니다.
|
||||
</details>
|
||||
|
||||
## 정리
|
||||
|
||||
- LLM의 JSON 답은 **글자**입니다. Code 노드로 파싱해야 목록이 됩니다.
|
||||
- 파싱은 꺼내기 → 바꾸기(실패하면 `error`) → 빈 할 일 버리기 → `미정` 채우기, 그리고 항상 같은 모양으로 돌려줍니다.
|
||||
- 스키마는 답의 **모양**, 지시문은 **무엇을 찾을지**를 알려 줍니다. 스키마도 내용은 보장하지 않습니다.
|
||||
- 뽑을 것을 늘리면 **알려 주기(스키마) · 부탁하기(지시문) · 꺼내기(파싱)** 세 곳을 고칩니다.
|
||||
- 간단한 변환은 Edit Fields, 파싱·검증은 Code. 노드 하나마다 실행해 OUTPUT을 확인합니다.
|
||||
@@ -0,0 +1,85 @@
|
||||
# 3단계 · 반복·병렬 제어 구조와 검증
|
||||
|
||||
- 교시: 5~6교시 (14:00~15:50)
|
||||
- 실습: [Lab 03](../../Last3Mon/66/LAB-03-Q-ITEMS-AND-PARALLEL.md)
|
||||
- 이 단계가 끝나면: 목록을 항목 하나씩 다루고 다시 모으며(1 → N → 1), 빈 목록을 따로 처리하고, 서로 기다릴 필요 없는 일을 두 갈래로 나눌 수 있다. 어려운 테스트로 워크플로우를 완성한다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["1. 하나씩 · 모으기"] --> B["2. 빈 목록"] --> C["3. 두 갈래와 Merge"] --> D["4. 설계와 실행"] --> E["5. 테스트로 완성"]
|
||||
```
|
||||
|
||||
## 1. 하나씩 다루고 다시 모으기
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
P["Parse actions<br/>1 (목록 4개)"] --> SO["One item per action<br/>Split Out · 1 → 4"] --> M["Mark check<br/>Set · 항목마다"] --> C["Collect actions<br/>Aggregate · 4 → 1"] --> B["Build report"]
|
||||
```
|
||||
|
||||
| 노드 | 하는 일 |
|
||||
| --- | --- |
|
||||
| Split Out | 목록 필드(`actions`)를 항목 여러 개로 펼침 |
|
||||
| Set (Edit Fields) | **항목마다** 실행: `needs_check = owner나 due가 "미정"` |
|
||||
| Aggregate | 항목들을 다시 목록 하나로 |
|
||||
|
||||
Code 하나로도 같은 결과를 낼 수 있지만, 노드로 나누면 n8n 화면과 Executions에서 **어디서 몇 개가 되는지** 보입니다. 수업 준비 때 `cases/lab03.json` 5/5 (주간 회의 확인 필요 1개).
|
||||
|
||||
## 2. 빈 목록
|
||||
|
||||
할 일이 0개인 회의(`no-action`)를 펼치면 **항목이 0개**가 되어 그 뒤 노드가 실행되지 않고, 응답 노드에 닿지 않습니다.
|
||||
|
||||
| 막는 법 | |
|
||||
| --- | --- |
|
||||
| 펼치기 전에 If `count > 0` | 0개면 Set "No actions"(`count: 0`, `actions: []`)로 바로 응답 |
|
||||
|
||||
Lab 01 과제의 "빈 회의록 400"과 같은 생각: **비었을 때의 길**을 따로 둡니다.
|
||||
|
||||
## 3. 두 갈래와 Merge
|
||||
|
||||
요약과 할 일 뽑기는 서로의 결과가 필요 없습니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
H{"Has notes?"} --> S{{"Summarize"}} --> J["Join results<br/>(Merge, 입력 2)"]
|
||||
H --> E{{"Extract actions"}} --> J
|
||||
J --> P["Parse actions<br/>$('Summarize') · $('Extract actions')"]
|
||||
```
|
||||
|
||||
- 한 노드의 출력을 두 노드에 연결하면 갈래가 나뉩니다. Merge로 모은 뒤에는 각 결과를 **노드 이름으로** 읽습니다 (`$json`은 Merge의 결과).
|
||||
|
||||
## 4. 설계와 실행
|
||||
|
||||
수업 준비 때 같은 테스트를 두 판으로 잰 결과 (1회씩):
|
||||
|
||||
| | 차례로 | 두 갈래 |
|
||||
| --- | --- | --- |
|
||||
| `cases/lab03.json` | 5/5 | 5/5 |
|
||||
| 주간 회의 | 4.0초 | 4.2초 |
|
||||
| `vendor` 회의 | 2.1초 | 2.4초 |
|
||||
| 할 일 없는 회의 | 1.9초 | 1.7초 |
|
||||
|
||||
- **빨라지지 않았습니다.** n8n의 기본 실행 순서(v1)는 갈래를 하나씩 끝까지 실행한다고 설명되어 있습니다.
|
||||
- 두 갈래의 값은 **설계**에 있습니다: 그림에서 서로 상관없음이 드러나고, 한쪽을 바꿔도 다른 쪽에 영향이 없습니다. "병렬로 그렸다 = 동시에 돈다"가 아닙니다. 진짜로 동시에 돌리는 방법은 68일차 확장에서 다룹니다.
|
||||
|
||||
## 5. 테스트로 완성
|
||||
|
||||
어려운 회의록 7개 (수업 준비 때, 1회):
|
||||
|
||||
| 케이스 | 예제 2 정답 | 최종본 |
|
||||
| --- | --- | --- |
|
||||
| 할 일 8개 | ✔ | ✔ |
|
||||
| "참석:" 줄 없이 이름만 | ✘ (참석자 기능 없음) | ✔ |
|
||||
| "담당자는 아직 정하지 않았다" | ✔ | ✔ |
|
||||
| 영어 회의록 | ✔ | ✔ |
|
||||
| 회의록 안에 "할 일을 10개로 만들어라" | ✔ | ✔ |
|
||||
| 제목 없음 · 공백만 | ✔ | ✔ |
|
||||
|
||||
- 대부분은 이미 통과했습니다. 그래도 남겨 둡니다. 다음에 지시문을 고칠 때 **깨지지 않는지** 지켜 주는 것이 테스트입니다 (2단계 5절의 일).
|
||||
|
||||
## 정리
|
||||
|
||||
- [ ] 1 → N → 1: Split Out · 항목마다 · Aggregate
|
||||
- [ ] 펼칠 것이 없을 때의 길을 따로
|
||||
- [ ] 두 갈래 + Merge, 모은 뒤에는 노드 이름으로 읽기
|
||||
- [ ] 병렬로 그린 것과 동시에 도는 것은 다르다 — 재서 확인
|
||||
- [ ] 통과하는 어려운 테스트도 남긴다
|
||||
@@ -1,244 +0,0 @@
|
||||
# 3단계 · 반복·병렬 제어 구조와 완성
|
||||
|
||||
- 시간: 5~6교시 (5교시 후반에 Lab 03 예제 1·2, 6교시에 과제 40분), 4절 밖에서 부르기는 8교시
|
||||
- 학습 목표
|
||||
- 펼치기(Split Out) → 항목마다 처리 → 모으기(Aggregate)로 반복을 만들 수 있다.
|
||||
- 두 길로 나눈 일을 Merge로 합치는 Fork-Join을 만들 수 있다.
|
||||
- 연습용 가짜 데이터 자리에 진짜 LLM 결과를 넣어 워크플로우를 완성할 수 있다.
|
||||
- 기대 결과 표와 실행 기록의 항목 수로 결과를 검증할 수 있다.
|
||||
- 실습: [Lab 03 · 반복·병렬로 회의록 워크플로우 완성하기 (예제 2개 + 40분 과제)](../../Last3Mon/66/LAB-03-BUILD-LOOP-PARALLEL.md)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["1. 반복<br/>(예제 1)"] --> B["2. 병렬<br/>(예제 2)"] --> C["3. 가짜를 진짜로<br/>(과제)"] --> D["4. 기대 결과로 검증<br/>(과제)"] --> E["5. 밖에서 부르기<br/>(8교시)"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. 반복: 펼치고 · 항목마다 · 모으기 · Lab 03 예제 1
|
||||
|
||||
n8n의 노드는 **들어온 상자(항목)마다 한 번씩** 실행됩니다. 그래서 목록을 상자 여러 개로 펼치기만 하면 반복이 됩니다. 별도의 "for 문" 노드가 필요 없습니다.
|
||||
|
||||
예제 1은 할 일 3건이 든 가짜 목록으로 연습합니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
S["Sample actions<br/>상자 1개<br/>actions [3]"] -->|"1"| SO["One item per action<br/>Split Out"]
|
||||
SO -->|"3"| N["Normalize action<br/>Edit Fields<br/>(3번 실행)"]
|
||||
N -->|"3"| AG["Collect actions<br/>Aggregate"]
|
||||
AG -->|"1"| OUT["상자 1개<br/>actions [3] + priority"]
|
||||
```
|
||||
|
||||
| 노드 | 설정 | 하는 일 |
|
||||
| --- | --- | --- |
|
||||
| Split Out | Fields To Split Out: `actions` | 목록 → 할 일 하나씩 상자로 (**1 → 3**) |
|
||||
| Edit Fields | `task`, `owner`, `due`, `priority` | 상자마다 실행 (3번) |
|
||||
| Aggregate | All Item Data, 결과 필드 `actions` | 상자들 → 다시 목록 하나 (**3 → 1**) |
|
||||
|
||||
**펼쳤으면 반드시 모은다.** 이것이 반복의 짝입니다. 오후 과제에서는 진짜 회의록이라 같은 모양이 **1 → 4 → 1**이 됩니다.
|
||||
|
||||
### 1.1 모으지 않으면? (5교시 시연, 수업 전 실측)
|
||||
|
||||
| 틀린 곳 | `weekly` 결과 |
|
||||
| --- | --- |
|
||||
| `Normalize action` → 바로 응답 | HTTP 200인데 **첫 할 일 하나만** — 응답 노드는 첫 상자로 한 번만 답함 |
|
||||
| Aggregate 없이 `Build report` | HTTP 500 `Error in workflow` — 목록을 기대했는데 할 일 하나짜리 상자가 옴 |
|
||||
|
||||
첫 번째가 더 위험합니다. 오류 없이 **조용히 틀린 답**이 나갑니다. 그래서 실행 기록에서 노드마다 **항목 수**를 읽는 습관을 들입니다.
|
||||
|
||||
### 1.2 상자마다 실행될 때 `$json`이 가리키는 것
|
||||
|
||||
| 위치 | `$json` | 다른 값이 필요하면 |
|
||||
| --- | --- | --- |
|
||||
| Split Out 뒤 (상자마다) | 할 일 **하나** (`task`, `owner`, `due`) | 회의 제목: `$('Settings').first().json.title` |
|
||||
| Aggregate 뒤 (한 번) | 모은 결과 `{actions: [...]}` | 요약: `$('Join results').first().json.summary` |
|
||||
|
||||
`$('노드 이름')`은 "그 노드의 OUTPUT을 가져와"라는 뜻입니다. 노드 이름을 안내와 똑같이 지어야 하는 이유입니다.
|
||||
|
||||
### 1.3 Loop Over Items는 언제?
|
||||
|
||||
| 상황 | 선택 |
|
||||
| --- | --- |
|
||||
| 상자마다 같은 변환만 (오늘) | Split Out + 일반 노드 |
|
||||
| 상자마다 외부 API·LLM을 부르는데 한꺼번에 보내면 호출 제한에 걸림 | **Loop Over Items**로 몇 개씩 나눠 차례로 |
|
||||
|
||||
반복 안에 LLM을 두면 **시간과 비용이 항목 수에 비례**합니다 (75일차 비용 관리).
|
||||
|
||||
---
|
||||
|
||||
## 2. 병렬: 나누고 · 합치기 · Lab 03 예제 2
|
||||
|
||||
할 일 찾기와 요약은 서로의 결과가 필요 없습니다. 한 노드에서 **두 길로 나누고(Fork)**, 둘 다 끝나면 **합칩니다(Join)**. 합치는 노드가 Merge입니다.
|
||||
|
||||
예제 2는 예제 1에 가짜 요약 길을 하나 더 붙입니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
T(["Start"]) --> S["Sample actions → … → Collect actions<br/>{actions}"] -->|"Input 1"| J["Join results<br/>Merge · Combine · Position"]
|
||||
T --> SS["Sample summary<br/>{summary}"] -->|"Input 2"| J
|
||||
J --> O["상자 1개<br/>{actions, summary}"]
|
||||
```
|
||||
|
||||
| Merge 설정 | 뜻 |
|
||||
| --- | --- |
|
||||
| Mode `Combine` | 두 입력의 상자를 하나로 합침 |
|
||||
| Combine By `Position` | 1번 입력의 첫 상자 + 2번 입력의 첫 상자 → 상자 하나 |
|
||||
|
||||
- Merge는 **두 입력이 모두 올 때까지** 기다립니다.
|
||||
- n8n은 두 길을 동시에가 아니라 **차례로** 실행합니다. 그래도 그림에서 두 일이 서로 독립이라는 것이 드러나고, 한쪽을 고쳐도 다른 쪽에 영향이 없습니다.
|
||||
|
||||
### 2.1 Merge의 다른 모드
|
||||
|
||||
| 모드 | 하는 일 | 쓰는 때 |
|
||||
| --- | --- | --- |
|
||||
| Append | 두 입력을 이어 붙임 (2 + 3 = 5개) | 같은 모양 목록 두 개 |
|
||||
| **Combine · By Position** | 같은 순서끼리 합침 (1 + 1 = 1개) | **오늘** |
|
||||
| Combine · By Matching Fields | 같은 값(`customer_id` 등)끼리 짝 | 주문 목록 + 고객 정보 |
|
||||
| Choose Branch | 한쪽만 내보냄 | 한쪽 결과만 필요할 때 |
|
||||
|
||||
### 2.2 한 길이 실패하면
|
||||
|
||||
요약 길의 LLM 호출이 실패하면, 할 일 찾기가 성공해도 Merge가 기다리다 **결과가 나오지 않습니다.** 길이 늘수록 실패할 곳도 늘어납니다. 대처는 72일차(재시도)와 76일차(Error Workflow)에서 배웁니다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 가짜를 진짜로 바꾸기 · Lab 03 과제
|
||||
|
||||
예제 1·2는 가짜 데이터였습니다. 과제는 그 자리에 Lab 02의 **진짜 LLM 결과**를 넣어 `66 Meeting notes (my)`를 완성합니다.
|
||||
|
||||
| 예제의 가짜 | 과제의 진짜 | 하는 일 |
|
||||
| --- | --- | --- |
|
||||
| `Sample actions` | `LLM extract actions` → `Parse actions` (Lab 02) | 할 일 목록 |
|
||||
| `Sample summary` | `LLM summarize` → `Keep summary` (새로) | 요약 |
|
||||
| `Join results` | 그대로 복사 | 두 결과 합치기 |
|
||||
| Split Out → Normalize → Aggregate | 그대로 복사 | 할 일마다 우선순위, 다시 모으기 |
|
||||
| (없음) | `Any actions?`, `Build report`, `No actions` (새로) | 할 일이 있을 때 / 없을 때 |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
H{"Has notes?"} -->|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["Split Out → Normalize → Aggregate"] --> B["Build report"] --> R["Send result"]
|
||||
A -->|false| NA["No actions"] --> R
|
||||
classDef new stroke-width:3px
|
||||
class L2,K,J,A,SO,B,NA new
|
||||
```
|
||||
|
||||
### 3.1 순서에 담긴 이유
|
||||
|
||||
| 질문 | 답 |
|
||||
| --- | --- |
|
||||
| 왜 Merge를 `Any actions?` **앞**에 두나? | 할 일이 없는 회의에도 **요약**은 있어야 하니까. 합친 뒤에 나누면 두 길 모두 `summary`를 가짐 |
|
||||
| 왜 `Any actions?`가 Split Out **앞**에 있나? | 할 일이 0건이면 펼칠 것도, 정리문에 쓸 것도 없음 → 펼치기 전에 길을 나눠 `할 일 없음` 결과를 따로 만듦 |
|
||||
| 정리문(`report_text`)은 왜 코드로? | 매번 같은 모양이어야 메신저·메일에 붙이기 좋고, 시간·비용이 들지 않음 |
|
||||
|
||||
### 3.2 두 길을 같은 필드로 끝내기
|
||||
|
||||
| 필드 | 할 일 정리 (`Build report`) | 할 일 없음 (`No actions`) |
|
||||
| --- | --- | --- |
|
||||
| `title` | 회의 제목 | 회의 제목 |
|
||||
| `route` | `할 일 정리` | `할 일 없음` |
|
||||
| `count` | 4 | 0 |
|
||||
| `summary` | 요약 | 요약 |
|
||||
| `actions` | 할 일 4개 (+ `priority`) | `[]` |
|
||||
| (과제) `need_check` | 확인 필요 건수 | 0 |
|
||||
|
||||
과제의 마지막 단계 **확인 필요**는 Lab 01 과제에서 설계한 것입니다. 1단계 6절의 질문 "하나마다? 모은 뒤? 모든 길?"을 다시 꺼내 봅니다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 기대 결과로 검증하기 · Lab 03 과제
|
||||
|
||||
"오류가 안 났다"는 "맞았다"가 아닙니다. 두 가지로 확인합니다.
|
||||
|
||||
**① 기대 결과 표와 비교** (수업 전 측정)
|
||||
|
||||
| 요청 | 기대 결과 | 처리 시간 |
|
||||
| --- | --- | --- |
|
||||
| `weekly` (할 일 4건) | `할 일 정리`, `count: 4`, 오늘 마감 1건 `높음`, `need_check: 1` | 약 3~4초 |
|
||||
| `no-action` (결정 사항 없음) | `할 일 없음`, `count: 0`, `need_check: 0` | 약 2초 |
|
||||
| `empty` (빈 회의록) | HTTP 400 | 1초 미만 |
|
||||
|
||||
**② 실행 기록의 항목 수 읽기**: `Join results` 1 → `One item per action` 4 → `Normalize action` 4 → `Collect actions` 1 → `Build report` 1
|
||||
|
||||
수업 준비 때 실제로 본 **오류 없이 틀리는** 모양입니다.
|
||||
|
||||
| 증상 | 원인 |
|
||||
| --- | --- |
|
||||
| 할 일 하나만 옴 | Aggregate를 거치지 않음 (1.1절) |
|
||||
| `no-action`에서 HTTP 200인데 빈 응답 | `Any actions?`의 false 길이 끊김 |
|
||||
| `need_check: 4` | `check`의 형식이 Boolean이 아니라 String → 글자 `"false"`도 참으로 셈 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 밖에서 부르기: 설정 한 줄로 대상 바꾸기 · 8교시
|
||||
|
||||
65일차의 플랫폼 전환 실행기로 오늘의 업무 `meeting`도 부를 수 있습니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
APP["call('meeting', {title, notes})"] --> CFG["platforms.json<br/>meeting → n8n webhook · miniflow flow"]
|
||||
CFG --> N8["n8n<br/>/webhook/my-meeting-actions"]
|
||||
CFG --> MF["miniflow<br/>flows/08-meeting-actions.json"]
|
||||
```
|
||||
|
||||
- 내가 만든 워크플로우를 부르려면 `platforms.json`의 웹훅 경로 **한 줄만** `my-meeting-actions`로 바꿉니다. 호출 코드는 그대로입니다.
|
||||
- n8n이 멈추면 같은 업무를 `miniflow`의 `08` 워크플로우가 이어받습니다. 이것이 되려면 두 플랫폼이 **같은 입력(`title`, `notes`)과 같은 결과 필드**를 지켜야 합니다.
|
||||
|
||||
| 하는 일 | n8n (오늘 만든 것) | miniflow `08-meeting-actions.json` |
|
||||
| --- | --- | --- |
|
||||
| 입력 | Webhook `body.title`, `body.notes` | `-i title=... -i notes=...` |
|
||||
| 할 일 찾기 | HTTP Request → OpenAI (스키마) | LLM 노드 |
|
||||
| 파싱·검증 | Code `Parse actions` | `parse_actions` Tool |
|
||||
| 결과 | `{title, route, count, summary, actions, ...}` | 같은 필드 |
|
||||
|
||||
---
|
||||
|
||||
## 6. Lab 03 안내
|
||||
|
||||
- 실습 안내: [Last3Mon/66/LAB-03-BUILD-LOOP-PARALLEL.md](../../Last3Mon/66/LAB-03-BUILD-LOOP-PARALLEL.md)
|
||||
|
||||
| 순서 | 시간 | 할 일 | 교안 |
|
||||
| --- | --- | --- | --- |
|
||||
| 예제 1 | 5교시 끝 12분 | 가짜 할 일 3건 펼치고 모으기, 항목 수 1 → 3 → 3 → 1 | 1절 |
|
||||
| 예제 2 | 5교시 끝 10분 | `Sample summary`를 Merge(Position)로 합치기 | 2절 |
|
||||
| **과제** | 6교시 40분 | 예제 노드를 복사해 내 워크플로우 완성 + **확인 필요** | 3~4절 |
|
||||
| 마무리 | 8교시 함께 | `platforms.json` 한 줄 바꾸고 `miniflow call meeting` | 5절 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 스스로 확인하기
|
||||
|
||||
<details><summary>Q1. `One item per action`이 4개, `Collect actions`가 1개입니다. 그 사이의 `Normalize action`은 몇 번 실행되었나요?</summary>
|
||||
|
||||
4번입니다. 대부분의 n8n 노드는 들어온 항목마다 한 번씩 실행됩니다.
|
||||
</details>
|
||||
|
||||
<details><summary>Q2. `Build report`에서 회의 제목을 `$json.title`이 아니라 `$('Settings').first().json.title`로 꺼내는 이유는?</summary>
|
||||
|
||||
`Build report` 바로 앞은 Aggregate이고, 그 결과에는 `actions`만 있습니다. 제목은 앞쪽 `Settings` 노드에 있으므로 이름으로 꺼냅니다.
|
||||
</details>
|
||||
|
||||
<details><summary>Q3. Merge를 `Any actions?` 앞에 두는 이유는?</summary>
|
||||
|
||||
할 일이 없는 회의에도 요약이 필요하기 때문입니다. 먼저 합치면 할 일 정리 길과 할 일 없음 길 모두 `summary`를 가집니다.
|
||||
</details>
|
||||
|
||||
<details><summary>Q4. 주문 목록과 고객 정보를 `customer_id`로 합치려면 Merge를 어떻게 설정하나요?</summary>
|
||||
|
||||
Combine · By Matching Fields로 `customer_id`를 기준으로 합칩니다. By Position은 순서가 같을 때만 맞습니다.
|
||||
</details>
|
||||
|
||||
<details><summary>Q5. 요약 길의 LLM 호출이 실패하면 할 일 찾기가 성공해도 응답이 나오지 않는 이유는?</summary>
|
||||
|
||||
Merge는 두 입력이 모두 와야 합치기 때문입니다. 병렬 길이 늘면 실패할 곳도 늘어납니다.
|
||||
</details>
|
||||
|
||||
## 정리
|
||||
|
||||
- 반복은 **펼치고(Split Out) · 항목마다 · 모으기(Aggregate)**. 펼쳤으면 반드시 모읍니다.
|
||||
- 병렬은 **나누고 · Merge로 합치기**. Merge는 두 입력을 모두 기다립니다.
|
||||
- 예제의 가짜 데이터 자리에 진짜 LLM 결과를 넣으면 워크플로우가 완성됩니다.
|
||||
- 모든 길이 같은 필드로 끝나게 하고, **기대 결과 표와 항목 수**로 검증합니다. 오류가 없어도 틀릴 수 있습니다.
|
||||
- 같은 입력·같은 결과 형식을 지키면 설정 한 줄로 호출 대상을 바꾸고, 플랫폼 전환도 그대로 됩니다.
|
||||
@@ -0,0 +1,83 @@
|
||||
# 7·8교시 · 바이브 코딩 (`turl`)
|
||||
|
||||
- 교시: 7교시 16:00~16:50 (각자 만들기), 8교시 17:00~17:50 (발표 · 정리 · 강사 바이브 코딩 · 팁 · 예고)
|
||||
- 과제: [VIBE-Q-TURL](../../Last3Mon/66/VIBE-Q-TURL.md)
|
||||
- **오늘부터 7·8교시는 퀴즈 대신 매일 프로그램 하나를 바이브 코딩으로 만듭니다.** 오늘은 `turl`: curl을 터미널 화면(TUI)으로. 오늘 만든 `my-meeting` 웹훅을 이 도구로 불러 봅니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["7교시<br/>각자 만들기 50분"] --> B["발표<br/>10분"] --> C["강사 정리<br/>10분"] --> D["강사 바이브 코딩<br/>15분"] --> E["보충 · 팁<br/>10분"] --> F["67일차 예고<br/>5분"]
|
||||
```
|
||||
|
||||
## 0. 7·8교시 진행 방식 (매일 같음)
|
||||
|
||||
1. 수강생이 직접 CLI 코딩 에이전트로 특정 기능의 프로그램 만들기 (7교시)
|
||||
2. 수강생 한두 명이 만든 과정을 발표해서 공유
|
||||
3. 강사가 이후 필요한 내용 정리
|
||||
4. 강사가 더 필요한 내용으로 바이브 코딩 진행
|
||||
5. 부족한 내용 보충과 팁
|
||||
|
||||
## 1. 7교시 · 각자 만들기 (50분)
|
||||
|
||||
| 시간 | 단계 | 확인 |
|
||||
| --- | --- | --- |
|
||||
| 16:00~16:03 | 0. 준비 (`~/vibe/turl`, `git init`, 에이전트 실행) | |
|
||||
| 16:03~16:15 | 1. 글자로 보내기 (R1) | `turl send POST .../my-meeting --body-file ...`, `pytest` |
|
||||
| 16:15~16:30 | 2. TUI (R1 · R2) | 화면에서 보내기, 파일에서 Body |
|
||||
| 16:30~16:43 | 3. 기록 · curl · 오류 (R3 · R4 · R5) | 껐다 켜기, curl 명령 실행, 꺼진 주소 |
|
||||
| 16:43~16:50 | 4. 정리 · 발표 메모 | |
|
||||
|
||||
| 볼 것 | 막혔을 때 한마디 |
|
||||
| --- | --- |
|
||||
| 한 번에 다 만들어 달라고 했나 | "1단계만 먼저. 글자로 보내지는지 보고 다음으로" |
|
||||
| 실행해 보지 않고 다음 요청 | "에이전트가 됐다고 해도 직접 `uv run turl ...`" |
|
||||
| 테스트가 진짜 서버를 부름 | "가짜 서버로 테스트하게 해 달라고 하세요" |
|
||||
| 단계마다 commit 안 함 | "망가지면 `git diff`, `git checkout .`으로 돌아갈 곳이 필요" |
|
||||
|
||||
수업 준비 때 turl을 미리 만들어 보지는 않았습니다. 강사 바이브 코딩(4절)에서 걸린 시간을 재어 정답본(`VIBE-A-TURL.md`)에 남깁니다.
|
||||
|
||||
## 2. 8교시 · 발표 (10분)
|
||||
|
||||
- 1~2명. 7교시에 돌면서 **막혔다가 스스로 푼 사람**을 먼저 고릅니다 (완성도보다 과정).
|
||||
- 3분 발표 · 2분 질문: 잘 먹힌 요청문 / 안 먹힌 요청문 / 에이전트가 틀린 곳과 알아챈 방법 / 시연.
|
||||
|
||||
## 3. 강사 정리 (10분)
|
||||
|
||||
| | n8n 회의록 워크플로우 | turl |
|
||||
| --- | --- | --- |
|
||||
| 규칙 파일 | `AGENTS.md` (환경 · 검증된 노드 · 계약 · 금지) | 각자 만든 `AGENTS.md`/`CLAUDE.md` (없으면 이번에 만들기) |
|
||||
| 작게 나누기 | 예제 1 → 예제 2 → 과제 | 1단계 → 2단계 → 3단계 |
|
||||
| 확인 도구 | `flow.py check · push · test` | `uv run turl`, `pytest` |
|
||||
| 기대 결과 | `cases/*.json` | R1~R5 확인 표, 테스트 |
|
||||
| 바꿔 끼우기 | Chat Model (OpenAI ↔ Ollama) | 진짜 서버 ↔ 가짜 서버 |
|
||||
| 비밀 지키기 | Credential id만, 키 없음 | 기록에서 비밀 헤더 가리기 |
|
||||
|
||||
## 4. 강사 바이브 코딩 (15분)
|
||||
|
||||
| 주제 | 요청문 요지 | 보여 줄 것 |
|
||||
| --- | --- | --- |
|
||||
| 프로젝트 규칙 파일 | "이 프로젝트의 AGENTS.md를 만들어: 구조, 실행·테스트 명령, 규칙" | 다음 요청부터 같은 실수를 안 함 |
|
||||
| 요청 모음 | "requests/ 폴더의 *.json 을 목록으로 보여 주고 고르면 Body로" | Lab의 요청 파일을 그대로 씀 |
|
||||
| 같은 요청 N번 | "같은 요청을 10번 보내 시간 목록 출력" | 72일차 `tbench`의 씨앗 |
|
||||
|
||||
걸린 시간과 결과를 `Last3Mon/66/VIBE-A-TURL.md`에 적어 둡니다.
|
||||
|
||||
## 5. 보충 · 팁 (10분)
|
||||
|
||||
| 팁 | 설명 |
|
||||
| --- | --- |
|
||||
| 요청문은 "목표 · 범위 · 확인 방법" | "1단계만, TUI는 아직, `uv run turl send ...`로 확인" |
|
||||
| 에이전트가 끝났다고 하면 **내가 실행** | 에이전트의 보고는 주장, 실행 결과가 증거 |
|
||||
| 실패를 그대로 붙여 주기 | 오류 메시지 · 보고서 파일 경로를 요약하지 말고 그대로 |
|
||||
| 3번 고쳐도 안 되면 방향 바꾸기 | 요청을 더 작게 나누거나, 다른 방법을 물어보기 |
|
||||
| 단계마다 `git commit` | 에이전트가 망가뜨려도 돌아갈 곳 |
|
||||
|
||||
## 6. 67일차 예고 (5분)
|
||||
|
||||
- 오늘: 정해진 흐름(요약 → 할 일 → 하나씩 → 모으기). 내일: **Agent가 Tool을 골라** 사내 데이터(FAQ, 접수 현황)로 답하고, 결과를 검증합니다.
|
||||
- 바이브 코딩: `tman` (tldr 한국어 + man을 탭으로 보여 주고 설명을 번역하는 TUI). 오늘 `turl`은 지우지 마세요. 내일 웹훅을 부를 때 씁니다.
|
||||
|
||||
## 확인 (강사)
|
||||
|
||||
- [ ] 7교시 시작 전 `~/vibe/turl` 만드는 명령을 화면에 띄웠다.
|
||||
- [ ] 발표자 1~2명, 강사 정리 표, 강사 바이브 코딩 1개(시간 기록), 팁, 예고.
|
||||
+27
-20
@@ -1,40 +1,47 @@
|
||||
# 66일차 · 단일 업무 자동화 워크플로우 구현
|
||||
# 66일차 · 단일 업무 자동화 워크플로우 구현 (바이브 코딩)
|
||||
|
||||
- 일자: 2026-10-16 (66일차)
|
||||
- 교과목: 노코드 멀티 에이전트 워크플로우 설계 및 운영
|
||||
- 단원: AI 워크플로우 설계 및 구현
|
||||
- 학습 내용: 노코드 Workflow 도구 활용, 단일 업무 자동화 Workflow 구현
|
||||
- 실습 폴더: [Last3Mon/66](../../Last3Mon/66/README.md)
|
||||
- 실습 환경: 65일차 n8n 2.42.6 + OpenAI API(`gpt-4.1-mini`, Credential `OpenAI (.env)`) 그대로 사용, Ollama는 대체용
|
||||
- **실습 준비: [00-DAY66-PREP.md](00-DAY66-PREP.md) (수업 전날까지 1~2절 완료)**
|
||||
- 실습 환경: 65일차 n8n 2.42.6 + OpenAI API(`gpt-4.1-mini`, Credential `OpenAI (.env)`) + **CLI 코딩 에이전트**(Codex · Claude Code · Antigravity 중 하나)
|
||||
- **실습 준비: [00-DAY66-PREP.md](00-DAY66-PREP.md) (수업 전날까지 1~3절 완료, CLI 코딩 에이전트 설치·로그인 포함)**
|
||||
|
||||
## 오늘부터 바뀌는 수업 방식
|
||||
|
||||
| 전 (64·65일차) | 오늘부터 |
|
||||
| --- | --- |
|
||||
| n8n 화면에서 노드를 끌어다 놓고 연결 | **CLI 코딩 에이전트**에게 요청문을 주고 워크플로우 JSON을 만들게 함 |
|
||||
| 노드 설정을 하나씩 입력 | 나는 **결과 · 요청문 · 기대 결과(테스트)** 를 쓰고, 실행 결과를 확인 |
|
||||
| 7교시 퀴즈 | 7·8교시 **바이브 코딩**: 매일 작은 프로그램 하나 만들기 · 발표 · 정리 |
|
||||
|
||||
## 오늘의 시간표
|
||||
|
||||
| 교시 | 시간 | 내용 | 자료 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 09:00~09:50 | 오늘의 개요 · 1단계 시작 | [00-DAY66-OVERVIEW.md](00-DAY66-OVERVIEW.md), [1단계](01-STAGE1-DESIGN-AND-DATA-FLOW.md) |
|
||||
| 2 | 10:00~10:50 | 1단계 · 단일 업무 설계와 데이터 흐름, Lab 01 | [1단계](01-STAGE1-DESIGN-AND-DATA-FLOW.md), [Lab 01](../../Last3Mon/66/LAB-01-DESIGN-DATA-FLOW.md) |
|
||||
| 3 | 11:00~11:50 | 2단계 · LLM JSON 출력, 파싱과 변환, Lab 02 예제 1 | [2단계](02-STAGE2-PARSE-AND-TRANSFORM.md), [Lab 02](../../Last3Mon/66/LAB-02-BUILD-PARSE-TRANSFORM.md) |
|
||||
| - | 12:00~13:00 | 점심 | |
|
||||
| 4 | 13:00~13:50 | 2단계 · Lab 02 예제 2와 과제 (참석자 뽑기) | [Lab 02](../../Last3Mon/66/LAB-02-BUILD-PARSE-TRANSFORM.md) |
|
||||
| 5 | 14:00~14:50 | 3단계 · 반복·병렬 제어 구조와 완성, Lab 03 예제 1·2 | [3단계](03-STAGE3-LOOP-PARALLEL-AND-FINISH.md), [Lab 03](../../Last3Mon/66/LAB-03-BUILD-LOOP-PARALLEL.md) |
|
||||
| 6 | 15:00~15:50 | 3단계 · Lab 03 과제 40분 (내 워크플로우 완성 + 확인 필요) · 풀이 | [Lab 03](../../Last3Mon/66/LAB-03-BUILD-LOOP-PARALLEL.md) |
|
||||
| 7 | 16:00~16:50 | 퀴즈 (A·B·C 중 선택, 20분) · 풀이 | [퀴즈와 정리](04-QUIZ-AND-WRAPUP.md) |
|
||||
| 8 | 17:00~17:50 | 오늘의 정리 · Python에서 부르기(함께) · 다음 시간 예고 · 복습 과제 | [퀴즈와 정리](04-QUIZ-AND-WRAPUP.md) |
|
||||
| 1 | 09:00~09:50 | 오늘의 개요 · 1단계 · 바이브 코딩으로 n8n 만들기, 결과 먼저 | [개요](00-DAY66-OVERVIEW.md), [1단계](01-STAGE1-VIBE-N8N-AND-RESULT-FIRST.md) |
|
||||
| 2 | 10:00~10:50 | 1단계 · Lab 01 (결과 먼저, 첫 요청문, 과제: 빈 회의록·제목 없음) | [Lab 01](../../Last3Mon/66/LAB-01-Q-RESULT-FIRST.md) |
|
||||
| 3 | 11:00~11:50 | 2단계 · LLM JSON 출력, 파싱과 변환, Lab 02 예제 1·2 | [2단계](02-STAGE2-JSON-AND-PARSE.md), [Lab 02](../../Last3Mon/66/LAB-02-Q-JSON-AND-PARSE.md) |
|
||||
| 4 | 12:00~12:50 | 2단계 · Lab 02 과제 35분 (참석자도 뽑기) · 풀이 | [Lab 02](../../Last3Mon/66/LAB-02-Q-JSON-AND-PARSE.md) |
|
||||
| - | 13:00~14:00 | 점심 | |
|
||||
| 5 | 14:00~14:50 | 3단계 · 반복·병렬 제어 구조와 검증, Lab 03 예제 1·2 | [3단계](03-STAGE3-ITEMS-PARALLEL-AND-VERIFY.md), [Lab 03](../../Last3Mon/66/LAB-03-Q-ITEMS-AND-PARALLEL.md) |
|
||||
| 6 | 15:00~15:50 | 3단계 · Lab 03 과제 40분 (어려운 회의록으로 완성) · 풀이 | [Lab 03](../../Last3Mon/66/LAB-03-Q-ITEMS-AND-PARALLEL.md) |
|
||||
| 7 | 16:00~16:50 | 바이브 코딩 · `turl` 만들기 (각자) | [VIBE-Q-TURL](../../Last3Mon/66/VIBE-Q-TURL.md), [진행](04-VIBE-CODING.md) |
|
||||
| 8 | 17:00~17:50 | 바이브 코딩 발표 · 정리 · 강사 바이브 코딩 · 팁 · 67일차 예고 | [진행](04-VIBE-CODING.md) |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
P["개요"] --> S1["1단계<br/>설계·데이터 흐름"] --> S2["2단계<br/>파싱·변환"] --> S3["3단계<br/>반복·병렬·완성"] --> Q["퀴즈·정리"]
|
||||
P["개요"] --> S1["1단계<br/>바이브 코딩 n8n<br/>결과 먼저"] --> S2["2단계<br/>JSON · 파싱 · 변환"] --> S3["3단계<br/>항목 · 갈래 · 검증"] --> V["7·8교시<br/>바이브 코딩 turl"]
|
||||
```
|
||||
|
||||
## 자료 목록
|
||||
|
||||
| 파일 | 내용 |
|
||||
| --- | --- |
|
||||
| `00-DAY66-PREP.md` | 실습 준비: 어제 환경 켜기, JSON 객체·목록, 새 노드 미리 보기 (수업 전) |
|
||||
| `00-DAY66-OVERVIEW.md` | 개요 15장 (PPT 대신 Markdown, 가로줄로 장 구분). 1교시에 앞부분, 각 단계 설명 때 해당 장을 함께 띄움 |
|
||||
| `01-STAGE1-DESIGN-AND-DATA-FLOW.md` | 목적 하나로 좁히기, 결과 먼저, 항목(상자) 읽기 1 → 4 → 1, 제어 구조, 요청이 바뀌면 (Lab 01 예제·과제 순서) |
|
||||
| `02-STAGE2-PARSE-AND-TRANSFORM.md` | LLM 답은 글자, 파싱, 답의 모양 알려 주기(스키마), 하나 더 뽑기, Edit Fields (Lab 02 예제·과제 순서) |
|
||||
| `03-STAGE3-LOOP-PARALLEL-AND-FINISH.md` | 펼치고·모으기, Merge로 합치기, 가짜를 진짜로, 기대 결과로 검증, 밖에서 부르기 (Lab 03 예제·과제 순서) |
|
||||
| `04-QUIZ-AND-WRAPUP.md` | 퀴즈 안내, 오늘의 정리, 자주 막히는 부분, 67일차 예고 |
|
||||
| `quiz/` | 퀴즈 문항(A·B·C), Google Form 생성 스크립트, 응답 분석 엑셀 |
|
||||
| `00-DAY66-PREP.md` | 실습 준비: n8n 켜기, CLI 코딩 에이전트 설치·로그인, `flow.py doctor` (수업 전) |
|
||||
| `00-DAY66-OVERVIEW.md` | 개요 13장 (PPT 대신 Markdown, 가로줄로 장 구분) |
|
||||
| `01-STAGE1-VIBE-N8N-AND-RESULT-FIRST.md` | 무엇이 바뀌나, 워크플로우는 JSON, AGENTS.md와 flow.py, 요청문 다섯 가지, 결과 먼저, 항목 흐름 |
|
||||
| `02-STAGE2-JSON-AND-PARSE.md` | LLM의 답은 글자, 모양 알려 주기, 코드로 읽기, 빈칸은 코드가, 하나 바꾸면 다른 것도 바뀐다 |
|
||||
| `03-STAGE3-ITEMS-PARALLEL-AND-VERIFY.md` | 1 → N → 1 (펼치기 · 항목마다 · 모으기), 빈 목록, 두 갈래와 Merge, 설계와 실행, 테스트로 완성 |
|
||||
| `04-VIBE-CODING.md` | 7·8교시 바이브 코딩 진행 (turl), 발표, 강사 정리, 추가 바이브 코딩, 팁 |
|
||||
Reference in new issue
Block a user