
생성 수정
IBM * 레드햇 AX 과정 · 24강
데이터 생명주기와 수집 — 크롤링에서 서버 구성까지
2026년 7월 13일(월) ~ 7월 17일(금)
9. 데이터 생명주기 — set · frame · base ⭐
9.1 3주차 축 1이 여기서 세 칸이 됩니다
3주차에서 "컴퓨터의 거의 모든 개념이 디스크와 메모리 두 세계 중 하나에 속한다"고 배웠습니다. 이 두 칸이 이번 주에 세 칸으로 확장됩니다.
| 3주차 (2칸) | 12주차 (3칸) | |
|---|---|---|
| 1 | 디스크 — 영구·정적 | set (Disk) — 정적 데이터셋 |
| 2 | 메모리 — 휘발·동적 | frame (Memory) — 구조화된 DataFrame |
| 3 | — | base (Runtime) — 기준 동작 객체 |
왜 세 번째 칸이 필요한가. 3주차에서 이미 힌트가 있었습니다. 프로그램(메모리에 로드됨) 과 프로세스(실제로 CPU를 쓰며 실행 중) 를 구분했던 것을 기억하세요. 데이터에도 똑같은 구분이 필요합니다. 메모리에 표 형태로 올라와 있기만 한 것(frame)과 그 표를 재료로 실제로 돌아가며 상태가 변하는 것(base)은 다릅니다. 엑셀 파일을 열어 화면에 띄운 것과, 그 숫자로 시뮬레이션이 돌아가고 있는 것의 차이입니다.
9.2 전체 흐름
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 1. set │ ── 업로드 ──> │ 2. frame │ ─────────────> │ 3. base │
│ (Disk 영역) │ <─ 다운로드 ─ │ (Memory 영역) │ │ (Runtime영역) │
└──────────────┘ └──────────────┘ └──────────────┘
데이터는 시스템의 물리적 레이어에 따라 저장(Disk) ➔ 적재(Memory) ➔ 실행(Runtime) 의 3단계 파이프라인을 거쳐 흐릅니다.
9.3 [Step 1] set — Disk Storage
- 물리 위치: 디스크 (SSD / HDD / RDBMS / NoSQL 등 영구 저장소)
- 상태: 비활성 정적 데이터 세트(Static Data Set)
- 설명: 시스템이 구동되기 전, 혹은 영구 보존을 위해 파일 및 파일 데이터 형태로 디스크에 물리적으로 기록되어 있는 상태입니다. 직접적인 실시간 연산 대상이 아닙니다.
왜 이름이 set 인가 — 수학의 집합(set)입니다. 그냥 모여 있는 것이라 순서도 연산도 없습니다. CSV 파일, jsonl 파일, DB 테이블, 내려받은 모델 가중치 파일이 전부 여기 있습니다.
⭐ 4장의 모델 파일도
set입니다. 학습이 끝난 밸브 값들의 목록이 디스크에 정적으로 누워 있는 것입니다.
9.4 [Step 2] frame — In-Memory Object
- 물리 위치: 메모리 (RAM)
- 상태: 구조화된 가상 프레임 (DataFrame / Structured Object)
- 설명: 시뮬레이션 및 연산을 위해 디스크에서 읽어 들여(업로드) 메모리에 구조화된 형태로 적재한 상태입니다.
- 양방향 연결성: 메모리상에서 실시간으로 처리된 상태값이나 변경 내역은 필요에 따라 다시 디스크로 저장(다운로드 / 쓰기)됩니다.
왜 이름이 frame 인가 — 판다스의 DataFrame 입니다. 행과 열이라는 틀(frame)이 씌워진 것입니다. 디스크의 CSV는 그냥 글자이지만, 메모리에 올려 frame 이 되는 순간 "3번째 행의 price 열" 같은 접근이 가능해집니다.
# set → frame
import pandas as pd
df = pd.read_csv("resources/crawled/chart_20260716.csv") # 디스크에서 메모리로
# frame → set
df.to_csv("resources/processed/chart_cleaned.csv", index=False) # 다시 디스크로
⭐ 3주차에서 "터미널 속 죽어 있는 글자(개체)를 메모리상에 객체로 부활시킨다" 고 했습니다.
set → frame이 정확히 그 문장의 데이터 버전입니다.
9.5 [Step 3] base — Runtime Instance
- 물리 위치: 실행 런타임 엔진 (Application Context)
- 상태: 기준 동작 객체 (Base Instance Class)
- 설명: 프레임(
frame) 데이터 구조를 바탕으로 실시간 서비스나 물리적·수학적 시뮬레이션 엔진 내부에서 동작하는 최종 구체화 객체입니다.
⚠️ 번역어 함정 (다시 확인) —
Base는 기초가 아닙니다. 미국식으로는 거점 · 저장소 · 기준점입니다. 야구의 베이스, 군대의 베이스캠프처럼 "여기서부터 움직이는 기준 자리" 입니다.Database가 "데이터의 기초"가 아니라 "데이터의 거점" 인 것과 같습니다.
그래서 base 는 "이 시뮬레이션이 돌아가는 기준이 되는 살아 있는 객체" 입니다. frame 이 표라면 base 는 그 표를 들고 실제로 일하고 있는 것입니다.
# frame → base
class Machine: # 기준 동작 객체
def __init__(self, row):
self.serial = row["serial"] # value — 안 변함
self.capacity = row["capacity"] # value — 안 변함
self.flow = 0.0 # price — 매 틱 변함
self.wear = 0.0 # price — 매 틱 변함
def tick(self, dt): # 런타임에서 상태가 변한다
self.flow = compute_flow(self.capacity, dt)
self.wear += self.flow * 0.001
machines = [Machine(row) for _, row in df.iterrows()] # frame → base
9.6 세 칸 비교표
| set | frame | base | |
|---|---|---|---|
| 물리 위치 | 디스크 (SSD/HDD/DB) | 메모리 (RAM) | 런타임 엔진 (Application Context) |
| 상태 | 정적 데이터 세트 | 구조화된 가상 프레임 | 기준 동작 객체 |
| 대표 형태 | CSV, jsonl, 테이블, 모델 파일 | DataFrame, 딕셔너리 | 인스턴스, 시뮬레이션 엔티티 |
| 연산 대상인가 | ❌ 아님 | 일괄 연산 가능 | ⭕ 매 순간 변함 |
| 3주차 대응 | 클래스 파일 / 프로젝트 / 프로덕트 | 프로그램 (로드됨) | 프로세스 (실행 중) |
| 전원이 꺼지면 | 남음 | 사라짐 | 사라짐 |
9.7 방향에는 이름이 있습니다 — 그리고 헷갈리기 쉽습니다
| 방향 | 이 장의 이름 | 3주차의 이름 |
|---|---|---|
| set → frame (디스크 → 메모리) | 업로드 | LOAD / 역직렬화 |
| frame → set (메모리 → 디스크) | 다운로드 / 쓰기 | SAVE / 직렬화 |
⚠️ 3주차에서 "업로드/다운로드는 인터넷 너머 남의 서버와 주고받는 것이니 SAVE/LOAD와 헷갈리지 말라"고 배웠습니다. 그런데 이 장의 그림에서는 디스크↔메모리 이동을 업로드/다운로드라고 부르고 있습니다. 이것은 모순이 아니라 관점의 차이입니다. 3주차는 내 컴퓨터 안이냐 밖이냐로 이름을 나눴고, 이 장은 아래쪽(디스크)에서 위쪽(메모리)으로 올린다는 방향감으로 이름을 붙였습니다. 문서마다 다르게 쓰이는 단어입니다. 그래서 실무에서는 반드시 "어디에서 어디로" 를 함께 말해야 합니다. "업로드했어요"만으로는 통하지 않습니다. "디스크에서 메모리로 올렸어요" 라고 말하세요.
10. 변수의 두 얼굴 — value와 price ⭐
10.1 런타임 객체 안에서 변수는 두 종류뿐입니다
base 인스턴스 내부의 변수는 값의 성격과 가변성 여부에 따라 두 가지 핵심 도메인으로 엄격하게 격리됩니다.
┌─── value ───> 변화 X (상수 속성: 고유 식별 및 물리 한계 정의)
[3. base Instance] ───┤
└─── price ───> 변화 O (상태 속성: 시간/상호작용에 따른 가변값) ──> 상태 = 환경
① value — 고유 상수. 속성 종류는 Immutable(불변성) 이고, 시스템 작동 중 어떠한 조건에서도 변경되지 않는 객체 고유의 속성입니다. 시뮬레이션의 기본 뼈대가 되며 객체의 본질과 물리적 한계치를 정의하는 기준값 역할을 합니다. 예시는 기기 일련번호, 설비 규격 크기, 고유 질량, 시스템 초기 제한값입니다. 판별 질문: "이 값이 바뀌면 이건 다른 물건인가?" 일련번호가 바뀌면 다른 기계이므로 value 입니다.
② price — 가변 상태. 속성 종류는 Mutable(가변성 / State) 이고, 시간의 흐름·사용자 상호작용·물리 엔진 알고리즘 등에 의해 끊임없이 동적으로 변화하는 가변 속성입니다. 현재 프레임에서의 상태값을 나타내며 실시간 트랜잭션의 결과물로 누적됩니다. 예시는 장비 감가상각 비용, 현재 유량, 소모성 잔여 비용, 생산 효율성 스코어입니다. 판별 질문: "이 값이 바뀌어도 같은 물건인가?" 유량이 바뀌어도 같은 기계이므로 price 입니다.
10.2 왜 하필 value 와 price 인가 ⚠️
| 잘못된 직관 | 실제 뜻 |
|---|---|
price = 가격, 돈 | ❌ 돈이 아닙니다. "시장에서 매 순간 새로 매겨지는 것" 이라는 비유입니다 |
value = 값어치, 가치 | ❌ 가치가 아닙니다. "고정되어 있는 고유 값" 이라는 뜻입니다 |
영어 감각으로 보면 이해가 됩니다. value 는 the amount something is worth, fixed and intrinsic — 물건에 새겨진 값이고, price 는 what it costs right now — 지금 이 순간 매겨진 값이라 내일이면 다릅니다. 즉 두 단어의 진짜 대비는 돈이냐 아니냐가 아니라 새겨진 것이냐 매겨지는 것이냐입니다.
💡 헷갈리면
value= 각인값,price= 호가값 으로 부르세요. 코드에서 이 이름을 그대로 쓰지 않아도 됩니다. 중요한 것은 두 종류를 섞지 않는다는 원칙입니다.
10.3 5주차의 정적 ↔ 동적과 같은 축입니다
| 영역 | 정적 (STATIC) | 전이 | 동적 (DYNAMIC) |
|---|---|---|---|
| 웹 (HTML/JS) | Attribute — <input value="홍길동"> HTML 문서에 박힌 초기 설정값 | LOAD → | Property — 입력창의 현재 값, DOM 객체의 실시간 상태 |
| OOP (Python) | Class 변수 — 설계도에 고정, 모든 인스턴스 공유 | INSTANCE → | Instance 변수 — 개별 객체가 독립적으로 갖는 값 |
| 메모리/데이터 | 🔒 상수(Constant) — const PI = 3.14 | RUN-TIME → | 변수(Variable) — x = 1 → 2 → 10 |
| 시뮬레이션 (이번 주) | value — 일련번호·규격·질량 | TICK → | price — 유량·감가상각·효율 스코어 |
⭐ 핵심 요약: 제시된 모든 쌍은 "처음 정해진 규칙·기록을 고수하느냐(정적)" 와 "프로그램이 실행되거나 사용자가 개입하면서 실시간 상태를 반영하느냐(동적)" 의 차이로 완벽하게 관통됩니다.
10.4 상태(State)와 환경(Environment)
- 상태(State) — 변화하는 모든 가변 변수
price들은 개별 인스턴스의 구체적인 상태(State) 를 이룹니다. - 환경(Environment) — 이러한 개별 상태값들의 동적인 총합(Accumulated Price States) 은 시뮬레이션 플랫폼 전체의 거시적 환경(Environment) 을 결정하는 핵심 컨텍스트가 됩니다.
price 하나 → 변수
한 객체의 price 전부 → 그 객체의 상태 (State)
모든 객체의 상태 전부 → 시스템의 환경 (Environment)
계층이 하나씩 올라갈 때마다 이름이 바뀝니다. 그런데 실체는 같은 숫자들이고 보는 배율만 다릅니다. 3주차에서 배운 프랙탈이 여기서도 나타납니다.
10.5 피드백 루프 ⭐
- 상태가 변함에 따라 시뮬레이션 공간의 환경(예: 공장의 전체 원가 변동, 최적화 효율 구간 변화)이 동적으로 재구성됩니다.
- 이러한 환경적 피드백은 다시 하위 객체들의 변수 연산에 영향을 주는 피드백 루프(Feedback Loop) 를 형성합니다.
┌──────────────────────────────────────────────┐
│ │
▼ │
[개별 객체의 price 변화] │
│ │
▼ │
[객체의 State] │
│ │
▼ │
[전체 Environment 재구성] ── 환경적 피드백 ────────┘
(원가 변동, 효율 구간 변화)
단순 계산과 시뮬레이션을 가르는 것이 바로 이 루프입니다. 루프가 없으면 입력을 넣으면 출력이 나오는 함수일 뿐이고, 루프가 있으면 시간이 흐르면서 스스로 상태가 달라지는 시스템이 됩니다.
⭐ 10주차에서 배운 정책 기반(Policy-based) 접근 — "무엇을 잘한 것으로 볼 것인가"만 정의하고 최적 행동을 스스로 찾는 방식 — 이 성립하려면 반드시 이 피드백 루프가 있어야 합니다. 강화학습의 환경(Environment) 이 바로 이
Environment입니다.
10.6 설계 원칙 세 줄
| 원칙 | 이유 |
|---|---|
value 와 price 를 같은 자리에 섞지 않는다 | 섞이면 "왜 이 값이 바뀌었지"를 추적할 수 없습니다 |
value 는 생성 시점에 확정하고 이후 쓰기를 막는다 | 불변을 코드로 강제해야 진짜 불변입니다 |
price 는 누가 언제 바꿨는지 기록한다 | 상태 변화가 곧 시스템의 역사입니다 |
11. 크롤링과 스크래핑 — 데이터를 어디서 가져오는가
11.1 두 단어는 다른 일을 합니다
파인튜닝을 하려면 데이터가 있어야 하고, 없으면 웹에서 모아야 합니다. 그때 쓰는 두 단어가 크롤링과 스크래핑인데, 자주 섞어 쓰지만 완전히 다른 일입니다.
- 크롤링(Crawling) — [웹 데이터 수집 방식]인데, [Seed URL에서 시작해 링크를 타고 무한 반복하며 전체 지도를 그린다]는 점에서 스크래핑과 구별됩니다. 웹 스파이더링(Web Spidering) 이라고도 하며, 검색엔진 로봇이 하는 일입니다.
- 스크래핑(Scraping) — [웹 데이터 수집 방식]인데, [목표 페이지의 HTML 구조를 분석해 필요한 데이터만 추출(Parsing)한다]는 점에서 크롤링과 구별됩니다.
| 크롤링 (Crawling) | 스크래핑 (Scraping) | |
|---|---|---|
| 목적 | 어디에 무엇이 있는지 지도를 그린다 | 그 페이지에서 원하는 값을 꺼낸다 |
| 출발점 | Seed URL | 목표 페이지 URL |
| 동작 | 링크를 따라 무한 반복 순회 | 한 페이지의 HTML 구조 파싱 |
| 결과물 | URL 목록, 사이트 구조 | 정형화된 데이터 (제목·순위·가격) |
| 별명 | 웹 스파이더링 | 파싱(Parsing) |
| 대표 사용자 | 검색엔진 로봇 | 가격 비교, 데이터 수집 스크립트 |
| 앞 장 연결 | 3주차의 역색인을 만들기 위한 전 단계 | 1장의 시맨틱 태그가 있으면 훨씬 쉬워짐 |
⭐ 둘은 보통 짝으로 씁니다. 크롤러가 URL 목록을 만들고, 스크래퍼가 그 URL들을 하나씩 방문해 값을 꺼냅니다. 그래서 이번 주에 만든 파일도 두 개입니다 (13.11절).
💡 1장이 여기서 회수됩니다. 대상 사이트가 시맨틱 태그로 잘 짜여 있으면 스크래핑이 훨씬 쉽습니다.
<article>안의<h2>를 뽑으면 되기 때문입니다.<div class="a3f">만 잔뜩이면 규칙을 추측해야 합니다. 시맨틱 태그는 남을 위한 배려가 아니라 기계 전체를 위한 인프라라는 것이 여기서 실감됩니다.
11.2 데이터를 모을 때 반드시 확인할 것 ⚠️
AI 변호사 에이전트를 QLoRA로 만든다고 할 때, 데이터 수집처는 두 곳입니다.
| 수집처 | 무엇을 얻는가 | 주의 |
|---|---|---|
| 국가법령정보센터 API | 성문법의 정형 데이터 (법령 조문) | 공식 API 제공. API 이용 조건 확인 |
| 대한민국 법원 종합법률정보 | 판례·판결문 | ⚠️ 차단 정책과 저작권 가이드라인을 반드시 확인 |
⚠️ 이 경고를 가볍게 넘기지 마세요.
- 차단 정책 —
robots.txt와 이용약관을 확인합니다. 과도한 요청은 IP 차단으로 이어지므로 요청 간격(delay)을 반드시 둡니다.- 저작권 — 데이터를 "얻을 수 있다"와 "학습에 써도 된다"는 완전히 다른 문제입니다. 특히 판결문에는 개인정보가 포함될 수 있습니다.
- 3주차에서 배운 개인정보 보호 종합대책(유출 시 CEO 책임 법제화)이 여기에도 적용됩니다. 학습 데이터에 개인정보가 들어가면 가중치에 녹아들어 빼낼 수 없습니다. 3.2절에서 말한 "RAG는 삭제 가능한 지식"의 반대편입니다.
그리고 4.8절에서 본 대로, 이 프로젝트에는 도메인 적응 학습용 데이터(법령·판결문 원문)와 인스트럭션 튜닝용 데이터(질문–답변 쌍)가 모두 필요합니다.
11.3 수집 대상은 Redis에서, 결과는 jsonl로
[크롤링 화면] ──(URL·키워드 입력)──▶ [Redis]
│
┌───────────────┴───────────────┐
▼ ▼
[크롤러 인터렉터] [스크래퍼 인터렉터]
zerg_zergling_crawler_interactor zerg_hydralisk_scraper_interactor
│ │
└───────────────┬───────────────┘
▼
resources/crawled/*.jsonl
- 대상 웹사이트와 키워드는 Redis에서 가져와 각각 실행합니다.
- 크롤링 화면에서 입력한 URL·키워드는 Redis에 저장합니다.
- 결과는
resources/crawled밑에jsonl파일로 저장합니다.
왜 Redis인가 — 수집 대상은 자주 바뀌고, 여러 워커가 함께 봐야 하고, 영구 보관할 필요는 없는 값입니다. 10주차에서 배운 대로 Redis는 캐시·세션·큐에 쓰는 키-값 저장소라 이 성격에 정확히 맞습니다.
왜 jsonl인가 — jsonl(JSON Lines)은 한 줄에 JSON 객체 하나씩 적는 형식입니다.
{"rank": 1, "title": "...", "artist": "...", "crawled_at": "2026-07-16T09:42:00"}
{"rank": 2, "title": "...", "artist": "...", "crawled_at": "2026-07-16T09:42:00"}
| jsonl의 성질 | 왜 수집 데이터에 적합한가 |
|---|---|
| 한 줄 = 한 레코드 | 수집 도중 끊겨도 거기까지는 유효한 파일입니다 |
| 이어쓰기(append)가 자연스러움 | 크롤링은 계속 늘어나는 작업입니다 |
| 한 줄씩 읽을 수 있음 | 파일이 커도 메모리에 다 올리지 않아도 됩니다 |
| 학습 데이터 표준 형식 | 파인튜닝 라이브러리들이 대부분 jsonl을 받습니다 |
⭐ 9장의 축 5로 읽으면
resources/crawled/*.jsonl이set(Disk) 입니다.pd.read_json(..., lines=True)로 읽으면frame(Memory) 이 되고, 학습 파이프라인에 들어가면base(Runtime) 가 됩니다.
실습 대상 URL (구조가 규칙적이라 연습에 적합합니다) — 벅스 차트 🔗 https://music.bugs.co.kr/chart / 멜론 차트 🔗 https://www.melon.com/chart/index.htm
12. 이번 주의 아키텍처 — 두 대의 서버와 스타 토폴로지
12.1 서버 이름을 정리합니다
컴퓨터가 두 대가 되는 순간 이름부터 정해야 합니다. 이름이 흔들리면 모든 대화가 흔들립니다.
| 장비 | 이 프로젝트에서의 이름 | 역할 |
|---|---|---|
| 데스크탑 | remote-server | 배포·운영용. 백엔드만 구동 |
| 랩탑 | local-server | 개발용. 프론트·백엔드 모두 |
⭐
remote-server는 직접 접근하지 않고, 반드시local-server를 통해 접근합니다.
접근 경로를 하나로 고정하면 ① 작업 흐름이 하나로 고정되어 "어디서 고쳤더라"가 없어지고, ② 원격 서버가 개발 도구로 오염되지 않으며, ③ 동기화 규칙이 단순해집니다(항상 local → GitHub → remote 한 방향). 이것 역시 하네스입니다. 여러 갈래로 갈 수 있는 것을 정해진 통로 하나로 좁히는 것입니다.
브랜치 규칙은 원격 SSH(데스크탑)가 desktop, 로컬 WSL(랩탑)이 laptop 입니다. 9주차에서 배운 GitHub의 main-branch 구조 = 스타 토폴로지 가 그대로 적용됩니다. main 이 허브이고 desktop·laptop 이 스포크이며, 스포크끼리 직접 주고받지 않고 반드시 허브를 거칩니다.
12.2 왜 원격 서버에서 직접 코딩하지 않는가
실제 이유는 아주 현실적입니다. 리모트 서버 VSCode에서는 AI 코딩 도구에 이미지 복붙이 안 됩니다. 그래서 로컬 서버 VSCode 개발 방식으로 전환했습니다. 그리고 local-server 의 docker-compose.yaml 에서 프론트엔드 항목을 제거합니다. remote-server는 백엔드만 구현하기 때문입니다.
💡 이런 종류의 제약은 문서에 잘 안 적혀 있습니다. 그런데 실무에서는 이런 것이 아키텍처를 바꿉니다. "이론적으로 더 좋은 구조"보다 "내가 매일 쓸 수 있는 구조" 가 이깁니다.
12.3 스타크래프트 종족 메타포 — 온톨로지 클래스 배정
9주차에서 만든 온톨로지 허브(apps/star_craft)에 역할 이름을 붙입니다. 비유가 예쁘지만 중요한 것은 비유가 아니라 데이터 타입별 분류입니다.
| 종족 | 담당 | 왜 그 종족인가 | 온톨로지 클래스 | 다루는 데이터 |
|---|---|---|---|---|
| 저그(Zerg) | 비전 처리 (CNN) | 오버로드·점막처럼 맵 전체를 감시함 | Has_Eye | 이미지 / 동영상 |
| 프로토스(Protoss) | LLM 자동 보고서 | 칼라 네트워크에 방대한 지식이 압축되어 있음 | Access_Knowledge | 텍스트 / 문서 |
| 테란(Terran) | 시계열 추론 | 기계·시간 축으로 관리·추적 | Track_Timeline | 로그 / 시간별 숫자 |
⭐ 이 표에서 외울 것은 종족 이름이 아니라 오른쪽 두 칸입니다. 데이터 타입(이미지 / 텍스트 / 시계열)에 따라 AI 기술 계열이 갈린다는 것이 본질입니다. 9주차에서 배운 온톨로지 — "무엇이 무엇과 어떤 의미로 이어지는가" — 를 실제 클래스 이름으로 옮긴 것입니다.
11장에서 만든 파일 이름을 다시 보세요. zerg_zergling_crawler_interactor.py 의 저그링은 값싸고 많이 뽑는 유닛이라 넓게 퍼져 지도를 그리는 크롤러이고, zerg_hydralisk_scraper_interactor.py 의 히드라리스크는 사거리 공격 유닛이라 목표를 정해 값을 뽑아오는 스크래퍼입니다. 이름이 곧 역할 설명이 됩니다. 이것도 1장의 시맨틱입니다.
12.4 샘플 프로젝트 폴더 매핑
| 폴더명 | 실제 도메인 |
|---|---|
money_ball | 축구 (콘텐츠) |
sherlock | 커뮤니티 |
silicon_valley | 어드민 / AI 에이전트 |
star_craft | 스타 토폴로지 온톨로지 (허브) |
titanic | 타이타닉 (데이터 분석 실습) |
💡 이런 코드네임은 취향이 아니라 실용입니다. 도메인 이름을 직접 쓰면 사업 방향이 바뀔 때 폴더를 다 바꿔야 하지만, 코드네임을 쓰면 안이 바뀌어도 이름은 유지됩니다. 다만 어디에 무엇이 있는지 표를 반드시 문서에 남겨야 합니다. 안 남기면 신규 인원이 아무것도 못 찾습니다.
12.5 소버린 챗봇과 모델 배치
이번 주의 목표 기능을 한 문장으로 쓰면 "프론트 채팅창에서, DB에 추가된 축구 데이터만 다루는 소버린(Sovereign) 챗봇" 입니다. "소버린"이 붙은 이유는 데이터가 밖으로 나가지 않는다는 뜻이기 때문입니다. 질문도, 검색된 문서도, 답변도 전부 우리 인프라 안에서 처리됩니다. 여기에 장비가 VRAM 6GB급 소비자 GPU라는 제약이 붙어 한국어가 가능한 작은 모델이어야 합니다. 하드웨어가 아키텍처를 결정합니다.
처음 설계는 스타 토폴로지 온톨로지 구조에 모델을 배치하는 것이었습니다.
[허브: EXAONE 7B] ← 종합 판단과 최종 응답
/ | \
[스포크: 2B] [스포크: 2B] [스포크: 2B] ← 각 도메인의 가벼운 처리
그런데 PoC 단계에서 이 구조를 일단 접었습니다.
⭐ PoC 단계에서는 EXAONE 7B를 삭제하고
Qwen2.5-1.5B-Instruct하나로 라우터·응답을 겸용합니다.
6GB VRAM에 7B를 올리면 다른 것을 할 여유가 없고, PoC의 목적은 "이 구조가 되는가"를 확인하는 것이지 성능을 내는 것이 아니기 때문입니다. POC = Proof of Concept(개념 증명) 입니다. 개념이 증명되면 그때 모델을 키우고, 4.5절의 어댑터 패턴 덕분에 나중에 베이스 모델만 갈아끼우면 됩니다.
13. 실습 — 이번 주에 손으로 한 것
13.1 우분투 홈에서 시작해 VSCode 열기
작업표시줄의 터미널로 로컬 PC의 우분투에 들어갑니다.
cd ~ # 홈 디렉터리로 이동 — 길을 잃었을 때 가장 먼저 치는 명령
ls # 폴더 구조 확인
cd ~/projects/local-server
ls # docker-compose.yaml 이 보이는지 확인
code . # 현재 폴더(.)를 VSCode로 연다
⚠️ 아무 폴더에서나 열면 안 됩니다.
docker-compose.yaml이 보이는 폴더에서 열어야 컨테이너 관련 확장 기능과 경로가 정상적으로 잡힙니다.
13.2 VSCode 좌측 하단에 WSL 이 표시되는지 확인 ⚠️
이 확인을 빠뜨리면 이후 모든 작업이 헛수고가 됩니다. VSCode 좌측 하단 모서리의 초록색 표시를 보세요.
| 표시 | 판정 | 뜻 |
|---|---|---|
WSL: Ubuntu | ⭕ 정상 | 우분투 안에서 코딩 중 |
SSH: ... | ❌ 틀림 | 원격 서버에 붙어 있음 |
| 아무 표시 없음 | ❌ 틀림 | 윈도우에서 코딩 중 |
⭐ 원칙: 윈도우에서 코딩하지 않고 우분투에서 코딩합니다. 배포 대상 환경이 리눅스이기 때문입니다. 윈도우에서 짠 코드는 경로 구분자, 줄바꿈 문자, 권한 체계가 전부 달라서 배포하는 순간 깨집니다.
13.3 원격 서버로 접속하기 — Connect to Host
좌측 하단 WSL 표시를 클릭하고 Connect to Host 를 선택합니다. 목록에 무엇이 뜨는지로 설정 품질을 판정할 수 있습니다.
| 목록에 뜨는 것 | 판정 |
|---|---|
remote-server의 Cloudflare 주소 (api.example.cloud) | ⭕ 최상 |
IP 주소 (192.168.x.x) | ⚠️ 차선 |
IP는 공유기를 재부팅하거나 회선이 바뀌면 접속 설정을 전부 고쳐야 하지만, Cloudflare 터널을 통한 도메인 주소는 IP가 바뀌어도 그대로 입니다.
⭐ 이것도 하네스입니다. 바뀌는 것(IP) 과 내 설정 사이에 안 바뀌는 이름(도메인) 을 끼워 넣은 것으로, 7.4절의 어댑터 레이어와 같은 발상입니다.
13.4 docker-compose에서 프론트엔드 항목 제거
# ~/projects/local-server/docker-compose.yaml
services:
# frontend: ← remote-server에서는 이 블록을 통째로 제거 (백엔드만 구동)
backend:
build: ./fastapi
ports: ["8000:8000"]
env_file: .env
db:
image: pgvector/pgvector:pg17
environment: { POSTGRES_USER: postgres, POSTGRES_PASSWORD: postgres, POSTGRES_DB: tailor }
ports: ["5432:5432"]
13.5 동기화 표준 프롬프트 ⭐
이번 주에 가장 자주 쓴 프롬프트입니다. 그대로 외워 두세요.
깃허브에 푸시하고 모든 브랜치를 동기화하고,
desktop 브랜치는 ssh 서버 pull 해서 다 동기화해줘
이 한 문장이 담고 있는 절차를 풀면 이렇습니다.
# 1) 로컬(laptop)의 변경을 커밋하고 푸시
git add -A
git commit -m "..."
git push origin laptop
# 2) 허브(main)로 합치고 모든 브랜치를 맞춤
git checkout main
git merge laptop
git push origin main
# 3) 원격 서버(desktop)에서 내려받아 동기화
ssh <remote-server>
cd ~/projects/...
git pull origin main
💡 익숙해지면 "동기화해줘"만으로도 됩니다. AI 코딩 도구가 프로젝트의 브랜치 구조를 이해하고 나면 축약된 지시로 충분해집니다. 다만 처음에는 반드시 전체 문장을 쓰세요. 그리고 이 절차를
CLAUDE.md에 적어두면 그것 자체가 하네스가 됩니다.
13.6 ERD를 pgvector에 배포하기 — alembic
머니볼(축구) 도메인의 ERD를 실제 테이블로 만듭니다. ① ERD에 따라 pgvector에 테이블을 생성하고, ② Ubuntu 24의 pgvector에 alembic으로 작업을 지시하며, ③ 프롬프트 자체를 .md 파일 형식으로 생성합니다.
alembic 은 파이썬 진영의 DB 마이그레이션 도구입니다. 3주차에서 배운 마이그레이션 = 데이터를 새 시스템으로 옮기는 것의 스키마 버전입니다.
# 마이그레이션 파일 생성 (모델과 실제 DB의 차이를 자동 감지)
alembic revision --autogenerate -m "create moneyball tables"
alembic upgrade head # 실제 DB에 적용
alembic downgrade -1 # 되돌리기
⭐ 왜 SQL을 직접 치지 않고 alembic을 쓰는가 — 스키마 변경에 역사와 되돌리기가 생기기 때문입니다.
CREATE TABLE을 손으로 치면 기록이 아무 데도 안 남아 팀원의 DB와 내 DB가 달라집니다. Git이 코드에 하는 일을 alembic이 DB 스키마에 합니다.
💡 "프롬프트 자체를 md 파일로 생성한다" 는 지시가 중요합니다. 채팅창에만 쓰면 사라지지만
.md파일로 남기면 다시 실행할 수 있고, 리뷰할 수 있고, 버전 관리가 됩니다. 프롬프트도 소스코드로 취급하는 것 — 이것이 하네스 엔지니어링의 태도입니다.
13.7 허깅페이스에서 모델 내려받기
pip install -U "huggingface_hub[cli]" # 허깅페이스 CLI 설치
hf download LGAI-EXAONE/EXAONE-3.5-7.8B-Instruct # 허브용 후보
hf download Qwen/Qwen2.5-1.5B-Instruct # 라우터/응답 겸용
내려받은 모델은 디스크에 저장되고(9장의 set), 실제로 쓸 때 VRAM으로 로드됩니다.
# 모델 가중치 — 절대 깃허브에 올라가면 안 됩니다
models/
*.safetensors
*.gguf
*.bin
.cache/huggingface/
⚠️ 모델 파일은 수 GB입니다. 실수로 커밋하면 푸시가 몇십 분씩 걸리거나 실패하고(GitHub 파일 크기 제한), 한 번 커밋되면 삭제해도 히스토리에 남으며, 팀원이 clone할 때마다 수 GB를 받게 됩니다.
13.8 pgAdmin 설치·실행
DB를 터미널이 아니라 화면으로 보고 싶을 때 씁니다.
| 로그인 | 값 | Register Server | 값 | |
|---|---|---|---|---|
admin@example.com | Host | host.docker.internal | ||
| Password | admin | Port | 5432 | |
| Username | postgres | |||
| Password | postgres | |||
| Database | tailor |
⭐
host.docker.internal이 핵심입니다. pgAdmin이 컨테이너 안에서 돌기 때문입니다. 컨테이너 안에서localhost는 그 컨테이너 자신을 가리키므로, 다른 컨테이너(또는 호스트)에 있는 DB를localhost로는 절대 못 찾습니다.host.docker.internal은 "나를 실행하고 있는 호스트 컴퓨터" 를 가리키는 도커의 특수 이름입니다. "DB 연결이 안 돼요"의 압도적 1위 원인이 이것입니다.
⚠️
admin/postgres같은 비밀번호는 로컬 개발 전용입니다. 배포 환경에서는 절대 쓰지 않습니다.
13.9 중복 컨테이너 정리
docker ps -a # 전체 목록 확인 (멈춘 것 포함)
docker rm -f <컨테이너이름_또는_ID> # 중복된 것 정리
docker images # 남은 이미지 확인
정리 대상이 된 것들은 <프로젝트>-backend(중복 생성분), redis:7-alpine, pgvector/pgvector:pg17, n8nio/n8n:latest, neo4j:5 였습니다. 그리고 같은 일이 반복되지 않도록 규칙을 하네스로 추가했습니다 → 7.8절의 fastapi/_docs/docker-rules.md.
⭐ 정리하고 끝내면 다음 주에 또 늘어납니다. 규칙으로 만들어야 끝납니다. 이것이 하네스 엔지니어링의 사고방식입니다. 문제를 고치지 말고, 문제가 생기는 구조를 고칩니다.
13.10 게이트웨이 3분기 구현
# apps/star_craft/domain/gateway/semantic_intent_router.py
from enum import Enum
class Intent(str, Enum):
RAG = "rag" # ① 우리 DB의 지식으로 답해야 함
CRUD = "crud" # ② 데이터를 조회·생성·수정·삭제해야 함
GEMINI = "gemini" # ③ 도메인 밖 범용 지식이 필요함
class SemanticIntentRouter:
"""시맨틱 인텐트 분류기 — 답을 만들지 않고 '어디로 보낼지'만 정한다."""
def classify(self, question: str) -> Intent:
# QLoRA로 튜닝한 Qwen2.5-1.5B-Instruct 로 분류
return Intent(self._model.predict(question))
# apps/star_craft/use_cases/gateway_interactor.py
class GatewayInteractor:
def handle(self, question: str) -> str:
intent = self.router.classify(question)
log.info("intent=%s question=%s", intent, question) # 8.7절 경로 로그
if intent is Intent.RAG:
return self.rag_interactor.run(question)
if intent is Intent.CRUD:
return self.crud_interactor.run(question)
return self.gemini_interactor.run(question) # ③
# apps/silicon_valley/use_cases/terran_vessel_gemini_interactor.py
from core.matrix.vault_keymaker_secret_manager import KeyMaker
class TerranVesselGeminiInteractor:
def run(self, prompt: str) -> str:
key = KeyMaker.get("GEMINI_API_KEY") # 8.4절 — .env 직접 접근 금지
return gemini_client(key).generate(prompt)
13.11 크롤러와 스크래퍼 인터렉터 만들기
# apps/star_craft/use_cases/zerg_zergling_crawler_interactor.py
class ZergZerglingCrawlerInteractor:
"""크롤러 — Seed URL에서 링크를 타고 지도를 그린다."""
def run(self) -> list[str]:
seed_urls = self.redis.lrange("crawl:seed_urls", 0, -1) # 대상은 Redis에서
found = []
for url in seed_urls:
found.extend(self._spider(url, depth=2))
return found
# apps/star_craft/use_cases/zerg_hydralisk_scraper_interactor.py
import json, pathlib
class ZergHydraliskScraperInteractor:
"""스크래퍼 — 목표 페이지의 HTML 구조를 파싱해 필요한 값만 뽑는다."""
def run(self, url: str) -> pathlib.Path:
keywords = self.redis.lrange("crawl:keywords", 0, -1) # 키워드도 Redis에서
rows = self._parse(url, keywords)
out = pathlib.Path("resources/crawled") / f"{self._stamp()}.jsonl"
out.parent.mkdir(parents=True, exist_ok=True)
with out.open("a", encoding="utf-8") as f: # append — 이어쓰기
for row in rows:
f.write(json.dumps(row, ensure_ascii=False) + "\n")
return out
⚠️ 실행 전에 반드시 확인할 것 (11.2절): 요청 간격(REQUEST_DELAY = 1.0 초)을 두고, User-Agent 로 신원을 밝히고, robots.txt 와 이용약관을 먼저 확인합니다.
13.12 QLoRA 학습 전 점검
nvidia-smi # 1) GPU가 잡히는가
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"
nvidia-smi --query-gpu=memory.used,memory.total --format=csv # 3) VRAM 여유 확인
# 4) 필요 패키지
pip install -U transformers peft bitsandbytes accelerate datasets trl
| 패키지 | 역할 |
|---|---|
transformers | 모델·토크나이저를 불러오는 기본 라이브러리 |
peft | LoRA·QLoRA 어댑터를 붙이는 라이브러리 |
bitsandbytes | 4비트 양자화(NF4) 를 담당 |
accelerate | 디바이스 배치·분산 학습 보조 |
datasets | 학습 데이터셋(jsonl 등) 로딩 |
trl | 인스트럭션 튜닝용 학습 루프 |
⭐ 이름을 보면 4장에서 배운 개념과 라이브러리가 일대일로 대응합니다.
peft= PEFT,bitsandbytes= 양자화. 개념을 알면 설치 목록이 저절로 읽힙니다. 반대로 개념 없이 명령어만 복사하면, 하나만 빠져도 무엇이 없어서 실패한 것인지 모릅니다.
13.13 RAG 채팅창을 Next.js에 배치
RAG 채팅 UI는 Next.js 쪽에 만들고, 백엔드(FastAPI)는 API만 제공합니다. UI 레퍼런스로 참고한 곳은 🔗 http://lapa.ninja 와 🔗 https://dribbble.com/search/ai-chat-ui 입니다.
💡 채팅 UI는 직접 디자인하기보다 검증된 패턴을 가져오는 편이 낫습니다. 말풍선 정렬, 스트리밍 표시, 로딩 상태, 근거 출처 표시 — 이미 수천 번 다듬어진 형태가 있습니다.
14. 이 주의 읽을거리와 그 맥락
14.1 AWS Personalize 요금
🔗 https://aws.amazon.com/ko/personalize/pricing/
이게 왜 이 주의 자료인가: 이번 주 내내 "우리가 직접 모델을 튜닝한다"는 이야기를 했는데, 추천 기능은 AWS의 관리형 서비스로 사 버릴 수도 있습니다. 요금표를 보는 것은 계산기를 두드리기 위해서가 아니라 "직접 만들 것인가, 사서 쓸 것인가" 라는 질문을 늘 옆에 두기 위해서입니다. 10주차 축 5(누가 관리하는가: EC2 ↔ RDS ↔ S3)가 여기서 AI 기능 자체로 확장됩니다.
| 사는 편이 낫다 | 만드는 편이 낫다 |
|---|---|
| 우리 도메인이 특별하지 않다 | 우리 도메인 지식이 경쟁력이다 |
| 데이터를 밖에 둬도 된다 | 데이터가 밖으로 나가면 안 된다 (소버린) |
| 트래픽이 예측 가능하고 적다 | 호출량이 많아 종량 과금이 부담된다 |
14.2 UI 레퍼런스 — lapa.ninja / Dribbble
🔗 http://lapa.ninja 🔗 https://dribbble.com/search/ai-chat-ui
이게 왜 이 주의 자료인가: 이번 주에 RAG 채팅창을 Next.js에 배치했기 때문입니다(13.13절). 백엔드를 아무리 잘 만들어도 사용자는 채팅창만 봅니다. RAG의 품질은 근거 출처를 어떻게 보여주는가에서 체감됩니다.
14.3 벅스 · 멜론 차트
🔗 https://music.bugs.co.kr/chart 🔗 https://www.melon.com/chart/index.htm
이게 왜 이 주의 자료인가: 크롤링·스크래핑 실습 대상입니다(11.3절). 차트 페이지는 순위·제목·아티스트라는 구조가 규칙적으로 반복되어 파싱 연습에 적합하고, 1장에서 배운 시맨틱 태그가 실제 사이트에서 얼마나 지켜지는지 를 직접 확인할 수 있습니다.
14.4 법률 데이터 수집처
국가법령정보센터 API(성문법 정형 데이터)와 대한민국 법원 종합법률정보(판례·판결문, ⚠️ 차단 정책·저작권 가이드라인 필수 확인)입니다.
이게 왜 이 주의 자료인가: 4장의 QLoRA를 무엇으로 학습시킬 것인가의 실제 답이고, 11.2절에서 본 대로 "얻을 수 있다"와 "써도 된다"가 다르다는 것을 배우는 사례이기도 합니다. AI 개발에서 데이터 확보는 기술 문제이기 전에 법적·윤리적 문제입니다.
14.5 sLLM 개발 사례 영상
이게 왜 이 주의 자료인가: 이번 주 금요일(7/17)의 마무리 자료입니다. 월요일부터 목요일까지 개념(시맨틱·RAG·PEFT·QLoRA·하네스)과 손(서버 정리·모델 다운로드·게이트웨이·크롤러)을 오갔습니다. 마지막 날에 실제로 sLLM을 만들어 쓰는 사례를 보는 것은, 흩어진 조각들이 하나의 제품으로 어떻게 합쳐지는지를 확인하는 과정입니다.
💡 사례를 볼 때의 요령: "이 사람들이 RAG를 썼는가, 파인튜닝을 썼는가, 둘 다인가" 만 보세요. 그리고 왜 그 선택을 했는지 근거를 찾으세요. 6.2절의 결정표 다섯 질문에 대입해 보면 대부분 설명됩니다.
14.6 이번 주에 나온 약어 하나 — POC
POC = Proof of Concept (개념 증명) — "이 아이디어가 실제로 동작하는가"를 최소 규모로 확인하는 단계입니다.
| PoC | MVP | 프로덕션 | |
|---|---|---|---|
| 목적 | 되는지 확인 | 최소 기능으로 사용자 검증 | 실서비스 |
| 사용자 | 우리 팀 | 초기 사용자 | 전체 |
| 품질 기준 | 동작하면 됨 | 핵심 흐름은 완성 | 안정성·보안·성능 |
| 이번 주의 사례 | Qwen 1.5B 하나로 라우터·응답 겸용 | — | — |
⚠️ PoC와 MVP를 섞으면 프로젝트가 늘어집니다. PoC에서 예쁜 UI를 만들거나 성능을 튜닝하기 시작하면 끝이 없습니다. PoC의 성공 기준은 "질문이 세 갈래로 정확히 갈리는가" 하나입니다.
부록 A. 용어 사전
| 용어 | 영문 | 상위 범주 | 정의 |
|---|---|---|---|
| 시맨틱 | Semantic | 설계 사상 | 형태가 아니라 의미를 기계가 읽을 수 있게 만드는 것 |
| 시맨틱 태그 | Semantic Tag | HTML 태그 | 태그 이름 자체가 그 영역의 역할을 선언하는 태그 |
| 시맨틱 인텐트 | Semantic Intent | 입력 해석 방식 | 단어 일치가 아니라 문장 맥락 속 목적을 판단하는 것 |
| 시맨틱 웹 | Semantic Web | 웹 설계 사상 | 문서를 기계도 뜻으로 읽을 수 있게 구조화한 웹 |
| 웹 접근성 | Web Accessibility | 품질 요건 | 스크린 리더 등 보조 기술로도 쓸 수 있게 만드는 것 |
| SEO | Search Engine Optimization | 전략 | 검색엔진이 문서를 잘 이해하도록 만드는 최적화 |
| RAG | Retrieval-Augmented Generation | 지식 공급 방식 | 외부 저장소에서 찾아온 문서를 프롬프트에 붙여 답을 만드는 것 |
| 환각 | Hallucination | 현상 | 모델이 근거 없이 그럴듯한 거짓을 만들어내는 것 |
| 청킹 | Chunking | 전처리 | 문서를 검색 단위 조각으로 자르는 것 |
| 파인튜닝 | Fine-Tuning | 학습 방식 | 사전 학습 모델에 특화 데이터를 추가 학습시키는 것 |
| PEFT | Parameter-Efficient Fine-Tuning | 파인튜닝 | 원본은 동결하고 1~2%의 보조망만 학습하는 방식 |
| LoRA | Low-Rank Adaptation | PEFT 기법 | 보조 신경망을 얹어 그것만 학습하는 대표 기법 |
| QLoRA | Quantized LoRA | PEFT 기법 | 4비트(NF4) 양자화 후 LoRA를 얹는 초경량 기법 |
| 양자화 | Quantization | 압축 기법 | 가중치의 정밀도(소수점 자릿수)를 낮춰 메모리를 줄이는 것 |
| NF4 | NormalFloat 4-bit | 데이터 형식 | QLoRA에서 쓰는 4비트 표현 형식 |
| 어댑터(모델) | Adapter | 산출물 | 파인튜닝 결과로 나오는 작은 보조 가중치 파일 |
| 가중치 | Weights | 모델 구성요소 | 뉴런 사이 시냅스 결합 강도. 기억의 실체 |
| 파국적 망각 | Catastrophic Forgetting | 현상 | 새로 학습하다 기존 능력을 잃는 것 |
| 도메인 적응 학습 | Domain Adaptation | 학습 유형 | 그 분야의 말과 사고방식을 익히게 하는 학습 |
| 인스트럭션 튜닝 | Instruction Tuning | 학습 유형 | 지시를 받고 답하는 형식을 익히게 하는 학습 |
| sLLM | small LLM | 모델 분류 | 단일 GPU로 구동 가능한 소형 언어 모델 |
| 하네스 | Harness | 아키텍처 | 자유롭게 날뛸 수 있는 것을 정해진 통로로만 흐르게 하는 틀 |
| 가드레일 | Guardrail | 아키텍처 | 넘어가면 안 되는 선을 코드·규칙으로 정한 것 |
| 하네스 가드레일 | Harness Guardrail | 아키텍처 | 외부 변동성과 내부 로직 사이의 안전한 완충 지대 |
| 어댑터 레이어 | Adapter Layer | 하네스 구성요소 | 외부 규격을 내부 표준으로 변환하는 중계 층 |
| 평가 루프 | Evaluation Loop | 하네스 구성요소 | 샌드박스에서 출력의 품질·안전을 사전 검사하는 루프 |
| 프롬프트 인젝션 | Prompt Injection | 공격 | 입력에 지시를 심어 시스템 지시를 무력화하려는 시도 |
| Failsafe | — | 설계 원칙 | 고장 나도 안전한 방향으로 고장 나게 설계 |
| Fallback | — | 설계 원칙 | 주 경로가 막히면 대체 경로로 우회 |
| 게이트웨이 | Gateway | 구성요소 | 요청이 가장 먼저 도착해 어디로 보낼지 정하는 문 |
| 키메이커 | KeyMaker | 구성요소 | 비밀 키 접근을 한 곳으로 모은 시크릿 매니저 |
| set | — | 데이터 단계 | 디스크의 정적 데이터 세트 |
| frame | — | 데이터 단계 | 메모리에 구조화되어 올라온 DataFrame |
| base | — | 데이터 단계 | 런타임에서 동작하는 기준 동작 객체 |
| value | — | 변수 속성 | Immutable 고유 상수 (일련번호·규격·질량) |
| price | — | 변수 속성 | Mutable 가변 상태 (감가상각·유량·효율) |
| 상태 | State | 개념 | 한 인스턴스의 price 값들의 집합 |
| 환경 | Environment | 개념 | 모든 인스턴스 상태의 동적인 총합 |
| 피드백 루프 | Feedback Loop | 구조 | 환경이 다시 하위 객체 연산에 영향을 주는 순환 |
| 크롤링 | Crawling | 수집 방식 | Seed URL에서 링크를 타고 전체 지도를 그리는 것 |
| 스크래핑 | Scraping | 수집 방식 | 목표 페이지 HTML을 파싱해 필요한 값만 뽑는 것 |
| jsonl | JSON Lines | 파일 형식 | 한 줄에 JSON 객체 하나씩 적는 형식 |
| Redis | — | 저장소 | 인메모리 키-값 저장소. 캐시·세션·큐용 |
| alembic | — | 도구 | 파이썬 진영의 DB 스키마 마이그레이션 도구 |
| pgvector | — | DB 확장 | PostgreSQL에 벡터 검색을 더한 확장 |
| pgAdmin | — | 도구 | PostgreSQL을 화면으로 관리하는 GUI |
host.docker.internal | — | 도커 특수 이름 | 컨테이너 안에서 호스트 컴퓨터를 가리키는 주소 |
| PoC | Proof of Concept | 개발 단계 | 아이디어가 동작하는지만 확인하는 최소 구현 |
| 소버린 AI | Sovereign AI | 운영 형태 | 데이터가 조직 밖으로 나가지 않는 AI 구성 |
| 허브 / 스포크 | Hub / Spoke | 토폴로지 | 스타 토폴로지의 중심(라우팅) / 연결된 각 지점 |
| 온톨로지 클래스 | Ontology Class | 온톨로지 | 개념을 분류하는 범주 (Has_Eye 등) |
부록 B. 스스로 확인하기
먼저 답해보고, 막히면 옆의 절을 펼치세요.
| # | 질문 | 답이 있는 곳 |
|---|---|---|
| 1 | 시맨틱 태그와 시맨틱 인텐트는 무엇이 같은가? | 1.1, 1.8 |
| 2 | <div class="header"> 는 왜 시맨틱이 아닌가? | 1.6 |
| 3 | "RAG는 기억이 아니라 ○○○○이다" — 빈칸은? | 2.4 |
| 4 | RAG 세 단계의 영어 이름과 각 단계가 하는 일은? | 3.1 |
| 5 | RAG 답이 이상할 때 모델보다 먼저 봐야 할 것은? | 3.3 |
| 6 | PEFT가 학습하는 파라미터는 전체의 몇 %인가? | 4.2 |
| 7 | 6GB VRAM 환경의 원픽 모델과 그 이유 세 가지는? | 4.6 |
| 8 | 가중치를 한 문장으로 정의하면? | 5.1 |
| 9 | Full FT / PEFT / QLoRA / RAG 는 가중치를 각각 어떻게 다루는가? | 5.2 |
| 10 | "우리 회사 규정을 알게 하고 싶다"는 무엇으로 푸는가? | 6.1 |
| 11 | 게이트웨이 라우터를 왜 큰 모델 API가 아니라 작은 로컬 모델로 만드는가? | 8.3 |
| 12 | 카파시의 하네스 원칙 한 문장은? | 7.1 |
| 13 | 하네스 가드레일의 두 기둥은? | 7.4, 7.5 |
| 14 | Failsafe와 Fallback의 차이는? | 7.6 |
| 15 | 모델이 RAG를 건너뛰고 환각할 때 하네스는 무엇을 하는가? | 7.7 |
| 16 | 인터렉터가 .env 에 직접 접근하면 안 되는 이유 셋은? | 8.4 |
| 17 | set / frame / base 는 각각 어디에 있는가? | 9.2~9.6 |
| 18 | Base 는 왜 "기초"가 아닌가? | 9.5 |
| 19 | value 와 price 를 가르는 판별 질문은? | 10.1 |
| 20 | 상태(State)와 환경(Environment)의 관계는? | 10.4 |
| 21 | 크롤링과 스크래핑의 결과물은 각각 무엇인가? | 11.1 |
| 22 | 수집 결과를 왜 jsonl 로 저장하는가? (네 가지) | 11.3 |
| 23 | PoC의 성공 기준은 무엇이어야 하는가? | 14.6 |
부록 C. 자주 하는 실수
| 실수 | 바로잡기 |
|---|---|
| "시맨틱 태그를 쓰면 페이지가 빨라진다" | ❌ 렌더링 결과·속도는 거의 같습니다. 바뀌는 것은 기계가 읽어낼 정보의 양 |
"class="header" 를 썼으니 시맨틱이다" | ❌ class 는 내가 지은 별명입니다. 규격이 정한 이름이어야 기계가 압니다 |
| "RAG는 AI가 인터넷을 검색하는 기능" | ❌ 내가 미리 넣어둔 저장소만 뒤집니다. 없는 건 영영 못 찾습니다 |
| "RAG로 알려준 건 다음에도 기억한다" | ❌ RAG는 기억이 아니라 컨텍스트입니다. 요청이 끝나면 사라집니다 |
| "RAG를 쓰면 환각이 없어진다" | ⚠️ 줄어드는 것입니다. 그래서 출처를 붙여 검증 가능하게 만듭니다 |
| "정확한 사실을 외우게 하려고 파인튜닝한다" | ❌ 파인튜닝은 사실 암기에 서툽니다. 사실은 RAG입니다 |
| "파인튜닝하면 모델이 똑똑해진다" | ⚠️ 바뀌는 것은 주로 말하는 방식·형식입니다. 추론 능력 자체가 오르지 않습니다 |
| "RAG 답이 이상하니 모델을 바꾸자" | ❌ 먼저 검색된 청크를 눈으로 봅니다. 근거가 틀렸으면 검색 문제입니다 |
| "QLoRA는 성능이 나쁜 저가형 기법이다" | ⚠️ 정밀도를 낮추는 대신 개인 GPU 한 대로 가능하게 만든 기법입니다 |
| "작은 모델로 시작하면 나중에 다시 만들어야 한다" | ❌ 어댑터 패턴이라 베이스 모델 이름만 갈아끼우면 됩니다 |
| "3B가 1.5B보다 항상 정확하다" | ⚠️ 분류(라우팅) 과제에서는 차이가 거의 없습니다. 과제 종류를 먼저 보세요 |
| "학습 데이터는 많을수록 좋다" | ❌ 적고 깨끗한 수백 건 > 지저분한 수만 건 |
| "모델 파일도 깃에 올려두면 편하다" | ❌ 수 GB입니다. 한 번 커밋되면 히스토리에 영구히 남습니다. .gitignore 필수 |
| "프롬프트를 더 강하게 쓰면 환각을 막을 수 있다" | ⚠️ 확률적 개선일 뿐입니다. 판정하고 강제하는 구조(하네스) 를 만드세요 |
| "Failsafe와 Fallback은 같은 말" | ❌ Failsafe는 안전하게 고장 나기, Fallback은 대체 경로로 우회 |
| "평가 루프는 LLM에게 채점시키면 된다" | ⚠️ 독립적이어야 합니다. 채점자가 응시자면 채점이 아닙니다 |
| "인텐트는 잘게 나눌수록 정확하다" | ❌ 많아지면 정확도가 떨어집니다. 처리 경로가 다른가로 나누세요 |
"모델 클라이언트든 라우터든 core 에 두면 된다" | ❌ 도메인 지식이 있으면 apps, 없으면 core 입니다 |
"frame 이면 다 올라온 것이니 base 는 같은 말" | ❌ 표로 올라와 있는 것과 돌아가며 상태가 변하는 것은 다릅니다 |
"Base 는 기초라는 뜻" | ❌ 미국식으로는 거점·저장소·기준점입니다 |
| "업로드/다운로드는 항상 인터넷 이야기" | ⚠️ 문서에 따라 디스크↔메모리에도 씁니다. 항상 "어디에서 어디로"를 말하세요 |
"price 는 가격이라는 뜻" | ❌ 지금 이 순간 매겨지는 값이라는 비유입니다. 돈이 아닙니다 |
| "value와 price를 한 딕셔너리에 같이 담자" | ⚠️ 섞이면 무엇이 왜 바뀌었는지 추적 불가가 됩니다 |
| "크롤링과 스크래핑은 같은 말" | ❌ 크롤링은 지도 그리기, 스크래핑은 값 뽑기입니다 |
| "공개된 페이지니까 마음대로 수집해도 된다" | ❌ 차단 정책·저작권·개인정보를 반드시 확인합니다 |
"pgAdmin에서 Host를 localhost 로" | ❌ 컨테이너 안의 localhost 는 자기 자신입니다. host.docker.internal |
| "VSCode 좌측 하단이 SSH여도 상관없다" | ❌ WSL: Ubuntu 여야 합니다. 윈도우/원격에서 코딩하면 배포 때 깨집니다 |
| "컨테이너는 중복돼도 지우면 되니까 괜찮다" | ⚠️ 정리만 하면 또 생깁니다. docker-rules.md 하네스로 규칙화 하세요 |
부록 D. 날짜별 색인
| 날짜 | 다룬 내용 | 이 장의 절 |
|---|---|---|
| 7/13 (월) | 로컬 우분투 홈 이동·구조 확인, 서버 명칭 정리(remote-server / local-server), code . 로 VSCode 열기, WSL 표시 확인, Connect to Host(Cloudflare 주소 우선), docker-compose 프론트 항목 제거, 이미지 복붙 제약으로 로컬 개발 전환, 동기화 표준 프롬프트, 스타크래프트 종족 메타포와 온톨로지 클래스, 샘플 프로젝트 폴더 매핑, 머니볼 ERD → pgvector + alembic | 12.1 |
| 7/14 (화) | AWS Personalize 요금, 소버린 챗봇 구상(VRAM 6GB 한국어 모델), 스타 토폴로지에 허브 7B·스포크 2B 배치, 허깅페이스 hf download, EXAONE 추론 통합 위치(silicon_valley 확장), 질문 경로 로그 작성, 허브는 apps/star_craft 고정, RAG 채팅창 Next.js 배치와 UI 레퍼런스, 모델 .gitignore 처리, pgAdmin 설치·서버 등록, POC 개념 | 8.6, 8.7, 12.5, 13.7, 13.8, 13.13, 14.1, 14.2, 14.6 |
| 7/15 (수) | ⭐ 시맨틱의 두 얼굴(태그·인텐트), ⭐ 인간과 AI의 학습 구조 대응(Search↔RAG / 요점정리↔PEFT), RAG 3단계와 장점, PEFT·LoRA·QLoRA, ⭐ 가중치와 기술별 취급 비교표, ⭐ 하네스 가드레일(카파시 원칙, 어댑터 레이어, TDD 평가 루프), 모델이 RAG를 건너뛰고 환각할 때의 하네스 설계, 모델을 apps/core 중 어디에 둘 것인가, QLoRA 전 확인사항, VRAM 6GB 원픽 Qwen2.5-1.5B-Instruct, 게이트웨이 3분기(RAG/CRUD/Gemini), 키메이커, 올라마 경로 문제 | 1, 2, 3, 4, 5, 6, 7, 8 |
| 7/16 (목) | ⭐ 데이터 생명주기 set·frame·base, ⭐ 변수 속성 value/price와 상태·환경·피드백 루프, AI 변호사 QLoRA용 데이터 수집처(법령정보센터·종합법률정보)와 저작권 주의, 도메인 적응 학습 + 인스트럭션 튜닝, 크롤링 vs 스크래핑, 중복 컨테이너 정리와 docker-rules.md 하네스, 크롤러·스크래퍼 인터렉터 생성(대상은 Redis), 결과는 resources/crawled 밑 jsonl, 실습 대상 URL(벅스·멜론) | 4.8, 7.8, 9, 10, 11, 13.9, 13.11, 14.3, 14.4 |
| 7/17 (금) | sLLM 개발 사례 영상 — 한 주 개념의 통합 확인 | 14.5 |
부록 E. Gemini / NotebookLM 프롬프트
복습 문제 받기
이 12주차 교재만 근거로 8문제를 내줘. 비전공자 기준으로.
"시맨틱의 두 얼굴", "RAG와 PEFT의 대응", "가중치 취급 비교표"는 반드시 포함해줘.
답은 바로 주지 말고, 내가 답한 다음에 채점하고
틀린 이유를 교재의 어느 절을 근거로 설명해줘. 교재에 없는 내용은 꺼내지 마.
인포그래픽 ① — 이 장의 중심 개념
「2.1 방법은 두 가지뿐입니다」와 「5.2 네 기술의 가중치 취급 비교표」를 합쳐
인포그래픽 1장으로 만들어줘.
- 위쪽: 인간(Search / 요점정리)과 AI(RAG / PEFT·QLoRA)를 좌우 두 기둥으로 대응
- 아래쪽: Full Fine-Tuning / PEFT(LoRA) / QLoRA / RAG 네 칸에 "가중치 밸브"를 아이콘으로
— 전부 조절 / 옆에 보조 밸브 / 압축 후 보조 밸브 / 0.1%도 안 건드림
- "밖에서 찾기 ↔ 안을 고치기" 축과 "전부 ↔ 일부" 축을 가로세로로 표시
- 전문용어는 한글(영문) 병기
인포그래픽 ② — 데이터 생명주기와 변수
「9.2 전체 흐름」과 「10. 변수의 두 얼굴」을 한 장으로 만들어줘.
- 왼쪽에서 오른쪽으로 set(Disk) → frame(Memory) → base(Runtime) 3칸
- set↔frame 사이 화살표는 양방향(업로드/다운로드)으로
- base 칸에서 가지를 쳐서 value(불변: 일련번호·규격·질량)와
price(가변: 감가상각·유량·효율 스코어)로 갈라지게
- price들이 모여 State, State들이 모여 Environment가 되고
Environment가 다시 아래로 화살표를 내려 피드백 루프를 그리게
- 3주차의 "디스크 ↔ 메모리" 두 칸이 세 칸으로 확장된 것임을 위쪽에 작게 표시
인포그래픽 ③ — 하네스 가드레일
「7.3 구조도」와 「8.1 게이트웨이가 놓이는 자리」를 합쳐 세로 흐름도로 만들어줘.
- 맨 위: 외부 환경 / AI 모델 (예측 불가능)
- 가운데: 하네스 가드레일 박스 안에 ①표준화된 어댑터 레이어 ②TDD 평가 루프 두 칸
- 그 아래: 게이트웨이가 RAG / CRUD / Gemini API 세 갈래로 분기
- 맨 아래: 내부 시스템 / 비즈니스 로직
- 오른쪽 세로 띠에 "예측 불가능 → 검증됨 → 안정" 을 색으로 표시
막힐 때
12주차에서 "___" 가 이해가 안 돼.
이 교재의 판별 축 5개(형태/의미, 밖에서찾기/안을고치기, 전부/일부,
불변/가변, 디스크/메모리/런타임)만 써서 다시 설명해줘.
교재에 없는 새 비유는 만들지 마.
내 과제에 적용하기
내가 하려는 일은 "___" 야.
이 교재의 6.2절 결정표(다섯 개 질문)에 하나씩 답을 매기고,
프롬프트 / RAG / 파인튜닝 중 어디서 끝나야 하는지 판정해줘.
파인튜닝이라고 판정되면 4.7절 확인사항 7개를 내 환경에 맞춰 체크리스트로 만들어줘.
앞 장 연결 확인
이 12주차 교재에서 "앞에서 배운", "앞 장에서 이어집니다", "다시 만납니다"
라고 표시된 부분을 전부 찾아서, 몇 주차의 어떤 개념이 이번 주에 어떤 이름으로
다시 나왔는지 표로 정리해줘. 특히 3주차의 디스크/메모리 축과
5주차의 정적/동적 축이 이번 주에 어떻게 확장되었는지는 따로 한 줄씩 설명해줘.
LangTailor