75일차 자료 추가 (답 제외): 실습 15개 · 실행 결과, gpt-5.4-nano · 단가, 교안 개요 30장 · 4단계 정리, 바이브 코딩 tcost 요청서

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mcchaeandClaude Opus 5.5 committed 2026-10-28 22:38:01 +09:00
1 parent d663d045e3
commit 01dc892303
70 files changed
+10890

No files matched your search

+420
View File
@@ -0,0 +1,420 @@
# 75일차 개요 · 컴퓨팅 자원·API 사용량 관리와 비용 최적화
> 노코드 멀티 에이전트 워크플로우 설계 및 운영 · 2026-10-29
> 30장 · 각 장은 가로줄(`---`)로 나뉩니다. 1교시에는 1~6장을 보고, 나머지 장은 각 단계 강의(1 · 3 · 5교시) 때 함께 띄웁니다.
> 숫자는 모두 강사 노트북 실측입니다 (65일차 n8n 2.42.6, OpenAI `gpt-4.1-mini` · `gpt-5.4-nano`, 10/28 정답 워크플로우를 학생 순서대로 1회). 단가는 OpenAI 모델 문서에서 2026-10-28에 확인한 값입니다.
| 장 | 범위 | 띄우는 때 |
| --- | --- | --- |
| 1~6 | 표지 · 목표 · 시간표 · 비싼 헬프데스크 · 일하는 순서 · 실습 15개 | 1교시 시작 |
| 7~14 | 1단계 · 사용량 재기 | 1교시 강의 |
| 15~21 | 2단계 · 비용 줄이기 | 3교시 강의 |
| 22~28 | 3단계 · 예산과 한도 | 5교시 강의 |
| 29~30 | 바이브 코딩 · 정리와 예고 (8교시 35분 정리는 [04-STAGE4-WRAPUP](04-STAGE4-WRAPUP.md)) | 7교시 · 8교시 |
---
## 1 / 30 · 표지
```mermaid
flowchart LR
M["재기<br/>토큰 · 비용 · 기록"] --> R["줄이기<br/>보내는 것 · 모델 · 호출 수"] --> K["지키기<br/>예산 · 한도 · 보고서"]
classDef d fill:#0F3D3E,stroke:#0F3D3E,color:#fff
classDef o fill:#F2A541,stroke:#F2A541,color:#fff
class M,K o
class R d
```
**75일차**
# 컴퓨팅 자원·API 사용량 관리와 비용 최적화
질문 하나에 얼마인지 재고, 품질은 그대로 두고 줄이고, 넘치지 않게 지킨다
<sub>노코드 멀티 에이전트 워크플로우 설계 및 운영 · 2026-10-29</sub>
---
## 2 / 30 · 오늘 할 수 있게 될 세 가지
| | 목표 | 내용 |
| --- | --- | --- |
| 1 | **잰다** | 자원 · 토큰 · 돈을 구분하고, 단계마다 비용을 결과에 남기고, 종류별로 한 달을 어림하고, 요청마다 기록한다 (멈춰도 답은 나감) |
| 2 | **줄인다** | 보내는 것 · 단가 · 호출 수를 한 칸씩, 품질 테스트와 답 읽기로 확인하고, 긴 입력에도 상한을 둔다 |
| 3 | **지킨다** | 하루 예산을 넘으면 원문으로, 몰아 부르면 429, 예산이 언제 멈추는지 재고, 보고서 · what-if는 코드로 |
---
## 3 / 30 · 오늘의 시간표
| 교시 | 시간 | 내용 |
| --- | --- | --- |
| 1 | 09:00~09:50 | 개요 · **1단계 강의** · 사용량 재기 |
| 2 | 10:00~10:50 | **Lab 01 실습 5개** · 자원 · 비용 · 종류별 비용 · 기록 · 기록이 멈추면 |
| 3 | 11:00~11:50 | **2단계 강의** · 비용 줄이기 |
| 4 | 12:00~12:50 | **Lab 02 실습 5개** · 보내는 것 · 모델 · 답까지 nano? · LLM 한 번 · 긴 질문 |
| - | 13:00~14:00 | 점심 |
| 5 | 14:00~14:50 | **3단계 강의** · 예산과 한도 |
| 6 | 15:00~15:50 | **Lab 03 실습 5개** · 하루 예산 · 1분 한도 · 예산 바꿔 보기 · 보고서 · what-if |
| 7 | 16:00~16:50 | 바이브 코딩 `tcost`: 요청서 → TODO → SPEC → IMP-PLAN → 만들기 → 설치 |
| 8 | 17:00~17:50 | 발표 · **4단계 정리** (의미 · 효과 · 회사에서 쓰는 법) · 76일차 예고 |
---
## 4 / 30 · 오늘 고치는 것: 비싼 헬프데스크
```mermaid
flowchart LR
Q(["질문"]) --> C{{"분류 LLM"}} --> A{{"답 LLM<br/>+ 사내 규정 전체"}} --> R{{"검토 LLM"}} --> O(["답"])
```
| 수업 준비 때 | VPN 질문 하나 | LLM 호출 |
| --- | --- | --- |
| 시작 (`base-cost`) | **$0.001040** (입력 1,999 · 출력 150) | 3 |
| 하루 뒤 (Lab 02 과제 1) | **$0.000156** | 1 |
- 72~74일차와 같은 IT 문의 헬프데스크 (FAQ · 접수 건). 오늘은 **같은 답을 더 싸게**, 그리고 **넘치지 않게**.
---
## 5 / 30 · 일하는 순서 (오늘은 "재기 → 줄이기 → 품질 확인")
```mermaid
flowchart LR
A["① 재기<br/>call · test 줄 끝 비용"] --> B["② 요청문을<br/>메모장에"] --> C["③ 에이전트에게"] --> D["④ 에이전트가<br/>my-work · check"] --> E["⑤ 내가<br/>push · test -v"]
E -->|✘| F["⑥ 실패 줄 · 보고서 경로<br/>그대로 붙여 주기"] --> C
E -->|✔| G["⑦ 답 읽기 · COST.md 한 줄"]
```
- 예산 · 한도 · 글자 수 상한은 **내가** `flow.py budget`으로. 끝나면 `budget --reset`.
- 오늘은 호출마다 실제로 돈이 듭니다 (질문 하나에 약 $0.0001~$0.0012). 같은 테스트를 수십 번 반복하지 않습니다.
---
## 6 / 30 · 오늘의 실습 15개
| | 예제 1 | 예제 2 | 예제 3 | 과제 1 | 과제 2 |
| --- | --- | --- | --- | --- | --- |
| Lab 01 | 자원 · 단계별 토큰 (에이전트 없이) | 결과에 `cost_usd` | 종류별 비용 · 한 달 (에이전트 없이) | 요청마다 기록 | 기록이 멈춰도 답 |
| Lab 02 | 보내는 것 줄이기 | 단계마다 맞는 모델 | 답까지 nano면? (에이전트 없이) | LLM 한 번 · $0.0002 | 긴 질문의 상한 |
| Lab 03 | 하루 예산 → 원문 | 1분 한도 → 429 | 예산을 바꿔 가며 (에이전트 없이) | 비용 보고서 | 바꾸면 얼마 (what-if) |
- 실습 문서에 복사해 붙일 요청문은 없습니다. 막히면 `▶`, 요청문은 **빈칸 4개**를 채워서.
---
## 7 / 30 · 1단계 · 왜 비용인가
| | 일반 웹 서비스 | LLM API |
| --- | --- | --- |
| 무엇에 돈을 내나 | 서버 시간 · 저장 · 전송 | **보낸 · 받은 토큰** |
| 요청 하나의 비용 | 작고 거의 같음 | 요청마다 크게 다름 |
| 실수 하나의 크기 | 작음 | 지시문에 긴 자료 → **모든 요청**이 비쌈 |
- FinOps 세 단계: **알기**(1단계) → **줄이기**(2단계) → **운영하기**(3단계). 재지 않고 줄이면 무엇을 잃었는지 모릅니다.
- 쓸모 있는 숫자는 **요청 하나의 비용** (단위 경제).
---
## 8 / 30 · 1단계 · 자원: 내 컴퓨터와 공급자 (Lab 01 예제 1)
| 컨테이너 (`docker stats`) | CPU | 메모리 |
| --- | --- | --- |
| n8n | 0.27% | 642.6MiB |
| runners | 0.21% | 100.5MiB |
| data-api | 0.02% | 20.3MiB |
- API로 부르면 LLM 계산은 공급자 서버에서 → 내 CPU는 거의 쓰지 않음. 돈은 **토큰**에서.
- 내 쪽에서 늘어나는 것: 동시 실행 · 실행 기록 · 로그 (3단계 7절, 76 · 77일차).
---
## 9 / 30 · 1단계 · 토큰과 단가
```text
비용 = (입력 토큰 × 입력 단가 + 출력 토큰 × 출력 단가) ÷ 1,000,000
```
| 모델 (1M 토큰당 $) | 입력 | 캐시된 입력 | 출력 |
| --- | --- | --- | --- |
| `gpt-4.1-mini` | 0.40 | 0.10 | 1.60 |
| `gpt-5.4-nano` | 0.20 | 0.02 | 1.25 |
- VPN 질문: (1,999 × 0.40 + 150 × 1.60) ÷ 10⁶ = **$0.00104**. 하루 1,000번 · 22일 ≈ $22.9.
- **출력이 더 비싸고, 가격은 바뀝니다** (단가표에 확인한 날짜).
---
## 10 / 30 · 1단계 · 토큰이 보이는 호출 (Lab 01 예제 1 · 2)
| `base-cost` VPN | 입력 | 출력 |
| --- | --- | --- |
| classify | 78 | 6 |
| answer | **1,781** | 78 |
| review | 140 | 66 |
- 응답의 `usage`가 청구의 기준. AI Agent 노드는 답만 주고 토큰을 버림 → 오늘은 **HTTP로 OpenAI를 직접**.
- 입력의 89%가 `answer` 하나. 접수 질문도 1,791 → **매번 같이 보내는 것**이 있다.
---
## 11 / 30 · 1단계 · 단계별 비용, 단가는 밖에 (Lab 01 예제 2)
```mermaid
flowchart LR
P[("/prices<br/>모델 → 단가")] -.-> B["Build result<br/>단계마다 토큰 × 그 모델 단가"] --> O(["usage.by_step.*.cost_usd<br/>+ cost_usd"])
```
- 단가를 코드에 숫자로 → 가격 · 모델이 바뀌면 **조용히 틀림**. 70일차 설정 표 · 74일차 저장소와 같은 생각.
- 모르는 모델은 0원이 아니라 **오류**. 소수 8자리.
- 결과: VPN **$0.001040** (손 계산과 같음), `lab01` 3/3.
---
## 12 / 30 · 1단계 · 종류별 비용과 한 달 (Lab 01 예제 3)
| 종류 | 평균 (`lab01-mix`) | 비중 (가정) |
| --- | --- | --- |
| FAQ 넷 | $0.000980 | 60% |
| 접수 둘 | $0.000918 | 30% |
| 그 밖 | $0.000830 | 10% |
- 가중 평균 $0.000946 × 1,000건 × 22일 ≈ **한 달 $20.8**.
- 일곱 질문이 **$0.00083 ~ 0.00104로 비슷** → 고정비(규정 전체)가 대부분. 고정비부터 줄인다.
---
## 13 / 30 · 1단계 · 기록: 요청 · 호출 · 사람 (Lab 01 과제 1)
| 요약 (`/usage/summary`) | 질문 3개 뒤 |
| --- | --- |
| requests (요청) | 3 |
| calls (호출) | 9 |
| 합계 | $0.002951 |
| 사람별 | 이준호 $0.001040 · 박서연 $0.000959 · 한지훈 $0.000953 |
- 호출마다 한 줄, 같은 요청은 같은 `request_id` (`$execution.id`). 기록이 없으면 **예산 · 한도도 없다**.
- 기록에는 숫자와 분류 칸만 (질문 원문 · 개인정보 · 키는 남기지 않음).
---
## 14 / 30 · 1단계 · 기록이 멈추면 (Lab 01 과제 2)
| 기록 API | 고치기 전 | 고친 뒤 (`timeout` 3초 · 실패해도 계속) |
| --- | --- | --- |
| 고장 (500) | 답 · `recorded` 없음 | 답 · `recorded: false` |
| 느림 (8초) | **11.2초** | **6.1초** · `recorded: false` |
- 부가 기능(기록)이 본 기능(답)을 막지 않게. 값: 빠진 기록은 **예산에서도 빠짐** → 표시를 남김.
- 수업 준비 때: 3초에 포기했는데 서버가 8초 뒤 **결국 저장** → 다음 테스트의 요청 수가 하나 많았음. **포기해도 상대의 일은 끝나지 않을 수 있다.**
---
## 15 / 30 · 2단계 · 비용의 세 칸
```text
요청 하나의 비용 ≈ 호출 수 × 호출당 토큰 × 토큰 단가
```
| 칸 | 줄이는 방법 | 오늘 |
| --- | --- | --- |
| 호출당 토큰 | 필요한 것만 보내기 · RAG · 출력 상한 · 긴 입력 자르기 | 예제 1 · 과제 2 |
| 토큰 단가 | 단계에 맞는 작은 모델 · 캐시 · 배치 | 예제 2 · 3 |
| 호출 수 | 규칙으로 먼저 · 중복 단계 빼기 · 캐시 | 과제 1 |
- **품질 테스트를 먼저 고정**하고, 한 번에 한 칸, 가장 큰 몫부터.
---
## 16 / 30 · 2단계 · 보내는 것 줄이기 (Lab 02 예제 1)
| | 전 | 후 |
| --- | --- | --- |
| `answer` 입력 (VPN) | 1,781 | **165** (91% 줄임) |
| VPN 비용 | $0.001007 | **$0.000346** |
| 품질 테스트 | ✔ | ✔ |
- 규정 전체 빼기, FAQ 3칸 · 접수 5칸 (연락처 · 담당자 빼기 = **데이터 최소화**), `max_completion_tokens` 200.
- 규정이 필요하면 통째로가 아니라 **검색해서 관련 부분만** (71일차 RAG).
---
## 17 / 30 · 2단계 · 단계마다 맞는 모델 (Lab 02 예제 2)
| 모델 | 입력 | 출력 |
| --- | --- | --- |
| `gpt-4.1-mini` | 0.40 | 1.60 |
| `gpt-5.4-nano` | 0.20 (절반) | 1.25 (약 0.78배) |
- 분류 · 검토(쉬운 일)만 nano, 답은 mini → VPN $0.000346 → **$0.000278**.
- "작은 모델이면 몇 배 싸다"는 늘 맞지 않음 → **단가표로 계산**. 노드와 함께 **계산의 모델 이름도**.
---
## 18 / 30 · 2단계 · 싸졌는데 나빠졌다면 (Lab 02 예제 3)
| 답 단계 | 낱말 검사 | 일곱 질문 평균 |
| --- | --- | --- |
| mini (내 것) | 5/5 | $0.000233 |
| nano (비교본) | **5/5** | $0.000198 (약 15% 덜) |
- nano 답을 읽으면: `**처리 중**` 마크다운, "ticket_id가 없어…" 안쪽 말, "접수 건은 찾지 못했습니다" 군더더기.
- **통과 ≠ 같은 품질.** 사람이 읽고, 찾은 것은 테스트로. 오늘은 답을 mini로 둔다.
---
## 19 / 30 · 2단계 · 가장 싼 호출은 하지 않은 호출 (Lab 02 과제 1)
```mermaid
flowchart LR
Q(["질문"]) --> R{"규칙이 아나?<br/>T-숫자 · FAQ 검색"}
R -->|예 · 대부분| A{{"답 LLM 1번"}}
R -->|아니오| C{{"분류 nano"}} --> A
```
| | 전 | 후 |
| --- | --- | --- |
| FAQ · 접수 | 3 calls · 다섯이 $0.0002 넘음 | **1 call · $0.000108 ~ 0.000166** |
| `lab02-challenge` | 0/7 | **7/7** |
| 시간 | 3초대 | 1초대 |
---
## 20 / 30 · 2단계 · 긴 입력의 상한 (Lab 02 과제 2)
| 6,000자 넘는 로그 질문 | 전 | 후 (앞 200 + 뒤 300자) |
| --- | --- | --- |
| 프린터 (끝에 질문) | **$0.001196** (짧은 질문의 7배 넘게) | **$0.000237** · "대기열" |
| VPN (앞에 질문) | $0.001099 | $0.000260 · "인증서" |
- 입력 상한이 없으면 **요청 하나의 비용에도 상한이 없다** (Denial of Wallet).
- 입력 상한 + 출력 상한 → 요청 하나의 **최대 비용** → 예산을 지킬 수 있다.
---
## 21 / 30 · 2단계 · 한 번에 한 칸씩
| 단계 | 바꾼 칸 | VPN 비용 | 호출 |
| --- | --- | --- | --- |
| 기준 | | $0.001007 | 3 |
| 예제 1 | 호출당 토큰 | $0.000346 | 3 |
| 예제 2 | 단가 | $0.000278 | 3 |
| 과제 1 | 호출 수 | **$0.000156** | 1 |
- 바꿀 때마다 `COST.md`에 한 줄: 바꾼 것 · 전 → 후 · 품질 테스트 · **잃은 것**.
---
## 22 / 30 · 3단계 · 넘치지 않게
| 장치 | 막는 것 | 오늘 |
| --- | --- | --- |
| 예산 | 전체가 너무 많이 | 하루 `daily_usd` $1.0 |
| 속도 제한 | 한 사람이 짧은 시간에 몰아서 | 1분 `user_limit_per_min` 20 |
| 요청 상한 | 요청 하나가 너무 큼 | `max_question_chars` 500 · `max_completion_tokens` 200 |
- 공급자 한도는 **계정 전체**에 걸림 → 한 사람 때문에 모두 막힘. 그래서 내 쪽에 사람별 한도.
- 값은 **사람이** 정하고(`/budget`), 워크플로우는 읽기만.
---
## 23 / 30 · 3단계 · 하루 예산, 넘으면 원문 (Lab 03 예제 1)
```mermaid
flowchart LR
D["FAQ · 접수 조회"] --> B{"예산 안?"}
B -->|예| L{{"LLM 답"}}
B -->|아니오| O(["한도 초과 · 원문 · $0"])
```
- 멈추는 대신 **낮춰서 답하기** (graceful degradation). 확인은 LLM 앞 · 데이터 뒤.
- 72일차 LLM 장애의 `간단 답변`과 `route`를 구분 → 장애인지 예산인지 가른다.
- 결과: `한도 초과 · 0 calls · $0`, "인증서" · "처리 중" (3/3).
---
## 24 / 30 · 3단계 · 사용자별 한도와 429 (Lab 03 예제 2)
| 세는 방법 | 특징 |
| --- | --- |
| 고정 창 | 쉬움, 경계에서 두 배 |
| 이동 창 (오늘) | 고르게 막음 |
| 토큰 버킷 · 새는 통 | 짧은 몰림 허용 · 속도를 고르게 |
- **가장 앞**에서 429 + `retry_after` → 이준호 세 번째: **0.0초 · $0**, 다른 사람은 그대로.
- 세는 곳은 하나 (서버가 여러 대여도). 사람은 로그인 정보로 가린다.
---
## 25 / 30 · 3단계 · 예산은 넘은 뒤 멈춘다 (Lab 03 예제 3)
| 예산 $0.0003 | 결과 | 누적 |
| --- | --- | --- |
| 1 · 2번째 | 자동 답변 | $0.000293 (아래) |
| 3번째 | 자동 답변 | **$0.000430 (43% 넘음)** |
| 4 · 5 · 6번째 | 한도 초과 · $0 | 그대로 |
- 넘지 않으려면 "남은 돈 < 한 건 최대 비용이면 멈춤" → **상한이 있어야 예산도 지킨다**.
- 경고는 멈추기 **전에** (80%). 한도 초과 답도 기록해야 몇 명이 원문을 받았는지 센다.
---
## 26 / 30 · 3단계 · 비용 보고서 (Lab 03 과제 1)
| 넣은 네 줄 | 손 계산 = 보고서 |
| --- | --- |
| 요청 · 호출 · 합계 | 3 · 4 · $0.001989 |
| 평균 (요청 수로) · 100건 × 22일 | $0.000663 · **$1.4586** |
| 가장 많이 쓴 사람 · 남은 예산 | 박서연 · $0.998011 |
- 숫자는 **코드가** 계산 (틀리지 않고 · 공짜 · 늘 같게), LLM은 설명만 (79일차).
- 실제 사용량 일곱 건 · 하루 1,000건 → **한 달 $2.80** (아침 어림 $20.8의 약 7분의 1).
---
## 27 / 30 · 3단계 · 바꾸면 얼마: what-if (Lab 03 과제 2)
| what-if (넣은 네 줄) | 비용 | 아끼는 돈 |
| --- | --- | --- |
| 모두 nano | $0.00117 | $0.000819 |
| 모두 mini | $0.002016 | **−$0.000027** |
- 실제 사용량: $0.000929 → nano였다면 $0.00061 (34% 덜). **같은 토큰 · 같은 품질을 가정한 숫자.**
- 계산은 "해 볼 만한가", 결정은 "재고 · 읽고 · 게이트로" (74일차). 모르는 모델은 400.
---
## 28 / 30 · 3단계 · 운영: 재기 → 줄이기 → 지키기
```mermaid
flowchart LR
M["재기<br/>usage · 기록"] --> R["줄이기<br/>한 칸씩 · 품질 테스트"] --> K["지키기<br/>예산 · 한도 · 상한"] --> P["보고<br/>정기적으로 · what-if"] --> M
```
- 누가 값을 정하나(사람) · 언제 보나(매주 · 매달) · 넘으면 무엇을 주나(원문 · 429) · 무엇을 남기나(`COST.md`).
- 내 쪽 자원(동시 실행 · 실행 기록)도 같은 보고서에.
---
## 29 / 30 · 7교시 · 바이브 코딩 `tcost`
- LLM을 부르기 **전에** 글 · 워크플로우의 토큰과 비용을 계산하고, 테스트 보고서(`runs/*.json`)의 **실제 사용량을 모아** 한 달을 예상하는 TUI. **OpenAI를 부르지 않습니다.**
- 순서: [요청서](../../Last3Mon/75/VIBE-REQ-TCOST.md) → `Doc/TODO.md` → `Doc/SPEC.md` → `Doc/IMP-PLAN.md` → 단계별 구현 → `uv tool install` → 다른 폴더에서.
- 요구 14개 (R1~R14). 50분 안에 엔진 1 · 2단계 · TUI 1단계 · 설치.
```mermaid
flowchart LR
R["요청서"] --> T["TODO"] --> S["SPEC"] --> P["IMP-PLAN"] --> B["엔진 → TUI"] --> I["uv tool install"]
```
---
## 30 / 30 · 오늘의 정리 · 76일차 예고
| 오늘 | 한 줄 |
| --- | --- |
| 1단계 | 응답의 `usage`로 단계마다 비용, 단가는 밖에, 종류별로 한 달, 호출마다 기록 (멈춰도 답) |
| 2단계 | 보내는 것 · 단가 · 호출 수를 한 칸씩, 통과해도 답을 읽고, 입력 · 출력 상한 |
| 3단계 | 예산을 넘으면 원문, 몰아 부르면 429, 예산은 넘은 뒤 멈춘다, 보고서 · what-if는 코드로 |
**76일차 · 에러 처리와 예외 대응**: 오늘 기록 API가 멈췄을 때 답은 보냈지만, 빠진 기록은 `recorded: false`로 남기기만 했습니다. 내일은 실패한 일을 **모아 두고 다시 처리하고, 사람에게 알리는** 운영 체계를 만듭니다.
+96
View File
@@ -0,0 +1,96 @@
# 75일차 실습 준비 · 수업 전에 미리 해 두면 좋은 것
- 수업일: 2026-10-29 (75일차) · 컴퓨팅 자원·API 사용량 관리와 비용 최적화
- 준비 시간: 약 10분
- 먼저 WSL에서 `cd ~/aio-01d4-stu && git pull`로 오늘 자료를 받습니다 (수강생 저장소: https://gl.isitsoft.kr/publicproj/aio-01d4-stu).
- **수업 전날까지** 1~2절을 끝내 주세요.
```mermaid
flowchart LR
A["1. n8n · 데이터 API 켜기<br/>(75일차 판)"] --> B["2. 기준 게시 ·<br/>doctor ready"] --> C["3. 미리 읽기"]
```
---
## 1. n8n과 데이터 API 켜기 (전날 필수, 3분)
오늘은 **75일차 판 데이터 API**(74일차 + 단가 · 사용량 기록 · 예산 · 한도)를 씁니다. 같은 이름이라 이 폴더에서 다시 만들면 바뀝니다.
```bash
sudo systemctl disable --now ollama 2>/dev/null # WSL에 설치한 Ollama가 있으면 끄고 자동 시작도 끔
cd ~/aio-01d4-stu/Last3Mon/65/n8n
docker compose rm -sf ollama # Ollama 컨테이너가 있으면 지움 (없으면 그냥 지나감)
docker compose start
cd ~/aio-01d4-stu/Last3Mon/75/data-api
docker compose up -d --force-recreate
sleep 3 # 데이터 API가 뜰 때까지
curl -s localhost:8800/prices # {"currency": "USD", "unit": "per 1M tokens", ... "gpt-5.4-nano" ...
```
- **66일차부터 LLM은 OpenAI API 키(`Last3Mon/.env`, Credential `OpenAI (.env)`)만 씁니다.** 오늘은 이 키로 쓴 토큰과 돈을 직접 잽니다. 작은 모델은 `gpt-5.4-nano`입니다 (`gpt-4.1-nano`는 OpenAI가 더 이상 받지 않음).
- `network day65-n8n_default not found` → n8n이 켜져 있지 않음. 위 n8n 부분부터 다시.
## 2. 기준 게시와 준비 확인 (전날 필수, 3분)
오늘 비용을 잴 **비싼 헬프데스크**를 게시해 둡니다 (만들지 않고 게시만).
```bash
cd ~/aio-01d4-stu/Last3Mon/75
uv run tools/flow.py push reference/baseline-cost.json
uv run tools/flow.py call base-cost requests/faq-vpn.json # usage.by_step 에 단계별 토큰 (한 번에 약 $0.001)
uv run tools/flow.py doctor
```
```text
✔ n8n container day65-n8n-n8n-1
✔ n8n http://localhost:5678
✔ data API http://localhost:8800
✔ prices /prices · budget /budget (day 75 data API)
✔ n8n → http://data-api:8800
✔ credential 'OpenAI (.env)' (openAiCourse0001)
✔ node catalog (for check)
✔ CLI coding agent: codex, claude, agy
ready
```
| ✘ 항목 | 할 일 |
| --- | --- |
| n8n container · data API | 1절 |
| prices · budget | 데이터 API가 예전 판 → 1절 `--force-recreate` · `sleep 3` |
| `! budget is not the start state` | `uv run tools/flow.py budget --reset` |
| `! fault switch is on` | `uv run tools/flow.py fault all ok` |
| credential | `cd ~/aio-01d4-stu/Last3Mon/65/n8n && bash register-credentials.sh` |
| CLI coding agent | [67일차 준비](../67/00-DAY67-PREP.md) 2절 |
---
## 3. 미리 읽어 오면 좋은 내용 (선택, 약 5분)
### 3.1 질문 하나에 얼마일까
LLM API는 **토큰**(글자 조각) 수만큼 돈을 받습니다. 보낸 것(입력)과 받은 것(출력)의 단가가 다르고, 모델마다 다릅니다. 지시문에 사내 규정을 통째로 붙여 매번 보내면 어떻게 될까요? 2절의 `call base-cost`에서 `usage.by_step`의 입력 토큰이 가장 많은 단계를 찾아 와도 좋습니다.
### 3.2 용어
| 용어 | 한 줄 설명 |
| --- | --- |
| 토큰 | LLM이 글을 세는 단위 (한국어는 대략 글자 1~2개에 1토큰) |
| 입력 · 출력 토큰 | 보낸 지시문 · 질문 · 자료 / 받은 답 (출력이 더 비쌈) |
| 단가 | 1M(백만) 토큰당 달러, 입력 · 출력 · 모델마다 다름 |
| 계량 (metering) | 요청마다 쓴 양을 재서 남기기 |
| 예산 · 한도 | 하루에 쓸 돈의 상한 · 한 사람이 짧은 시간에 부를 수 있는 횟수 |
| 429 | "요청이 너무 많다"는 HTTP 상태 코드 |
### 3.3 7교시 요청서
[VIBE-REQ-TCOST](../../Last3Mon/75/VIBE-REQ-TCOST.md)를 한 번 읽어 옵니다. 재무 · 운영 담당이 원하는 것 열두 가지 중 "꼭"은 무엇일까요?
## 4. 당일 준비물
- 충전된 노트북과 충전기, WSL의 Docker, Windows Terminal(WSL) 창 **두 개**, 웹 브라우저
## 확인
- [ ] `curl -s localhost:8800/prices`에 `gpt-4.1-mini` · `gpt-5.4-nano` 단가
- [ ] `call base-cost`가 `usage.by_step`을 돌려줌
- [ ] `uv run tools/flow.py doctor` → 마지막 줄 `ready`
+461
View File
@@ -0,0 +1,461 @@
# 1단계 · 사용량 재기
- 교시: 1교시 강의 (09:00~09:50) · 2교시 [Lab 01](../../Last3Mon/75/LAB-01-Q-MEASURE-USAGE.md) 실습 5개 (10:00~10:50)
- 이 단계가 끝나면: AI 워크플로우가 쓰는 것 세 가지(컴퓨팅 자원 · API 토큰 · 돈)를 구분하고, 토큰과 단가로 질문 하나의 비용을 계산하고, 단계마다 비용을 결과에 남기고, 질문 종류별 비용으로 한 달을 어림하고, 요청마다 사용량을 기록하고, 기록이 멈춰도 답은 나가게 할 수 있다.
- 숫자: 모두 강사 노트북 실측입니다 (65일차 n8n 2.42.6, OpenAI `gpt-4.1-mini` · `gpt-5.4-nano`, 10/28 실습을 5개로 늘린 뒤 정답 워크플로우를 학생 순서대로 1회). 단가는 OpenAI 모델 문서에서 2026-10-28에 확인한 값입니다. 출력 전부는 수업 뒤 공개되는 `Last3Mon/75/answers/LAB-TEST-RESULT.md`에 있습니다.
- 실습 가리키기: 이 문서에서 "Lab 01 예제 1"은 2교시 [Lab 01 문서](../../Last3Mon/75/LAB-01-Q-MEASURE-USAGE.md)의 그 절입니다. 파일 경로(`cases/` · `requests/` · `my-work/` · `reference/`)는 실습 폴더 `~/aio-01d4-stu/Last3Mon/75` 기준입니다.
```mermaid
flowchart LR
A["1. 왜 비용인가"] --> B["2. 자원"] --> C["3. 토큰과 단가"] --> D["4. 토큰이 보이는 호출"] --> E["5. 단계별 비용"]
E --> F["6. 종류별 비용 · 한 달"] --> G["7. 기록"] --> H["8. 기록이 멈추면"] --> I["9. 요청문(프롬프트)으로"]
```
## 0. 이 시간 (50분)
| 시간 | 절 · 개요 | 하는 일 |
| --- | --- | --- |
| 09:00~09:06 | 개요 1~6장 | 오늘 하루, 비싼 헬프데스크, 실습 15개 |
| 09:06~09:12 | 1 · 개요 7장 | 쓴 만큼 내는 비용, FinOps, 요청 하나의 비용 |
| 09:12~09:17 | 2 · 개요 8장 | 내 컴퓨터의 자원과 공급자의 자원 |
| 09:17~09:24 | 3 · 개요 9장 | 토큰, 입력 · 출력 단가, 계산 |
| 09:24~09:29 | 4 · 개요 10장 | 응답의 `usage`, 숨기는 노드, 관측 도구 |
| 09:29~09:34 | 5 · 개요 11장 | 단계별 비용, 단가는 밖에 |
| 09:34~09:39 | 6 · 개요 12장 | 고정비 · 변동비, 가중 평균, 한 달 어림 |
| 09:39~09:45 | 7 · 8 · 개요 13~14장 | 기록(요청 · 호출 · 사람), 기록이 멈추면 |
| 09:45~09:50 | 9 · 10 · 확인 질문 | 요청문, 자주 하는 실수 |
### 2교시 Lab 01에서 이어서 하는 것
| 2교시 실습 (시각) | 이 강의의 절 | 실습에서 하는 일 | 쓰는 파일 · 명령 |
| --- | --- | --- | --- |
| 시작 (10:00) | — | 비용 계획 복사 | `cp templates/COST-PLAN-TEMPLATE.md my-work/COST.md` (오늘 잰 숫자를 모두 여기에) |
| Lab 01 예제 1 (10:00~10:10, 에이전트 없이) | 2 · 3 | 자원 · 단계별 토큰 · 단가 보기, 손 계산 | `docker stats --no-stream`, `call base-cost requests/faq-vpn.json` → `COST.md` 1 · 2절 |
| Lab 01 예제 2 (10:10~10:22) | 4 · 5 | 에이전트에게 단계마다 `cost_usd`를 넣게 하기 | `my-work/cost.json` (기준을 복사), 테스트 `cases/lab01.json` |
| Lab 01 예제 3 (10:22~10:30, 에이전트 없이) | 6 | 일곱 질문의 비용, 한 달 어림 | `test cases/lab01-mix.json` → `COST.md` 3절 |
| Lab 01 과제 1 (10:30~10:40) | 7 | 요청마다 `/usage`에 기록 | 테스트 `cases/lab01-challenge.json`, `flow.py usage` |
| Lab 01 과제 2 (10:40~10:50) | 8 | 기록이 멈춰도 답은 나감 | 테스트 `cases/lab01-challenge2.json` (`fault usage error` · `slow`) |
## 1. 왜 비용인가: "청구서를 보고 놀라는 날"
### 일반 설명
예전에는 서버를 사면 한 달 비용이 정해져 있었습니다. 클라우드와 API는 다릅니다. **쓴 만큼 냅니다 (pay as you go).** 처음에는 싸고 편하지만, 쓰는 사람이 늘거나 한 번에 쓰는 양이 늘면 비용도 그만큼 늘어납니다. 그리고 그 사실을 **청구서가 오고 나서야** 아는 일이 흔합니다.
LLM API는 이 성질이 더 강합니다.
| | 일반 웹 서비스 | LLM API |
| --- | --- | --- |
| 무엇에 돈을 내나 | 서버 시간 · 저장 공간 · 전송량 | **보낸 · 받은 토큰** |
| 요청 하나의 비용 | 아주 작고 거의 같음 | 요청마다 크게 다름 (보낸 글의 길이, 답의 길이, 모델) |
| 누가 비용을 정하나 | 주로 개발자 | 개발자 + **사용자**(질문 길이) + 지시문 · 자료를 붙이는 방식 |
| 실수 하나의 크기 | 작음 | 지시문에 긴 자료를 붙이면 **모든 요청**이 비싸짐 |
**FinOps.** 클라우드 비용을 엔지니어 · 재무 · 사업이 함께 관리하는 방법을 FinOps라고 부릅니다. FinOps Foundation은 이것을 세 단계로 설명합니다.
| 단계 | 하는 일 | 오늘 |
| --- | --- | --- |
| 알기 (Inform) | 누가 · 무엇에 · 얼마를 쓰는지 보이게 | 1단계 · 재기 · 기록 |
| 줄이기 (Optimize) | 같은 결과를 더 싸게 | 2단계 · 줄이기 |
| 운영하기 (Operate) | 예산 · 한도 · 보고를 습관으로 | 3단계 · 예산 · 한도 · 보고서 |
순서가 중요합니다. **재지 않고 줄이면** 무엇이 줄었는지도, 무엇을 잃었는지도 모릅니다. 72일차 성능 튜닝에서 "먼저 재고, 하나씩 바꾸고, 다시 잰다"를 했습니다. 비용도 같습니다.
**요청 하나의 비용 (unit cost).** 전체 청구액만 보면 "많이 썼다"만 압니다. 운영에서 쓸모 있는 숫자는 **요청 하나에 얼마**입니다. 이 숫자가 있으면 사용자가 두 배가 될 때 비용이 얼마가 될지, 새 기능이 요청당 얼마를 더하는지, 가격을 얼마로 매겨야 남는지를 말할 수 있습니다. 이것을 단위 경제(unit economics)라고 부릅니다.
| 숫자 | 뜻 | 어디에 쓰나 |
| --- | --- | --- |
| 요청 하나의 비용 | 질문 하나에 드는 돈 | 한 달 예상, 기능 비교 |
| 사용자 한 명의 하루 비용 | 한 사람이 하루에 쓰는 돈 | 한도 정하기 (3단계) |
| 기능별 비용 | 분류 · 답 · 검토 각각 | 줄일 곳 찾기 (2단계) |
| 하루 · 한 달 합계 | 청구액 | 예산과 비교 (3단계) |
**청구액이 갑자기 느는 흔한 까닭.** 운영 중인 LLM 앱의 비용이 갑자기 늘었을 때 원인을 찾아보면 대개 아래 중 하나입니다. 어느 것이든 **요청 하나의 비용**이나 **요청 수** 중 하나가 바뀐 것이고, 기록이 있으면 어느 쪽인지 바로 보입니다.
| 원인 | 무엇이 늘었나 | 알아채는 법 |
| --- | --- | --- |
| 지시문에 자료를 더 붙임 | 요청 하나의 입력 토큰 | 단계별 입력 토큰이 갑자기 큼 |
| 대화 기록을 통째로 다시 보냄 | 대화가 길수록 입력 토큰 | 같은 사람의 요청마다 입력이 커짐 |
| 재시도 · 반복 호출 | 호출 수 | 요청 수는 같은데 호출 수가 늘어남 |
| 더 비싼 모델로 바꿈 | 단가 | 모델별 합계의 비중이 바뀜 |
| 누가 자동으로 많이 부름 (스크립트 · 고장 난 루프) | 요청 수 | 한 사람 · 한 시간대에 몰림 |
| 아주 긴 입력 (로그 · 문서 붙여 넣기) | 몇몇 요청의 입력 토큰 | 평균은 조금, 최대값이 크게 |
**비용은 품질 · 속도와 함께 봅니다.** 비용만 보면 가장 싼 답은 "답하지 않기"입니다. 그래서 비용을 줄일 때는 **품질 테스트**(오늘 Lab의 `contains`)와 **시간**(72일차 p95)을 같이 봅니다. 셋 중 하나만 좋아지고 다른 것이 나빠지면, 그것은 개선이 아니라 맞바꿈입니다. 맞바꿈이 괜찮은지는 사람이 정합니다.
### 오늘 실습에서
오늘의 헬프데스크는 72~74일차와 같은 IT 문의 창구입니다 (FAQ 검색 + 접수 건 조회 + 답). 다만 오늘 시작하는 `base-cost`는 **일부러 비싸게** 만든 것입니다. 질문마다 LLM을 세 번(분류 · 답 · 검토) 부르고, 답 단계에 사내 규정 문서 여덟 개를 통째로 붙여 보냅니다.
| 시간 | 할 일 | 실습 |
| --- | --- | --- |
| 1~2교시 | 자원 · 토큰 · 비용 재기, 결과에 비용, 종류별 비용 · 한 달, 기록, 기록이 멈추면 | Lab 01 |
| 3~4교시 | 보내는 것 줄이기, 단계마다 모델, 답까지 작은 모델이면?, LLM 한 번, 긴 질문의 상한 | Lab 02 |
| 5~6교시 | 하루 예산, 사용자별 한도, 예산을 바꿔 보기, 비용 보고서, 바꾸면 얼마 | Lab 03 |
하루 동안 VPN 질문 하나의 비용은 이렇게 바뀝니다 (수업 준비 때 실측).
| 때 | VPN 질문 하나 | LLM 호출 |
| --- | --- | --- |
| 시작 (`base-cost`) | **$0.001040** | 3 |
| Lab 02 예제 1 뒤 (보내는 것 줄이기) | $0.000346 | 3 |
| Lab 02 예제 2 뒤 (분류 · 검토 nano) | $0.000278 | 3 |
| Lab 02 과제 1 뒤 (LLM 한 번) | **$0.000156** | 1 |
## 2. 자원: 내 컴퓨터가 쓰는 것과 공급자가 쓰는 것 (2교시 Lab 01 예제 1)
### 일반 설명
**컴퓨팅 자원**은 프로그램이 돌아가는 데 쓰는 것입니다.
| 자원 | 무엇 | 모자라면 |
| --- | --- | --- |
| CPU | 계산 | 느려짐, 응답 시간이 늘어남 |
| 메모리 | 지금 쓰는 데이터를 올려 두는 곳 | 프로그램이 강제로 꺼짐 (OOM) |
| 디스크 | 저장 (실행 기록 · 로그 · 데이터베이스) | 저장 실패, 서비스 멈춤 |
| 네트워크 | 다른 서버와 주고받기 | 느림, 시간 초과 |
**LLM을 어디서 돌리느냐에 따라 자원과 비용이 갈립니다.**
| | 직접 돌리기 (self-host, 66일차 전의 Ollama) | API로 부르기 (오늘) |
| --- | --- | --- |
| LLM 계산 | 내 컴퓨터의 CPU · GPU · 메모리 | 공급자의 서버 |
| 내 컴퓨터가 쓰는 것 | 많음 (모델 크기만큼 메모리) | 거의 없음 (요청을 보내고 기다림) |
| 돈 | 장비 · 전기 (쓰지 않아도 듦) | 토큰 (쓴 만큼) |
| 늘어날 때 | 장비를 더 사야 | 그냥 더 부름 (비용도 그만큼) |
API로 부르면 LLM 계산은 내 쪽 자원을 거의 쓰지 않습니다. 그래도 내 쪽에서 자원을 쓰는 것이 있습니다.
| 내 쪽에서 자원을 쓰는 것 | 늘어나는 까닭 | 다루는 법 |
| --- | --- | --- |
| 워크플로우 엔진 (n8n) 메모리 | 동시에 도는 실행, 큰 데이터(긴 글 · 파일) | 동시 실행 수 한도, 큰 데이터는 나눠서 |
| 실행 기록 (execution data) | 실행마다 입력 · 출력을 저장 | 보관 기간 · 개수 정하기 (오래된 것 지우기) |
| 로그 · 기록 | 요청마다 한 줄씩 | 보관 기간, 필요한 칸만 |
| 대기 | 느린 API를 기다리는 동안 연결 · 메모리를 잡고 있음 | timeout (72일차) |
**컨테이너는 자원에 한도를 둘 수 있습니다.** Docker는 컨테이너마다 쓸 수 있는 메모리 · CPU의 상한을 정할 수 있습니다. 상한이 없으면 한 컨테이너가 컴퓨터 전체를 써서 다른 서비스까지 멈추게 할 수 있습니다. 운영 서버에서는 상한을 두고, `docker stats` 같은 도구로 지금 얼마나 쓰는지 봅니다.
**동시 실행도 자원입니다.** 워크플로우 엔진은 요청 하나를 처리하는 동안 실행 하나를 잡고 있습니다. LLM 답을 기다리는 몇 초 동안에도 그 실행은 메모리를 씁니다. 동시에 들어오는 요청이 많으면 기다리는 실행이 쌓여 메모리가 늘고, 엔진이 받을 수 있는 수를 넘으면 뒤의 요청은 줄을 서거나 실패합니다. 그래서 운영에서는 **동시 실행 수의 상한**을 정하고, 넘치는 요청은 큐에 넣거나 "잠시 후"로 돌려보냅니다 (3단계의 429와 같은 생각).
**자원 비용과 API 비용은 성질이 다릅니다.** 서버 자원은 대개 **쓰지 않아도** 시간만큼 냅니다 (켜 둔 서버). API 비용은 **부르지 않으면 0**입니다. 그래서 자원은 "얼마나 큰 서버가 필요한가"(용량 계획)로, API는 "요청 하나에 얼마 · 하루에 몇 번"(사용량 관리)으로 관리합니다. 오늘은 주로 뒤쪽입니다.
### 오늘 실습에서
Lab 01 예제 1의 `docker stats --no-stream` (수업 준비 때):
| 컨테이너 | CPU | 메모리 |
| --- | --- | --- |
| n8n | 0.27% | 642.6MiB |
| runners (Code 노드를 돌리는 곳) | 0.21% | 100.5MiB |
| data-api | 0.02% | 20.3MiB |
질문을 보내는 동안에도 CPU는 1%를 넘지 않았습니다. LLM은 OpenAI 서버에서 계산하기 때문입니다. 오늘 돈이 드는 곳은 내 노트북이 아니라 **토큰**입니다. n8n의 메모리는 실행 기록이 쌓이면 늘어나므로, 76 · 77일차에 실행 기록을 다룰 때 다시 봅니다.
## 3. 토큰과 단가 (2교시 Lab 01 예제 1)
### 일반 설명
**토큰은 LLM이 글을 세는 단위입니다.** 모델은 글을 글자나 낱말이 아니라 **토큰**이라는 조각으로 나눠 읽고 씁니다. 자주 나오는 글자 묶음은 한 토큰이 되고, 드문 것은 여러 토큰으로 나뉩니다. 그래서 같은 뜻이라도 언어 · 글의 종류에 따라 토큰 수가 다릅니다.
| 글 | 토큰이 되는 모양 (대략) |
| --- | --- |
| 영어 문장 | 낱말 하나가 토큰 1~2개 |
| 한국어 문장 | 글자 1~2개가 토큰 하나쯤 (모델의 토큰 나누는 법에 따라 다름) |
| 숫자 · 기호 · 로그 | 잘게 나뉘어 생각보다 많음 |
| JSON | 따옴표 · 괄호 · 칸 이름도 모두 토큰 |
토큰을 나누는 규칙(토크나이저)은 모델 계열마다 다릅니다. 같은 글을 다른 모델로 보내면 토큰 수가 조금 다를 수 있습니다. 정확한 수는 **공급자가 응답에 알려 주는 값**이 기준이고, 부르기 전에 미리 셀 때는 그 모델 계열의 토크나이저(예: OpenAI는 `tiktoken`)로 어림합니다 (7교시 `tcost`).
**입력과 출력은 따로 셉니다.**
| | 무엇 | 예 |
| --- | --- | --- |
| 입력 토큰 (`prompt_tokens`) | 보낸 것 전부: 지시문(system) + 질문 + 붙인 자료 + 앞 대화 | FAQ · 접수 건 · 규정 |
| 출력 토큰 (`completion_tokens`) | 모델이 만든 답 | 답 문장 |
**단가는 1M(백만) 토큰당 달러입니다.** 그리고 대개 **출력이 입력보다 비쌉니다.** 출력은 모델이 토큰을 하나씩 만들어 내야 하므로 계산이 더 들기 때문입니다.
```text
비용 = (입력 토큰 × 입력 단가 + 출력 토큰 × 출력 단가) ÷ 1,000,000
```
오늘 쓰는 두 모델의 단가 (OpenAI 모델 문서, 2026-10-28 확인, 1M 토큰당 달러):
| 모델 | 입력 | 캐시된 입력 | 출력 | 출력 ÷ 입력 |
| --- | --- | --- | --- | --- |
| `gpt-4.1-mini` | 0.40 | 0.10 | 1.60 | 4배 |
| `gpt-5.4-nano` | 0.20 | 0.02 | 1.25 | 약 6배 |
- **캐시된 입력**: 공급자가 같은 앞부분(긴 지시문)을 최근에 본 적이 있으면 그 부분을 싸게 받는 것입니다. 지시문을 앞에, 바뀌는 질문을 뒤에 두면 캐시가 잘 맞습니다. 오늘 계산은 캐시를 빼고 합니다 (보수적으로).
- **배치(Batch)**: 바로 답이 필요 없는 일(밤새 문서 요약 등)은 모아서 보내면 더 싸게 받는 방식도 있습니다. 실시간 헬프데스크에는 맞지 않습니다.
- **가격은 바뀝니다.** 공급자는 단가를 내리기도 하고, 새 모델을 내고 옛 모델을 지원 종료하기도 합니다 (74일차 `gpt-4.1-nano`). 그래서 단가표에는 **언제 확인했는지**를 같이 적습니다.
**계산을 손으로 한 번 해 봅니다.** 입력 2,000 · 출력 150토큰, `gpt-4.1-mini`라면:
```text
(2,000 × 0.40 + 150 × 1.60) ÷ 1,000,000 = (800 + 240) ÷ 1,000,000 = $0.00104
```
아주 작은 숫자입니다. 하지만 하루 1,000번 · 한 달 22일이면 $0.00104 × 22,000 ≈ $22.9입니다. 직원 수 · 질문 수가 늘거나, 같은 일을 하는 워크플로우가 여러 개면 곱해집니다. **작은 숫자에 큰 숫자를 곱하는 것**이 API 비용의 성질입니다.
**부르기 전에 세 보는 방법.** 지시문 · 자료를 바꾸기 전에 비용을 알고 싶으면, 그 글을 토크나이저로 세어 단가를 곱합니다. 공급자의 토큰 세기 도구(OpenAI `tiktoken`, 다른 공급자의 token count API)를 쓰면 API를 실제로 부르지 않고도 어림할 수 있습니다. 다만 실제 요청에는 실행 때 붙는 질문 · 검색 결과 · 대화 기록이 더해지므로, 미리 센 값은 **하한**에 가깝습니다. 7교시 `tcost`가 이것을 만듭니다.
**토큰을 쓰는 것은 돈만이 아닙니다.** 모델마다 한 번에 받을 수 있는 토큰 수(컨텍스트 창)의 상한이 있고, 입력이 길수록 응답이 늦어집니다. 긴 입력은 비용 · 속도 · 한도를 모두 씁니다.
### 오늘 실습에서
Lab 01 예제 1의 `call base-cost requests/faq-vpn.json` (수업 준비 때):
| 단계 | 입력 | 출력 |
| --- | --- | --- |
| classify | 78 | 6 |
| answer | **1,781** | 78 |
| review | 140 | 66 |
| 합 | 1,999 | 150 |
입력의 89%가 `answer` 단계 하나에서 나옵니다. 접수 질문(T-1002)도 입력 1,973 · `answer` 1,791로 거의 같습니다. 질문이 VPN이든 접수 상태든 **매번 같이 보내는 것**이 있다는 뜻입니다. 위 손 계산이 바로 이 VPN 질문입니다 ($0.00104).
## 4. 토큰이 보이는 호출 (2교시 Lab 01 예제 2)
### 일반 설명
LLM API는 답과 함께 **얼마나 썼는지**를 돌려줍니다. OpenAI Chat Completions 응답의 `usage`가 그것입니다.
```json
{
"choices": [{"message": {"content": "VPN 클라이언트를 최신 버전으로 …"}}],
"usage": {"prompt_tokens": 1781, "completion_tokens": 78, "total_tokens": 1859}
}
```
Anthropic · Google 등 다른 공급자도 이름은 조금 달라도(`input_tokens` · `output_tokens` 등) 같은 정보를 줍니다. **이 숫자가 청구의 기준**이므로, 비용을 재려면 이것을 받아 두어야 합니다.
**편한 도구가 숫자를 숨기기도 합니다.** 프레임워크 · 노코드 도구의 "AI Agent" 같은 상자는 답 글자만 다음 단계로 넘기고 `usage`를 버리는 경우가 있습니다. 편하지만, 그러면 비용을 잴 수 없습니다. 방법은 두 가지입니다.
| 방법 | 장점 | 단점 |
| --- | --- | --- |
| 공급자 API를 직접 부름 (HTTP) | `usage`를 그대로 받음, 무엇을 보내는지 다 보임 | 대화 · 도구 호출을 직접 다뤄야 함 |
| 관측(observability) 도구를 붙임 | 기존 흐름을 크게 안 바꿈, 화면이 좋음 | 도구를 하나 더 운영 |
**LLM 관측 도구.** LLM 호출마다 입력 · 출력 · 토큰 · 비용 · 시간을 모아 보여 주는 도구들이 있습니다 (예: Langfuse, Helicone, LangSmith, 클라우드의 모니터링 서비스). OpenTelemetry에도 생성형 AI 호출을 기록하는 공통 규칙(GenAI semantic conventions)이 정해지고 있습니다. 이름은 달라도 모으는 것은 같습니다: **어느 요청의 · 어느 단계가 · 어느 모델로 · 몇 토큰 · 얼마 · 몇 초.** 오늘은 이것을 n8n과 데이터 API로 직접 만들어 봅니다 (과제 1).
**토큰 수와 함께 남길 것.**
| 칸 | 왜 |
| --- | --- |
| 모델 이름 (실제로 불린 것) | 단가가 모델마다 다름. 74일차처럼 모델이 바뀐 날을 찾음 |
| 단계 (분류 · 답 · 검토) | 어디서 많이 쓰는지 (2단계) |
| 요청 번호 | 한 요청의 여러 호출을 묶음 (7절) |
| 누가 · 어떤 경로 | 사람별 · 경로별 합 (3단계) |
**내 기록과 공급자 청구는 맞춰 봅니다.** 내가 남긴 토큰 · 비용의 합과 공급자 사용량 화면(대시보드)의 숫자가 조금 다를 수 있습니다. 기록이 빠진 요청(8절), 테스트 · 실험으로 부른 것, 단가표가 오래된 것, 캐시 · 배치 할인이 그 까닭입니다. 운영하는 쪽은 정기적으로(예: 매주) 두 숫자를 나란히 놓고, 차이가 크면 어디서 새는지 찾습니다. 내 기록은 **나눠 보기**(누가 · 어디서)에, 공급자 청구는 **실제로 낼 돈**에 씁니다.
### 오늘 실습에서
오늘 워크플로우는 OpenAI를 **HTTP Request 노드로 직접** 부릅니다 (AGENTS.md 5절). 그래서 노드의 출력에 `usage`가 그대로 있고, 마지막 `Build result`(Code)가 단계마다 모아 `usage.by_step`으로 결과에 넣습니다. 66~74일차의 AI Agent 노드는 답만 주고 토큰 수를 주지 않아서 오늘은 쓰지 않습니다.
## 5. 단계별 비용, 단가는 밖에 (2교시 Lab 01 예제 2)
### 일반 설명
**비용을 어디에 붙이나 (cost attribution).** 청구서는 "OpenAI $123"처럼 한 줄로 옵니다. 줄일 곳을 찾으려면 그 돈이 **어느 기능 · 어느 단계 · 누구**에게서 나왔는지 나눠야 합니다. 그래서 요청마다, 단계마다 비용을 계산해 붙입니다.
```mermaid
flowchart LR
B["청구서 $123"] --> F["기능별<br/>헬프데스크 · 요약 · 번역"] --> S["단계별<br/>분류 · 답 · 검토"] --> U["사람 · 팀별"]
```
**단가는 코드 밖에 둡니다.** 단가를 코드에 숫자로 박으면 두 가지 사고가 납니다. 가격이 바뀌었는데 코드를 안 고쳐 비용 숫자가 **조용히 틀리고**, 모델을 바꿨는데 단가를 그대로 둬 **다른 모델의 값**으로 계산합니다. 그래서 단가표를 한 곳(설정 · 데이터)에 두고, 계산하는 쪽은 **모델 이름으로 찾아** 씁니다. 70일차 설정 표, 74일차 릴리스 · 지시문 저장소와 같은 생각입니다: **바뀌는 값은 한 곳에 두고, 모두 그것을 읽는다.**
| 단가를 | 가격이 바뀌면 | 모델을 바꾸면 |
| --- | --- | --- |
| 코드에 숫자로 | 코드를 모두 찾아 고침 (빠뜨리기 쉬움) | 단가도 같이 고쳐야 함 |
| 단가표에 (모델 이름 → 단가) | 단가표 한 곳만 | 계산은 저절로 그 모델 값 |
**모르는 모델은 0원이 아닙니다.** 단가표에 없는 모델 이름이 오면 "없으니 0"으로 계산하기 쉽습니다. 그러면 그 모델은 공짜로 보이고, 비용 보고서는 거짓말을 합니다. 없는 이름은 오류로 알려야 합니다 (Lab 03 과제 2, 7교시 `tcost`).
**반올림을 정해 둡니다.** 요청 하나의 비용은 $0.000156처럼 아주 작아서, 계산할 때 소수점 아래가 길게 나옵니다 (0.00015560000000001). 자리를 정하지 않으면 합계 · 테스트가 흔들립니다. 오늘 계약은 **소수 8자리**입니다.
### 오늘 실습에서
Lab 01 예제 2는 `Get prices`(HTTP `GET /prices`)를 앞에 두고, `Build result`가 단계마다 그 단계 모델의 단가로 `cost_usd`를 계산해 `usage.by_step.<단계>.cost_usd`와 합계 `cost_usd`를 결과에 넣습니다. 수업 준비 때 VPN 질문의 결과가 **$0.001040** — 3절의 손 계산과 같았습니다. 테스트는 `range`로 비용이 0보다 크고 $0.01 이하인지 봅니다. 줄 끝에 `· 자동 답변 · 3 calls · $0.001040`처럼 호출 수와 비용이 나옵니다.
## 6. 질문 종류별 비용과 한 달 어림 (2교시 Lab 01 예제 3)
### 일반 설명
**한 달 비용 = 요청 하나의 비용 × 하루 요청 수 × 일하는 날.** 식은 간단하지만, "요청 하나의 비용"을 무엇으로 잡느냐가 어렵습니다. 요청마다 비용이 다르기 때문입니다.
**고정비와 변동비로 나눠 봅니다.**
| | 무엇 | 예 |
| --- | --- | --- |
| 고정비 | 질문과 관계없이 매번 드는 몫 | 늘 보내는 긴 지시문, 늘 부르는 단계 |
| 변동비 | 질문마다 달라지는 몫 | 질문 길이, 검색된 FAQ 개수, 답의 길이 |
고정비가 크면 어떤 질문이든 비용이 비슷하고, 줄일 때는 **고정비부터** 줄이는 것이 효과가 큽니다. 고정비가 작아지면 그다음에 변동비(긴 질문, 긴 답)가 눈에 띕니다 (Lab 02).
**종류마다 재고, 비중으로 섞습니다 (가중 평균).**
```text
한 달 ≈ Σ(종류별 평균 비용 × 그 종류의 비중) × 하루 요청 수 × 일하는 날
```
비중은 지난 기록에서 셉니다 (과제 1의 기록, 77일차 로그 분석). 기록이 없으면 가정한 값을 쓰고, **가정이라고 적어 둡니다.**
**평균만 보면 안 되는 때.** 대부분의 질문은 싸지만 가끔 아주 비싼 질문(긴 로그를 붙인 질문)이 온다면, 평균은 그 몇 개에 끌려 올라갑니다. 72일차에 시간을 p50 · p95로 본 것처럼, 비용도 **분포**를 봅니다. "질문 하나의 최대 비용"이 정해져 있지 않으면 한 달 예상도 믿기 어렵습니다 (Lab 02 과제 2의 상한).
**어림은 범위로 말합니다.** LLM 답의 길이는 실행마다 조금씩 달라서, 같은 질문도 비용이 약간 다릅니다. 그래서 한 달 예상은 "$20.8"보다 "약 $21, 사용량이 두 배면 $42"처럼 **가정과 함께** 말합니다. 실제 숫자는 기록을 쌓은 뒤 보고서로 다시 봅니다 (3단계).
### 오늘 실습에서
Lab 01 예제 3의 `test cases/lab01-mix.json` (수업 준비 때, 예제 2 정답):
| 종류 | 질문 | 비용 | 종류 평균 |
| --- | --- | --- | --- |
| FAQ | VPN · 프린터 · 비밀번호 · 메일 | $0.001040 · 0.000960 · 0.000953 · 0.000965 | $0.000980 |
| 접수 | T-1002 · T-1001 | $0.000946 · 0.000890 | $0.000918 |
| 그 밖 | 회의실 화면 공유 | $0.000830 | $0.000830 |
일곱 질문 모두 3 calls, 비용이 **$0.00083 ~ $0.00104로 비슷합니다.** 규정 전체를 매번 보내는 고정비가 비용의 대부분이기 때문입니다. 비중 60 · 30 · 10%(연습용 가정)로 섞으면 $0.000946 × 1,000건 × 22일 ≈ **한 달 $20.8**, 일곱 개 단순 평균이면 ≈ $20.7. 고정비가 커서 두 어림이 거의 같습니다. Lab 02로 고정비를 없애면 질문마다 $0.000097 ~ $0.000166으로 벌어집니다 (변동비가 보임).
## 7. 기록: 요청 · 호출 · 사람 (2교시 Lab 01 과제 1)
### 일반 설명
답에 비용을 남기면 **질문한 사람만** 그 숫자를 봅니다. 오늘 전체로 얼마를 썼는지, 누가 많이 쓰는지, 예산을 넘었는지는 **어딘가에 쌓인 기록**이 있어야 압니다. 쓴 양을 재서 남기는 것을 **계량(metering)**이라고 부릅니다. 전기 계량기처럼, 재지 않으면 나눌 수도 막을 수도 없습니다.
**요청과 호출은 다릅니다.**
| | 뜻 | 질문 3개 · 각 3단계 |
| --- | --- | --- |
| 요청 (request) | 사용자가 한 번 물은 것 | 3 |
| 호출 (call) | LLM을 한 번 부른 것 | 9 |
기록은 **호출마다 한 줄**이 정확합니다 (단계 · 모델마다 토큰 · 단가가 다르므로). 그 줄들이 같은 요청이라는 것을 알리려고 **같은 요청 번호**를 붙입니다. 분산 시스템에서 한 요청이 여러 서비스를 지나갈 때 같은 추적 번호(trace id)를 붙이는 것과 같습니다 (77일차).
**태그를 붙입니다.** 기록 한 줄에 사람 · 팀 · 기능 · 경로를 같이 남기면 나중에 어떤 칸으로든 나눠 합칠 수 있습니다.
| 나눠 보기 | 쓰는 곳 |
| --- | --- |
| 사람 · 팀별 | 사용자별 한도 (3단계), 부서에 비용을 알려 주기 (showback) · 나눠 청구하기 (chargeback) |
| 모델별 | 모델을 바꾸면 얼마 (3단계 과제 2) |
| 경로별 (`자동 답변` · `한도 초과` · `잠시 후`) | 예산 · 한도가 얼마나 자주 걸리나 |
| 단계별 | 줄일 곳 (2단계) |
**기록에 남기지 않을 것.** 비용 기록은 여러 사람이 오래 봅니다. 그래서 **질문 · 답의 원문**이나 연락처 같은 개인정보는 넣지 않고, 숫자와 분류 칸(누가 · 어느 단계 · 어느 모델 · 몇 토큰 · 얼마 · 어느 경로)만 남깁니다. 원문이 필요한 분석은 따로 권한을 둔 곳에서 합니다 (73일차 감사 로그). "누가"도 이름 대신 사번 · 익명 번호로 남기는 회사가 많습니다.
| 칸 | 비용 기록에 | 까닭 |
| --- | --- | --- |
| 요청 번호 · 단계 · 모델 · 토큰 · 비용 · 경로 · 시각 | 남김 | 합치고 나누는 데 필요 |
| 사람 | 남김 (가능하면 번호로) | 사람별 한도 · 합계 |
| 질문 · 답 원문 | 남기지 않음 | 개인정보 · 사내 정보가 섞임 |
| API 키 · 비밀번호 | 절대 남기지 않음 | 기록은 많은 사람이 읽음 (73일차) |
**기록은 예산 · 한도의 바탕입니다.** "오늘 $1 넘으면 LLM을 쓰지 않는다"(예산), "한 사람이 1분에 20번 넘으면 잠시 막는다"(한도)는 모두 기록을 세어야 할 수 있습니다. 기록이 빠지면 예산 · 한도도 그만큼 덜 셉니다 (8절).
### 오늘 실습에서
Lab 01 과제 1은 `Build result` 뒤에 `Record usage`(HTTP `POST /usage`)를 넣어 `usage.by_step`의 단계마다 한 줄 `{request_id, user, step, model, prompt_tokens, completion_tokens, cost_usd, route}`를 남깁니다. `request_id`는 n8n의 실행 번호(`$execution.id`)입니다. 수업 준비 때 질문 3개 뒤 `/usage/summary`가 **requests 3 · calls 9 · $0.002951**, 사람별 이준호 $0.001040 · 박서연 $0.000959 · 한지훈 $0.000953이었습니다. 기록 노드를 지나면 다음 노드의 입력이 기록 API의 응답(`{"stored": 3}`)이 되므로, 응답하기 전에 `Build result`의 결과를 다시 꺼냅니다.
## 8. 기록이 멈추면 (2교시 Lab 01 과제 2)
### 일반 설명
**부가 기능이 본 기능을 막으면 안 됩니다.** 사용자에게 중요한 것은 답이고, 기록은 운영하는 쪽의 일입니다. 그런데 기록이 끝나야 답을 보내게 만들면, 기록 서버가 느릴 때 사용자가 기다리고, 멈추면 답도 못 받습니다. 부가 기능(기록 · 알림 · 통계)은 **실패해도 본 기능이 계속되게** 만듭니다.
| 방법 | 어떻게 | 오늘 |
| --- | --- | --- |
| 시간 제한 | 기록을 N초까지만 기다림 | `timeout` 3초 |
| 실패해도 계속 | 기록이 실패하면 그 사실만 남기고 답을 보냄 | `onError: continueRegularOutput` |
| 나중에 보내기 | 기록을 큐에 넣고 따로 보냄 (비동기) | (오늘은 하지 않음) |
| 표시 남기기 | 기록이 빠졌다는 표시 | `recorded: false` |
**계속 가는 쪽의 값.** 74일차 지시문 저장소처럼 여기도 **계속 가는 쪽(fail-open)**입니다. 그런데 값이 있습니다. 기록이 빠진 요청은 예산 · 한도에 세어지지 않아, 실제보다 덜 쓴 것처럼 보입니다. 그래서 빠졌다는 표시(`recorded: false`)를 남겨 나중에 몇 건이 빠졌는지 셀 수 있게 합니다.
| | 73일차 판별 LLM | 74일차 지시문 저장소 | 오늘 사용량 기록 |
| --- | --- | --- | --- |
| 멈추면 | 막는다 (fail-closed) | 기본 지시문으로 답 | 답은 그대로, 기록만 빠짐 |
| 남기는 표시 | `guard_down` | `prompt_source: fallback` | `recorded: false` |
| 치르는 값 | 사용자가 기다림 | 지시문이 최신이 아닐 수 있음 | 예산 · 한도가 덜 셈 |
**시간 초과로 포기해도 상대의 일은 끝나지 않을 수 있습니다.** 내 쪽에서 3초에 포기해도, 상대 서버는 그 요청을 계속 처리해 **결국 저장할 수** 있습니다. 그러면 나는 "기록 안 됨"이라고 남겼는데 실제로는 기록된 상태가 됩니다. 분산 시스템에서는 이런 일이 흔해서, 같은 기록이 두 번 들어가도 한 번으로 세게 하는 장치(같은 요청 번호면 한 번만 세기, 멱등성 idempotency)를 둡니다. 오늘 요약이 `request_id`로 요청 수를 세는 것도 그 하나입니다.
### 오늘 실습에서
Lab 01 과제 2 (수업 준비 때):
| `cases/lab01-challenge2.json` | 고치기 전 (과제 1 정답) | 고친 뒤 |
| --- | --- | --- |
| 기록 API 고장 (`usage error`, 500) | 답은 나감 · `recorded` 없음 | `recorded: false` |
| 기록 API 느림 (`usage slow`, 8초) | **11.2초** 걸려 답 · `recorded` 없음 | **6.1초** · `recorded: false` |
| 고친 뒤 · 기록됨 | `recorded` 없음 | `recorded: true` |
| 요약 · 기록된 요청 | 1건 | 1건 |
**수업 준비 중에 실제로 일어난 일.** 처음 데이터 API는 느림 모드에서 8초를 기다린 뒤 **결국 저장**했습니다. 워크플로우는 3초에 포기하고 `recorded: false`로 답했는데, 늦게 저장된 한 줄이 다음 테스트를 시작한 뒤에 들어와 그 테스트의 요청 수가 하나 많았습니다. 위 "포기해도 상대의 일은 끝나지 않는다"가 실제로 일어난 것입니다. 실습용 데이터 API는 "늦게 답하고 저장하지 않음"(멈춘 기록 서버)으로 바꿨습니다.
## 9. 요청문(프롬프트)으로
**요청문(프롬프트)이란.** 실습에서 CLI 코딩 에이전트(codex · claude · agy)에게 보내는 글입니다. 에이전트는 이 글만 보고 n8n 워크플로우 JSON을 만들거나 고칩니다. 이 절은 이 단계에서 배운 것을 **요청문(프롬프트)에 어떤 칸으로 적을지** 실습 전에 정리하는 자리입니다. 실습 문서의 `▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대`가 이 칸을 실제 문장으로 만든 것이고, 빈칸 ①~④는 그 문장 속 정확한 값입니다.
| 칸 | 에이전트에게 알려 주는 것 | 빠지면 생기는 일 |
| --- | --- | --- |
| 무엇을 | 만들거나 바꿀 기능 | 에이전트가 짐작해서 다른 것을 만듦 |
| 어디에 | 고칠 파일 · 노드 · 자리 | 엉뚱한 노드를 고치거나 새 파일을 만듦 |
| 어떤 모양으로 | 결과 JSON의 칸 이름 · 값 · 응답 코드 | 테스트가 보는 칸 이름과 달라 실패 |
| 그대로 · 하지 말 것 | 건드리면 안 되는 것 | 다른 곳까지 바뀌어 무엇 때문인지 모르게 됨 |
| 확인 | 통과해야 할 테스트 파일 | "다 됐다"를 판단할 기준이 없음 |
67~71일차의 다섯 가지(① 파일 ② 입력 ③ 처리 ④ 결과 모양 ⑤ 기대 결과)와 같은 뜻입니다. 파일 · 입력이 "어디에", 처리가 "무엇을", 결과 모양이 "어떤 모양으로", 기대 결과가 "확인"입니다. 72일차부터는 **이미 있는 워크플로우를 고치는** 요청이 많아 "그대로"가 따로 한 칸이 됐습니다.
오늘도 요청문의 뼈대는 같습니다: **무엇을 · 어디에 · 어떤 모양으로 · 무엇은 그대로 · 무엇으로 확인**. 비용 요청에서는 하나가 더 붙습니다: **LLM을 몇 번 · 어느 모델로 · 무엇을 보내는가, 그리고 숫자는 어디서 읽는가(단가 · 예산)**.
아래 표의 열은 이 단계에서 에이전트를 쓰는 실습, 줄은 위 다섯 칸입니다. 각 칸에 그 실습에서 채울 것을 적었습니다.
| 요청문에 | Lab 01 예제 2 | Lab 01 과제 1 | Lab 01 과제 2 |
| --- | --- | --- | --- |
| 무엇을 | 단계마다 비용 | 요청마다 기록 | 기록이 멈춰도 답 |
| 어디에 | `Has question?` 뒤 `Get prices`, `Build result` | `Build result` 뒤 `Record usage` · `Result again` | `Record usage`의 설정, `Result again` |
| 어떤 모양으로 | `usage.by_step.<단계>.cost_usd`, `cost_usd` (8자리) | 단계마다 한 줄, 같은 `request_id` | `recorded: true \| false` |
| 그대로 | 단가를 숫자로 쓰지 않음 (`/prices`에서) | 응답은 `Build result` 그대로 | `answer` · `cost_usd` |
| 확인 | `cases/lab01.json` | `cases/lab01-challenge.json` | `cases/lab01-challenge2.json` |
예를 들어 Lab 01 예제 2 열을 위에서부터 이어 읽으면 요청문(프롬프트)이 됩니다 (화살표 오른쪽은 다섯 칸 중 어디인지):
```text
단계마다 비용을 계산해 결과에 넣어 줘. ← 무엇을
Has question? 뒤에 Get prices(GET /prices), 계산은 Build result 에서. ← 어디에
usage.by_step.<단계>.cost_usd 와 합 cost_usd, 소수 8자리. ← 어떤 모양으로
단가는 숫자로 쓰지 말고 /prices 에서 그 단계 모델 이름으로 찾아. ← 그대로
cases/lab01.json 이 통과해야 해. ← 확인
```
## 10. 자주 하는 실수
| 실수 | 왜 문제인가 | 이렇게 |
| --- | --- | --- |
| 재지 않고 "아마 여기가 비쌀 것" | 엉뚱한 곳을 줄임 | 단계별 토큰부터 (`usage.by_step`) |
| 1,000,000으로 나누지 않음 | 비용이 백만 배 | 단가 단위(1M 토큰당)를 식에 |
| 단가를 코드에 숫자로 | 가격 · 모델이 바뀌면 조용히 틀림 | 단가표에서 모델 이름으로 |
| 모르는 모델을 0원으로 | 공짜로 보이는 거짓 보고서 | 오류로 알림 |
| 출력 토큰을 빼고 계산 | 출력이 더 비싸서 크게 틀림 | 입력 · 출력 따로 |
| VPN 하나만 재서 곱함 | 종류마다 비용이 다르면 틀림 | 종류별로 재고 비중으로 |
| 기록을 호출마다 다른 번호로 | 요청 수가 호출 수로 보임 | 같은 요청은 같은 `request_id` |
| 기록이 끝나야 답을 보냄 | 기록 서버가 느리면 사용자가 기다림 | timeout + 실패해도 계속 + `recorded` |
## 확인 질문
1. LLM API 비용이 일반 웹 서비스의 서버 비용과 다른 점 두 가지는?
2. 입력 2,000 · 출력 150토큰, 입력 $0.40 · 출력 $1.60(1M당)이면 한 번에 얼마인가요? 하루 1,000번 · 22일이면?
3. 질문 종류가 달라도 비용이 거의 같았습니다. 무엇을 알려 주나요? 무엇부터 줄이면 효과가 클까요?
4. 단가를 워크플로우 Code 안에 숫자로 적어 두면 어떤 사고가 날 수 있나요?
5. 질문 하나에 LLM을 세 번 부르면 기록은 몇 줄이고, "요청 수"는 어떻게 세나요?
6. 기록 API가 멈췄을 때 답은 보내고 `recorded: false`를 남깁니다. 이렇게 하면 무엇을 잃나요? 수업 준비 때 "포기했는데 늦게 저장된" 일은 왜 일어났나요?
## 정리
- [ ] 쓰는 것 셋: 컴퓨팅 자원 · API 토큰 · 돈 — API로 부르면 돈은 거의 토큰에서
- [ ] 비용 = (입력 × 입력 단가 + 출력 × 출력 단가) ÷ 1,000,000, 출력이 더 비싸다
- [ ] 응답의 `usage`를 받고, 단계마다 그 모델의 단가(단가표에서)로 계산한다
- [ ] 고정비 · 변동비로 나눠 보고, 종류별 · 비중으로 한 달을 어림한다
- [ ] 호출마다 기록하되 같은 요청은 같은 번호, 기록이 멈춰도 답은 나가고 `recorded`를 남긴다
+359
View File
@@ -0,0 +1,359 @@
# 2단계 · 비용 줄이기
- 교시: 3교시 강의 (11:00~11:50) · 4교시 [Lab 02](../../Last3Mon/75/LAB-02-Q-REDUCE-COST.md) 실습 5개 (12:00~12:50)
- 이 단계가 끝나면: 비용을 정하는 세 칸(호출 수 · 호출당 토큰 · 단가)을 구분하고, 한 번에 한 칸씩 줄이고, 바꿀 때마다 품질 테스트와 답 읽기로 잃은 것이 없는지 확인하고, 아주 긴 입력에도 요청 하나의 비용에 상한을 둘 수 있다.
- 숫자: 모두 강사 노트북 실측입니다 (65일차 n8n 2.42.6, OpenAI `gpt-4.1-mini` · `gpt-5.4-nano`, 10/28 정답 워크플로우를 학생 순서대로 1회). 출력 전부는 수업 뒤 공개되는 `Last3Mon/75/answers/LAB-TEST-RESULT.md`에 있습니다.
- 실습 가리키기: 이 문서에서 "Lab 02 예제 1"은 4교시 [Lab 02 문서](../../Last3Mon/75/LAB-02-Q-REDUCE-COST.md)의 그 절입니다. 파일 경로는 실습 폴더 `~/aio-01d4-stu/Last3Mon/75` 기준입니다.
```mermaid
flowchart LR
A["1. 비용의 세 칸"] --> B["2. 보내는 것 줄이기"] --> C["3. 단계마다 맞는 모델"] --> D["4. 싸졌는데 나빠졌다면"]
D --> E["5. 하지 않은 호출"] --> F["6. 긴 입력의 상한"] --> G["7. 줄인 것을 남기기"] --> H["8. 요청문(프롬프트)으로"]
```
## 0. 이 시간 (50분)
| 시간 | 절 · 개요 | 하는 일 |
| --- | --- | --- |
| 11:00~11:07 | 1 · 개요 15장 | 호출 수 × 토큰 × 단가, 줄이는 방법 지도, 품질 테스트를 먼저 |
| 11:07~11:15 | 2 · 개요 16장 | 필요한 것만 보내기, 필드 줄이기, 출력 상한, 캐시 |
| 11:15~11:22 | 3 · 개요 17장 | 모델 라우팅, 단가표를 다시 보기 |
| 11:22~11:29 | 4 · 개요 18장 | 통과했는데 나빠진 답, 사람이 읽기, 맞바꿈 |
| 11:29~11:36 | 5 · 개요 19장 | 규칙으로 먼저, 중복 단계 빼기 |
| 11:36~11:43 | 6 · 개요 20~21장 | 입력 · 출력 상한 = 요청 하나의 최대 비용, Denial of Wallet |
| 11:43~11:50 | 7 · 8 · 9 · 확인 질문 | 줄인 것을 남기기, 요청문, 자주 하는 실수 |
### 4교시 Lab 02에서 이어서 하는 것
| 4교시 실습 (시각) | 이 강의의 절 | 실습에서 하는 일 | 쓰는 파일 · 명령 |
| --- | --- | --- | --- |
| Lab 02 예제 1 (12:00~12:10) | 2 | 규정 전체를 빼고 필요한 칸만, 출력 상한 | `my-work/cost.json`, 테스트 `cases/lab02.json` (앞 세 케이스), `diff` |
| Lab 02 예제 2 (12:10~12:18) | 3 | 분류 · 검토를 `gpt-5.4-nano` | 테스트 `cases/lab02.json` 5개, `cases/lab01.json` |
| Lab 02 예제 3 (12:18~12:25, 에이전트 없이) | 4 | 답까지 nano인 비교본을 재고 **읽기** | `push reference/lab02-example3-nano-answer.json`, `test cases/lab02.json --webhook nano-answer -v`, `lab01-mix` 둘 |
| Lab 02 과제 1 (12:25~12:38) | 5 | 규칙 분류 · 검토 빼기 → LLM 한 번 | 테스트 `cases/lab02-challenge.json` 7개 |
| Lab 02 과제 2 (12:38~12:50) | 6 | 긴 질문은 앞 40% + 뒤 60%만 | `requests/long-printer.json`, 테스트 `cases/lab02-challenge2.json` |
## 1. 비용의 세 칸: 호출 수 × 호출당 토큰 × 단가
### 일반 설명
1단계의 식을 요청 하나로 묶으면 이렇습니다.
```text
요청 하나의 비용 = Σ (그 요청의 호출마다) 입력 토큰 × 입력 단가 + 출력 토큰 × 출력 단가
≈ 호출 수 × 호출당 토큰 × 토큰 단가
```
비용을 줄이는 방법은 모두 이 세 칸 중 하나를 줄이는 것입니다.
| 칸 | 줄이는 방법 (업계에서 쓰는 이름) | 오늘 |
| --- | --- | --- |
| 호출당 토큰 | 보내는 자료 줄이기 · 필요한 부분만 찾아 보내기(RAG) · 지시문 다듬기 · 출력 상한 · 긴 입력 자르기 | 예제 1 · 과제 2 |
| 토큰 단가 | 단계에 맞는 작은 모델(모델 라우팅) · 캐시된 입력 할인 · 배치 할인 | 예제 2 · 3 |
| 호출 수 | 규칙으로 먼저 정하기 · 중복 단계 빼기 · 같은 질문의 답 다시 쓰기(캐시, 72일차) | 과제 1 |
**한 번에 한 칸씩.** 세 칸을 한꺼번에 바꾸면 비용이 줄어도 무엇 덕분인지, 답이 나빠져도 무엇 때문인지 모릅니다. 72일차 성능 튜닝 · 74일차 버전 관리의 "한 번에 하나만 바꾸고 다시 잰다"가 여기서도 같습니다.
**품질 테스트를 먼저 고정합니다.** 비용을 줄이는 일은 늘 품질을 깎을 위험이 있습니다. 그래서 줄이기 전에 **"이것은 지켜야 한다"를 테스트로 적어 두고**, 바꿀 때마다 그 테스트를 다시 돌립니다. 테스트를 통과시키려고 기대 결과를 바꾸면 안 됩니다 (AGENTS.md 9절: 답의 내용 검사를 없애지 않는다).
```mermaid
flowchart LR
Q["품질 테스트 고정<br/>(contains)"] --> M["재기"] --> C["한 칸만 바꾸기"] --> T["테스트 · 비용 다시"] --> R["답 읽기"]
R -->|잃은 것 없음| K["남기기"]
R -->|잃은 것 있음| B["되돌리거나 맞바꿈을 결정"]
```
**숨은 비용도 셉니다.** 청구서에는 사용자가 받은 답만이 아니라 **부른 것 전부**가 들어갑니다.
| 숨은 비용 | 어디서 | 줄이는 법 |
| --- | --- | --- |
| 재시도 | 실패 · 시간 초과 뒤 다시 부름 (72일차) | 재시도 횟수 상한, 실패 원인부터 |
| 실패한 호출 | 형식이 틀려 버린 답, 중간에 끊긴 답 | 출력 형식을 단순하게, 짧게 |
| 버려지는 호출 | 결과를 쓰지 않는 단계 (오늘 `base-cost`의 분류는 결과를 쓰지 않음) | 쓰지 않는 단계 빼기 |
| 테스트 · 실험 | 개발 · 수업 준비 때 돌린 것 | 같은 테스트를 수십 번 돌리지 않기, 작은 표본 |
**어디부터 줄이나.** 1단계에서 잰 단계별 토큰을 봅니다. **가장 큰 몫**부터입니다. 전체의 89%가 한 단계의 입력이라면, 다른 곳을 아무리 줄여도 11% 안에서 움직입니다. 이것은 72일차에 가장 오래 걸리는 단계부터 줄인 것과 같습니다 (병목부터).
### 오늘 실습에서
하루 동안 VPN 질문 하나의 비용 (수업 준비 때, 같은 질문):
| 단계 | 바꾼 칸 | `answer` 입력 | 호출 | 비용 | 품질 테스트 |
| --- | --- | --- | --- | --- | --- |
| 기준 (Lab 01 과제 2 정답) | | 1,781 | 3 | $0.001007 | ✔ |
| 예제 1 · 보내는 것 줄이기 | 호출당 토큰 | **165** | 3 | $0.000346 | ✔ |
| 예제 2 · 분류 · 검토 nano | 단가 | 165 | 3 | $0.000278 | ✔ |
| 과제 1 · LLM 한 번 | 호출 수 | 약 165 | **1** | **$0.000156** | ✔ |
## 2. 보내는 것 줄이기 (4교시 Lab 02 예제 1)
### 일반 설명
**"혹시 필요할지 몰라서" 붙인 글이 비용의 대부분이 되기 쉽습니다.** 지시문에 사내 규정 · 매뉴얼 · 예시를 통째로 붙이면 답이 좋아질 것 같지만, 모델은 그 글을 **요청마다** 다시 읽고 공급자는 **요청마다** 돈을 받습니다. 그리고 관련 없는 긴 글은 모델을 헷갈리게 해 답이 오히려 나빠지기도 합니다.
**무엇을 보낼지 고르는 것도 설계입니다.** 요즘은 이것을 컨텍스트 엔지니어링(context engineering)이라고 부릅니다. 지시문 · 자료 · 대화 기록 중 **이번 답에 필요한 것만** 골라 보내는 일입니다.
| 보내는 것 | 줄이는 방법 |
| --- | --- |
| 참고 자료 (규정 · 매뉴얼) | 통째로 대신 질문과 관련된 부분만 검색해서 (RAG, 71일차) |
| 데이터 (FAQ · 접수 건) | 답에 쓰는 칸만 (`id` · `title` · `answer` / `status` · `note`) |
| 지시문 | 같은 말을 두 번 하지 않기, 예시는 꼭 필요한 만큼 |
| 대화 기록 | 최근 몇 개만, 또는 요약해서 |
| 출력 | 상한(`max_completion_tokens`), "3문장 이내" 같은 지시 |
**RAG에도 비용이 있습니다.** 필요한 부분만 찾아 보내면 답 단계의 입력은 줄지만, 찾는 데에도 돈이 듭니다. 문서를 벡터로 바꾸는 임베딩(71일차 `text-embedding-3-large`)은 색인을 만들 때 문서 전체에 한 번, 질문마다 질문에 한 번 듭니다. 대개 답 LLM에 긴 자료를 매번 보내는 것보다 훨씬 싸지만, 문서가 자주 바뀌어 색인을 자주 다시 만들면 그 비용도 셉니다. **줄인 곳 옆에 새로 생긴 비용**까지 합쳐야 진짜 절약입니다.
**예시(few-shot)도 비용입니다.** 지시문에 "이런 질문엔 이렇게 답하라"는 예시를 여러 개 붙이면 답의 모양이 고르게 되지만, 예시의 토큰도 **요청마다** 냅니다. 예시가 정말 효과가 있는지 예시 없이 같은 테스트를 돌려 비교하고, 효과가 있는 만큼만 남깁니다. 긴 지시문을 짧게 줄여 주는 압축 기법들(prompt compression)도 있지만, 압축하면 뜻이 바뀔 수 있어 역시 테스트로 확인합니다.
**칸을 빼면 안전해지기도 합니다.** 접수 건에는 연락처 · 담당자 같은 칸이 있습니다. 답에 쓰지 않는 칸을 LLM에 보내지 않으면 토큰도 줄고, 73일차처럼 개인정보가 답으로 새어 나갈 길도 하나 줄어듭니다. 개인정보 보호에서 말하는 **데이터 최소화**(필요한 것만 다룬다)와 같은 원칙입니다.
**출력 상한.** 출력은 입력보다 비쌉니다 (`gpt-4.1-mini`는 4배). `max_completion_tokens`는 답의 최대 길이입니다. 너무 작으면 답이 잘리고, 너무 크면 상한의 뜻이 없습니다. 지시문의 "3문장 이내"와 맞는 값을 둡니다. 그리고 공급자 · 모델마다 이름이 다를 수 있습니다 (`gpt-5.4-nano`는 예전 이름 `max_tokens`를 받지 않음).
**고정된 앞부분은 캐시가 받아 줍니다.** 공급자는 같은 앞부분을 최근에 받은 적이 있으면 그 입력을 싸게 받기도 합니다 (캐시된 입력, 1단계 3절). 그래서 바뀌지 않는 지시문은 **앞에**, 질문처럼 바뀌는 것은 **뒤에** 둡니다. 다만 캐시는 할인일 뿐이고, 필요 없는 글을 빼는 것이 먼저입니다.
### 오늘 실습에서
Lab 02 예제 1 (수업 준비 때):
| | 전 (`answers/08`) | 후 (`answers/09`) |
| --- | --- | --- |
| `answer` 입력 · VPN | 1,781 | **165** (91% 줄임) |
| `answer` 입력 · 접수 | 1,791 | 152 |
| VPN 비용 | $0.001007 | **$0.000346** |
| 품질 (인증서 · 처리 중 · 비밀번호 찾기) | ✔ | ✔ |
| `diff` | | `Answer` 본문 3,491 → 822자, 한 곳만 |
규정 전체는 답을 더 좋게 만들지 않았습니다. 지시문이 "FAQ와 접수 건으로만 답하라"고 했기 때문입니다. 규정이 필요한 질문이 있다면 통째로가 아니라 71일차처럼 **검색해서 관련 부분만** 보내는 것이 답입니다.
## 3. 단계마다 맞는 모델 (4교시 Lab 02 예제 2)
### 일반 설명
**모든 일에 같은 모델을 쓸 까닭은 없습니다.** 세 낱말 중 하나를 고르는 분류와, 사실을 틀리면 안 되는 답은 어려움이 다릅니다. 일의 어려움에 맞춰 모델을 고르는 것을 **모델 라우팅(model routing)**이라고 부릅니다. 쉬운 모델로 먼저 해 보고 자신이 없을 때만 큰 모델로 넘기는 방식은 **계단식(cascade)**이라고 부릅니다.
| 일 | 어려움 | 맞는 모델 |
| --- | --- | --- |
| 분류 · 낱말 뽑기 · 형식 바꾸기 | 쉬움 | 작은 모델 |
| 말투 다듬기 | 쉬움~보통 | 작은 모델, 또는 코드 검사 |
| 사실에 맞는 답 · 판단 | 어려움 | 검증된 모델 |
| 긴 추론 · 계획 | 어려움 | 큰 모델 |
**라우팅을 맡는 도구도 있습니다.** 여러 공급자 · 모델을 한 주소로 부르고, 규칙(작업 종류 · 길이 · 비용 한도)에 따라 모델을 골라 주는 게이트웨이 · 라우터 도구들이 있습니다 (예: LiteLLM, OpenRouter, 클라우드 공급자의 모델 게이트웨이). 이런 도구를 써도 **어느 일에 어느 모델이 충분한지**는 내 테스트로 정해야 합니다. 도구는 고른 결과를 실행해 줄 뿐입니다.
**단가표를 다시 봅니다.** "작은 모델이면 몇 배 싸다"는 늘 맞지 않습니다. 오늘 두 모델은 입력이 절반, 출력은 약 0.78배입니다. 그래서 **입력이 많은 단계**를 바꿀수록 효과가 크고, 출력이 많은 단계는 덜합니다. 공급자 · 시기마다 단가가 달라서 바꾸기 전에 직접 계산해야 합니다.
| 모델 | 입력 ($/1M) | 출력 ($/1M) |
| --- | --- | --- |
| `gpt-4.1-mini` | 0.40 | 1.60 |
| `gpt-5.4-nano` | 0.20 (절반) | 1.25 (약 0.78배) |
**추론하는 모델은 생각에도 토큰을 씁니다.** 요즘 모델 중에는 답하기 전에 속으로 추론하는 모델(reasoning model)이 있고, 그 추론에 쓴 토큰은 화면에 보이지 않아도 **출력 토큰으로 청구됩니다** (OpenAI 응답의 `usage.completion_tokens_details.reasoning_tokens`). 추론 정도를 고르는 옵션(`reasoning_effort` 등)이 있으면 쉬운 일에는 낮게 둡니다. 오늘 `gpt-5.4-nano`는 기본값이 추론을 하지 않는 쪽이라 수업 준비 때 `reasoning_tokens`가 0이었습니다. 모델을 바꿀 때는 단가만이 아니라 **같은 일에 토큰을 얼마나 쓰는지**도 재야 합니다.
**바꾼 단계는 평가로 확인합니다.** 작은 모델이 그 일을 정말 잘하는지는 **그 단계만의 테스트**로 봅니다. 74일차처럼 모델만 바꾼 릴리스를 만들어 golden 게이트를 지나게 하는 것이 정석입니다. 그리고 모델 이름을 바꿀 때 **비용 계산의 모델 이름도 같이** 바꿔야 합니다. 노드만 바꾸면 실제로는 nano를 부르는데 기록은 mini라고 하고, 비용도 mini 단가로 계산됩니다 (74일차 "한 곳에서 정하고 모두 그것을 읽는다").
### 오늘 실습에서
Lab 02 예제 2 (수업 준비 때): 분류 · 검토를 `gpt-5.4-nano`로 바꾸자 VPN 질문이 $0.000346 → **$0.000278**. 분류 · 검토는 입력이 많은 단계가 아니라 줄어든 폭이 작았습니다. `lab02` 5/5 · `lab01` 3/3, 답은 mini 그대로라 품질 테스트도 그대로 통과했습니다.
## 4. 싸졌는데 나빠졌다면 (4교시 Lab 02 예제 3)
### 일반 설명
**통과했다고 같은 품질은 아닙니다.** 오늘 테스트는 답에 **꼭 있어야 할 낱말**이 있는지만 봅니다. 낱말이 있어도 형식 · 말투 · 군더더기 · 안쪽 말이 새는지는 잡지 못합니다. 비용을 줄이다 품질이 조금씩 나빠지는 것을 **품질 회귀(quality regression)**라고 부르고, 테스트가 약하면 아무도 모르는 사이에 쌓입니다.
| 테스트가 잡는 것 | 테스트가 놓치기 쉬운 것 |
| --- | --- |
| 꼭 있어야 할 낱말 | 형식 (마크다운 기호가 메신저에 그대로 나감) |
| 응답 코드 · 칸 | 군더더기 (묻지 않은 말) |
| 비용 · 시간 범위 | 안쪽 말 (데이터 API의 오류 글자 · 칸 이름) |
| | 말투 · 친절함 |
**사람이 읽습니다.** 그래서 모델 · 지시문을 바꾸면 테스트와 함께 **답 몇 개를 사람이 직접 읽습니다.** 읽고 찾은 것은 다음부터 테스트로 만듭니다 (`not_contains: ["**"]`). 답이 많으면 다른 LLM에게 기준표를 주고 채점하게 하는 방법(LLM-as-a-judge)도 쓰지만, 그 채점도 사람이 표본을 확인합니다.
**평가 세트를 키웁니다.** 사람이 읽다가 찾은 나쁜 답은 그냥 고치고 끝내지 않고, **테스트 케이스로 남깁니다.** 그러면 다음에 누가 모델 · 지시문을 바꿔도 같은 실수를 다시 잡습니다. 74일차의 강한 golden이 이렇게 자랐습니다. 비용 작업은 "더 싼 쪽으로 바꿔 볼까?"가 자주 되풀이되므로, 평가 세트가 탄탄할수록 다음 절약을 빠르고 안전하게 할 수 있습니다.
**아끼는 돈과 잃는 것을 나란히 놓습니다.** 결정은 사람이 합니다.
| 놓고 볼 것 | 예 |
| --- | --- |
| 아끼는 돈 | 요청 하나 · 한 달 몇 % |
| 잃는 것 | 형식 · 말투 · 정확도 · 시간 |
| 되돌리는 값 | 나빠진 답을 받은 사용자, 고치는 시간 |
아끼는 돈이 작고 잃는 것이 보이면 **바꾸지 않는 것**이 맞는 결정입니다. 이것도 개선 작업의 정상적인 결론입니다.
### 오늘 실습에서
Lab 02 예제 3 (수업 준비 때, 비교본 `nano-answer` = 예제 2 정답에서 답 단계만 nano):
| | 답 mini (내 것) | 답 nano |
| --- | --- | --- |
| `lab02` 낱말 검사 | 5/5 | **5/5** |
| 일곱 질문 평균 비용 (`lab01-mix`) | $0.000233 | $0.000198 (약 15% 덜) |
답을 읽으면 nano 쪽에 접수 답의 `**처리 중**` 같은 마크다운, "접수 건은 ticket_id가 없어 상태를 확인할 수 없으며…" 같은 묻지 않은 안쪽 말, "접수 건은 찾지 못했습니다" 같은 군더더기가 있었습니다. 15%를 아끼려고 이것을 받을지는 사람이 정합니다. 오늘은 **답은 mini로 둡니다.** 74일차 2.0.0(답까지 nano)이 golden을 통과하고도 "비용을 본 뒤"로 미룬 것을 오늘 숫자로 본 셈입니다.
## 5. 가장 싼 호출은 하지 않은 호출 (4교시 Lab 02 과제 1)
### 일반 설명
토큰과 단가를 줄인 뒤에 남는 것은 **호출 수**입니다. 호출을 없애면 그 호출의 비용 · 시간 · 실패 가능성이 한꺼번에 사라집니다.
| 방법 | 언제 | 예 |
| --- | --- | --- |
| 규칙으로 먼저 | 판단이 분명한 경우가 대부분일 때 | 질문에 `T-1002`가 있으면 접수, FAQ 검색에 걸리면 FAQ |
| 중복 단계 빼기 | 두 단계가 같은 일을 할 때 | 답의 지시문이 이미 말투를 정하는데 검토가 또 함 |
| 코드로 검사 | 검사할 것이 길이 · 금지어 · 형식일 때 | 69일차 검토자 Code |
| 답 다시 쓰기 (캐시) | 같은 질문이 자주 올 때 | 72일차 FAQ 답 캐시 (사람별 데이터는 캐시하지 않음) |
**규칙이 모르는 것만 LLM에게.** 규칙은 공짜이고 빠르고 늘 같습니다. 대신 모든 경우를 알지 못합니다. 그래서 **규칙이 분명히 아는 것은 규칙으로**, 모르는 것만 LLM에게 넘깁니다. 대부분의 요청이 규칙으로 처리되면 평균 비용이 크게 줄고, 드문 질문만 비용이 조금 더 듭니다.
```mermaid
flowchart LR
Q(["질문"]) --> R{"규칙이 아나?"}
R -->|예 · 대부분| A{{"답 LLM 1번"}}
R -->|아니오 · 드묾| C{{"분류 LLM (작은 모델)"}} --> A
```
**캐시는 같은 질문이 반복될 때만.** 같은 질문의 답을 저장해 두고 다시 쓰면 호출이 0이 됩니다 (72일차). 다만 답이 **사람마다 다른** 질문(내 접수 건, 내 휴가 일수)은 캐시하면 남의 답이 나갑니다. 그리고 원본(FAQ)이 바뀌면 저장해 둔 답도 낡습니다. 캐시는 "같은 질문 · 같은 답 · 자주"가 모두 맞을 때 씁니다.
**뺀 단계가 하던 일을 확인합니다.** 단계를 빼기 전에 그 단계가 실제로 무엇을 했는지 봅니다. 검토 단계가 말투만 다듬었다면 빼도 되지만, 사실을 고치고 있었다면 빼면 안 됩니다. 빼고 나서 **품질 테스트가 그대로인지**로 확인합니다.
### 오늘 실습에서
Lab 02 과제 1 (수업 준비 때):
| | 고치기 전 (예제 2 정답) | 고친 뒤 |
| --- | --- | --- |
| FAQ 넷 · 접수 둘 | 3 calls · $0.000164 ~ 0.000297 (다섯이 $0.0002 넘음) | **1 call · $0.000108 ~ 0.000166** |
| 규칙으로 모르는 질문 (회의실) | 3 calls | 2 calls · $0.000097 |
| `lab02-challenge` | 0/7 | **7/7** |
| 시간 | 3초대 | 1초대 |
품질 테스트(`lab02` 5/5 · `lab01` 3/3 · `lab01-challenge` 4/4)도 그대로였습니다. 질문 3개를 기록한 합계가 Lab 01 때 $0.002951에서 **$0.000409**로, 약 7분의 1이 됐습니다.
## 6. 긴 입력의 상한 (4교시 Lab 02 과제 2)
### 일반 설명
**입력 길이에 상한이 없으면 요청 하나의 비용에도 상한이 없습니다.** 비용은 보낸 토큰에 비례합니다. 사용자가 로그 · 문서를 통째로 붙여 넣으면 그 한 번이 평소의 몇 배가 됩니다. 실수일 수도 있고, 일부러일 수도 있습니다. 공격자가 아주 긴 입력을 계속 보내 **상대의 돈을 바닥내는** 공격을 Denial of Wallet이라고 부릅니다 (73일차 "아주 긴 입력으로 괴롭히기"의 비용 쪽 얼굴).
**두 상한이 있어야 최대 비용을 말할 수 있습니다.**
```text
요청 하나의 최대 비용 ≈ (최대 입력 토큰 × 입력 단가 + 최대 출력 토큰 × 출력 단가) ÷ 1,000,000
↑ 입력 상한 (오늘 과제 2) ↑ 출력 상한 (예제 1의 max_completion_tokens)
```
최대 비용을 알면 예산을 지킬 수 있습니다 (3단계: "남은 예산이 한 건의 최대 비용보다 적으면 멈춘다").
**긴 입력을 다루는 방법.**
| 방법 | 비용 | 사용자 | 언제 |
| --- | --- | --- | --- |
| 거절 (HTTP 413 "너무 김") | 0 | 다시 써야 함 | 길면 안 되는 입력 (검색어 등) |
| 앞뒤만 남기기 | 상한 안 | 대개 답을 받음 | 질문이 앞이나 끝에 있는 붙여 넣기 |
| 나눠서 요약한 뒤 답 | 요약 호출이 또 듦 | 답을 받음 | 문서 전체가 필요할 때 |
| 검색해서 관련 부분만 | 상한 안 | 답을 받음 | 긴 자료에서 찾을 때 (RAG) |
**출력 상한에 걸린 답은 표시가 남습니다.** 답이 출력 상한에 닿아 중간에 끊기면 공급자 응답의 끝난 까닭이 `length`로 옵니다 (OpenAI `finish_reason`). 정상으로 끝나면 `stop`입니다. 끊긴 답이 자주 나오면 상한이 너무 작거나 지시문이 너무 긴 답을 부르고 있는 것이므로, 기록에 이 값도 남겨 셉니다.
**상한 값은 사람이 정하고 밖에 둡니다.** 너무 작으면 정상 질문도 잘리고, 너무 크면 상한의 뜻이 없습니다. 값은 단가처럼 **코드 밖**(설정 · 데이터)에 두고, 바꾸면 테스트로 확인합니다. 그리고 잘랐다는 사실(`trimmed`)을 결과에 남겨, 얼마나 자주 잘리는지 나중에 셀 수 있게 합니다.
### 오늘 실습에서
Lab 02 과제 2 (수업 준비 때, 상한 500자 = 앞 200 + 뒤 300):
| 질문 | 고치기 전 (과제 1 정답) | 고친 뒤 |
| --- | --- | --- |
| 프린터 로그 + 끝에 질문 (6,298자) | **$0.001196** (짧은 질문의 7배 넘게) | **$0.000237** · "대기열" |
| 앞에 질문 + VPN 로그 (6,103자) | $0.001099 | $0.000260 · "인증서" |
| 짧은 질문 (24자) | | `trimmed: false` · `question_chars: 24` |
| 사람이 상한을 10000으로 | | 자르지 않음 · $0.001196 |
수업 준비 때 처음에는 긴 질문도 $0.0002 이하를 기대했는데, 500자로 줄여도 $0.00024 ~ 0.00026, 300자로 줄여도 $0.00021 ~ 0.00022였습니다. 답(출력)과 FAQ만으로 약 $0.0001이 들기 때문입니다. 그래서 기대를 **$0.0003 이하**(줄이기 전의 약 4분의 1)로 정했습니다. 입력을 아무리 줄여도 **출력 · 고정 자료의 몫은 남는다**는 것이 이 숫자에서 보입니다.
## 7. 줄인 것을 남기기
### 일반 설명
비용을 줄인 작업은 **무엇을 바꿔 · 얼마를 아끼고 · 무엇을 잃었는지**를 한 줄씩 남깁니다. 나중에 품질 문제가 생기면 이 기록에서 "어느 절약 때문인지"를 찾고, 단가가 바뀌면 다시 계산합니다. 74일차 변경 기록(CHANGELOG)의 비용판입니다.
| 칸 | 예 |
| --- | --- |
| 바꾼 것 (한 칸만) | `Answer`에서 규정 전체를 뺌 |
| 비용 전 → 후 | VPN $0.001007 → $0.000346 |
| 품질 테스트 | `lab02` 앞 세 케이스 ✔ |
| 잃은 것 | 규정에만 있는 질문은 "찾지 못했습니다" |
| 되돌리는 법 | 이전 워크플로우 (74일차 릴리스) |
### 오늘 실습에서
`my-work/COST.md` 5절에 Lab 02의 다섯 실습을 한 줄씩 적습니다. 특히 예제 3의 "바꾸지 않기로 한 결정"과 그 까닭도 한 줄입니다.
## 8. 요청문(프롬프트)으로
**요청문(프롬프트)이란.** 실습에서 CLI 코딩 에이전트(codex · claude · agy)에게 보내는 글입니다. 에이전트는 이 글만 보고 n8n 워크플로우 JSON을 만들거나 고칩니다. 이 절은 이 단계에서 배운 것을 **요청문(프롬프트)에 어떤 칸으로 적을지** 실습 전에 정리하는 자리입니다. 실습 문서의 `▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대`가 이 칸을 실제 문장으로 만든 것이고, 빈칸 ①~④는 그 문장 속 정확한 값입니다.
| 칸 | 에이전트에게 알려 주는 것 | 빠지면 생기는 일 |
| --- | --- | --- |
| 무엇을 | 만들거나 바꿀 기능 | 에이전트가 짐작해서 다른 것을 만듦 |
| 어디에 | 고칠 파일 · 노드 · 자리 | 엉뚱한 노드를 고치거나 새 파일을 만듦 |
| 어떤 모양으로 | 결과 JSON의 칸 이름 · 값 · 응답 코드 | 테스트가 보는 칸 이름과 달라 실패 |
| 그대로 · 하지 말 것 | 건드리면 안 되는 것 | 다른 곳까지 바뀌어 무엇 때문인지 모르게 됨 |
| 확인 | 통과해야 할 테스트 파일 | "다 됐다"를 판단할 기준이 없음 |
67~71일차의 다섯 가지(① 파일 ② 입력 ③ 처리 ④ 결과 모양 ⑤ 기대 결과)와 같은 뜻입니다. 파일 · 입력이 "어디에", 처리가 "무엇을", 결과 모양이 "어떤 모양으로", 기대 결과가 "확인"입니다. 72일차부터는 **이미 있는 워크플로우를 고치는** 요청이 많아 "그대로"가 따로 한 칸이 됐습니다.
비용을 줄이는 요청에서는 **"그대로"가 가장 중요한 칸**입니다. 한 번에 한 칸만 바꾸려면 나머지를 그대로 두라고 적어야 하고, 품질 테스트(답의 낱말)를 지키라고 적어야 합니다.
아래 표의 열은 이 단계에서 에이전트를 쓰는 실습, 줄은 위 다섯 칸입니다.
| 요청문에 | Lab 02 예제 1 | Lab 02 예제 2 | Lab 02 과제 1 | Lab 02 과제 2 |
| --- | --- | --- | --- | --- |
| 무엇을 | 보내는 것 줄이기 | 쉬운 단계를 작은 모델로 | LLM 한 번 | 긴 질문 줄이기 |
| 어디에 | `Answer` 본문 | `Classify` · `Review`, `Build result`의 단계 목록 | `Get ticket` 뒤 `Rule classify` · `Known?`, `Review` 빼기 | `Get budget` · `Prepare question`, FAQ 검색 · 규칙 · 답이 읽는 질문 |
| 어떤 모양으로 | FAQ 3칸 · 접수 5칸, `max_completion_tokens` 200 | `usage.by_step.classify.model` = `gpt-5.4-nano` | `llm_calls` 1 (모를 때 2) | `trimmed` · `question_chars` |
| 그대로 | 분류 · 검토 단계 | 답은 `gpt-4.1-mini` | 답의 내용 (품질 테스트) | 과제 1의 결과 · 상한 값은 `/budget`에서 |
| 확인 | `cases/lab02.json` 앞 셋 | `cases/lab02.json` · `lab01` | `cases/lab02-challenge.json` · `lab02` · `lab01` · `lab01-challenge` | `cases/lab02-challenge2.json` · `lab02-challenge` |
예를 들어 Lab 02 예제 1 열을 위에서부터 이어 읽으면 요청문(프롬프트)이 됩니다 (화살표 오른쪽은 다섯 칸 중 어디인지):
```text
답 단계에 보내는 것을 줄여 줘. ← 무엇을
my-work/cost.json 의 Answer 본문에서. ← 어디에
규정 전체는 빼고, FAQ 는 id · title · answer, 접수 건은 다섯 칸만, max_completion_tokens 200. ← 어떤 모양으로
Classify · Review 는 그대로. ← 그대로
cases/lab02.json 앞 세 케이스가 통과해야 해. ← 확인
```
## 9. 자주 하는 실수
| 실수 | 왜 문제인가 | 이렇게 |
| --- | --- | --- |
| 세 칸을 한꺼번에 바꿈 | 무엇 덕분 · 무엇 때문인지 모름 | 한 번에 한 칸, `COST.md`에 한 줄 |
| 작은 곳부터 줄임 | 전체가 거의 안 줄어듦 | 가장 큰 몫(단계별 토큰)부터 |
| 테스트를 통과시키려고 기대를 바꿈 | 품질이 몰래 나빠짐 | 워크플로우를 고친다 |
| 통과했으니 같은 품질이라고 봄 | 형식 · 군더더기 · 안쪽 말을 놓침 | 답을 읽고, 찾은 것은 테스트로 |
| 노드의 모델만 바꿈 | 기록 · 비용이 다른 모델로 계산됨 | 계산의 모델 이름도 함께 |
| 출력 상한을 너무 작게 | 답이 잘림 | 지시문의 길이와 맞게 |
| 긴 입력을 그대로 둠 | 한 사람이 예산을 다 씀 | 입력 상한 · 출력 상한 → 최대 비용 |
| 상한 값을 코드에 | 바꿀 때마다 워크플로우 수정 | `/budget`처럼 밖에 |
## 확인 질문
1. 비용을 정하는 세 칸은? 오늘 Lab 02의 다섯 실습은 각각 어느 칸을 줄였나요?
2. 답 단계 입력이 1,781에서 165토큰으로 줄었는데 품질 테스트는 그대로였습니다. 무엇을 알려 주나요?
3. 작은 모델의 단가가 입력은 절반, 출력은 0.78배입니다. 어느 단계를 바꾸면 효과가 큰가요?
4. 답까지 nano로 바꾼 비교본이 테스트를 다 통과했는데도 바꾸지 않았습니다. 왜인가요?
5. 규칙으로 분류하고 검토를 뺐습니다. 검토를 빼도 되는지 무엇으로 확인했나요?
6. 긴 질문을 500자로 줄여도 $0.0002 아래로 내려가지 않았습니다. 무엇이 남았나요?
## 정리
- [ ] 비용 = 호출 수 × 호출당 토큰 × 단가, 한 번에 한 칸씩, 가장 큰 몫부터
- [ ] 품질 테스트를 먼저 고정하고, 바꿀 때마다 다시 돌리고 답을 읽는다
- [ ] 필요한 것만 보내고(데이터 최소화), 출력에 상한을 둔다
- [ ] 쉬운 단계만 작은 모델, 단가표로 효과를 계산하고 계산의 모델 이름도 함께
- [ ] 가장 싼 호출은 하지 않은 호출 — 규칙으로 먼저, 중복 단계 빼기
- [ ] 입력 · 출력 상한으로 요청 하나의 최대 비용을 정한다
+332
View File
@@ -0,0 +1,332 @@
# 3단계 · 예산과 한도
- 교시: 5교시 강의 (14:00~14:50) · 6교시 [Lab 03](../../Last3Mon/75/LAB-03-Q-BUDGET-AND-LIMITS.md) 실습 5개 (15:00~15:50)
- 이 단계가 끝나면: 비용이 넘치지 않게 하는 장치(하루 예산 · 사용자별 한도 · 요청 하나의 상한)를 구분하고, 예산을 넘으면 LLM 없이 낮춰서 답하고, 한 사람이 몰아 부르면 429로 잠시 막고, 예산이 언제 멈추는지 재고, 지금까지의 기록으로 한 달 비용과 "모델을 바꾸면 얼마"를 코드로 계산할 수 있다.
- 숫자: 모두 강사 노트북 실측입니다 (65일차 n8n 2.42.6, OpenAI `gpt-4.1-mini` · `gpt-5.4-nano`, 10/28 정답 워크플로우를 학생 순서대로 1회). 출력 전부는 수업 뒤 공개되는 `Last3Mon/75/answers/LAB-TEST-RESULT.md`에 있습니다.
- 실습 가리키기: 이 문서에서 "Lab 03 예제 1"은 6교시 [Lab 03 문서](../../Last3Mon/75/LAB-03-Q-BUDGET-AND-LIMITS.md)의 그 절입니다. 파일 경로는 실습 폴더 `~/aio-01d4-stu/Last3Mon/75` 기준입니다.
```mermaid
flowchart LR
A["1. 넘치지 않게"] --> B["2. 하루 예산"] --> C["3. 사용자별 한도 · 429"] --> D["4. 예산은 넘은 뒤 멈춘다"]
D --> E["5. 비용 보고서"] --> F["6. 바꾸면 얼마"] --> G["7. 내 쪽 자원 관리"] --> H["8. 요청문(프롬프트)으로"]
```
## 0. 이 시간 (50분)
| 시간 | 절 · 개요 | 하는 일 |
| --- | --- | --- |
| 14:00~14:07 | 1 · 개요 22장 | 예산 · 한도 · 상한, 공급자 한도와 내 한도 |
| 14:07~14:14 | 2 · 개요 23장 | 넘으면 낮춰서 답하기, 어디에 두나 |
| 14:14~14:22 | 3 · 개요 24장 | 속도 제한 방식, 429와 다시 시도 |
| 14:22~14:28 | 4 · 개요 25장 | 넘은 뒤 멈춤, 경고 단계, 하루가 바뀔 때 |
| 14:28~14:36 | 5 · 개요 26장 | 보고서는 코드가 계산, 정확한 테스트 |
| 14:36~14:42 | 6 · 개요 27장 | what-if, 가정의 한계 |
| 14:42~14:46 | 7 · 개요 28장 | 동시 실행 · 실행 기록 |
| 14:46~14:50 | 8 · 9 · 확인 질문 | 요청문, 자주 하는 실수 |
### 6교시 Lab 03에서 이어서 하는 것
| 6교시 실습 (시각) | 이 강의의 절 | 실습에서 하는 일 | 쓰는 파일 · 명령 |
| --- | --- | --- | --- |
| Lab 03 예제 1 (15:00~15:10) | 2 | 예산을 넘으면 LLM 없이 원문 | `budget`, 테스트 `cases/lab03.json` |
| Lab 03 예제 2 (15:10~15:20) | 3 | 사람마다 1분 한도 → 429 | 테스트 `cases/lab03-limit.json`, `lab03` · `lab02-challenge` |
| Lab 03 예제 3 (15:20~15:27, 에이전트 없이) | 4 | 예산 $0.0003으로 여섯 질문 | `budget --daily 0.0003` → `test cases/lab03-drill.json` → `usage` → `budget --reset` |
| Lab 03 과제 1 (15:27~15:40) | 5 | 비용 보고서 `my-cost-report` | `my-work/cost-report.json`, 테스트 `cases/lab03-challenge.json` |
| Lab 03 과제 2 (15:40~15:50) | 6 | `what_if_model` | 테스트 `cases/lab03-challenge2.json` |
## 1. 넘치지 않게: 예산 · 한도 · 상한
### 일반 설명
1 · 2단계로 **얼마인지 알고, 줄였습니다.** 그래도 쓰는 사람이 늘거나, 누가 몰아서 부르거나, 아주 긴 질문이 오면 비용은 넘칠 수 있습니다. 운영에서는 넘치기 **전에** 멈추는 장치를 둡니다.
| 장치 | 무엇을 막나 | 단위 | 오늘 |
| --- | --- | --- | --- |
| 예산 (budget) | 전체가 너무 많이 씀 | 하루 · 한 달 금액 | Lab 03 예제 1 |
| 속도 제한 (rate limit) | 한 사람 · 한 프로그램이 짧은 시간에 몰아 부름 | 분당 · 초당 요청 수 | Lab 03 예제 2 |
| 할당량 (quota) | 한 사람 · 한 팀이 오래 많이 씀 | 하루 · 한 달 요청 수 · 금액 | (사람별 하루 한도로 넓힐 수 있음) |
| 요청 상한 | 요청 하나가 너무 큼 | 입력 글자 수 · 출력 토큰 | Lab 02 과제 2 · 예제 1 |
**공급자 쪽 한도와 내 쪽 한도.** 공급자도 한도를 둡니다. API 계정마다 분당 요청 수 · 분당 토큰 수(rate limit)가 정해져 있고, 넘으면 429를 받습니다. 계정 전체의 월 사용 한도를 정해 둘 수도 있습니다. 그런데 공급자 한도는 **계정 전체**에 걸리므로, 한 사람이 몰아 부르면 **모든 직원**이 막힙니다. 그래서 내 서비스 안에서 사람마다 · 기능마다 더 작은 한도를 둡니다.
| | 공급자 한도 | 내 한도 |
| --- | --- | --- |
| 단위 | 계정 · 모델 | 사람 · 팀 · 기능 |
| 넘으면 | 모든 요청이 429 | 그 사람 · 그 기능만 |
| 누가 정하나 | 공급자 (요금제) | 우리 |
| 목적 | 공급자 서버 보호 | 예산 · 공정성 |
**넘었을 때 무엇을 주나도 설계입니다.** "오류"로 끝내면 사용자는 아무것도 못 받습니다. 낮춰서 답하기(2절), 잠시 뒤 다시 하라는 안내(3절), 담당자 연결 중 무엇을 줄지 정합니다. 그리고 **넘었다는 사실을 기록**해서 한도가 적절했는지 나중에 판단합니다.
**팀마다 몫을 나누기도 합니다.** 여러 부서가 같은 AI 서비스를 쓰면, 회사는 부서마다 한 달 몫(할당량)을 정하고 쓴 만큼을 알려 주거나(showback) 부서 예산에서 떼어 가기도(chargeback) 합니다. 그러려면 1단계의 기록에 **팀 · 기능 칸**이 있어야 합니다. 몫이 정해지면 각 팀은 자기 기능의 비용을 스스로 줄일 까닭이 생깁니다.
**값은 사람이 정합니다.** 예산 · 한도는 기술이 아니라 **결정**입니다. 한 달 예산을 일하는 날로 나눈 값, 지난달 기록의 평균에 여유를 둔 값, 사용자 수 × 한 사람 몫 등 근거를 정해 적습니다. 그래서 값은 코드 밖(`/budget`)에 두고, 사람이 `flow.py budget`으로 바꿉니다. 워크플로우는 **읽기만** 합니다 (AGENTS.md 9절).
### 오늘 실습에서
오늘 데이터 API의 `/budget`이 사람이 정한 값 셋을 줍니다 (`data/budget.json`, 처음 상태).
| 값 | 처음 | 쓰는 곳 |
| --- | --- | --- |
| `daily_usd` | $1.0 | 예제 1 (넘으면 `한도 초과`) |
| `user_limit_per_min` | 20 | 예제 2 (넘으면 429) |
| `max_question_chars` | 500 | Lab 02 과제 2 (넘으면 앞뒤만) |
`used_usd`는 기록된 `cost_usd`의 합이고 `over`는 `used_usd ≥ daily_usd`입니다. 그래서 Lab 01 과제의 기록이 없으면 예산도 없습니다.
## 2. 하루 예산, 넘으면 낮춰서 답하기 (6교시 Lab 03 예제 1)
### 일반 설명
**예산을 넘으면 멈추는 대신 낮춰서 답합니다.** 예산을 넘었다고 "오늘은 끝"이라고 하면 직원은 오후 내내 도움을 못 받습니다. 그런데 헬프데스크가 답하는 재료(FAQ 원문 · 접수 상태)는 **이미 데이터에 있습니다.** LLM이 문장을 다듬지 않을 뿐, 내용은 줄 수 있습니다. 이것을 **우아한 저하(graceful degradation)**라고 합니다.
| 수준 | 무엇을 주나 | 비용 |
| --- | --- | --- |
| 평소 | LLM이 다듬은 답 | 요청마다 |
| 낮춤 | 데이터 원문 + "오늘 자동 답변 한도를 넘어 원문으로 안내합니다" | 0 |
| 더 낮춤 | "담당자에게 연결합니다" | 0 |
**어디에 두나.** 예산 확인은 LLM보다 **앞**, 데이터 조회보다는 **뒤**에 둡니다. 낮춰서 답하려면 데이터가 먼저 있어야 하고, LLM을 부른 뒤에 확인하면 이미 돈을 쓴 뒤이기 때문입니다.
```mermaid
flowchart LR
D["FAQ · 접수 조회<br/>(데이터, 0원)"] --> B{"예산 안?"}
B -->|예| L{{"LLM 답"}}
B -->|아니오| O(["원문으로 답 · $0"])
```
**무엇을 먼저 지킬지 정해 둡니다.** 예산이 바닥났을 때 모든 기능을 똑같이 낮출 필요는 없습니다. 보안 사고 신고처럼 **꼭 LLM이 있어야 하는 일**은 따로 남은 몫을 두고, 덜 급한 일(요약 · 말투 다듬기)부터 낮춥니다. 이것을 미리 정해 두지 않으면, 예산이 바닥난 날 가장 중요한 문의도 원문만 받습니다.
**사용자에게 솔직하게 알립니다.** 낮춘 답에는 "오늘 자동 답변 한도를 넘어 원문으로 안내합니다"처럼 **왜 평소와 다른지**를 한 줄 붙입니다. 아무 말 없이 답의 질만 떨어지면 사용자는 서비스가 고장 났다고 생각하고 문의가 늘어납니다. 언제 다시 평소대로 되는지(내일 · 담당자 연결)도 알려 주면 더 좋습니다.
**장애로 낮춘 것과 예산으로 낮춘 것은 구분합니다.** 72일차에는 LLM이 **멈췄을 때** 데이터 그대로 답했습니다 (`간단 답변`). 오늘은 LLM이 멀쩡한데 **쓰지 않기로** 한 것입니다. 답의 모양은 비슷해도 `route`를 다르게 두면, 나중에 로그를 보는 사람이 "장애였나, 예산이었나"를 가를 수 있습니다. 원인이 다르면 대응도 다릅니다 (장애는 고치고, 예산은 값을 다시 정함).
### 오늘 실습에서
Lab 03 예제 1 (수업 준비 때): 예산 $0.001에 이미 $0.002를 쓴 상태로 시험했습니다. 고치기 전에는 그래도 `자동 답변`으로 LLM을 불렀고 ($0.000154), 고친 뒤에는 **`한도 초과` · 0 calls · $0**에 FAQ 원문("인증서")과 접수 상태("처리 중")가 나갔습니다. 한도 안이면 평소대로 $0.000156.
## 3. 사용자별 한도와 429 (6교시 Lab 03 예제 2)
### 일반 설명
**하루 예산은 모두의 것입니다.** 한 사람이 스크립트로 1분에 수백 번 부르면 예산이 오전에 바닥나고, 나머지 직원은 원문만 받습니다. 그래서 **사람마다** 짧은 시간의 한도를 둡니다. 이것을 **속도 제한(rate limiting)**이라고 합니다. 공정성 · 비용 · 서버 보호를 함께 얻습니다.
**세는 방법 넷.**
| 방법 | 어떻게 | 특징 |
| --- | --- | --- |
| 고정 창 (fixed window) | 매 분 0초부터 몇 번 | 쉬움. 59초와 61초에 몰면 짧은 사이에 두 배 |
| 이동 창 (sliding window) | 지금부터 거꾸로 60초 동안 몇 번 | 고르게 막음. 기록을 시각과 함께 남겨야 함 (오늘) |
| 토큰 버킷 (token bucket) | 일정한 속도로 채워지는 통에서 하나씩 꺼냄 | 짧은 몰림은 허용, 오래 몰리면 막음. 공급자들이 많이 씀 |
| 새는 통 (leaky bucket) | 들어온 것을 일정한 속도로만 내보냄 | 내보내는 속도가 고름 |
**429는 "잠시 뒤 다시"라는 약속입니다.** HTTP 429 `Too Many Requests`는 표준 응답이고, `Retry-After`(헤더 · 또는 본문의 `retry_after`)로 몇 초 뒤에 다시 부르면 되는지 알려 줍니다. 부르는 프로그램은 이것을 보고 기다렸다가 다시 시도합니다. 바로바로 다시 부르면 한도에 또 걸리므로, 72일차처럼 **기다리는 시간을 늘려 가며**(exponential backoff) 다시 시도합니다.
**가장 앞에 둡니다.** 한도를 넘은 요청에는 데이터 조회도 LLM도 할 까닭이 없습니다. 맨 앞에서 돌려보내면 **0원 · 거의 0초**입니다. 예산 확인(2절)과 자리가 다른 까닭은 원문 답이 필요 없기 때문입니다.
**횟수 대신 토큰 · 금액으로 셀 수도 있습니다.** 요청 수로만 세면 짧은 질문 20번과 긴 로그 질문 20번이 같은 한도를 씁니다. 그런데 비용은 몇 배 차이가 납니다. 그래서 공급자들은 분당 요청 수와 함께 **분당 토큰 수**에도 한도를 두고, 서비스 안에서도 "한 사람이 하루 $0.05까지"처럼 금액으로 셀 수 있습니다. 오늘은 요청 수로 세고, 긴 질문은 Lab 02 과제 2의 상한이 따로 막습니다.
**세는 곳은 하나여야 합니다.** 워크플로우 엔진을 여러 대로 늘리면, 각자 자기 메모리에 횟수를 세면 한 사람이 서버마다 한도만큼 부를 수 있습니다. 그래서 횟수는 **모두가 같이 보는 곳**(데이터베이스, Redis 같은 저장소, 오늘은 데이터 API)에서 셉니다. 공급자 · API 게이트웨이 · 클라우드의 한도 기능도 같은 이유로 한곳에서 셉니다.
**한도 값을 정할 때 생각할 것.**
| 생각할 것 | 예 |
| --- | --- |
| 정상 사용자의 최대 | 사람이 1분에 몇 번 묻나 (고쳐 묻기 포함) |
| 공용 계정 · 자동화 | 여러 사람이 한 이름으로, 다른 시스템이 부를 때 |
| 무엇으로 사람을 가르나 | 로그인 정보 (사용자가 보낸 이름은 바꿀 수 있음 — 67일차) |
| 넘었을 때 알림 | 몇 번 넘으면 담당자에게 (76일차) |
### 오늘 실습에서
Lab 03 예제 2 (수업 준비 때, 한도 2): 이준호의 세 번째 요청이 고치기 전에는 200 · 1.3초 · $0.000156이었고, 고친 뒤에는 **429 · 잠시 후 · 0.0초 · $0**. 다른 사람(한지훈)은 그대로 200. 데이터 API의 `/usage/user/<이름>?window=60`은 기록된 요청을 이동 창으로 셉니다. 오늘 실습의 "사람"은 요청 본문의 `user`라서 바꿔 보낼 수 있습니다. 실제 서비스에서는 로그인 정보로 가릅니다.
## 4. 예산은 넘은 뒤 멈춘다 (6교시 Lab 03 예제 3)
### 일반 설명
**"쓴 돈 ≥ 예산이면 멈춘다"는 넘은 뒤에 멈춥니다.** 예산이 조금이라도 남아 있으면 다음 요청은 평소대로 답하고, 그 요청의 비용만큼 예산을 넘습니다. 그래서 실제로 쓴 돈은 "예산 + 마지막 한 건"까지 갑니다. 동시에 여러 요청이 들어오면 여러 건만큼 넘을 수도 있습니다.
| 방식 | 어떻게 | 넘을 수 있나 |
| --- | --- | --- |
| 넘은 뒤 멈춤 (오늘) | 쓴 돈 ≥ 예산이면 멈춤 | 마지막 몇 건만큼 |
| 미리 잡기 (reserve) | 부르기 전에 "최대 비용"만큼 예산에서 떼어 두고, 끝나면 실제 비용으로 정산 | 넘지 않음 |
| 여유 두기 | 남은 돈 < 한 건의 최대 비용이면 멈춤 | 거의 넘지 않음 |
미리 잡기 · 여유 두기를 하려면 **한 건의 최대 비용**을 알아야 합니다. 그것을 정해 주는 것이 2단계의 입력 상한 · 출력 상한입니다. **상한이 있어야 예산도 지킬 수 있습니다.**
**경고는 멈추기 전에.** 예산의 80% · 90%에 닿으면 담당자에게 먼저 알립니다 (76일차 알림). 멈춘 뒤에 알게 되면 직원들이 이미 원문만 받고 있습니다. 클라우드 공급자의 예산 기능도 대개 "몇 % 넘으면 알림"을 함께 둡니다.
| 단계 | 행동 |
| --- | --- |
| 80% | 담당자에게 알림 |
| 100% | 낮춰서 답하기 (원문) |
| 그 뒤 | 다음 날 0시에 다시 0부터, 또는 사람이 예산을 늘림 |
**하루가 바뀌면 누가 0으로 돌리나.** "하루 예산"은 "오늘 0시부터 쓴 돈"을 세야 합니다. 기록에 시각이 있으면 세는 쪽이 오늘 것만 더합니다. 사람이 매일 손으로 지우는 방식은 빠뜨리기 쉽습니다. 그리고 "오늘"이 어느 시간대 기준인지도 정해 둡니다 (서버 시각과 사무실 시각이 다를 수 있음).
**갑자기 늘면 먼저 알아챕니다.** 예산 안이라도 평소의 몇 배로 갑자기 늘면 무언가 잘못된 신호입니다 (고장 난 루프, 누가 자동으로 부름, 긴 입력 공격). 시간대별 · 사람별 사용량을 평소와 비교해 크게 벗어나면 알리는 것을 **이상 탐지(anomaly detection)**라고 합니다. 단순하게는 "지난 7일 같은 시간대 평균의 3배를 넘으면 알림" 같은 규칙으로 시작합니다 (76 · 77일차).
**넘어서 낮춘 답도 셉니다.** 낮춰서 답한 요청은 LLM을 부르지 않아 **비용 기록에 남지 않습니다.** 그러면 "오늘 몇 명이 원문만 받았나"를 셀 수 없고, 예산이 적절했는지도 판단할 수 없습니다. 비용이 0인 요청도 한 줄 남기는 것이 좋습니다.
### 오늘 실습에서
Lab 03 예제 3 (수업 준비 때, 예산 $0.0003):
| 질문 | 결과 | 쓴 돈 (누적) |
| --- | --- | --- |
| 1 · VPN | `자동 답변` $0.000141 | $0.000141 |
| 2 · 프린터 | `자동 답변` $0.000152 | $0.000293 (예산 아래) |
| 3 · 비밀번호 | `자동 답변` $0.000136 | **$0.000430 (넘음)** |
| 4 · 5 · 6 | **`한도 초과` · $0** | 그대로 |
예산을 $0.00013, **43%** 넘었습니다. `usage`의 route별 합계에는 `자동 답변` 세 줄만 있고, 한도 초과 세 건은 기록에 없었습니다. `--reset`($1.0) 뒤에는 여섯 모두 `자동 답변`.
## 5. 비용 보고서 (6교시 Lab 03 과제 1)
### 일반 설명
**보고서의 숫자는 코드가 계산합니다.** 합계 · 평균 · 곱셈은 코드가 **틀리지 않고 · 공짜로 · 늘 같게** 합니다. LLM에게 숫자 계산을 맡기면 틀릴 수도, 돈도 들고, 같은 입력에 다른 숫자가 나올 수도 있습니다. LLM은 숫자를 **설명하는** 데 씁니다 (79일차: "이번 달은 지난달보다 왜 늘었나"를 글로).
| 일 | 맡길 곳 |
| --- | --- |
| 합 · 평균 · 예측 · 순위 | 코드 |
| 숫자를 읽기 쉬운 글로 설명 | LLM (숫자는 코드가 준 것만 쓰게) |
| 무엇을 할지 결정 | 사람 |
**비용 보고서에 들어갈 것.**
| 칸 | 질문 | 오늘 |
| --- | --- | --- |
| 합계 · 요청 수 · 호출 수 | 얼마를 · 몇 번 | `cost_usd` · `requests` · `calls` |
| 요청 하나의 평균 | 단위 비용 | `avg_usd` (요청 수로 나눔) |
| 모델별 · 사람별 | 어디서 · 누가 | `by_model` · `top_user` |
| 한 달 예상 | 이대로면 얼마 | `projected_month_usd` |
| 예산 대비 | 남았나 · 넘었나 | `remaining_usd` · `over_budget` |
**예측은 단순한 것부터.** 오늘의 한 달 예상은 "평균 × 하루 요청 × 일하는 날"입니다. 실제로는 요일 · 월말 · 신입 입사 같은 패턴이 있어서, 기록이 쌓이면 지난 몇 주의 추세로 예측합니다. 하지만 처음에는 **가정을 밝힌 단순한 식**이 낫습니다. 복잡한 예측은 맞았는지 틀렸는지 설명하기 어렵습니다.
**보고서는 정해진 때에 봅니다.** 비용 보고서는 만들어 두는 것보다 **정기적으로 보는 것**이 중요합니다. 매주 · 매달 초에 같은 칸(합계 · 평균 · 모델별 · 사람별 · 예산 대비)을 보고, 지난번과 다르면 까닭을 적습니다. FinOps의 "운영하기" 단계가 이것입니다. 숫자가 자동으로 나오면 사람은 **판단**에 시간을 씁니다.
**정확한 숫자를 테스트합니다.** LLM 답은 `contains` · `range`로 시험했지만, 보고서는 입력이 정해지면 결과가 **정확히 하나**입니다. 그래서 알려진 기록 몇 줄을 넣고 `equals`로 소수점까지 봅니다. 반올림 자리와 순서(반올림은 마지막에)를 계약으로 정해 두지 않으면 0.0019890000001 같은 값에서 실패합니다.
### 오늘 실습에서
Lab 03 과제 1 (수업 준비 때): 테스트가 넣은 네 줄로 요청 3 · 호출 4 · $0.001989 · 평균 $0.000663 · 100건 × 22일 **$1.4586** · 가장 많이 쓴 사람 박서연 · 남은 예산 $0.998011 — 손 계산과 정확히 같았습니다 (5/5). 그다음 실제 사용량(과제 1 정답의 일곱 질문)으로 하루 1,000건을 넣으면 평균 $0.000127, **한 달 $2.80**. 1단계 예제 3의 어림 $20.8에서 약 7분의 1입니다.
## 6. 바꾸면 얼마: what-if (6교시 Lab 03 과제 2)
### 일반 설명
**바꾸기 전에 계산합니다.** 모델을 바꾸는 것은 74일차처럼 릴리스 · 게이트 · 카나리를 거치는 일입니다. 그 전에 "바꾸면 얼마 아끼나"를 알면 **해 볼 만한지** 먼저 정할 수 있습니다. 기록에는 모델마다 입력 · 출력 토큰이 있으니, 단가만 바꿔 다시 곱하면 됩니다. 이렇게 "만약 ~라면"을 계산하는 것을 **what-if 분석** · 시나리오 분석이라고 합니다.
```text
what-if 비용 = (기록된 입력 토큰 합 × 새 모델 입력 단가 + 기록된 출력 토큰 합 × 새 모델 출력 단가) ÷ 1,000,000
아끼는 돈 = 지금 비용 − what-if 비용 (음수면 더 비쌈)
```
**가정을 밝힙니다.** what-if는 **같은 토큰 수 · 같은 품질**을 가정합니다. 실제로는 다릅니다.
| 가정 | 실제 |
| --- | --- |
| 토큰 수가 같다 | 모델마다 답 길이 · 토큰 나누는 법 · 추론 토큰이 다름 |
| 품질이 같다 | 형식 · 군더더기 · 정확도가 달라질 수 있음 (2단계 4절) |
| 요청 수가 같다 | 싸지면 더 많이 쓰기도 함 |
그래서 what-if는 "**아끼는 돈의 대략**"이고, 바꿀지는 품질 게이트로 정합니다. 반대로 아끼는 돈이 **음수**(더 비싼 모델)거나 아주 작으면, 게이트를 돌릴 것도 없이 바꿀 까닭이 없다는 것이 바로 보입니다.
**여러 값을 넣어 봅니다 (민감도).** what-if는 한 숫자보다 **여러 가정**을 나란히 볼 때 쓸모가 큽니다. 하루 요청이 500 · 1,000 · 2,000일 때, 모델이 mini · nano일 때 한 달이 얼마인지 표로 놓으면, 어떤 가정이 결과를 가장 크게 흔드는지 보입니다. 대개 **요청 수**가 모델보다 크게 흔듭니다. 그래서 모델을 바꾸기 전에 "요청 수가 늘면 어떻게 할지"(한도 · 예산)를 먼저 정해 두는 것이 순서입니다.
**모르는 이름은 막습니다.** 단가표에 없는 모델 이름을 0원으로 계산하면 "100% 아낀다"는 거짓 숫자가 나옵니다. 74일차 "없는 릴리스는 400"처럼, 모르는 이름은 오류와 알려진 목록으로 돌려보냅니다.
### 오늘 실습에서
Lab 03 과제 2 (수업 준비 때): 테스트가 넣은 네 줄이 모두 nano였다면 $0.00117 (아끼는 돈 $0.000819, 한 달 $1.4586 → $0.858), 모두 mini였다면 $0.002016로 아끼는 돈 **−$0.000027**. 없는 모델 `gpt-9`는 400. 실제 사용량 일곱 건으로는 $0.000929 → nano였다면 $0.00061 (34% 덜). 2단계 예제 3에서 **실제로** 답 단계만 nano로 바꿔 잰 것은 15% 덜이었고 답의 형식이 나빠졌습니다 (구성이 달라 바로 비교할 수는 없음). 계산은 "해 볼 만한가", 결정은 "재고 읽고 게이트로".
## 7. 내 쪽 자원 관리: 동시 실행 · 실행 기록
### 일반 설명
오늘 비용의 대부분은 API 토큰이지만, 워크플로우 엔진을 운영하는 쪽에서는 **내 서버의 자원**도 관리합니다 (1단계 2절).
| 자원 | 무엇이 늘리나 | 관리 |
| --- | --- | --- |
| 동시 실행 | 몰리는 요청, 느린 API를 기다리는 실행 | 동시 실행 수 상한, 넘치면 큐 · 429 |
| 실행 기록 (입력 · 출력 저장) | 실행마다, 큰 데이터일수록 | 보관 기간 · 개수 정하기, 실패한 것만 남기기 |
| 로그 · 사용량 기록 | 요청마다 | 보관 기간, 원문 대신 숫자 |
| 컨테이너 메모리 · CPU | 위 모두 | 컨테이너 상한, `docker stats`로 보기 |
n8n도 이것을 설정으로 둡니다. 예를 들어 동시에 도는 운영 실행 수의 상한(`N8N_CONCURRENCY_PRODUCTION_LIMIT`), 오래된 실행 기록을 지우는 설정(`EXECUTIONS_DATA_PRUNE` · `EXECUTIONS_DATA_MAX_AGE`), 성공한 실행은 저장하지 않는 설정 등입니다. 값은 서버 크기 · 요청 수 · 얼마나 오래 되짚어 봐야 하는지(77일차 로그 분석)를 보고 정합니다.
**자원 비용도 같은 보고서에.** 서버 · 저장 공간처럼 켜 둔 만큼 드는 비용은 API 비용과 성질이 다르지만(1단계 2절), 한 서비스의 비용으로 함께 봐야 합니다. "요청 하나에 API $0.00016 + 서버 몫 얼마"처럼 합치면 직접 돌리기(self-host)와 API를 비교할 때도, 사용자가 늘 때 무엇이 먼저 늘어나는지 볼 때도 쓸모가 있습니다.
**자원 한도도 "넘치면 무엇을 주나"를 정합니다.** 동시 실행이 꽉 찼을 때 새 요청을 무한히 기다리게 하면 사용자도 서버도 지칩니다. 3절의 429처럼 "잠시 뒤 다시"를 빨리 주는 것이 낫습니다.
### 오늘 실습에서
오늘 실습은 강사 노트북 · 학생 노트북 한 대에서 한 사람이 부르므로 동시 실행 · 실행 기록의 상한은 바꾸지 않습니다. 1단계 예제 1의 `docker stats`(n8n 642.6MiB)를 기억해 두고, 76일차(실패한 실행 모으기) · 77일차(실행 기록 · 로그 분석)에서 다시 봅니다.
## 8. 요청문(프롬프트)으로
**요청문(프롬프트)이란.** 실습에서 CLI 코딩 에이전트(codex · claude · agy)에게 보내는 글입니다. 에이전트는 이 글만 보고 n8n 워크플로우 JSON을 만들거나 고칩니다. 이 절은 이 단계에서 배운 것을 **요청문(프롬프트)에 어떤 칸으로 적을지** 실습 전에 정리하는 자리입니다. 실습 문서의 `▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대`가 이 칸을 실제 문장으로 만든 것이고, 빈칸 ①~④는 그 문장 속 정확한 값입니다.
| 칸 | 에이전트에게 알려 주는 것 | 빠지면 생기는 일 |
| --- | --- | --- |
| 무엇을 | 만들거나 바꿀 기능 | 에이전트가 짐작해서 다른 것을 만듦 |
| 어디에 | 고칠 파일 · 노드 · 자리 | 엉뚱한 노드를 고치거나 새 파일을 만듦 |
| 어떤 모양으로 | 결과 JSON의 칸 이름 · 값 · 응답 코드 | 테스트가 보는 칸 이름과 달라 실패 |
| 그대로 · 하지 말 것 | 건드리면 안 되는 것 | 다른 곳까지 바뀌어 무엇 때문인지 모르게 됨 |
| 확인 | 통과해야 할 테스트 파일 | "다 됐다"를 판단할 기준이 없음 |
67~71일차의 다섯 가지(① 파일 ② 입력 ③ 처리 ④ 결과 모양 ⑤ 기대 결과)와 같은 뜻입니다. 파일 · 입력이 "어디에", 처리가 "무엇을", 결과 모양이 "어떤 모양으로", 기대 결과가 "확인"입니다. 72일차부터는 **이미 있는 워크플로우를 고치는** 요청이 많아 "그대로"가 따로 한 칸이 됐습니다.
예산 · 한도 요청에서는 **"어디에"(순서)**가 특히 중요합니다. 같은 확인이라도 LLM 앞이냐 뒤냐, 데이터 조회 앞이냐 뒤냐에 따라 비용과 답이 달라집니다. 그리고 "값은 `/budget`에서 읽고 워크플로우가 바꾸지 않는다"를 "그대로"에 적습니다.
| 요청문에 | Lab 03 예제 1 | Lab 03 예제 2 | Lab 03 과제 1 | Lab 03 과제 2 |
| --- | --- | --- | --- | --- |
| 무엇을 | 예산을 넘으면 원문 | 사람마다 1분 한도 | 비용 보고서 | 바꾸면 얼마 |
| 어디에 | `Get ticket` 뒤 (LLM 앞) `Within budget?` · `Over budget` | `Get budget` 바로 뒤 (가장 앞) `Recent requests` · `Under user limit?` | 새 파일 `my-work/cost-report.json` | `Get prices`, `Make report`, `Known model?` |
| 어떤 모양으로 | `한도 초과` · `llm_calls` 0 · `cost_usd` 0 | 429 `{route: 잠시 후, retry_after: 60, …}` | AGENTS.md 3절 보고서 (6자리) | `what_if: {model, cost_usd, saving_usd, projected_month_usd}`, 없는 모델 400 |
| 그대로 | 평소 갈래 · 예산 값은 읽기만 | 평소 갈래 | LLM 없이 | 과제 1의 결과 |
| 확인 | `cases/lab03.json` | `cases/lab03-limit.json` · `lab03` · `lab02-challenge` | `cases/lab03-challenge.json` | `cases/lab03-challenge2.json` · `lab03-challenge` |
예를 들어 Lab 03 예제 2 열을 위에서부터 이어 읽으면 요청문(프롬프트)이 됩니다 (화살표 오른쪽은 다섯 칸 중 어디인지):
```text
같은 사람이 1분에 한도를 넘게 부르면 잠시 막아 줘. ← 무엇을
my-work/cost.json 의 Get budget 바로 뒤, 가장 앞에 Recent requests 와 Under user limit?. ← 어디에
넘으면 HTTP 429 {route: '잠시 후', answer, retry_after: 60, llm_calls: 0, cost_usd: 0}. ← 어떤 모양으로
한도 안이면 지금 갈래 그대로, 한도 값은 /budget 의 user_limit_per_min 을 읽기만. ← 그대로
cases/lab03-limit.json, cases/lab03.json, cases/lab02-challenge.json 이 통과해야 해. ← 확인
```
## 9. 자주 하는 실수
| 실수 | 왜 문제인가 | 이렇게 |
| --- | --- | --- |
| 예산을 넘으면 "오류" | 직원이 아무 도움도 못 받음 | 데이터 원문으로 낮춰서 답 |
| 예산 확인을 LLM 뒤에 | 이미 돈을 쓴 뒤 | LLM 앞, 데이터 조회 뒤 |
| 한도 확인을 맨 뒤에 | 막을 요청도 조회 · LLM을 함 | 가장 앞 |
| 장애와 예산을 같은 `route`로 | 원인을 가를 수 없음 | `간단 답변` · `한도 초과` · `잠시 후` |
| 예산 · 한도를 워크플로우가 바꿈 | 누가 정했는지 모름, 사고 | 사람이 `/budget`에, 워크플로우는 읽기만 |
| 상한 없이 예산만 | 마지막 한 건이 얼마나 넘을지 모름 | 입력 · 출력 상한으로 최대 비용 |
| 보고서 숫자를 LLM이 | 틀림 · 돈 · 흔들림 | 코드가 계산, LLM은 설명만 |
| 평균을 호출 수로 나눔 | 한 달이 작게 나옴 | 요청 수로 |
| what-if를 그대로 믿고 바꿈 | 토큰 · 품질이 다를 수 있음 | 재고 · 읽고 · 게이트로 |
## 확인 질문
1. 공급자의 rate limit이 있는데 내 서비스 안에 사람별 한도를 또 두는 까닭은?
2. 예산을 넘었을 때 FAQ 원문으로 답하는 것과 72일차 LLM 장애 때의 간단 답변은 무엇이 같고 무엇이 다른가요?
3. 예산 확인과 사람별 한도 확인은 워크플로우의 다른 자리에 둡니다. 각각 어디이고 왜인가요?
4. 예산 $0.0003에서 실제로는 $0.000430까지 썼습니다. 왜 넘었고, 넘지 않게 하려면 무엇이 필요한가요?
5. 비용 보고서의 숫자를 LLM에게 계산하게 하면 안 되는 까닭 셋은?
6. what-if가 "모두 nano면 34% 덜"이라고 했습니다. 이 숫자만 보고 바꾸면 안 되는 까닭은?
## 정리
- [ ] 넘치지 않게: 예산(전체) · 속도 제한(사람 · 짧은 시간) · 요청 상한(요청 하나)
- [ ] 예산을 넘으면 LLM 없이 데이터 원문으로, 확인은 LLM 앞 · 데이터 뒤, `route`는 원인별로
- [ ] 사람별 한도는 가장 앞, 429와 `retry_after`, 값은 사람이 정하고 워크플로우는 읽기만
- [ ] 예산은 넘은 뒤 멈춘다 — 경고는 먼저, 넘지 않으려면 상한으로 최대 비용
- [ ] 보고서 · what-if는 코드가 계산하고, 가정을 밝히고, 바꿀지는 품질 게이트로
+212
View File
@@ -0,0 +1,212 @@
# 4단계 · 정리: 오늘 한 일의 의미 · 효과 · 실전 응용
- 교시: 8교시 17:10~17:45 (35분, 바이브 코딩 발표 10분 뒤) · 자료: 이 문서 + [개요](00-DAY75-OVERVIEW.md) 30장
- 이 시간이 끝나면: 오늘 1~3단계에서 한 일이 **무엇을 지키기 위한 것이었는지**, 숫자로 **무엇을 줄였고 무엇을 치렀는지**, 회사에서는 같은 생각을 **어떤 이름과 도구로** 쓰는지 말할 수 있고, 최종 프로젝트에 무엇을 가져갈지 정할 수 있다.
- 숫자: 오늘 교안의 실측(강사 노트북, 10/28 정답 워크플로우 1회, 단가는 2026-10-28 OpenAI 모델 문서)입니다. "예시"라고 적은 계산은 가정을 둔 어림입니다.
```mermaid
flowchart LR
A["1. 오늘 한 장"] --> B["2. 왜 비용이<br/>운영의 일인가"] --> C["3. 효과와 값을 숫자로"] --> D["4. 회사에서 부르는 이름"]
D --> E["5. 산업별 응용"] --> F["6. 비용 대시보드"] --> G["7. 하지 말아야 할 때"] --> H["8. 직무 · 면접"] --> I["9. 최종 프로젝트로"]
```
## 0. 이 시간 (35분)
| 시간 | 절 | 하는 일 |
| --- | --- | --- |
| 17:10~17:15 | 1 · 2 | 오늘 한 장, 왜 비용이 운영의 일인가 |
| 17:15~17:20 | 3 | 오늘 숫자로 본 효과와 값, 한 달로 보면 (예시) |
| 17:20~17:28 | 4 · 5 | 같은 생각의 업계 이름 · 도구, 산업별 응용 예 |
| 17:28~17:33 | 6 · 7 | 비용 지표 여섯 개, 하지 말아야 할 때 |
| 17:33~17:40 | 8 · 9 | 직무와 면접 질문, 최종 프로젝트 체크리스트 |
| 17:40~17:45 | 10 · 토론 | 다음 일차와의 연결, 질문 둘 |
## 1. 오늘 한 장
| 단계 | 한 일 | 오늘 숫자 |
| --- | --- | --- |
| 1단계 · 재기 | 자원 · 토큰 · 비용, 단계별 `cost_usd`, 종류별 한 달 어림, 요청마다 기록, 기록이 멈춰도 답 | VPN 하나 **$0.00104** (입력의 **89%**가 답 단계), 한 달 어림 **$20.8**, 기록 느림 11.2초 → **6.1초** |
| 2단계 · 줄이기 | 보내는 것 · 단계마다 모델 · 답까지 nano? · LLM 한 번 · 긴 질문 상한 | 답 단계 입력 1,781 → **165**, VPN $0.001007 → **$0.000156**, 답 nano는 **15%** 덜 들지만 형식이 나빠짐, 긴 질문 $0.001196 → **$0.000237** |
| 3단계 · 지키기 | 하루 예산 → 원문, 1분 한도 → 429, 예산 바꿔 보기, 보고서, what-if | 한도 초과 **$0**, 429 **0.0초**, 예산 $0.0003을 **43%** 넘음, 실제 사용량 한 달 **$2.80**, what-if nano **34%** 덜 |
| 바이브 코딩 | 부르기 전에 토큰 · 비용을 계산하는 `tcost` (TODO → SPEC → IMP-PLAN → 설치) | 요구 14개 |
한 문장으로: **질문 하나에 얼마인지 재고, 품질 테스트를 지키며 한 칸씩 줄이고, 넘치지 않게 예산 · 한도 · 상한을 둔다.**
## 2. 왜 비용이 운영의 일인가
72일차는 "빠르게, 멈춰도 답하게", 73일차는 "속지 않게, 새지 않게", 74일차는 "바꿔도 망가지지 않게"였습니다. 오늘은 "**써도 감당할 수 있게**"입니다. LLM 서비스는 쓰는 사람이 늘수록 비용이 **그대로 따라 늘고**, 한 사람 · 한 지시문의 실수가 모든 요청의 비용을 바꿉니다.
| | 만들기 | 운영하기 (비용 관리) |
| --- | --- | --- |
| 질문 | "답이 맞나?" | "질문 하나에 얼마? 한 달에 얼마? 넘치면?" |
| 기준 | 테스트 통과 | 같은 테스트를 지키면서 **비용 전 · 후** |
| 실패 | 오류 | **청구서** (오류처럼 보이지 않음) |
| 정하는 사람 | 개발자 | 개발자 + 재무 · 사업 (예산) + 사용자 (질문 길이) |
- 비용 사고는 대개 **조용합니다.** `base-cost`는 모든 테스트를 통과하면서 질문마다 필요 없는 규정 약 1,600토큰을 보냈고, 긴 로그 질문은 아무 오류 없이 평소의 7배 넘게 썼습니다. 재지 않으면 청구서가 올 때까지 모릅니다.
- 비용을 줄이는 일은 **품질과의 맞바꿈**이 되기 쉽습니다. 답까지 nano로 바꾼 비교본은 테스트를 다 통과했지만 답의 형식이 나빠졌습니다. 그래서 숫자와 함께 **답을 읽고**, 결정은 사람이 합니다.
## 3. 효과와 값을 숫자로
### 3.1 오늘 실측 전 · 후
| 무엇 | 전 | 후 | 바꾼 것 |
| --- | --- | --- | --- |
| VPN 질문 하나의 비용 | $0.001040 (3 calls) | **$0.000156** (1 call) | 보내는 것 · 단계마다 모델 · 규칙 분류 · 검토 빼기 |
| 답 단계 입력 토큰 | 1,781 | **165** | 규정 전체 빼기, 필요한 칸만 |
| 질문 3개의 기록 합계 | $0.002951 (calls 9) | **$0.000409** | 위와 같음 |
| 긴 로그 질문 (6,298자) | $0.001196 | **$0.000237** | 입력 상한 (앞 200 + 뒤 300자) |
| 기록 API가 느릴 때 | 11.2초 | **6.1초** | timeout 3초 + 실패해도 계속 |
| 예산을 넘은 질문 | LLM을 부름 | **$0 · 원문** | 예산 확인 (LLM 앞) |
| 1분 한도를 넘은 요청 | 200 · $0.000156 | **429 · 0.0초 · $0** | 한도 확인 (가장 앞) |
**치른 값**:
- **규정에만 있는 질문**은 이제 "찾지 못했습니다"입니다 (규정 전체를 뺐으므로). 필요하면 검색해서 관련 부분만 보내는 일(71일차)을 따로 해야 합니다.
- **검토 단계**를 뺐으므로 말투 · 형식은 답 단계의 지시문에만 달렸습니다.
- **기록이 빠진 요청**은 예산 · 한도에서도 빠집니다 (`recorded: false`로 셀 수 있게만 함).
- **예산은 넘은 뒤 멈춥니다** (오늘 43% 넘음). 넘지 않으려면 최대 비용으로 미리 잡는 방식이 필요합니다.
### 3.2 한 달로 보면 (예시)
가정: 하루 질문 1,000건 · 한 달 22일 (오늘 실습의 가정, 질문 종류의 비중은 실습에서 쓴 FAQ 60 · 접수 30 · 그 밖 10%).
| | 아침 (`base-cost`) | 저녁 (Lab 02 과제 1 정답) |
| --- | --- | --- |
| 요청 하나의 평균 | $0.000946 (가중 평균, Lab 01 예제 3) | $0.000127 (실제 일곱 질문, Lab 03 과제 1) |
| 한 달 | **약 $20.8** | **약 $2.80** |
| 하루 예산 $1.0이면 | 하루 약 1,050건에서 멈춤 | 하루 약 7,900건까지 |
- 두 평균은 재는 방법이 조금 다릅니다 (아침은 가정한 비중, 저녁은 실제 일곱 질문). 그래도 **약 7분의 1**이라는 크기는 같은 질문 하나($0.00104 → $0.000156)에서도 보입니다.
- 사용자가 10배가 되면 아침 기준은 한 달 $208, 저녁 기준은 $28입니다. **요청 하나의 비용**을 줄이는 것이 규모가 커질수록 중요한 까닭입니다.
## 4. 회사에서는 같은 생각을 이렇게 부른다
| 오늘 한 것 | 업계 이름 | 쓰는 도구 · 표준 (예) |
| --- | --- | --- |
| 재기 → 줄이기 → 지키기 | FinOps (Inform · Optimize · Operate) | FinOps Foundation 프레임워크, 클라우드 비용 관리 화면 |
| 응답의 `usage`, 단계별 비용 | LLM 관측 (observability), 비용 할당 | Langfuse, Helicone, LangSmith, OpenTelemetry GenAI 규칙 |
| 요청마다 기록 · 사람별 합 | 계량 (metering), showback · chargeback | 사용량 기록 테이블, 태그 · 레이블 |
| 단가표를 밖에 · 모르는 모델은 오류 | 가격 카탈로그, 설정 외부화 | 공급자 가격 페이지, 사내 설정 저장소 |
| 필요한 것만 보내기 | 컨텍스트 엔지니어링, 데이터 최소화, RAG | 검색 색인, 프롬프트 템플릿 |
| 단계마다 맞는 모델 | 모델 라우팅 · 계단식(cascade) | LiteLLM, OpenRouter, 클라우드 모델 게이트웨이 |
| 같은 앞부분 할인 · 모아서 보내기 | 프롬프트 캐싱, 배치 처리 | 공급자의 캐시된 입력 단가, Batch API |
| 규칙으로 먼저 · 검토 빼기 | 호출 줄이기, 규칙 기반 라우팅 | Code 노드, 의도 분류 규칙 |
| 입력 · 출력 상한 | 요청 크기 제한, 무제한 소비 방지 | `max_completion_tokens`, OWASP LLM Top 10의 Unbounded Consumption (Denial of Wallet) |
| 하루 예산 · 원문으로 낮추기 | 예산 · 알림, 우아한 저하 | 클라우드 예산 알림(AWS Budgets 등), 공급자 사용 한도 |
| 1분 한도 · 429 | 속도 제한, 할당량 | API 게이트웨이(Kong · AWS API Gateway usage plan), nginx `limit_req`, Redis 카운터 |
| 비용 보고서 · what-if | 비용 보고 · 예측, 시나리오 분석 | 스프레드시트 · BI 대시보드, 월간 비용 리뷰 |
| `tcost` | 부르기 전 토큰 계산 | `tiktoken`, 공급자 token count API |
- 오늘은 n8n 노드 · 작은 데이터 API · `flow.py`로 직접 만들었습니다. 회사에서는 게이트웨이 · 관측 도구가 대신해 주는 경우가 많습니다. 그래도 **무엇을 재고, 어느 칸부터 줄이고, 예산 · 한도를 얼마로 둘지**는 오늘처럼 사람이 정합니다.
## 5. 산업별 응용 (예)
아래는 오늘 한 일을 다른 업무에 옮긴 예입니다 (실제 특정 회사의 사례가 아니라 흔한 모양).
| 업무 | 오늘의 무엇을 | 이렇게 |
| --- | --- | --- |
| 쇼핑몰 상담 챗봇 | 규칙으로 먼저 · 단계마다 모델 | 주문 번호가 있으면 조회만, 일반 문의만 LLM, 분류는 작은 모델 |
| 콜센터 통화 요약 | 입력 상한 · 배치 | 긴 통화는 나눠 요약, 실시간이 아닌 요약은 모아서 밤에 (배치 할인) |
| 사내 문서 검색 (71일차) | 보내는 것 줄이기 · 비용 보고서 | 문서 전체 대신 검색 결과 상위 몇 개만, 부서별 사용량을 매달 보고 |
| SaaS의 AI 기능 (고객사별) | 사람별 한도 · 할당량 · showback | 요금제마다 월 몫, 넘으면 429 · 업그레이드 안내, 고객사별 비용으로 가격을 정함 |
| 사내 IT 헬프데스크 (오늘) | 전부 | 예산을 넘으면 FAQ 원문, 몰아 부르면 429, 매달 보고서 |
| 개발팀 코드 리뷰 봇 | what-if · 모델 라우팅 | 작은 변경은 작은 모델, 큰 변경만 큰 모델, 바꾸기 전에 지난달 기록으로 what-if |
**토론 거리**: 내 회사(또는 가고 싶은 회사)의 AI 기능 하나를 골라, "요청 하나에 얼마일까 · 가장 큰 몫은 어디일까"를 어림해 봅니다.
## 6. 비용 대시보드에 올릴 숫자 여섯
오늘 기록(`/usage`)에 남긴 칸만으로 비용 지표가 됩니다 (77일차 로그 분석 · 79일차 운영 대시보드에서 실제로 모읍니다).
| 지표 | 어느 칸에서 | 무엇을 알 수 있나 |
| --- | --- | --- |
| 요청 하나의 평균 비용 | `cost_usd` ÷ 요청 수 | 단위 비용이 늘었나 (지시문 · 모델 변경 뒤) |
| 요청당 호출 수 | `calls` ÷ `requests` | 재시도 · 단계가 늘었나 |
| 모델별 비용 비중 | `by_model` | 비싼 모델이 어디서 쓰이나 |
| 사람 · 팀별 상위 | `by_user` | 몰아 쓰는 사람 · 자동화가 있나 |
| 경로별 수 | `by_route` (`자동 답변` · `한도 초과` · `잠시 후`) | 예산 · 한도가 너무 빡빡하거나 느슨한가 |
| 예산 대비 · 한 달 예상 | `/budget`, 보고서 | 이번 달 안에 넘을까 |
- 오늘 기록 한 줄에 `request_id` · `user` · `step` · `model` · `route`를 넣게 한 까닭이 이것입니다. **나눠 볼 칸이 없으면 줄일 곳도 찾지 못합니다.**
## 7. 하지 말아야 할 때 (흔한 실수)
| 하기 쉬운 일 | 왜 위험한가 | 대신 |
| --- | --- | --- |
| 재지 않고 줄임 | 엉뚱한 곳을 줄이고 무엇을 잃었는지 모름 | 단계별 토큰부터 |
| 세 칸을 한꺼번에 | 무엇 덕분 · 무엇 때문인지 모름 | 한 칸씩, `COST.md`에 한 줄 |
| 싸지면 무조건 좋다 | 품질이 몰래 나빠짐 (답 nano의 형식) | 테스트 + 답 읽기, 맞바꿈은 사람이 |
| 단가를 코드에 숫자로 | 가격 · 모델이 바뀌면 조용히 틀림 | 단가표, 확인한 날짜 |
| 모르는 모델을 0원으로 | "100% 아낀다"는 거짓 | 오류 · 알려진 목록 |
| 입력 길이를 그대로 받음 | 한 사람이 예산을 다 씀 | 입력 · 출력 상한 |
| 예산을 넘으면 오류 | 직원이 아무 도움도 못 받음 | 데이터 원문으로 낮춰서 |
| 보고서 숫자를 LLM이 | 틀림 · 돈 · 흔들림 | 코드가 계산, LLM은 설명 |
| what-if를 그대로 믿음 | 토큰 · 품질이 다를 수 있음 | 재고 · 읽고 · 게이트로 (74일차) |
## 8. 직무와 면접
오늘 한 일은 다음 직무들이 날마다 하는 일입니다: **AI · LLM 애플리케이션 엔지니어, LLMOps · 플랫폼 엔지니어, FinOps · 클라우드 비용 담당, 백엔드 엔지니어, AI 서비스 기획 · 운영.** 채용 공고의 "LLM 비용 최적화", "토큰 사용량 관리", "프롬프트 최적화", "모델 선택 · 라우팅", "rate limiting", "FinOps"가 오늘 내용입니다.
면접에서 흔히 받는 질문과 오늘 숫자로 답하는 법:
<details><summary>1. "LLM 비용이 너무 많이 나옵니다. 어디부터 보나요?"</summary>
먼저 요청 하나의 비용을 단계별로 나눠 봅니다. 오늘 헬프데스크는 VPN 질문 하나가 $0.00104였고, 응답의 `usage`를 단계별로 모아 보니 입력의 89%가 답 단계 하나(1,781토큰)에서 나왔습니다. 지시문에 사내 규정 전체를 붙여 보내고 있었기 때문입니다. 질문 종류가 달라도 비용이 비슷했던 것도 그 고정비 때문이었습니다. 가장 큰 몫부터 줄였습니다.
</details>
<details><summary>2. "비용을 줄이면서 품질은 어떻게 지키나요?"</summary>
줄이기 전에 품질 테스트를 고정하고, 한 번에 한 가지만 바꾸고, 바꿀 때마다 테스트와 함께 답을 직접 읽었습니다. 보내는 것을 줄이자 답 단계 입력이 1,781에서 165토큰, 비용이 $0.001007에서 $0.000346이 됐고 테스트는 그대로였습니다. 반대로 답 단계까지 작은 모델로 바꾼 비교본은 테스트를 다 통과했지만 마크다운 · 군더더기가 섞여서, 15%를 아끼는 대신 바꾸지 않기로 했습니다.
</details>
<details><summary>3. "작은 모델로 바꾸면 얼마나 싸지나요?"</summary>
단가표로 계산해 봐야 합니다. 오늘 두 모델은 입력이 절반, 출력은 0.78배라서 "몇 배"가 아니었습니다. 입력이 많은 단계일수록 효과가 큽니다. 쉬운 단계(분류 · 검토)만 바꾸고 답은 그대로 두었고, 바꾸기 전에는 기록된 토큰으로 what-if를 계산했습니다 (모두 작은 모델이면 34% 덜). what-if는 같은 토큰 · 품질을 가정하므로 결정은 품질 게이트로 합니다.
</details>
<details><summary>4. "한 사용자가 API를 몰아 쓰거나 아주 긴 입력을 보내면?"</summary>
세 겹으로 막습니다. 사람마다 1분 한도를 두고 넘으면 가장 앞에서 429와 `retry_after`를 줍니다 (0.0초 · $0). 입력에 글자 수 상한을 두어 6,000자 넘는 로그 질문이 $0.001196에서 $0.000237이 되게 했고, 출력에도 `max_completion_tokens` 상한을 둡니다. 그리고 하루 예산을 넘으면 LLM 없이 데이터 원문으로 답합니다. 두 상한이 있어야 요청 하나의 최대 비용을 말할 수 있고, 그래야 예산도 지킬 수 있습니다.
</details>
<details><summary>5. "예산을 넘지 않게 하려면?"</summary>
"쓴 돈이 예산 이상이면 멈춘다"는 넘은 뒤에 멈춥니다. 오늘 예산 $0.0003에서 세 번째 질문까지 답해 $0.000430, 43%를 넘었습니다. 넘지 않으려면 부르기 전에 한 건의 최대 비용만큼 예산에서 미리 떼어 두거나, 남은 돈이 그보다 적으면 멈춰야 합니다. 그 최대 비용은 입력 · 출력 상한으로 정해집니다. 그리고 80%에서 먼저 알림을 줍니다.
</details>
## 9. 최종 프로젝트로 가져갈 것
최종 프로젝트(85일차~)의 워크플로우에 아래를 넣으면 "운영을 생각한 프로젝트"가 됩니다.
- [ ] LLM 호출마다 `usage`를 받아 단계 · 모델별 `cost_usd`, 단가는 밖에 (확인한 날짜와 함께)
- [ ] 요청마다 사용량 기록 (`request_id` · 사람 · 단계 · 모델 · 경로, 원문은 남기지 않음), 기록이 멈춰도 답은 나감
- [ ] 질문 종류별 비용과 한 달 예상 (가정을 적어서)
- [ ] 보내는 것은 필요한 것만, 출력 상한
- [ ] 단계마다 맞는 모델 (바꾸기 전에 단가표 · what-if, 바꾼 뒤 품질 테스트 · 답 읽기)
- [ ] 규칙으로 먼저, 쓰지 않는 단계는 빼기
- [ ] 입력 상한 · 출력 상한 → 요청 하나의 최대 비용
- [ ] 하루 예산(넘으면 낮춰서 답) · 사람별 한도(429), 값은 사람이 정하고 밖에
- [ ] 비용 보고서 (코드가 계산), `COST.md`에 바꾼 것 · 아낀 것 · 잃은 것
## 10. 다음 일차와의 연결
| 일차 | 오늘의 무엇을 잇나 |
| --- | --- |
| 76 | 에러 처리 · 예외 대응 — 오늘 `recorded: false`로 남긴 빠진 기록 같은 실패를 모아 두고 다시 처리 · 알림 |
| 77 | 로그 분석 — 오늘의 사용량 기록을 사람별 · 경로별 · 시간대별로 분석, 비용이 갑자기 느는 때 찾기 |
| 78 | 성능 기준(SLO) — 비용 · 시간 · 품질을 함께 목표로 |
| 79 | 운영 대시보드 · `tops` — 6절의 비용 지표 여섯, `tcost`로 부르기 전 계산 |
## 토론 (5분)
1. 오늘 줄인 것 중 **품질과 맞바꾼 것**은 무엇이었나요? 그 맞바꿈은 누가 정해야 할까요?
2. 내 업무의 AI 기능에 하루 예산을 둔다면, 넘었을 때 사용자에게 무엇을 주는 것이 맞을까요 (원문 · 담당자 연결 · 잠시 후)?
## 정리
- [ ] 비용도 **운영**: 써도 감당할 수 있게, 그리고 요청 하나의 비용을 안다
- [ ] 효과는 같은 날 전 · 후 숫자로 ($0.00104 → $0.000156, 1,781 → 165토큰, 긴 질문 $0.0012 → $0.00024, 한도 초과 · 429는 $0)
- [ ] 같은 생각을 업계에서는 FinOps · LLM 관측 · 모델 라우팅 · 프롬프트 캐싱 · 속도 제한 · 예산 알림이라고 부른다
- [ ] 기록의 칸이 곧 비용 지표가 된다
- [ ] 최종 프로젝트에 체크리스트 9개를 가져간다
+93
View File
@@ -0,0 +1,93 @@
# 7·8교시 · 바이브 코딩 (`tcost`)
- 교시: 7교시 16:00~16:50 (각자 만들기), 8교시 17:00~17:50 (발표 10분 · [4단계 정리](04-STAGE4-WRAPUP.md) 35분 · 예고 5분)
- 과제: [VIBE-REQ-TCOST](../../Last3Mon/75/VIBE-REQ-TCOST.md)(요청서, 요청의 말 ①~⑫) → [VIBE-Q-TCOST](../../Last3Mon/75/VIBE-Q-TCOST.md)의 8단계: `Doc/TODO.md` → `Doc/SPEC.md` → `Doc/IMP-PLAN.md`(엔진 · TUI 단계) → 단계별 구현 → 확인 → `uv tool install` → 다른 폴더에서 사용 → 수업 뒤 `VIBE-A-TCOST.md` 공개
- 요구 14개(R1~R14). 50분 안에는 **엔진 1단계(R1 · R2 · R3 · R13) + 엔진 2단계(R5 · R6) + TUI 1단계(R8) + 설치(R14)**, 나머지는 IMP-PLAN의 다음 단계로 집에서.
- 72일차 `tbench` · 73일차 `tscan` · 74일차 `tver`와 같은 흐름입니다: 사람의 말(요청서)을 **확인할 수 있는 요구**로 바꾸는 일을 학생이 먼저 하고, VIBE-Q는 그 다음 단계(비교 대상)로 씁니다.
- 오늘은 `tcost`: LLM을 부르기 **전에** 글 파일 · 워크플로우의 토큰과 비용을 계산하고, 테스트 보고서(`runs/*.json`)의 실제 사용량을 모아 한 달을 예상하는 TUI. OpenAI를 부르지 않습니다. 오늘 n8n을 돌려서 잰 것을 돌리기 전에 내 도구로.
```mermaid
flowchart LR
R["요청서 → TODO · SPEC ·<br/>IMP-PLAN 20분"] --> A["7교시<br/>엔진 1 · 2 · TUI · 설치 30분"] --> B["발표<br/>10분"] --> C["4단계 정리<br/>35분"] --> F["76일차 예고<br/>5분"]
```
> 이 문서는 7 · 8교시 진행 문서입니다. 8교시의 정리 35분은 [04-STAGE4-WRAPUP](04-STAGE4-WRAPUP.md)으로 합니다 (74일차부터 4단계 정리를 따로 두었습니다).
## 1. 7교시 · 각자 만들기 (50분)
| 시간 | 단계 | 확인 |
| --- | --- | --- |
| 16:00~16:09 | 0 준비 · 1 요청서 → `Doc/TODO.md` (에이전트 없이) | 할 일 ①~⑫ · 꼭 / 나중 |
| 16:09~16:15 | 2 `Doc/SPEC.md` → VIBE-Q 요구 표(R1~R14)와 비교 · commit | 요구마다 확인 방법 (손으로 계산한 금액 · 건너뛴 수) |
| 16:15~16:20 | 3 `Doc/IMP-PLAN.md` (엔진 1~4단계 · TUI 1단계 · 설치) · commit | 단계마다 체크리스트 |
| 16:20~16:44 | 4 구현: 엔진 1단계 → 엔진 2단계 → TUI 1단계, 막히면 5 문제 해결 팁 | 단계마다 `pytest` · 직접 실행 · commit |
| 16:44~16:50 | 6 확인 · 7 `uv tool install --editable .` · 8 다른 폴더에서 `tcost` | `cd ~/aio-01d4-stu/Last3Mon/75 && tcost usage runs --per-day 1000` |
| 볼 것 | 막혔을 때 한마디 |
| --- | --- |
| TODO · SPEC 없이 VIBE-Q를 통째로 붙여 넣었나 | "요청서로 Doc/TODO.md부터. 손으로 계산한 금액이 SPEC에 없으면 맞았는지 모릅니다" |
| IMP-PLAN이 한 덩어리 · 첫 요청에 14개 | "엔진 1단계만. 단계마다 확인 · 체크 · commit" |
| 금액이 백만 배 | "단가는 1M 토큰당. ÷ 1,000,000" |
| 0.00056000000001 | "비용은 소수 8자리에서 반올림" |
| 모르는 모델이 $0 | "단가표에 없으면 오류, 목록을 보여 줘" |
| 테스트가 OpenAI를 부름 | "테스트는 네트워크 없이, 토큰은 tiktoken" |
| 보고서의 400 · 요약 케이스에서 오류 | "`usage.by_step`이 없는 결과는 skipped로 세고 넘어가기" |
| 평균이 이상하게 작음 | "호출 수가 아니라 요청 수로 나누기" |
| 설치한 `tcost`가 단가 파일을 못 찾음 | "prices.json을 wheel에 넣어 줘" |
| 실행해 보지 않고 다음 요청 | "에이전트가 됐다고 해도 직접 `uv run tcost`" |
정답 예(R1~R14를 모두 넣은 `tcost`와 정답본 `VIBE-A-TCOST.md`)는 수업 뒤 공개됩니다. 강사 바이브 코딩(4절)은 시간이 남으면 · 수업 뒤 하고, 걸린 시간을 정답본(`VIBE-A-TCOST.md`) 6절에 남깁니다.
## 2. 8교시 · 발표 (10분)
- 1~2명. **자기 `runs`의 실제 비용과 한 달 예상을 보여 줄 수 있는 사람**, 기준 · 내 워크플로우의 고정 지시문 토큰을 비교한 사람을 먼저 고릅니다.
- 3분 발표 · 2분 질문: **내 SPEC과 VIBE-Q가 달랐던 곳** / IMP-PLAN을 어떻게 나눴나 / 잘 먹힌 요청문 / 안 먹힌 요청문 / 에이전트가 틀린 곳 / 시연.
## 3. 강사 정리 (8교시 4단계 정리 안에서, 1절 표와 함께)
| | n8n 헬프데스크 (오늘 Lab) | tcost |
| --- | --- | --- |
| 토큰 세기 | 응답의 `usage` (부른 뒤) | `tiktoken` (부르기 전) |
| 단가 | 데이터 API `/prices` | 단가 파일 (`checked` 날짜 · 출처) |
| 비용 식 | 단계마다 × 그 모델 단가, 8자리 (Lab 01 예제 2) | 같은 식 (R1 · R2) |
| 모으기 | `/usage/summary` (Lab 01 과제 1) | `runs/*.json` (R5) |
| 한 달 | 보고서 `projected_month_usd` (Lab 03 과제 1) | `--per-day --days` (R6) |
| 바꾸면 얼마 | what-if (Lab 03 과제 2) | `compare` (R7) |
| 모르는 모델 | 400 `unknown model` | 종료 코드 2 (R12) |
- 풀이 거리: 긴 프린터 질문을 `tcost count --out 60`으로 미리 세면 2,613토큰 · $0.001141, n8n에서 실제로 돌리면 $0.001196이었습니다 (강사 본). 미리 센 값은 질문 글만이라 **하한**에 가깝습니다 (지시문 · FAQ가 더해짐).
## 4. 강사 바이브 코딩 (시간이 남으면 · 수업 뒤, 15분)
| 주제 | 요청문 요지 | 보여 줄 것 |
| --- | --- | --- |
| 실제와 맞춰 보기 | "보고서의 질문 글을 세어 prompt_tokens 와 차이를 % 로" | 내 셈과 공급자 셈의 차이 |
| 캐시 입력 단가 | "cached_tokens 가 있으면 cached input 단가로" | 같은 앞부분이 싸지는 것 |
| 비용 관문 붙이기 | "tcost workflow --max-usd 를 tver save 앞에" | 저장 = 비용 관문 통과 (74일차 정책과 같은 생각) |
걸린 시간과 결과를 `Last3Mon/75/VIBE-A-TCOST.md` 6절에 적어 둡니다.
## 5. 보충 · 팁 (읽을거리, 4단계 정리 7절과 함께)
| 팁 | 설명 |
| --- | --- |
| 요청서 → 요구가 먼저 | 확인 방법(정확한 금액 · 건너뛴 수 · 종료 코드)이 있는 요구가 있어야 에이전트의 "완료"를 판단할 수 있음 |
| 숫자 검사는 쉬운 숫자로 | 1,000토큰 × $0.40 = $0.0004 같은 손 계산으로 먼저 테스트 |
| 날짜는 넘겨받게 | "오늘"을 함수 인자로 받으면 테스트가 날마다 같음 |
| 없는 것은 0이 아니라 skipped · 오류 | 0으로 세면 평균 · 비용이 거짓말을 함 |
| 단가는 파일로 | 코드에 박지 않고, 확인한 날짜와 함께 |
| 미리 센 값은 하한 | 실제 요청에는 실행 때 붙는 것이 더해짐 |
| 단계마다 `git commit` | 에이전트가 망가뜨려도 돌아갈 곳 |
## 6. 76일차 예고 (5분)
- 오늘: 재기 · 줄이기 · 예산과 한도. 내일: **에러 처리와 예외 대응, 안정적 운영 체계** — 오늘 기록 API가 멈췄을 때 답은 보냈지만 빠진 기록은 `recorded: false`로 남기기만 했습니다. 내일은 실패한 일을 모아 두고(DLQ) 알리고 다시 처리합니다.
- 바이브 코딩: `tretry`. 오늘 `tcost`는 지우지 마세요. 79일차 `tops`에서 씁니다.
## 확인 (강사)
- [ ] 16:00 요청서 → TODO(에이전트 없이) → SPEC → IMP-PLAN 20분을 지켰다, 16:20부터 엔진 1단계.
- [ ] 7교시 시작 전 시험 자료(`reference/baseline-cost.json` · `lab02-example1-lean.json`, 오늘 `runs/`)를 화면에 띄웠다.
- [ ] 수업 뒤: `VIBE-A-TCOST.md`의 강사 바이브 코딩 시간을 채우고, 요청이 있을 때 학생 저장소에 정답(LAB-A · `answers/` · VIBE-A · `tcost/`)을 올린다.
- [ ] 발표자 1~2명, 강사 정리 표, 팁, 예고.
- [ ] 수업이 끝날 때 학생들의 예산 · 기록 · 고장 스위치가 처음 상태인지 (`budget --reset`, `usage --clear`, `fault usage ok`, `doctor`).
+41
View File
@@ -0,0 +1,41 @@
# 75일차 · 컴퓨팅 자원·API 사용량 관리와 비용 최적화 (바이브 코딩)
- 일자: 2026-10-29 (75일차)
- 교과목: 노코드 멀티 에이전트 워크플로우 설계 및 운영
- 단원: AI 워크플로우 운영 및 관리 역량 강화
- 학습 내용: 컴퓨팅 자원 및 API 사용량 관리, 비용 최적화 전략 적용
- 실습 폴더: [Last3Mon/75](../../Last3Mon/75/README.md)
- 실습 환경: 65일차 n8n 2.42.6 + OpenAI API(`gpt-4.1-mini`, `gpt-5.4-nano`) + 75일차 데이터 API(단가 · 사용량 기록 · 예산 · 한도) + **CLI 코딩 에이전트**
- **실습 준비: [00-DAY75-PREP.md](00-DAY75-PREP.md) (수업 전날까지 1~2절 완료)**
- 수업 형식: 1 · 3 · 5교시 강의, 2 · 4 · 6교시 실습 5개씩 (예제 3 · 과제 2), 7교시 바이브 코딩, 8교시 발표 · 4단계 정리
## 오늘의 시간표
| 교시 | 시간 | 내용 | 자료 |
| --- | --- | --- | --- |
| 1 | 09:00~09:50 | 개요 · 1단계 강의 · 사용량 재기 | [개요](00-DAY75-OVERVIEW.md) 1~14장, [1단계](01-STAGE1-MEASURE-USAGE.md) |
| 2 | 10:00~10:50 | Lab 01 실습 5개 (자원 · 토큰 · 비용 · 종류별 비용 · 기록 · 기록이 멈추면) | [Lab 01](../../Last3Mon/75/LAB-01-Q-MEASURE-USAGE.md) |
| 3 | 11:00~11:50 | 2단계 강의 · 비용 줄이기 | 개요 15~21장, [2단계](02-STAGE2-REDUCE-COST.md) |
| 4 | 12:00~12:50 | Lab 02 실습 5개 (보내는 것 · 모델 · 답까지 nano? · LLM 한 번 · 긴 질문) | [Lab 02](../../Last3Mon/75/LAB-02-Q-REDUCE-COST.md) |
| - | 13:00~14:00 | 점심 | |
| 5 | 14:00~14:50 | 3단계 강의 · 예산과 한도 | 개요 22~28장, [3단계](03-STAGE3-BUDGET-AND-LIMITS.md) |
| 6 | 15:00~15:50 | Lab 03 실습 5개 (하루 예산 · 1분 한도 · 예산 바꿔 보기 · 보고서 · what-if) | [Lab 03](../../Last3Mon/75/LAB-03-Q-BUDGET-AND-LIMITS.md) |
| 7 | 16:00~16:50 | 바이브 코딩 `tcost`: 요청서 → TODO → SPEC → IMP-PLAN → 만들기 → 설치 | [VIBE-REQ-TCOST](../../Last3Mon/75/VIBE-REQ-TCOST.md), [진행](05-VIBE-CODING.md) |
| 8 | 17:00~17:50 | 발표 · **4단계 정리** (의미 · 효과 · 회사에서 쓰는 법) · 76일차 예고 | [4단계 정리](04-STAGE4-WRAPUP.md), [진행](05-VIBE-CODING.md) |
```mermaid
flowchart LR
P["개요"] --> S1["1단계<br/>자원 · 토큰 · 비용<br/>종류별 · 기록"] --> S2["2단계<br/>보내는 것 · 모델<br/>호출 수 · 긴 입력"] --> S3["3단계<br/>예산 · 한도<br/>보고서 · what-if"] --> V["7교시<br/>tcost"] --> W["8교시<br/>4단계 정리"]
```
## 자료 목록
| 파일 | 내용 |
| --- | --- |
| `00-DAY75-PREP.md` | 실습 준비: n8n · 75일차 판 데이터 API 켜기, 기준(`base-cost`) 게시, `flow.py doctor` (수업 전) |
| `00-DAY75-OVERVIEW.md` | 개요 30장 (PPT 대신 Markdown, 가로줄로 장 구분) |
| `01-STAGE1-MEASURE-USAGE.md` | 왜 비용인가 · FinOps, 자원, 토큰과 단가, 토큰이 보이는 호출, 단계별 비용 · 단가는 밖에, 종류별 비용 · 한 달, 기록, 기록이 멈추면 (실제 사례) |
| `02-STAGE2-REDUCE-COST.md` | 비용의 세 칸, 보내는 것 줄이기, 단계마다 맞는 모델, 싸졌는데 나빠졌다면, 하지 않은 호출, 긴 입력의 상한, 줄인 것을 남기기 |
| `03-STAGE3-BUDGET-AND-LIMITS.md` | 넘치지 않게, 하루 예산 · 낮춰서 답하기, 사용자별 한도 · 429, 예산은 넘은 뒤 멈춘다, 비용 보고서, what-if, 내 쪽 자원 관리 |
| `04-STAGE4-WRAPUP.md` | 8교시 35분: 오늘의 의미 · 효과 숫자 · 업계 이름 · 산업별 응용 · 비용 지표 · 면접 · 최종 프로젝트 체크리스트 |
| `05-VIBE-CODING.md` | 7 · 8교시 바이브 코딩 진행 (tcost), 발표, 강사 정리, 강사 바이브 코딩, 팁, 예고 |