본문으로 건너뛰기
데이터 생명주기와 수집 — 크롤링에서 서버 구성까지

생성 수정

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 세 칸 비교표

setframebase
물리 위치디스크 (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.14RUN-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값
Emailadmin@example.comHosthost.docker.internal
PasswordadminPort5432
Usernamepostgres
Passwordpostgres
Databasetailor

⭐ 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모델·토크나이저를 불러오는 기본 라이브러리
peftLoRA·QLoRA 어댑터를 붙이는 라이브러리
bitsandbytes4비트 양자화(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 (개념 증명) — "이 아이디어가 실제로 동작하는가"를 최소 규모로 확인하는 단계입니다.

PoCMVP프로덕션
목적되는지 확인최소 기능으로 사용자 검증실서비스
사용자우리 팀초기 사용자전체
품질 기준동작하면 됨핵심 흐름은 완성안정성·보안·성능
이번 주의 사례Qwen 1.5B 하나로 라우터·응답 겸용——

⚠️ PoC와 MVP를 섞으면 프로젝트가 늘어집니다. PoC에서 예쁜 UI를 만들거나 성능을 튜닝하기 시작하면 끝이 없습니다. PoC의 성공 기준은 "질문이 세 갈래로 정확히 갈리는가" 하나입니다.


부록 A. 용어 사전

용어영문상위 범주정의
시맨틱Semantic설계 사상형태가 아니라 의미를 기계가 읽을 수 있게 만드는 것
시맨틱 태그Semantic TagHTML 태그태그 이름 자체가 그 영역의 역할을 선언하는 태그
시맨틱 인텐트Semantic Intent입력 해석 방식단어 일치가 아니라 문장 맥락 속 목적을 판단하는 것
시맨틱 웹Semantic Web웹 설계 사상문서를 기계도 뜻으로 읽을 수 있게 구조화한 웹
웹 접근성Web Accessibility품질 요건스크린 리더 등 보조 기술로도 쓸 수 있게 만드는 것
SEOSearch Engine Optimization전략검색엔진이 문서를 잘 이해하도록 만드는 최적화
RAGRetrieval-Augmented Generation지식 공급 방식외부 저장소에서 찾아온 문서를 프롬프트에 붙여 답을 만드는 것
환각Hallucination현상모델이 근거 없이 그럴듯한 거짓을 만들어내는 것
청킹Chunking전처리문서를 검색 단위 조각으로 자르는 것
파인튜닝Fine-Tuning학습 방식사전 학습 모델에 특화 데이터를 추가 학습시키는 것
PEFTParameter-Efficient Fine-Tuning파인튜닝원본은 동결하고 1~2%의 보조망만 학습하는 방식
LoRALow-Rank AdaptationPEFT 기법보조 신경망을 얹어 그것만 학습하는 대표 기법
QLoRAQuantized LoRAPEFT 기법4비트(NF4) 양자화 후 LoRA를 얹는 초경량 기법
양자화Quantization압축 기법가중치의 정밀도(소수점 자릿수)를 낮춰 메모리를 줄이는 것
NF4NormalFloat 4-bit데이터 형식QLoRA에서 쓰는 4비트 표현 형식
어댑터(모델)Adapter산출물파인튜닝 결과로 나오는 작은 보조 가중치 파일
가중치Weights모델 구성요소뉴런 사이 시냅스 결합 강도. 기억의 실체
파국적 망각Catastrophic Forgetting현상새로 학습하다 기존 능력을 잃는 것
도메인 적응 학습Domain Adaptation학습 유형그 분야의 말과 사고방식을 익히게 하는 학습
인스트럭션 튜닝Instruction Tuning학습 유형지시를 받고 답하는 형식을 익히게 하는 학습
sLLMsmall 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을 파싱해 필요한 값만 뽑는 것
jsonlJSON Lines파일 형식한 줄에 JSON 객체 하나씩 적는 형식
Redis—저장소인메모리 키-값 저장소. 캐시·세션·큐용
alembic—도구파이썬 진영의 DB 스키마 마이그레이션 도구
pgvector—DB 확장PostgreSQL에 벡터 검색을 더한 확장
pgAdmin—도구PostgreSQL을 화면으로 관리하는 GUI
host.docker.internal—도커 특수 이름컨테이너 안에서 호스트 컴퓨터를 가리키는 주소
PoCProof of Concept개발 단계아이디어가 동작하는지만 확인하는 최소 구현
소버린 AISovereign AI운영 형태데이터가 조직 밖으로 나가지 않는 AI 구성
허브 / 스포크Hub / Spoke토폴로지스타 토폴로지의 중심(라우팅) / 연결된 각 지점
온톨로지 클래스Ontology Class온톨로지개념을 분류하는 범주 (Has_Eye 등)

부록 B. 스스로 확인하기

먼저 답해보고, 막히면 옆의 절을 펼치세요.

#질문답이 있는 곳
1시맨틱 태그와 시맨틱 인텐트는 무엇이 같은가?1.1, 1.8
2<div class="header"> 는 왜 시맨틱이 아닌가?1.6
3"RAG는 기억이 아니라 ○○○○이다" — 빈칸은?2.4
4RAG 세 단계의 영어 이름과 각 단계가 하는 일은?3.1
5RAG 답이 이상할 때 모델보다 먼저 봐야 할 것은?3.3
6PEFT가 학습하는 파라미터는 전체의 몇 %인가?4.2
76GB VRAM 환경의 원픽 모델과 그 이유 세 가지는?4.6
8가중치를 한 문장으로 정의하면?5.1
9Full FT / PEFT / QLoRA / RAG 는 가중치를 각각 어떻게 다루는가?5.2
10"우리 회사 규정을 알게 하고 싶다"는 무엇으로 푸는가?6.1
11게이트웨이 라우터를 왜 큰 모델 API가 아니라 작은 로컬 모델로 만드는가?8.3
12카파시의 하네스 원칙 한 문장은?7.1
13하네스 가드레일의 두 기둥은?7.4, 7.5
14Failsafe와 Fallback의 차이는?7.6
15모델이 RAG를 건너뛰고 환각할 때 하네스는 무엇을 하는가?7.7
16인터렉터가 .env 에 직접 접근하면 안 되는 이유 셋은?8.4
17set / frame / base 는 각각 어디에 있는가?9.2~9.6
18Base 는 왜 "기초"가 아닌가?9.5
19value 와 price 를 가르는 판별 질문은?10.1
20상태(State)와 환경(Environment)의 관계는?10.4
21크롤링과 스크래핑의 결과물은 각각 무엇인가?11.1
22수집 결과를 왜 jsonl 로 저장하는가? (네 가지)11.3
23PoC의 성공 기준은 무엇이어야 하는가?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 + alembic12.112.4, 13.113.6
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주차의 정적/동적 축이 이번 주에 어떻게 확장되었는지는 따로 한 줄씩 설명해줘.