#튜토리얼
33개의 포스트

BM25는 한국어를 어떻게 쪼개나 — 조사·복합명사·영한 혼용, 토크나이저가 검색을 결정한다 (한국어 검색 스택 5편)
2편에서 BM25는 '약한 고리'였습니다. 왜 약했을까요. 이 글은 그 질문을 파고듭니다. 같은 문서, 같은 질문, 같은 BM25 공식에 토크나이저만 여덟 가지로 바꿔 보니 nDCG@10이 0.42에서 0.62까지 벌어졌습니다. 띄어쓰기로 자르면 '청년취업을'과 '청년취업'이 다른 단어가 되고, 형태소 분석기로 조사를 떼면 같은 단어가 되며, 글자 2-gram을 겹쳐 색인하면 사전에 없는 말까지 받아 줍니다. 한국어에서 어휘 검색이 어려운 세 가지 이유(교착어의 조사와 어미, 복합명사, 영한 혼용)를 예시 문장의 실제 토큰으로 보여 주고, 엘라스틱서치 nori의 decompound 모드와 불용 품사 기본값, Kiwi의 사용자 사전, BM25의 k1·b 다이얼을 실측으로 짚습니다. 결론은 BM25 자체가 약한 것이 아니라 한국어를 잘못 쪼갠 BM25가 약하다는 것입니다. 인터랙티브 4개와 삽화 8장.

4096차원에 HNSW가 필요한 이유 — 전수 비교의 벽, 차원의 저주, 그리고 그래프를 걸어서 찾는 법 (한국어 검색 스택 4편)
1편에서 가장 좋았던 Qwen3-Embedding-8B는 문장 하나를 4,096개 숫자로 바꿉니다. 문서 589건이면 질문 하나에 589번 내적하면 되니 1초도 안 걸립니다. 그런데 문서가 5,000만 건이면? 같은 방식으로는 질문 하나에 2,000억 번 곱셈이고, 서버가 아무리 좋아도 초 단위입니다. 벡터 DB가 HNSW라는 인덱스를 쓰는 이유가 여기 있습니다. 이 글은 왜 고차원에서는 트리 인덱스가 소용없는지(차원의 저주), HNSW가 어떻게 스킵 리스트와 작은 세상 그래프를 합쳐 로그 시간에 근사 최근접을 찾는지, M·ef_construction·ef_search 세 손잡이가 무엇을 바꾸는지를 그림과 장난감 시뮬레이터로 풀고, 4,096차원 벡터 1만·10만·30만 개로 전수 비교와 hnswlib을 직접 재서 비교합니다. 10만 개에서 전수 비교 56ms 대 HNSW 1.3ms(재현율 0.98). 그리고 4,096차원의 진짜 병목은 그래프가 아니라 벡터 자체라는 것, 그래서 3편의 마트료시카 절단과 양자화가 HNSW와 곱해진다는 것을 메모리 계산기로 보입니다. 인터랙티브 4개와 삽화 8장.

마트료시카 임베딩 — 벡터를 앞에서부터 잘라도 되는 이유와, 잘랐을 때 실제로 남는 것 (한국어 검색 스택 3편)
Qwen3-Embedding-8B의 벡터는 숫자 4,096개입니다. 5,000만 청크를 float32로 저장하면 벡터만 819GB입니다. 그런데 이 벡터의 앞 256개만 남기고 나머지를 버려도 검색 품질의 93%가 남는다면? 그것이 마트료시카 임베딩입니다. 2022년 논문 「Matryoshka Representation Learning」이 제안하고 OpenAI text-embedding-3, Nomic v2, Qwen3 Embedding이 채택한 이 기법은 '중요한 정보를 벡터의 앞쪽에 몰아넣도록' 학습해, 러시아 인형처럼 큰 벡터 안에 작은 벡터가 들어 있게 만듭니다. 이 글은 원리를 장난감 모형으로 손에 잡히게 설명하고, 같은 한국어 코퍼스로 Qwen3 4B·8B와 Nomic v2를 32차원까지 잘라 가며 잰 실측 곡선, 저장 비용 계산기, 그리고 '짧게 걸러서 길게 고르는' 두 단계 적응형 검색이 전체 차원의 품질을 얼마나 되찾는지를 보여 줍니다. 인터랙티브 4개와 삽화 7장.

키워드와 의미, 둘 다 쓴다 — BM25·벡터·RRF·리랭커, 논문과 실측으로 보는 하이브리드 검색 (한국어 검색 스택 2편)
원문 메모의 그림은 이랬습니다. BM25와 벡터 검색을 나란히 돌리고, 결과를 RRF로 합치고, 리랭커로 다시 고른다. 이 글은 그 그림의 각 칸을 논문과 실측으로 채웁니다. 2009년 SIGIR의 두 쪽짜리 논문이 제안한 RRF는 점수 대신 등수만 쓰는 단순한 공식인데 왜 통하는지, k=60은 어디서 왔는지, 2023년 TOIS 논문은 왜 '점수 융합이 RRF보다 낫다'고 했는지, 2025년의 '약한 고리' 발견은 무엇인지. 그리고 1편의 한국어 코퍼스로 직접 재 보니, 등가중 RRF는 벡터 단독보다 오히려 나빴고 이유는 BM25가 약한 고리였기 때문이었습니다. 반면 강한 임베딩 둘(Qwen3-8B + BGE-M3)을 RRF로 합치자 어느 쪽 단독보다 좋아졌습니다. 리랭커는 '따로 읽고 비교'하는 바이 인코더와 '같이 읽고 판정'하는 크로스 인코더의 차이로 풀고, 로컬 LLM을 리랭커로 써서 상위 10개의 순서가 얼마나 바뀌는지 잽니다. 인터랙티브 5개와 삽화 8장.

한국어 임베딩 모델, 우리 문서로 직접 재봤다 — Qwen3-Embedding 8B·4B·0.6B, BGE-M3, Nomic v2 MoE 실측 비교 (한국어 검색 스택 1편)
RAG를 만들 때 가장 먼저 부딪히는 질문이 '임베딩 모델은 뭘 쓰지?'입니다. 이 글은 그 질문에 남의 벤치마크가 아니라 우리 문서로 답합니다. 코어닷투데이 블로그와 뉴스 589건을 코퍼스로, 사람이 일부러 다른 말로 쓴 질문 22개와 로컬 LLM이 바꿔 쓴 질문 45개, 키워드 질문 18개를 만들고, 맥 한 대의 Ollama에서 Qwen3-Embedding 8B·4B·0.6B, BGE-M3, Nomic Embed v2 MoE 다섯 모델을 같은 조건으로 돌렸습니다. 임베딩이 무엇인지, 코사인 유사도가 왜 각도인지부터 시작해, '청년 일자리'라는 말이 없는 문서를 각 모델이 어떻게 찾는지 실제 값으로 보여 주고, 지시문 접두어 한 줄이 점수를 어떻게 바꾸는지, 같은 한국어 문서를 토크나이저가 얼마나 다르게 쪼개는지, 8B와 4B의 차이가 어디서 나는지를 따져 봅니다. 결론은 순위표 하나가 아니라 '어떤 환경에서 무엇을 고르나'이고, 인터랙티브 6개와 삽화 8장으로 함께 읽습니다. 2편은 BM25·RRF·리랭커, 3편은 마트료시카 임베딩입니다.

팀 프로젝트 폴더 구조 — 단일 파일에서 도메인형까지 (FastAPI 입문 7편)
한 파일짜리 할 일 API를 여러 사람이 동시에 고쳐도 충돌하지 않는 구조로 옮긴다. 단일 파일·계층형·도메인형 세 가지 폴더 구조를 비교하고 고르는 기준을 세운 뒤, 권장하는 도메인형 구조를 사용자·할 일 두 도메인으로 실제로 만들어 테스트까지 통과시킨다. 라우터는 얇게, 규칙은 service에, 저장은 repository에 두는 법, 도메인 사이 import 규칙, APIRouter·Depends·dependency_overrides, pyproject.toml·uv.lock·.env.example 같은 루트 파일, 팀 규칙과 PR 체크리스트까지 — FastAPI 입문 시리즈의 마지막 편이다. 댓글 기능 하나를 추가할 때 구조마다 무엇을 건드리는지 보여 주는 폴더 탐험기 위젯을 포함한다.

CRUD ① GET과 POST — Pydantic 모델로 요청 받고 응답하기 (FastAPI 입문 5편)
CRUD와 HTTP 메서드의 대응, REST식 주소 짓는 법을 정리한 뒤 할 일 API의 만들기(POST)와 읽기(GET)를 구현한다. 4편에서 익힌 Pydantic 모델을 FastAPI에 연결해 요청 본문을 검증하고, 입력 모델과 출력 모델을 나누는 이유, response_model로 민감한 필드를 숨기는 법, 201·404 상태 코드와 HTTPException 사용법을 실제 실행 결과와 함께 다룬다. JSON을 고치면 FastAPI의 응답이 바로 바뀌는 본문 검증기 위젯을 포함한다.

CRUD ② PUT·PATCH·DELETE — 통째로 바꾸기, 일부 고치기, 지우기 (FastAPI 입문 6편)
할 일 API의 나머지 절반인 수정과 삭제를 만든다. PUT(전체 교체)과 PATCH(부분 수정)가 무엇이 다른지, 왜 PUT은 안 보낸 필드를 지워 버리는지, PATCH에서 model_dump(exclude_unset=True)가 하는 일과 exclude_none과의 차이, 그리고 초보자가 거의 반드시 밟는 ‘PATCH로 null이 들어가는’ 함정과 해결법을 실제 실행 결과로 보여 준다. DELETE와 204, 멱등성까지 정리하고, 같은 본문을 다른 메서드로 보내 보는 실험실 위젯을 포함한다.

/docs 하나로 협업하기 — Swagger UI·ReDoc·OpenAPI 제대로 쓰는 법 (FastAPI 입문 3편)
FastAPI가 자동으로 만들어 주는 /docs(Swagger UI), /redoc, /openapi.json 세 주소를 실제 화면 캡처와 함께 설명한다. Try it out으로 요청을 보내고 응답을 읽는 법, tags·summary·description·examples·responses·deprecated로 문서 품질을 끌어올리는 법, openapi.json으로 프론트엔드 타입을 자동 생성하는 법, 운영 환경에서 문서를 닫는 법까지. 체험판 Swagger UI 위젯으로 할 일 API를 직접 호출해 볼 수 있다.

Pydantic 기초 — 데이터의 모양을 선언하고 검증하기 (FastAPI 입문 4편)
FastAPI 코드의 절반은 Pydantic이다. 이 편은 FastAPI를 잠시 내려놓고 Pydantic만 떼어 파이썬 코드로 익힌다. 모델을 만들고, 틀린 값에서 나오는 ValidationError를 읽고, 필수·선택·null의 네 가지 조합, 기본(lax) 모드의 타입 변환 규칙과 strict 모드, Field 제약, 중첩 모델, field_validator·model_validator, model_dump·model_validate, camelCase 별칭까지 — 전부 실제 실행 결과로 보여 준다. 필드 타입별로 값이 어떻게 바뀌는지 lax와 strict를 나란히 비교하는 위젯을 포함한다.

왜 파이썬이고, 왜 FastAPI인가 — 다른 언어·프레임워크와 비교해 보기 (FastAPI 입문 1편)
API가 무엇인지부터 시작해, 왜 많은 팀이 백엔드 언어로 파이썬을 고르고 그중에서도 FastAPI를 고르는지 Django·Flask·Express·Spring Boot·Go와 나란히 놓고 비교한다. 같은 API를 여섯 프레임워크로 짠 코드를 위젯으로 직접 비교해 보고, FastAPI를 고르지 말아야 할 때, 그리고 어떤 파이썬 버전(3.10~3.15)으로 돌려야 하는지 직접 돌려 본 결과와 함께 정리한다. 일곱 편짜리 FastAPI 입문 시리즈의 첫 편이다.

첫 FastAPI 앱과 라우팅 — 주소를 함수로 잇는 법 (FastAPI 입문 2편)
설치부터 fastapi dev로 첫 서버를 띄우기까지, 그리고 FastAPI의 가장 기본인 라우팅 — @app.get 데코레이터, 경로 매개변수, 쿼리 매개변수, 타입 힌트로 자동 검증되는 422 에러, 라우트 순서 함정, 405와 307 — 을 실제 실행 결과와 함께 설명한다. 주소를 입력하면 어느 함수로 가는지 보여 주는 라우트 매칭기 위젯으로 직접 확인해 볼 수 있다.