1강 개념 중심 ⏱ 약 40분

 

0. 학습 목표

→ LLM이 무엇을 못 하는지 직접 확인하고, 그것을 RAG가 어떻게 푸는지 전체 구조를 잡습니다.

더보기

0.1 이번 글에서 다룰 내용

 

0강에서 작업 폴더와 가상환경, 패키지 세 개, Ollama와 모델 두 개, 기초 데이터, 설정 파일까지 준비했습니다. 그런데 이 환경으로 무엇을 왜 만드는지는 아직 확인하지 않았습니다.

 

이 과정에서 만들 것은 지식재산권 관련 질문에 근거를 갖춘 답을 돌려주는 프로그램입니다. 그런데 "질문에 답하는 것"은 대규모 언어 모델(LLM, Large Language Model)이 이미 하는 일입니다.

 

왜 따로 만들어야 하는지부터 확인해야 합니다.

0강에서 준비한 qwen3.5:4b 모델에 직접 물어봅니다. 배포받은 데이터 안에 답이 들어 있는 질문을 하나 고릅니다.

 

쉘 프롬프트

# 배포 데이터 안에 답이 있는 질문을 로컬 LLM에 직접 던져 본다
ollama run qwen3.5:4b "특허심판원 국선대리인의 선임 및 운영에 관한 규칙은 언제부터 시행되었나요?"

 

화면에는 답이 나옵니다. 그런데 그 답을 확인해 보면 세 가지 중 하나입니다.

 

  • 모른다고 답한다
  • 날짜를 대는데 틀린 날짜다
  • 일반적인 설명만 하고 시행일은 말하지 않는다

어느 쪽이든 문제는 같습니다. 화면에 나온 문장이 맞는지 틀린지 사용자가 판단할 방법이 없습니다. 답변에 출처가 붙어 있지 않기 때문입니다.

정답은 2021년 11월 11일이고, 이 값은 배포받은 기초 데이터 10건 중 ip-004 문서에 들어 있습니다. 파일은 손에 있는데 LLM은 그 파일을 본 적이 없습니다. LLM은 인터넷에서 모은 텍스트로 학습했고, 그 학습에 이 문서가 포함되지 않았거나 포함되었더라도 정확히 기억하지 못합니다.

이 과정에서 만들 프로그램은 이 문제를 다음 순서로 해결합니다.

사용자 질문
   ↓
가지고 있는 문서 10건 중에서 질문과 관련 있는 것을 찾는다
   ↓
찾은 문서를 근거로 붙여 LLM에게 함께 보낸다
   ↓
LLM은 그 근거 안에서만 답을 만든다
   ↓
답변과 함께 어느 문서를 근거로 썼는지 표시한다
   ↓
관련 있는 문서를 하나도 찾지 못하면 LLM을 부르지 않고 거부한다

이 방식을 검색 증강 생성(RAG, Retrieval-Augmented Generation)이라고 부릅니다.

2강을 마치면 다음과 같이 동작하는 프로그램이 완성됩니다.

$ python ask.py "무료로 대리인을 지원받는 제도가 있나요?"
[LLM 호출: 예 / 소요 xx.x초]

특허심판원은 국선대리인을 선임해 줄 수 있으며, 대상은 수급자와 국가유공자,
장애인 등 지원이 필요한 사람과 산업재산권 분쟁 중인 소기업·중기업입니다. [근거 1]

근거:
  - 법령 / 특허심판원국선대리인의선임및운영에관한규칙 (유사도 0.xxxx, 문서번호 ip-004)

질문에 쓴 "무료", "지원받는 제도"라는 말은 그 문서에 한 글자도 없습니다. 그런데도 찾아냅니다. 어떻게 그렇게 되는지가 1강과 2강의 내용입니다.

이번 강의는 코드를 작성하지 않습니다. LLM의 한계를 직접 확인하고, RAG가 그것을 어떻게 푸는지, 그 과정이 1강과 2강으로 어떻게 나뉘는지를 정리합니다.

 

 

 

 

0.2 이번 강의 실습 내용

 

이번 강의에서 학습자가 직접 실행하는 것은 1절의 질문 던지기와 4절의 서버 확인입니다. 나머지 절은 그 결과를 해석하고 전체 구조를 잡는 내용입니다. 이번 강의에서는 코드를 작성하지 않습니다.

 

1절에서는 로컬 LLM에 질문 세 개를 던져 무엇이 안 되는지 기록합니다.

0.1은 한 건만 예로 들었고, 1절에서는 성격이 다른 질문 세 개로 확인합니다.

여기서 기록한 표가 2절의 판단 근거가 됩니다.

 

2절에서는 1절의 한계를 푸는 두 가지 방법을 비교합니다.

모델을 다시 학습시키는 방법과 문서를 찾아 붙여 주는 방법이 있습니다.

판단 기준을 표로 정리하고 이 과정이 RAG를 고른 이유를 확인합니다. RAG로도 해결되지 않는 것도 함께 정리합니다.

 

3절에서는 RAG가 답을 만들기까지 거치는 다섯 단계를 확인합니다.

각 단계를 빼면 무엇이 안 되는지를 함께 봅니다.

 

4절에서는 그 단계를 실행할 모델이 어디에 있는지 확인합니다.

0강에서 .env에 적어 둔 http://127.0.0.1:11434가 무엇인지 여기서 정리합니다.

이것을 모르고 2강에 들어가면 코드에 적힌 주소를 그대로 베끼게 됩니다.

 

5절에서는 다섯 단계를 이 과정에서 만들 파일과 강의 번호에 대응시킵니다.

실습 도중 "지금 어디를 하고 있는지" 헷갈리면 이 표로 돌아옵니다.

 

6절에서는 실행 경로가 두 가지로 나뉜다는 것을 확인합니다.

문서를 저장하는 경로와 질문에 답하는 경로가 실행 시점이 다릅니다.

이 구분을 못 하면 3강에서 검색 결과가 비었을 때 원인을 찾지 못합니다.

 

7절에서는 앞으로 만날 용어를 한자리에 모읍니다.

지금 외우는 것이 목적이 아니라, 실습 중에 막혔을 때 돌아올 자리를 만들어 두는 것이 목적입니다.

 

8절에서는 이 단계에서 다루지 않는 것을 미리 확인합니다.

3강 마지막에서 같은 표를 다시 보게 됩니다.

 

하는 일 앞 절에서 받는 것 다음 절에 넘기는 것
1 로컬 LLM의 한계 실측 0강에서 준비한 Ollama와 모델 관찰 기록 세 줄
2 해결 방법 두 가지 비교 관찰 기록 RAG를 고른 판단 근거와 남는 한계
3 RAG의 다섯 단계 확인 그 선택 단계별 역할
4 모델이 실행되는 위치 확인 다섯 단계 중 임베딩과 생성 서버 주소와 두 개의 요청 경로
5 단계를 파일과 강의에 대응 다섯 단계와 서버 구조 강의 지도와 프로젝트 구조
6 두 실행 경로 구분 프로젝트 구조 무엇을 언제 실행하는지
7 용어 정리 3~6절에 나온 말들 실습 중 돌아올 참조표
8 다루지 않는 것 확인 전체 구조 2단계 이후로 넘길 범위

이번 강의를 마치면 다음을 할 수 있습니다.

 

  • 로컬 LLM에 질문을 던져 답변에 출처가 없다는 것과 학습에 없는 내용을 정확히 답하지 못한다는 것을 실행 결과로 보인다.
  • 모델 재학습과 RAG 중 어느 쪽이 이 과정에 맞는지 데이터 변경 빈도·출처 요구·장비 조건을 기준으로 설명한다.
  • RAG를 적용해도 남는 한계 세 가지를 말하고, 그중 무엇을 사람이 맡아야 하는지 구분한다.
  • 2강과 3강이 RAG의 어느 단계를 맡는지 강의 번호와 파일 이름으로 대응시킨다.

선수 지식은 0강에서 구축한 실습 환경입니다. 파이썬 문법은 2강부터 필요하며 이번 강의에서는 쓰지 않습니다. 이번 강의에서는 임베딩과 벡터 검색이 계산되는 원리를 다루지 않으며, 필요한 만큼은 2강과 3강에서 실행 결과와 함께 설명합니다.

 

1. 로컬 LLM에 직접 물어 한계 확인하기

→ 성격이 다른 질문 세 개를 던져 무엇이 되고 무엇이 안 되는지 기록합니다.

더보기

0.1에서 질문 하나를 던져 봤습니다. 그 한 번으로는 "이 질문이 유독 어려웠던 것"인지 "LLM이 원래 못 하는 일"인지 구분할 수 없습니다. 성격이 다른 질문 세 개로 확인합니다.

0강 6절의 점검을 통과했다면 그대로 진행합니다. ollama: command not found가 나오면 0강 3절로 돌아가 Ollama부터 설치합니다.

1.1 성격이 다른 질문 세 개 던지기

 

첫 번째는 일반적인 법률 지식을 묻는 질문입니다. 배포 데이터의 ip-001 문서가 답하는 질문과 같은 내용입니다.

 

쉘 프롬프트

# 질문 A. 일반적인 법률 지식
ollama run qwen3.5:4b "상표권 침해 여부는 어떤 기준으로 판단하나요?"

두 번째는 같은 주제에 대해 근거를 요구하는 질문입니다.

 

쉘 프롬프트

# 질문 B. 같은 주제에 대해 근거를 요구
ollama run qwen3.5:4b "그 판단 기준을 제시한 대법원 판결의 선고일과 사건번호를 알려 주세요."

세 번째는 정답을 대조할 수 있는 질문입니다. 0.1에서 던진 것과 같습니다.

 

쉘 프롬프트

# 질문 C. 배포 데이터에 정답이 들어 있는 질문
ollama run qwen3.5:4b "특허심판원 국선대리인의 선임 및 운영에 관한 규칙은 언제부터 시행되었나요?"

세 질문 모두 화면에 답이 나옵니다. 답이 안 나오는 경우는 없습니다.

 

확인할 것은 답이 나오는지가 아니라 그 답을 신뢰할 수 있는지입니다.

 

 

 

 

1.2 관찰한 것을 기록하고 무엇이 문제인지 정리하기

 

세 질문의 결과를 표에 적습니다. 답변 내용을 그대로 옮길 필요는 없습니다. 아래 항목만 판단해 적습니다.

 

질문 답이 나왔는가 어느 문서를 근거로 했는지
표시되었는가
사용자가 맞는지
확인할 수 있는가
A. 상표권 침해 판단 기준      
B. 그 기준을 제시한 판결의 선고일과 사건번호      
C. 국선대리인 규칙의 시행일      

질문 C만 정답을 대조할 수 있습니다. 정답은 2021년 11월 11일이며, 배포 데이터 ip-004 문서의 date 값입니다. 화면에 나온 날짜와 비교해 적습니다.

같은 질문을 한 번 더 실행해 답변이 앞과 같은지도 확인합니다.

 

쉘 프롬프트

# 질문 A를 한 번 더 실행해 앞의 답과 비교
ollama run qwen3.5:4b "상표권 침해 여부는 어떤 기준으로 판단하나요?"

기록한 표에서 세 가지가 드러납니다.

첫째, 두 번째 열이 세 질문 모두 "아니오"입니다.

LLM은 답변만 돌려주고 그 답이 어디에서 나왔는지 알려 주지 않습니다.

사용자가 확인할 수 있는 것은 문장이 그럴듯한지뿐입니다.

 

둘째, 같은 질문에 답변 문장이 달라집니다.

어제 확인한 답과 오늘 나온 답이 다르면, 둘 중 무엇을 기준으로 업무를 진행해야 하는지 판단할 수 없습니다.

 

셋째, 질문 C가 특히 분명합니다.

정답이 손에 있는 파일 안에 적혀 있는데도 LLM은 그 값을 정확히 대지 못합니다.

학습에 포함되지 않았거나 정확히 기억하지 못하는 내용은 아무리 좋은 모델을 써도 나오지 않습니다.

 

0강 4절에서 배치한 data/ip_qa_basic.json이 그 파일이며, 2강에서 직접 열어 보게 됩니다.

 

특허사무소나 기업 지식재산팀에서 이 세 가지는 그대로 업무 위험이 됩니다.

담당자가 답변 하나를 근거로 판단을 내렸는데 출처를 댈 수 없으면, 그 판단을 검토할 방법이 없습니다.

잘못된 답변이 곧 업무 손실로 이어지는 자리에서는 답변보다 근거가 먼저입니다.

▶ 지금 해보세요

  1. 1.2의 표 세 줄을 채웁니다.
  2. 질문 C의 답변에 나온 날짜를 적고 2021-11-11과 같은지 표시합니다.
  3. 질문 A를 두 번 실행해 두 답변이 같은지 다른지 적습니다.
  4. 이 프로그램을 업무에 쓸 수 없는 이유를 위 표를 근거로 한 문장으로 적습니다.

 

2. 한계를 푸는 두 방법을 비교하고 이 과정의 선택 확인하기

→ 1절에서 기록한 한계를 푸는 두 가지 방법을 비교하고 선택 기준을 정리합니다.

더보기

1절에서 확인한 문제는 하나로 정리됩니다. LLM이 우리 문서를 모른다는 것입니다. 이 문제를 푸는 방법은 크게 두 가지입니다.

2.1 모델을 다시 학습시키는 방법과 문서를 찾아 붙여 주는 방법

 

 

구분 미세 조정(Fine-tuning) 검색 증강 생성(RAG)
하는 일 우리 문서로 모델 자체를 추가 학습시킨다 질문마다 관련 문서를 찾아 LLM에게 함께 건넨다
문서가 바뀌었을 때 다시 학습시켜야 한다 저장소의 문서만 갱신하면 된다
출처 표시 어렵다. 답이 어느 문서에서 나왔는지 모델 안에서 추적할 수 없다 쉽다. 검색으로 고른 문서가 곧 출처다
필요한 장비 학습용 GPU와 시간 실습 PC에서 실행 가능
모델 교체 처음부터 다시 학습 저장한 문서를 그대로 두고 LLM만 바꾼다

두 방법은 대립하는 것이 아니며 함께 쓰기도 합니다. 미세 조정은 모델의 말투와 형식을 바꾸는 데 강하고, RAG는 모델이 모르는 사실을 채우는 데 강합니다.

 

 

 

 

2.2 이 과정이 RAG를 고른 이유

 

기술이 더 좋아서가 아니라 이 과정에서 해결하려는 문제에 맞기 때문입니다. 판단 기준을 세 가지로 나눠 보면 다음과 같습니다.

 

판단 기준 이 과정의 상황 맞는 방법
문서가 자주 바뀌는가 판결문과 법령은 계속 새로 나온다 RAG
답변에 출처가 필요한가 법률 자료라 근거 없이는 쓸 수 없다 RAG
학습용 장비가 있는가 실습 PC에서 진행한다 RAG

세 항목이 모두 RAG를 가리킵니다. 반대로 사내 문서가 거의 바뀌지 않고, 출처보다 일정한 말투가 중요하고, 학습 장비가 있는 상황이라면 미세 조정이 나은 선택일 수 있습니다. 어느 쪽이 항상 옳은 것이 아니라 조건을 보고 고르는 것입니다.

 

 

 

 

2.3 RAG로도 해결되지 않는 것

 

RAG를 적용해도 남는 한계가 있습니다. 이 세 가지를 모르고 도입하면 "AI가 답했으니 맞겠지"라는 잘못된 신뢰가 생깁니다.

 

남는 한계 이 과정에서 하는 일 사람이 해야 하는 일
문서에 없는 내용은 여전히 답하지 못한다 답하지 못한다는 것을 거부 문구로 알린다 (3강) 답할 수 없는 질문이 많으면 문서를 보강할지 판단
검색이 관련 문서를 찾지 못하면 답도 만들 수 없다 유사도를 화면에 표시해 왜 그 문서가 나왔는지 보이게 한다 (3강) 검색 결과가 부적절하면 질문 표현이나 저장 방식을 조정
근거가 있어도 LLM이 그 근거를 잘못 요약할 수 있다 답변과 함께 출처를 표시해 원문을 찾아갈 수 있게 한다 (3강) 답변을 원문과 대조해 검토하고 승인

세 번째가 가장 중요합니다. RAG는 답변의 정확성을 보장하지 않습니다. 답변이 근거 안에서 만들어지도록 유도하고 그 근거를 밝힐 뿐입니다. 생성된 답변을 검토하고 업무에 쓸지 결정하는 것은 사람의 몫이며, 이 과정에서 출처를 반드시 함께 표시하는 이유가 그것입니다.

 

3. RAG가 답을 만들기까지 거치는 다섯 단계 확인하기

→ 2절에서 고른 방법이 실제로 어떤 단계로 나뉘는지 확인합니다.

더보기

"관련 문서를 찾아 함께 건넨다"를 실제로 하려면 준비할 것이 있습니다. 무엇을 문서 한 건으로 볼지 정해야 하고, 뜻이 비슷한 것을 찾을 수 있는 형태로 바꿔 두어야 하고, 찾은 것이 정말 관련 있는지 판단해야 합니다. 다섯 단계로 나뉩니다.

1. 문서 준비   배포받은 데이터에서 검색할 텍스트를 뽑아 한 줄 한 문서 형태로 정리
        ↓
2. 임베딩     각 문서를 뜻을 담은 숫자 목록(벡터)으로 변환
        ↓
3. 저장       벡터와 원문을 함께 벡터 저장소에 넣어 둠
        ↓
4. 검색       질문도 같은 방식으로 벡터로 바꿔 가까운 문서를 찾음
        ↓
5. 근거 기반 생성   찾은 문서를 근거로 붙여 LLM에 보내고, 근거가 없으면 보내지 않음

 

각 단계를 빼면 무엇이 안 되는지 보면 왜 다섯 개인지 알 수 있습니다.

 

단계 이 단계를 빼면
1. 문서 준비 검색에 넣을 텍스트가 정해지지 않아 무엇을 비교할지 알 수 없다
2. 임베딩 글자가 겹치는 것만 찾게 되어, 같은 뜻을 다르게 물으면 못 찾는다
3. 저장 질문할 때마다 10건 전체를 다시 계산해야 한다
4. 검색 어느 문서를 근거로 쓸지 고를 수 없다
5. 근거 기반 생성 1절에서 본 상태로 돌아간다. 답은 나오지만 출처가 없다

1절에서 확인한 것이 바로 5단계만 있는 상태입니다. 앞의 네 단계가 근거를 만들어 주어야 5단계가 의미를 가집니다.

 

4. 모델이 어디에서 실행되는지 확인하기

→ 1강부터 코드에 계속 등장하는 서버 주소가 무엇인지 정리합니다.

더보기

3절의 다섯 단계 중 2번(임베딩)과 5번(생성)은 모델이 하는 일입니다. 그 모델이 어디에 있는지 알아야 2강 코드를 이해할 수 있습니다.

4.1 프로그램과 모델은 서로 다른 곳에 있다

 

우리가 작성할 파이썬 코드 안에 모델이 들어 있지 않습니다. 모델은 Ollama라는 별도 프로그램이 메모리에 올려 두고, 파이썬은 그 프로그램에 HTTP 요청을 보내 결과를 받습니다.

[내 파이썬 코드]                    [Ollama 서버]
 embedder.py  ── HTTP 요청 ──→  /api/embed  → bge-m3 모델이 벡터를 만든다
              ←── 응답 ────────
 ask.py       ── HTTP 요청 ──→  /api/chat   → qwen3.5:4b 모델이 답변을 만든다
              ←── 응답 ────────

서버 주소는 http://127.0.0.1:11434 하나입니다. 0강 5.2에서 .envOLLAMA_BASE_URL에 적어 둔 값입니다.

 

부분
127.0.0.1 내 컴퓨터 자신을 가리키는 주소. 요청이 밖으로 나가지 않는다
11434 Ollama가 요청을 받으려고 열어 둔 번호
/api/embed 텍스트를 벡터로 바꿔 달라는 요청 경로
/api/chat 답변을 만들어 달라는 요청 경로

요청이 127.0.0.1로만 오간다는 점이 이 과정에서 중요합니다. 문서와 질문이 실습 PC 밖으로 나가지 않습니다. 지식재산권 자료처럼 외부로 보내기 곤란한 문서를 다룰 때 이 구조가 근거가 됩니다.

이 과정에서 만드는 서버는 없습니다. 정리하면 다음과 같습니다.

 

서버 역할 누가 실행하는가 이 과정에서 만드는가
Ollama 서버 (127.0.0.1:11434) 모델을 메모리에 올리고 API로 제공 Ollama가 설치될 때 systemd 서비스로 등록되어 자동 실행 만들지 않는다
HTTP API 서버 다른 사람이 화면으로 쓸 수 있게 감싸는 서버 학습자가 직접 구현 이 과정에서는 만들지 않는다. 8. RAG 강좌 4단계에서 다룬다

 

 

 

 

4.2 서버가 응답하는지 직접 확인하기

 

1.1에서 ollama list로 모델 목록을 봤습니다. 그것은 Ollama 명령이 대신 물어봐 준 결과입니다. 이번에는 우리 코드가 보낼 것과 같은 형태의 HTTP 요청을 직접 보내 봅니다.

 

쉘 프롬프트

# 파이썬 코드가 보낼 것과 같은 방식으로 서버에 요청해 본다
curl -s http://127.0.0.1:11434/api/tags | python3 -c "
import json, sys
models = json.load(sys.stdin)['models']
print('Ollama 서버가 응답했습니다. 등록된 모델:', len(models), '개')
for model in models:
    print(' -', model['name'])
"

모델 이름이 출력되면 서버가 살아 있는 것입니다.

2강의 embedder.py와 3강의 ask.py가 이 주소로 요청을 보냅니다.

 

Connection refused가 나오면 Ollama가 실행 중이 아닙니다. sudo systemctl start ollama로 시작한 뒤 다시 실행합니다. 2강과 3강에서 만나게 될 오류 중 가장 흔한 것이 이것이므로, 지금 증상과 해결 방법을 함께 기억해 둡니다.

 

5. 다섯 단계를 이 과정의 파일과 강의에 대응시키기

→ 다섯 단계가 어느 강의에서 어떤 파일이 되는지 확인합니다.

더보기

3절의 다섯 단계가 이 과정에서 파일 여섯 개와 강의 두 개로 나뉩니다. 4절에서 확인한 Ollama 서버는 그중 embedder.pyask.py가 사용합니다. 실습 도중 지금 어디를 하고 있는지 헷갈리면 이 표로 돌아옵니다.

 

단계 강의 만드는 파일 그 강의에서 확인하는 것
(준비) 0강 config.py 모델 이름과 경로를 코드 바깥에서 읽어 오는가
1. 문서 준비 2강 build_docs.py docs/qa_documents.jsonl이 10줄이고 종류별로 2건씩 나뉘는가
2. 임베딩 2강 embedder.py 문장 하나를 보내 돌려받은 벡터의 차원이 몇인가
3. 저장 2강 store.py 컬렉션에 10건이 들어갔고 거리 공간이 cosine인가
4. 검색 3강 search.py 글자가 겹치지 않는 질문으로도 관련 문서가 1위로 나오는가
5. 근거 기반 생성 3강 ask.py 범위 밖 질문에서 LLM을 호출하지 않고 거부하는가

2강이 파일 세 개를 만드는데도 한 시간에 들어가는 이유는 배포받은 데이터가 단순하기 때문입니다. 파일 하나에 10건이 들어 있고 모든 건의 키가 같아서, build_docs.py가 25줄 남짓이고 embedder.py가 20줄이 되지 않습니다. 파일 수가 아니라 각 파일이 다루는 데이터의 복잡도가 시간을 결정합니다.

 

남은 두 강의를 마치면 프로젝트 폴더가 다음 상태가 됩니다. 0강에서 만든 것은 이미 있습니다.

 

이 과정을 마쳤을 때의 프로젝트 상태

rag-basic/
├── .venv/                   # 🗸 0강에서 생성
├── .env                     # 🗸 0강에서 작성
├── config.py                # 🗸 0강에서 작성
├── build_docs.py            # X 2강에서 작성
├── embedder.py              # X 2강에서 작성
├── store.py                 # X 2강에서 작성
├── search.py                # X 3강에서 작성
├── ask.py                   # X 3강에서 작성
├── data/
│   ├── ip_qa_basic.json     # 🗸 0강에서 배치, 배포받은 기초 데이터 10건
│   └── 출처.md               # 🗸 0강에서 배치
├── docs/
│   └── qa_documents.jsonl   # X 2강에서 생성, 10줄
└── chroma_db/               # X 2강에서 생성

파일 여섯 개입니다. 강의마다 파일 몇 개를 완성하고, 그 파일을 실행한 결과로 성공을 확인합니다. 코드를 쓰지 않는 강의는 이번 1강뿐입니다.

 

6. 저장 경로와 질문 경로 구분하기

→ 실행 시점이 다른 두 경로를 구분합니다.

더보기

5절의 표를 보면 파일이 여섯 개인데, 이 여섯 개가 한 번에 다 실행되는 것은 아닙니다. 실행 시점이 다른 두 경로로 나뉩니다. 이 구분을 놓치면 3강에서 검색 결과가 비었을 때 원인을 찾지 못합니다.

 

경로 A. 문서를 저장하는 경로

ip_qa_basic.json  (배포받은 기초 데이터 10건)
        ↓  build_docs.py
docs/qa_documents.jsonl  (한 줄에 문서 하나)
        ↓  store.py — 내부에서 embedder.py 호출 → Ollama /api/embed
chroma_db/ 의 ip_docs 컬렉션  (벡터와 원문)

 

경로 B. 질문에 답하는 경로

사용자 질문 한 줄
        ↓  ask.py — 내부에서 search.py 호출 → Ollama /api/embed
질문과 가까운 문서 몇 건과 각각의 유사도
        ↓  유사도가 기준 미만이면 여기서 멈추고 거부 문구 반환
근거로 조립한 프롬프트
        ↓  Ollama /api/chat 의 qwen3.5:4b
답변과 출처

두 경로의 차이를 정리하면 다음과 같습니다.

 

구분 경로 A. 저장 경로 B. 질문
언제 실행하는가 문서가 추가되거나 바뀔 때만 질문할 때마다
입력 배포받은 JSON 파일 질문 문장 한 줄
출력 컬렉션에 저장된 벡터 답변과 출처
다루는 강의 2강 3강
사용하는 Ollama 경로 /api/embed /api/embed/api/chat

경로 A를 한 번도 실행하지 않으면 경로 B는 아무것도 찾지 못합니다.

3강에서 "검색 결과가 없다"는 증상을 만나면 대부분 이 순서를 건너뛴 경우입니다.

 

embedder.py가 두 경로에 모두 등장하는 점을 눈여겨봅니다. 문서를 저장할 때와 질문을 검색할 때 같은 함수를 씁니다. 두 벡터를 비교하려면 같은 모델이 같은 방식으로 만든 것이어야 하기 때문이며, 그 이유는 2강과 3강에서 다시 확인합니다.

 

7. 앞으로 만날 용어 한자리에 모으기

→ 1강과 2강에서 반복해 나올 말들을 한 번에 정리합니다.

더보기

3절부터 6절까지 여러 용어가 나왔습니다. 지금 외우는 것이 목적이 아닙니다. 실습 중에 말이 막히면 여기로 돌아오도록 자리를 만들어 두는 것이 목적입니다. 각 용어를 실제로 만나는 위치를 함께 적었습니다.

 

용어 처음 만나는 곳
LLM (Large Language Model) 대량의 텍스트로 학습해 문장을 만들어 내는 모델. 이 과정에서는 qwen3.5:4b 1강 1절
RAG (Retrieval-Augmented Generation) 질문에 관련된 문서를 찾아 LLM에게 함께 건네는 방식 1강 2절
임베딩 (embedding) 텍스트를 정해진 개수의 실수 목록으로 바꾸는 작업 2강
벡터 (vector) 임베딩으로 만들어진 실수 목록 2강
차원 (dimension) 벡터에 들어 있는 실수의 개수. 모델이 정하며 bge-m3는 1024 2강
벡터 저장소 벡터와 원문을 함께 보관하고 가까운 것을 찾아 주는 프로그램. 이 과정에서는 ChromaDB 2강
컬렉션 (collection) 벡터 저장소 안에서 문서 묶음 하나를 담는 단위. 이 과정에서는 ip_docs 2강
거리 공간 두 벡터가 얼마나 가까운지 재는 방법. 이 과정에서는 코사인 2강
메타데이터 (metadata) 문서 본문 외에 함께 저장하는 정보. 문서 종류, 제목, 날짜 2강
거리 (distance) 두 벡터가 얼마나 먼지를 나타내는 값. 작을수록 관련이 크다 3강
유사도 (similarity) 두 벡터가 얼마나 가까운지를 나타내는 값. 클수록 관련이 크다 3강
최소 유사도 (임계값) 이 값을 넘지 못하면 근거로 쓰지 않기로 정한 기준 3강
근거 LLM에게 함께 보내는, 검색으로 고른 문서 3강
프롬프트 (prompt) LLM에게 보내는 지시와 근거와 질문을 합친 문자열 3강
출처 답변이 어느 문서에서 나왔는지 표시한 것 3강

거리와 유사도가 방향이 반대라는 점만 지금 기억해 두십시오. 3강에서 이 둘을 뒤집어 읽는 실수를 일부러 만들어 보고 어떤 증상이 나오는지 확인합니다.

 

8. 이 단계에서 다루지 않는 것 확인하기

→ 이 과정의 범위와 다음 단계로 넘기는 것을 확인합니다.

더보기

이 과정은 8. RAG 강좌 네 단계 중 첫 단계입니다. RAG 한 벌을 처음부터 끝까지 만들어 보는 것이 목적이라, 실무에서 필요한 것 중 여러 가지를 일부러 뺐습니다.

 

특히 배포받은 데이터가 수업용으로 다듬어진 것이라는 점이 중요합니다. 실제 데이터는 다르게 생겼습니다. 어떻게 다듬었는지는 0강 4절에서 배치한 data/출처.md에 항목별로 적혀 있고, 2강 1절에서 같은 표를 함께 봅니다.

 

이 과정에서는 실제 데이터에서는 다루는 단계
파일 하나에 10건이 들어 있다 폴더 다섯 개에 나뉜 파일 25개를 찾아 읽어야 한다 2단계
모든 건의 키가 같다 문서 종류마다 키 이름이 다르다 2단계
제목이 모든 건에 있다 심결례에는 제목 키가 없다 2단계
날짜 형식이 하나다 네 가지 형식이 섞여 있다 2단계
임계값을 기본값 그대로 쓴다 자기 데이터에서 재어 정해야 한다 2단계
문서 종류로 검색 범위를 좁히지 않는다 조건을 걸어 좁혀야 한다 2단계
문서 하나가 최대 419자다 사례 하나가 3만 5천 자가 넘는다 3단계
파일이 모두 정상이다 일부 파일이 깨져 있다 3단계
만든 사람이 쉘에서 혼자 쓴다 다른 사람이 화면으로 쓸 수 있어야 한다 4단계
결과를 눈으로 확인한다 고칠 때마다 자동으로 확인해야 한다 4단계

왼쪽 칸이 이 과정에서 쉬운 이유입니다. 데이터가 정리되어 있어서 쉬운 것이지 RAG가 원래 쉬운 것이 아닙니다. 이 구분을 하지 못하면 다음 단계에서 같은 코드가 왜 깨지는지 이해할 수 없습니다.

이 표는 3강 마지막에서 실행 결과와 함께 다시 확인하고, 2단계 1강에서 학습자가 직접 채우게 됩니다.

 

9. 다음에 만들 것 — 데이터를 검색 문서로 바꾸고 벡터로 저장하기

→ 다음에 만들 파일과 그 이유를 확인합니다.

더보기

3절의 다섯 단계 중 앞의 세 개, 문서 준비와 임베딩과 저장을 2강에서 만듭니다.

2강에서는 먼저 0강 4절에서 배치한 data/ip_qa_basic.json을 열어 봅니다. 10건이 모두 같은 여섯 개 키를 가지고 있어 하나의 규칙으로 처리할 수 있다는 것을 확인하고, 이 파일이 실제 데이터를 수업용으로 다듬은 것이라는 사실과 무엇을 다듬었는지도 함께 봅니다.

 

그다음 질문과 답변을 하나로 합쳐 검색 문서를 만들고, 4절에서 확인한 Ollama 서버에 요청을 보내 각 문서를 벡터로 바꿔 ChromaDB에 저장합니다. 2강을 마치면 컬렉션에 10건이 들어가고, 그 저장이 제대로 되었는지 건수·차원·거리 공간·메타데이터 네 항목으로 확인하게 됩니다.

 

시작하기 전에 지금 상태에서 무엇이 안 되는지 미리 확인해 두십시오. 아래 명령은 배치한 데이터 파일에서 단어가 들어 있는 줄을 찾습니다.

 

쉘 프롬프트

# 문서에 적힌 말로 찾을 때와 다른 표현으로 찾을 때
grep -c "국선대리인" data/ip_qa_basic.json
grep -c "무료로 대리인을 지원받는 제도" data/ip_qa_basic.json
1
0

같은 문서를 가리키는 말인데 첫 줄은 1이고 둘째 줄은 0입니다. 2강은 이 결과에서 시작합니다.