RAG + 에이전트 — 의료 가이드라인 기반 AI 어시스턴트

…

한줄 요약

순수 에이전트는 학습 시점 이후 업데이트된 가이드라인을 알지 못한다. RAG-Agent는 벡터 DB에 최신 의료 문서를 저장하고 에이전트가 쿼리하는 구조로, 근거 기반 답변을 가능하게 한다.


순수 에이전트의 한계

LLM은 학습 시점까지의 의료 지식만 갖고 있다.

  • ADA 당뇨 가이드라인은 매년 업데이트된다
  • 식약처 허가사항은 수시로 변경된다
  • 병원 내부 프로토콜은 LLM이 전혀 모른다

이런 “최신성 문제”와 “기관 특수 지식 문제”를 동시에 해결하는 것이 RAG-Agent다.


RAG-Agent 아키텍처

[에이전트]
    ↓ 질문 수신
    ↓ "이 질문에 답하려면 가이드라인 검색이 필요하다"
    ↓ search_guideline_rag() 호출
    ↓
[RAG 레이어]
벡터 DB ──→ 의미 검색 ──→ 관련 청크 반환
    ↓
[에이전트]
청크를 컨텍스트로 받아 근거 기반 답변 생성
출처 인용 포함

의료 RAG 구축 — 문서 종류

MEDICAL_KNOWLEDGE_SOURCES = {
    "clinical_guidelines": {
        "sources": ["ADA 2025", "ACC/AHA 2024", "KDA 2024", "KDIGO 2024"],
        "update_frequency": "연 1회",
        "chunk_strategy": "section"
    },
    "drug_label": {
        "sources": ["식약처 허가사항", "FDA label"],
        "update_frequency": "수시",
        "chunk_strategy": "paragraph"
    },
    "hospital_protocol": {
        "sources": ["원내 임상 경로", "수술 전 체크리스트"],
        "update_frequency": "분기",
        "chunk_strategy": "procedure_step"
    }
}

문서 청킹(Chunking) 전략

문서를 어떻게 나누느냐가 검색 품질을 결정한다.

from langchain.text_splitter import RecursiveCharacterTextSplitter

def chunk_medical_document(text: str, doc_type: str) -> list[str]:
    """문서 유형에 따라 청킹 전략을 다르게 적용한다."""

    if doc_type == "guideline":
        # 가이드라인: 섹션 단위로 분리 (권고 등급 유지)
        splitter = RecursiveCharacterTextSplitter(
            chunk_size=800,
            chunk_overlap=150,       # 권고 맥락이 끊기지 않도록 오버랩
            separators=["\n## ", "\n### ", "\n\n", "\n"]
        )

    elif doc_type == "drug_label":
        # 약물 허가사항: 항목 단위 분리 (용법용량, 금기, 주의사항)
        splitter = RecursiveCharacterTextSplitter(
            chunk_size=500,
            chunk_overlap=50,
            separators=["\n[", "\n용법", "\n금기", "\n주의"]
        )

    else:
        # 기본
        splitter = RecursiveCharacterTextSplitter(
            chunk_size=600,
            chunk_overlap=100
        )

    return splitter.split_text(text)

벡터 DB 구축 및 검색

from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
import hashlib

qdrant = QdrantClient(url="http://localhost:6333")

def index_medical_document(text: str, metadata: dict):
    """의료 문서를 벡터 DB에 색인한다."""
    chunks = chunk_medical_document(text, metadata["doc_type"])

    points = []
    for i, chunk in enumerate(chunks):
        vector = embed_text(chunk)  # 임베딩 모델 호출
        point_id = int(hashlib.md5(f"{metadata['source']}_{i}".encode()).hexdigest()[:8], 16)

        points.append(PointStruct(
            id=point_id,
            vector=vector,
            payload={
                "content": chunk,
                "source": metadata["source"],
                "doc_type": metadata["doc_type"],
                "updated_at": metadata["updated_at"],
                "page": metadata.get("page")
            }
        ))

    qdrant.upsert(collection_name="medical_knowledge", points=points)
    print(f"{metadata['source']}: {len(chunks)}개 청크 색인 완료")


def search_medical_knowledge(query: str, doc_type: str = None,
                              top_k: int = 5) -> list[dict]:
    """의미 검색으로 관련 의료 문서를 검색한다."""
    query_vector = embed_text(query)

    filter_condition = None
    if doc_type:
        filter_condition = {"must": [{"key": "doc_type", "match": {"value": doc_type}}]}

    results = qdrant.search(
        collection_name="medical_knowledge",
        query_vector=query_vector,
        query_filter=filter_condition,
        limit=top_k,
        with_payload=True
    )

    return [
        {
            "content": r.payload["content"],
            "source": r.payload["source"],
            "score": r.score,
            "updated_at": r.payload["updated_at"]
        }
        for r in results
    ]

RAG 도구를 에이전트에 연결

import anthropic
import json

client = anthropic.Anthropic()

rag_tool = {
    "name": "search_guideline_rag",
    "description": "벡터 DB에서 최신 의료 가이드라인을 의미 검색한다. "
                   "일반적인 의학 지식보다 최신 공식 가이드라인이 필요할 때 사용한다.",
    "input_schema": {
        "type": "object",
        "properties": {
            "query": {"type": "string", "description": "검색할 임상 질문"},
            "doc_type": {
                "type": "string",
                "enum": ["guideline", "drug_label", "hospital_protocol"],
                "description": "검색 대상 문서 유형"
            }
        },
        "required": ["query"]
    }
}

def rag_agent(clinical_question: str) -> str:
    messages = [{"role": "user", "content": clinical_question}]

    while True:
        response = client.messages.create(
            model="claude-opus-4-5",
            max_tokens=4096,
            tools=[rag_tool],
            messages=messages,
            system="임상 질문에 답할 때 반드시 search_guideline_rag 도구로 "
                   "최신 가이드라인을 확인하고, 출처를 명시하여 답변하라."
        )

        if response.stop_reason == "end_turn":
            return response.content[0].text

        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                # RAG 검색 실행
                search_results = search_medical_knowledge(
                    query=block.input["query"],
                    doc_type=block.input.get("doc_type")
                )
                # 검색 결과를 LLM이 읽을 수 있는 텍스트로 변환
                formatted = "\n\n".join([
                    f"[출처: {r['source']} ({r['updated_at']})]\n{r['content']}"
                    for r in search_results
                ])
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": formatted
                })

        messages.append({"role": "assistant", "content": response.content})
        messages.append({"role": "user", "content": tool_results})

방식특징의료 적합성
Keyword정확한 단어 일치약물명 검색에 유리
Semantic의미 기반 유사도임상 질문 이해에 유리
Hybrid두 방식 결합가장 높은 검색 품질

의료 RAG에서는 “당뇨 신합병증 치료”와 “당뇨병성 신증 관리”가 같은 의미임을 이해해야 하므로 Semantic 또는 Hybrid 방식을 권장한다.


핵심 정리

  • RAG-Agent는 에이전트가 벡터 DB에 저장된 최신 문서를 쿼리하여 근거 기반 답변을 생성한다
  • 청킹 전략은 가이드라인에는 섹션 단위(800자), 약물 허가사항에는 항목 단위(500자)가 적합하다
  • 검색 결과에 출처와 업데이트 날짜를 포함하면 의사가 원문을 확인할 수 있다
  • Hybrid 검색(키워드 + 의미)이 의료 문서에서 가장 높은 검색 품질을 보인다
  • RAG DB는 가이드라인 업데이트 주기에 맞춰 정기적으로 재색인해야 한다

관련 글