Databricks GenAI Associate

Databricks GenAI Associate 시험 노트개념 정리15 MIN

원본 문서 고르기와 추출 패키지 선택

RAG에 넣을 지식원을 어떻게 가려 내는지, PDF·HTML·DOCX·표에서 글을 뽑을 때 PyPDF·pdfplumber·Unstructured·BeautifulSoup 중 무엇을 고르는지, OCR과 잡음 제거를 언제 하는지 정리합니다.

Data Preparation 영역은 비중이 14%이고, 앞의 설계 영역이 「무엇을 만들지」를 정했다면 여기서는 검색에 넣을 글을 어떻게 마련하는가를 묻습니다. RAG의 답 품질은 모델보다 먼저 이 단계에서 갈립니다. 검색은 인덱스에 든 것만 돌려주므로, 원본이 틀렸거나 추출이 깨졌으면 뒤에서 아무리 프롬프트를 다듬어도 고칠 길이 없습니다. 이 노트는 그 첫 두 걸음, 곧 원본을 고르는 일과 원본에서 글을 뽑아내는 일을 다룹니다.

지식원 판별

답이 들어 있는 곳

지식원(knowledge source)은 RAG 애플리케이션이 답의 근거로 삼는 문서 묶음입니다. 문항은 대개 요구사항을 주고 「어느 자료를 인덱스에 넣어야 하는가」를 묻습니다. 판별 질문은 셋입니다.

  1. 사용자가 물을 질문의 답이 그 자료에 글로 적혀 있는가
  2. 그 자료가 지금도 유효한가 — 폐기된 규정, 옛 버전 매뉴얼은 틀린 답의 근거가 된다
  3. 질문하는 사람이 그 자료를 볼 권한이 있는가

셋째가 자주 빠집니다. 인사 규정 챗봇에 급여 테이블 원본을 넣으면 검색이 그 청크를 누구에게나 돌려줄 수 있습니다. Unity Catalog에 올린 볼륨과 테이블은 권한이 그대로 따라오므로, 권한이 다른 자료는 인덱스를 나누거나 메타데이터로 걸러 낼 수 있게 처음부터 갈라 둡니다.

넣지 않을 자료

반대로 인덱스에 넣으면 해가 되는 자료가 있습니다.

자료 넣으면 생기는 일
같은 문서의 옛 판과 새 판 두 판이 함께 검색돼 서로 다른 답이 섞인다
수치만 있는 원장 테이블 글이 아니라 검색이 의미를 못 잡는다. SQL이 할 일이다
회의록 초안·잡담 채널 확정되지 않은 말이 확정된 근거처럼 인용된다
질문과 무관한 부서 문서 비슷한 낱말 때문에 엉뚱한 청크가 상위에 선다

수치 테이블은 특히 자주 나오는 함정입니다. 「지난 분기 매출 합계」는 검색해서 청크를 읽을 일이 아니라 테이블에 질의할 일이고, 이 경우는 RAG가 아니라 Genie 같은 텍스트-투-SQL 도구의 자리입니다.

형식별 추출

PDF와 추출 패키지

원본 문서에서 사람이 읽는 글만 뽑아 평문으로 만드는 일이 추출(extraction)입니다. PDF는 글자가 페이지 위 좌표에 흩어져 있는 형식이라 문단·표·단 구분이 파일 안에 없고, 패키지가 좌표를 보고 다시 짜 맞춥니다. 그래서 무엇을 쓰느냐에 따라 결과가 달라집니다.

패키지 강한 곳 약한 곳
PyPDF(pypdf) 가볍고 빠르다. 글자층이 있는 단순한 PDF 표 구조가 무너지고 2단 문서의 줄 순서가 섞인다
pdfplumber 글자 좌표를 그대로 주고 표를 행·열로 뽑는다 느리다. 스캔본은 못 읽는다
Unstructured PDF·HTML·DOCX·PPTX를 한 함수로 받아 요소 종류를 붙인다 의존성이 무겁다. 고해상도 모드는 느리다
from pypdf import PdfReader
import pdfplumber

text = "\n".join(p.extract_text() or "" for p in PdfReader("manual.pdf").pages)

with pdfplumber.open("price_list.pdf") as pdf:
    tables = [t for page in pdf.pages for t in page.extract_tables()]

extract_text()가 빈 문자열을 돌려주는 페이지가 있으면 그 페이지에는 글자층이 없다는 뜻입니다. 이 신호가 아래 OCR 판단으로 이어집니다.

HTML과 DOCX

HTML은 태그가 구조를 들고 있어 PDF보다 쉽습니다. BeautifulSoup은 HTML을 트리로 읽어 태그 단위로 고르고 지울 수 있게 해 주는 파서입니다. DOCX는 python-docx로 문단과 제목 스타일을 읽거나 Unstructured에 맡깁니다.

from bs4 import BeautifulSoup

soup = BeautifulSoup(html, "html.parser")
for tag in soup.find_all(["nav", "header", "footer", "script", "style"]):
    tag.decompose()
body = soup.find("main") or soup.body
text = body.get_text("\n", strip=True)

decompose()는 태그를 내용째 트리에서 떼어 냅니다. 본문을 담은 main이나 article만 골라 읽는 것이 가장 확실한 잡음 제거입니다.

표

표는 추출에서 가장 잘 깨지는 자리입니다. 평문으로 흘려 버리면 「제품 A 1,200 제품 B 950」처럼 머리글과 값의 짝이 끊겨 검색된 청크를 읽어도 어느 값이 어느 열인지 알 수 없습니다. 표는 행·열을 살려 뽑은 뒤 마크다운 표나 「열이름: 값」 꼴의 줄로 바꿔 청크에 넣습니다. pdfplumber의 extract_tables()나 Unstructured가 Table로 표시한 요소가 그 출발점입니다.

Unstructured와 OCR

요소 단위 분할

Unstructured의 partition 함수는 파일을 받아 요소(element)의 목록을 돌려줍니다. 요소마다 Title·NarrativeText·ListItem·Table·Header·Footer 같은 종류가 붙어 있어, 뒤 단계에서 종류로 거르거나 제목을 기준으로 묶을 수 있습니다.

from unstructured.partition.auto import partition

elements = partition(filename="policy.pdf", strategy="hi_res")
kept = [e for e in elements if e.category not in ("Header", "Footer", "PageBreak")]

형식마다 다른 패키지를 붙이지 않아도 된다는 것이 가장 큰 장점이고, 요소 종류가 곧 다음 노트에서 다룰 구조 기반 청킹의 재료가 됩니다.

OCR이 필요한 경우

OCR(광학 문자 인식)은 이미지 속 글자를 읽어 텍스트로 바꾸는 기술입니다. 추출 패키지는 PDF 안의 글자층을 읽는 것이지 그림을 읽는 것이 아니므로, 다음 자리에서는 OCR 없이는 글이 한 자도 안 나옵니다.

  • 종이를 스캔한 PDF — 페이지 전체가 이미지 한 장이다
  • 사진으로 찍은 영수증·서식
  • 도면이나 캡처 이미지 속에 든 글자

판별법은 앞에서 본 그대로입니다. 텍스트 추출 결과가 비어 있거나 페이지 수에 비해 글자가 지나치게 적으면 OCR 대상입니다. Unstructured는 strategy로 이를 고르는데, fast는 글자층만 읽고 ocr_only는 OCR만 쓰며 hi_res는 레이아웃 모델로 요소를 가르면서 필요한 곳에 OCR을 씁니다. OCR은 느리고 오인식이 섞이므로 글자층이 있는 문서에까지 거는 것은 오답입니다.

잡음 걸러내기

머리말·바닥글·내비게이션

머리말(header)과 바닥글(footer)은 페이지마다 되풀이되는 회사명·문서 번호·쪽 번호입니다. 웹 문서에서는 메뉴·사이드바·쿠키 배너 같은 내비게이션이 같은 역할을 합니다. 이것들이 남으면 두 가지가 나빠집니다.

  • 모든 청크에 같은 글자가 들어가 청크끼리 임베딩이 서로 닮는다
  • 「회사명」 같은 낱말로 검색하면 본문과 무관한 청크가 전부 걸린다

PDF에서는 여러 페이지의 첫 줄·마지막 줄을 모아 되풀이되는 줄을 지우거나 Unstructured의 요소 종류로 걸러 내고, HTML에서는 앞의 BeautifulSoup 예처럼 태그째 지웁니다.

품질을 떨어뜨리는 잡음

그 밖에 검색 품질을 깎는 것들이 있습니다.

잡음 처리
줄 끝에서 끊긴 낱말과 하이픈 줄을 다시 잇는다
깨진 인코딩 문자 유니코드 정규화로 바로잡거나 버린다
똑같은 문서의 중복 사본 해시로 찾아 하나만 남긴다
목차·색인 페이지 낱말만 잔뜩 있어 검색에 잘 걸리니 뺀다
글자가 몇 자 안 되는 조각 길이 하한을 두고 버린다

잡음 제거는 추출 직후, 청킹 전에 합니다. 청크로 잘린 뒤에는 어느 줄이 머리말이었는지 판단할 문맥이 사라지기 때문입니다. 이 영역의 문항은 대개 「검색 결과에 쪽 번호와 회사명만 가득한 청크가 온다」 같은 증상을 주고 어느 단계를 고칠지를 묻는데, 답은 모델이나 프롬프트가 아니라 추출과 정제 쪽에 있습니다.

연습 문제

  1. 사내 규정 QA 봇을 만들려고 합니다. 인덱스에 넣기에 가장 알맞은 자료는?
    ① 현재 시행 중인 규정집 PDF
    ② 지난 5년간 개정 전 규정집 전체
    ③ 규정 개정을 논의한 잡담 채널 기록
    ④ 월별 경비 지출 원장 테이블
    ①. ②는 옛 판과 새 판이 섞여 다른 답을 낳고, ③은 확정되지 않은 말이며, ④는 수치 테이블이라 검색보다 SQL 질의가 맞습니다.
  2. PDF 500쪽 가운데 120쪽에서 PdfReader(...).pages[i].extract_text()가 빈 문자열을 돌려줍니다. 가장 먼저 의심할 것은?
    ① 임베딩 모델의 최대 토큰 초과
    ② 그 페이지들이 스캔 이미지라 글자층이 없다
    ③ Change Data Feed가 꺼져 있다
    ④ 청크 overlap이 너무 크다
    ②. 글자층이 없으면 추출 패키지가 읽을 글자가 없습니다. 그 페이지들에만 OCR을 겁니다.
  3. 가격표 PDF에서 품목과 가격의 짝이 청크 안에서 끊겨 검색된 답이 엉뚱한 가격을 인용합니다. 알맞은 조치를 둘 고르시오.
    ① pdfplumber의 extract_tables()로 표를 행·열째 뽑는다
    ② 표를 마크다운 표나 「열이름: 값」 줄로 바꿔 넣는다
    ③ 모든 페이지에 OCR을 건다
    ④ LLM의 temperature를 낮춘다
    ⑤ 청크 크기를 줄인다
    ①과 ②. 문제는 표 구조가 추출 단계에서 무너진 것입니다. ③은 글자층이 있는 문서에 불필요하고, ④·⑤는 무너진 짝을 되살리지 못합니다.
  4. 사내 위키 HTML에서 메뉴와 바닥글을 지우려고 BeautifulSoup으로 해당 태그를 찾았습니다. 태그를 내용째 트리에서 떼어 내는 메서드는?
    ① get_text()
    ② find_all()
    ③ decompose()
    ④ prettify()
    ③. find_all()은 찾기만 하고 get_text()는 글을 읽을 뿐입니다.
  5. Unstructured partition의 strategy에 대한 설명으로 옳은 것은?
    ① fast는 모든 페이지에 OCR을 건다
    ② ocr_only는 글자층만 읽는다
    ③ hi_res는 레이아웃 모델로 요소를 가르고 필요한 곳에 OCR을 쓴다
    ④ 전략과 무관하게 결과는 같다
    ③. fast가 글자층만 읽고 ocr_only가 OCR만 씁니다. ①과 ②는 둘이 뒤바뀌었습니다.
  6. 검색 상위 5개 청크가 모두 「㈜한빛 사내 문서 ─ 대외비 ─ 3/210쪽」 같은 줄로 시작하고 본문은 제각각입니다. 어느 단계를 고쳐야 하는가?
    ① 프롬프트에 「머리말을 무시하라」를 넣는다
    ② 추출 후 정제 단계에서 되풀이되는 머리말을 지운다
    ③ 더 큰 LLM으로 바꾼다
    ④ 검색 k를 늘린다
    ②. 되풀이되는 머리말이 청크 임베딩을 서로 닮게 만든 것입니다. 원인이 인덱스 안에 있으므로 생성 쪽을 고쳐서는 안 풀립니다.

추출은 화려하지 않지만 이 영역 문항의 절반이 여기서 풀립니다. 증상이 검색에서 보여도 원인은 대개 한두 단계 앞에 있고, 그 순서는 지식원 고르기 → 추출 → 정제 → 청킹입니다. 청킹은 다음 노트에서 다룹니다.

Databricks GenAI Associate 시험 노트 전체 보기