의료 RAG 실전 — 진료 가이드라인을 AI에 연결하기
한줄 요약
의료 RAG는 “가이드라인 DB + 벡터 검색 + LLM”의 조합이지만, 실제로는 데이터 준비와 신뢰 설계에 전체 공수의 80%가 들어간다.
본문
의료 RAG 파이프라인 전체 구조
[문서 수집 레이어]
ACC/AHA 가이드라인 PDF
KSSO 진료 지침
병원 내부 프로토콜
약물 상호작용 DB
↓
[문서 처리 레이어]
OCR (스캔 PDF 처리)
텍스트 정제
청킹 (섹션 단위)
메타데이터 추출 (출처, 버전, 날짜)
↓
[임베딩 레이어]
텍스트 → 벡터 변환
벡터 DB 저장
인덱스 구축
↓
[검색 레이어]
질문 → 벡터 변환
유사도 검색
메타데이터 필터 (날짜, 기관, 과)
관련 청크 Top 5 선택
↓
[생성 레이어]
청크 + 질문 → 프롬프트 구성
LLM 생성
출처 추적
↓
[신뢰 레이어]
출처 문서 링크 표시
신뢰도 점수 계산
면책 문구 자동 추가
이상값 탐지
1단계: 문서 수집과 정제
이게 가장 고통스러운 단계다. 현실의 의료 문서는:
- 스캔된 PDF라 텍스트 추출이 안 된다
- 표, 그림, 알고리즘이 많다
- 문서마다 형식이 다르다
- 여러 버전이 혼재한다
실용적인 접근:
import pdfplumber
import re
def extract_medical_document(pdf_path: str) -> dict:
"""진료 가이드라인 PDF에서 텍스트와 구조 추출"""
chunks = []
with pdfplumber.open(pdf_path) as pdf:
current_section = ""
current_text = ""
for page in pdf.pages:
text = page.extract_text()
# 섹션 제목 감지 (굵은 글씨 패턴)
lines = text.split('\n')
for line in lines:
if is_section_header(line): # 커스텀 로직
# 이전 섹션 저장
if current_text.strip():
chunks.append({
"section": current_section,
"content": current_text.strip(),
"page": page.page_number
})
current_section = line
current_text = ""
else:
current_text += line + " "
# 표 추출 (별도 처리)
tables = page.extract_tables()
for table in tables:
chunks.append({
"section": current_section,
"content": format_table(table),
"type": "table",
"page": page.page_number
})
return chunks
2단계: 메타데이터 설계
청크마다 검색 필터링에 쓸 메타데이터를 설계한다.
{
"chunk_id": "aha_hf_2022_section_3_2_chunk_4",
"content": "심부전 환자에서 ACE 억제제 또는 ARB는...",
"metadata": {
"source": "AHA Heart Failure Guidelines 2022",
"guideline_type": "international",
"specialty": "cardiology",
"version": "2022",
"effective_date": "2022-04-01",
"expiry_date": null,
"section": "3.2 약물 치료",
"page": 45,
"evidence_level": "Class I, Level A",
"language": "en"
}
}
메타데이터가 잘 설계되면 “2022년 이후 최신 심장학 가이드라인에서만 검색”이 가능해진다.
3단계: 검색 쿼리 최적화
사용자 질문을 그대로 검색 쿼리로 쓰면 성능이 떨어진다.
HyDE (Hypothetical Document Embedding) 가상의 이상적인 답변을 먼저 생성하고, 그 답변의 벡터로 검색한다.
def search_with_hyde(user_question: str, vector_db) -> list[str]:
# 1단계: 가상 답변 생성
hypothetical = llm.generate(
f"다음 질문에 대한 이상적인 진료 가이드라인 발췌문을 작성하세요: {user_question}"
)
# 2단계: 가상 답변으로 검색
results = vector_db.search(embed(hypothetical), top_k=5)
return results
실제 답변처럼 생긴 텍스트의 벡터가 관련 문서를 더 잘 찾는다.
쿼리 확장 한 질문을 여러 표현으로 만들어 각각 검색하고 결합한다.
원래 질문: "와파린 용량 조절"
확장:
- "warfarin dose adjustment"
- "항응고제 INR 목표"
- "쿠마딘 용량 변경"
→ 세 버전으로 검색 → 결과 합치기
4단계: 신뢰 레이어 설계
의료 RAG에서 가장 중요한 단계이지만 종종 생략된다.
출처 투명성
AI 응답:
"...ACE 억제제는 심부전 환자에서 사망률을 25% 감소시킵니다.
[출처]
📄 AHA Heart Failure Guidelines 2022
섹션 3.2 약물 치료 (45페이지)
Evidence Level: Class I, Level A
[원문 보기]"
신뢰도 표시
높은 신뢰: 검색된 문서와 질문의 유사도 > 0.85
→ 정상 응답
중간 신뢰: 유사도 0.65-0.85
→ "관련 문서를 찾았으나 완전히 일치하지 않을 수 있습니다"
낮은 신뢰: 유사도 < 0.65
→ "이 질문에 대한 관련 가이드라인을 찾지 못했습니다.
직접 가이드라인을 확인하세요."
가이드라인 유효성 확인
def check_guideline_validity(metadata: dict) -> str:
if metadata.get("expiry_date"):
if datetime.now() > metadata["expiry_date"]:
return "⚠️ 이 가이드라인은 만료되었습니다. 최신 버전을 확인하세요."
guideline_age = (datetime.now() - metadata["effective_date"]).days / 365
if guideline_age > 5:
return "ℹ️ 이 가이드라인은 5년 이상 경과했습니다. 갱신 여부를 확인하세요."
return ""
실제 구현 시 마주치는 현실 문제
한국어 의료 임베딩 성능: 영어로 학습된 임베딩 모델은 한국어 의료 용어에서 성능이 떨어진다. 한국어 의료 코퍼스로 파인튜닝된 임베딩 모델을 쓰거나, 영어와 한국어를 병렬로 색인한다.
PDF 스캔 품질: 오래된 병원 문서는 스캔 품질이 나쁘다. OCR 정확도가 낮으면 검색도 나빠진다. 수동 검수가 필요한 문서가 생각보다 많다.
문서 버전 관리: 가이드라인이 개정되면 구버전을 어떻게 할 것인가? 그냥 덮으면 과거 버전을 참조할 수 없다. 버전별로 관리하고, 기본적으로 최신 버전을 우선하는 전략이 필요하다.
의사의 신뢰: 기술보다 어려운 문제. “AI가 틀리면 누가 책임지는가?” 출처 투명성과 면책 구조를 명확히 설계해야 실제 임상에서 쓸 수 있다.
RAG 기술보다 이 마지막 문제가 의료 AI 도입의 진짜 병목이다.