LLM 엔지니어링11장 · 폐쇄망 문서 파이프라인과 폐쇄형 RAG15 / 23← 이전목차다음 →MetaContents

11장. 폐쇄망 문서 파이프라인과 폐쇄형 RAG

실습 1 — 오프라인으로 돌아가는지 확인하기

이 장의 모든 실습을 바깥 연결을 막은 채 돌리기 위해, 막는 코드를 out_offline.py 파일로 만들고 확인합니다. 임베딩 모델이 캐시에서 불러와지는지, Chroma 가 통계를 보내지 않는지, 사내 주소인 Ollama 는 열려 있는지 봅니다. 7장의 임베딩 모델(intfloat/multilingual-e5-small)을 한 번은 내려받아 두었어야 하고, Ollama 가 켜져 있어야 합니다. 주피터에서 따라 할 때는 커널을 새로 시작한 뒤에 돌립니다.

1단계. 바깥 연결을 막는 파일 만들기

Python
from pathlib import Path

# 모든 실습이 첫머리에서 불러 쓸 모듈을 파일로 만든다
Path("out_offline.py").write_text('''"""폐쇄망 흉내: localhost 밖으로 나가는 연결을 막고, 막은 주소를 적어 둔다."""
import os
import socket

BLOCKED = []                                   # 막은 주소를 차례로 적는다
LOCAL = {"localhost", "127.0.0.1", "::1", None}
_real = socket.getaddrinfo                     # 주소를 IP 로 바꾸는 원래 함수


def _only_local(host, *args, **kwargs):
    if host not in LOCAL:
        BLOCKED.append(host)
        raise OSError(f"폐쇄망: {host} 로 나가는 연결을 막았습니다")
    return _real(host, *args, **kwargs)


def guard():
    """라이브러리의 오프라인 설정을 켜고, 바깥 연결을 막는다. 라이브러리를 import 하기 전에 부른다."""
    os.environ["HF_HUB_OFFLINE"] = "1"          # Hugging Face: 허브에 묻지 않고 캐시만 쓴다
    os.environ["TRANSFORMERS_OFFLINE"] = "1"
    os.environ["ANONYMIZED_TELEMETRY"] = "False"   # Chroma: 사용 통계를 보내지 않는다
    socket.getaddrinfo = _only_local


def release():
    """막기를 푼다."""
    socket.getaddrinfo = _real
''', encoding="utf-8")

import sys
sys.path.insert(0, ".")                        # 작업 폴더(data)에 만든 out_offline.py 를 찾게 한다
import out_offline
out_offline.guard()
# 결과: 막기가 켜졌는지(바꿔 끼운 함수인지)
import socket
print("막기 켜짐:", socket.getaddrinfo is not out_offline._real)
출력
막기 켜짐: True

2단계. 바깥은 막히고 안쪽은 열렸는지 보기

Python
import urllib.request

# 바깥 주소: 연결하기 전에 막혀야 한다(실제로 인터넷에 나가지 않는다)
try:
    urllib.request.urlopen("https://huggingface.co", timeout=5)
except Exception as e:
    print("바깥:", type(e).__name__, e)

# 사내 주소(이 PC 의 Ollama): 열려 있어야 한다
print("안쪽:", urllib.request.urlopen("http://localhost:11434/api/version").read().decode())
print("막은 주소:", out_offline.BLOCKED)
출력
바깥: URLError <urlopen error 폐쇄망: huggingface.co 로 나가는 연결을 막았습니다>
안쪽: {"version":"0.40.1"}
막은 주소: ['huggingface.co']

3단계. 모델과 벡터DB 를 오프라인으로 열기

Python
from sentence_transformers import SentenceTransformer
import chromadb
from chromadb.config import Settings

# 캐시에 있는 임베딩 모델을 부른다. 오프라인 설정 덕에 허브에 묻지 않는다
emb = SentenceTransformer("intfloat/multilingual-e5-small", device="cpu")
v = emb.encode("query: 반차는 언제까지 신청하나요?", normalize_embeddings=True)
print("임베딩:", v.shape)

# Chroma 를 통계 없이 연다. anonymized_telemetry=False 가 통계 보내기를 끈다
client = chromadb.EphemeralClient(settings=Settings(anonymized_telemetry=False))
print("Chroma 컬렉션 수:", len(client.list_collections()))

# 결과: 여기까지 새로 막힌 주소가 없으면, 모델과 DB 가 바깥을 찾지 않은 것이다
print("막은 주소:", out_offline.BLOCKED)
출력
임베딩: (384,)
Chroma 컬렉션 수: 0
막은 주소: ['huggingface.co']

4단계. 들고 갈 모델 목록 만들기

Python
import json
import pandas as pd
from huggingface_hub import scan_cache_dir

rows = []
# Hugging Face 캐시에 든 모델(이 과정에서 내려받은 것)과 크기
for repo in scan_cache_dir().repos:
    rows.append({"종류": "HF", "이름": repo.repo_id, "크기MB": round(repo.size_on_disk / 1e6)})
# Ollama 에 든 모델과 크기(사내 주소이므로 열린다)
tags = json.loads(urllib.request.urlopen("http://localhost:11434/api/tags").read())
for m in tags["models"]:
    rows.append({"종류": "Ollama", "이름": m["name"], "크기MB": round(m["size"] / 1e6)})

carry = pd.DataFrame(rows).sort_values(["종류", "크기MB"], ascending=[True, False])
carry.to_csv("out_carry_list.csv", index=False, encoding="utf-8-sig")
print(carry.to_string(index=False))
print("합계:", f"{carry['크기MB'].sum():,}MB")
출력
    종류                                         이름  크기MB
    HF                         skt/kogpt2-base-v2  1029
    HF                          Qwen/Qwen2.5-0.5B  1000
    HF                 Qwen/Qwen2.5-0.5B-Instruct  1000
    HF                 Helsinki-NLP/opus-mt-ko-en   628
    HF             intfloat/multilingual-e5-small   493
    HF cross-encoder/mmarco-mMiniLMv2-L12-H384-v1   493
Ollama                                 qwen2.5:3b  1930
Ollama                                llama3.2:1b  1321
Ollama                               qwen2.5:1.5b   986
Ollama                                  gemma3:1b   806
Ollama                               qwen2.5:0.5b   398
Ollama                    nomic-embed-text:latest   274
합계: 10,358MB

이 실습이 뜻하는 것

이 실습은 보안 구역에 들어가기 전에 바깥 통신을 끊고, 들고 갈 물건을 점검하는 일과 같습니다. 출입구에 "바깥으로 거는 전화는 끊고 구내 전화만 잇는" 교환원을 세우는 규칙을 파일로 써 두고 켜 봅니다(1단계). 바깥 번호와 구내 번호로 각각 걸어 보고(2단계), 들고 들어간 사전(임베딩 모델)과 서류함(벡터DB)을 펼쳐도 바깥으로 전화를 걸지 않는지 보고(3단계), 마지막으로 들고 갈 물건 목록과 무게를 적습니다(4단계). 여기서 교환원이 guard() 가 바꿔 끼운 socket.getaddrinfo, 바깥 번호가 huggingface.co, 구내 번호가 localhost 의 Ollama, 물건 목록이 out_carry_list.csv 입니다. 벡터DB 의 DB 는 DataBase(데이터베이스)의 줄임입니다. 비유와 다른 점은, 진짜 폐쇄망은 네트워크 장비가 막지만 여기서는 파이썬 프로그램 하나 안에서만 막는 흉내라는 것입니다.

단계출력뜻
1. 막기막기 켜짐: True주소를 찾는 함수가 바꿔 끼운 함수로 바뀌었습니다. 이제 localhost 밖의 주소는 연결 전에 막힙니다
2. 바깥URLError … huggingface.co 로 나가는 연결을 막았습니다인터넷 주소로 가려 하자 실제로 나가기 전에 막혔습니다
2. 안쪽{"version":"0.40.1"}같은 PC 의 Ollama 는 판 번호로 답했습니다. 사내 주소는 열려 있다는 뜻입니다
2. 막은 주소['huggingface.co']지금까지 막은 주소는 방금 시험한 하나입니다
3. 모델과 DB임베딩: (384,), Chroma 컬렉션 수: 0임베딩 모델이 캐시에서 불러와져 문장 하나를 숫자 384개로 바꿨고, Chroma 도 빈 상태로 열렸습니다
3. 막은 주소['huggingface.co'] 그대로모델과 DB 를 여는 동안 새로 막힌 주소가 없습니다. 둘 다 바깥을 찾지 않았습니다
4. 들고 갈 목록HF 모델 6개, Ollama 모델 6개, 합계: 10,358MBHF 는 Hugging Face 입니다. 이 PC 에 든 모델을 모두 적으면 10GB 남짓입니다. 이 교안에서 쓰지 않는 모델도 섞여 있어 실제 반입 목록은 여기서 골라 만듭니다

한 줄로 줄이면, 바깥 연결을 막은 채로도 모델과 벡터DB 가 캐시와 사내 주소만으로 돌아간다는 것을 확인한 실습입니다. 여기서 만든 out_offline.py 를 실습 4, 6과 프로젝트 1이 첫머리에서 불러 씁니다. 다만 막은 것은 이 파이썬 프로그램 안의 주소 찾기뿐이라, PC 전체가 폐쇄망이 되었거나 다른 프로그램의 연결까지 막혔다는 뜻은 아닙니다.

코드 설명

  • socket.getaddrinfo 는 huggingface.co 같은 이름을 IP 로 바꾸는 함수입니다. 바깥으로 나가는 연결은 대부분 이 함수를 먼저 거치므로, 바꿔 끼우면 연결하기 전에 막을 수 있습니다.
  • 2단계에서 huggingface.co 로 가는 연결은 이름을 IP 로 바꾸는 단계에서 막혀 URLError 가 났고, 막은 주소에 huggingface.co 하나가 적혔습니다. 실제로 인터넷에 나가기 전에 막힌 것입니다. 같은 때 localhost 의 Ollama 는 판(0.40.1)을 돌려줬습니다.
  • 3단계에서 임베딩 모델을 부르고 Chroma 를 연 뒤에도 막은 주소는 huggingface.co 하나 그대로입니다. 두 라이브러리가 바깥을 찾지 않았다는 뜻입니다. 실행하면 "Could not reach the Hub … offline mode is enabled" 라는 알림이 나오는데, 허브에 묻지 않고 캐시를 쓴다는 뜻이라 오류가 아닙니다.
  • scan_cache_dir() 는 Hugging Face 캐시에 든 모델과 크기를, Ollama 의 /api/tags 는 Ollama 모델과 크기를 돌려줍니다. 이 PC 에는 Hugging Face 모델 6개와 Ollama 모델 6개, 모두 10,358MB 가 있었습니다. 이 교안에서 쓰지 않는 skt/kogpt2-base-v2 와 nomic-embed-text 도 들어 있으니, 반입 목록은 이 표에서 쓸 것만 골라 만듭니다.
← 이전 절11장 · 실습 지도다음 절 →11장 · 실습 2 — 사내 문서 모으기