RAG

RAGFlow 완전 가이드 — AI 할루시네이션의 근본 해결책, 출처 추적 가능한 엔터프라이즈 RAG

cell-devlog 2026. 5. 20. 14:58
반응형

"AI가 왜 이걸 지어냈냐?"는 질문이 반복된다면 RAG 파이프라인이 잘못된 겁니다. 문서 파싱이 깨지거나, 청킹이 문맥을 잘라버리거나, 검색된 청크가 엉뚱한 내용이거나. RAGFlow는 이 세 가지 실패 지점을 설계 원칙으로 해결합니다. 답변마다 어느 문서의 어느 부분에서 왔는지 추적 가능한 인용이 붙습니다.

[핵심 요약]
→ RAGFlow: Infiniflow가 만든 오픈소스 RAG 엔진 — GitHub 73,200+ 스타, Apache 2.0
→ 핵심 철학: 답변 품질은 문서 파싱 품질에서 시작 — "Garbage in, garbage out" 해결
→ DeepDoc: PDF·Word·Excel·스캔본·PPT의 표·이미지·복잡한 레이아웃 정확히 파싱
→ 인용 추적: 모든 답변에 출처 문서 + 청크 위치 표시 — 할루시네이션 검증 가능
→ 청킹 시각화: 어떻게 잘렸는지 UI에서 직접 확인 + 수동 개입 가능
→ 하이브리드 검색: BM25(키워드) + 벡터 검색 + 커스텀 스코어링 + 리랭킹
→ 에이전트 + MCP: 단순 RAG → 멀티 에이전트 워크플로우 + 외부 도구 연결
→ 최신 버전: v0.25.4 (2026년 5월 기준), Docker로 10분 내 셋업
→ 저장소: Elasticsearch(기본) 또는 Infinity 선택

왜 기존 RAG가 할루시네이션을 못 막나

[기존 RAG 1.0의 3가지 실패 지점]

실패 1: 문서 파싱 오류
→ PDF를 단순 텍스트 추출 → 표가 뭉개짐
→ 멀티컬럼 레이아웃 → 순서 섞임
→ 이미지 속 텍스트 → 누락
→ 스캔 문서 → 인식 불가
→ 잘못 파싱된 텍스트 → 아무리 좋은 검색도 무의미

실패 2: 청킹 문제
→ 고정 크기 청킹 → 문장 중간에서 잘림
→ 관련된 표와 설명이 서로 다른 청크에 분리
→ 청킹이 어떻게 됐는지 볼 방법 없음 → 디버깅 불가

실패 3: 검색-생성 단절
→ 의미적으로 유사한 청크가 검색됨
→ 그러나 실제로 답변에 필요한 컨텍스트가 아님
→ LLM이 부족한 컨텍스트를 채우기 위해 지어냄
→ 사용자는 어디서 온 답변인지 알 수 없음

[RAGFlow의 접근]
→ DeepDoc으로 파싱 정확도 근본 향상
→ 청킹 시각화 + 수동 개입으로 검증 가능
→ 하이브리드 검색 + 리랭킹으로 검색 정확도 향상
→ 모든 답변에 출처 인용 → 사용자가 직접 검증

실전 1 — Docker로 10분 설치

시스템 요구사항

최소:
→ CPU: 4코어
→ RAM: 16GB
→ 디스크: 50GB
→ Docker ≥ 24.0.0
→ Docker Compose ≥ v2.26.1

권장 (프로덕션):
→ CPU: 8코어+
→ RAM: 32GB+
→ GPU: NVIDIA GPU (DeepDoc 가속용, 선택)
→ 디스크: 100GB+

GPU 없이도 동작 (CPU 모드)

설치

# 1. vm.max_map_count 설정 (Elasticsearch 필수)
sudo sysctl -w vm.max_map_count=262144
# 재부팅 후에도 유지
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf

# 2. 레포지토리 클론
git clone https://github.com/infiniflow/ragflow.git
cd ragflow/docker

# 3. 버전 확인 및 설정
# docker/.env 에서 버전 확인
cat .env | grep RAGFLOW_IMAGE
# RAGFLOW_IMAGE=infiniflow/ragflow:v0.25.4

# 4. 실행 — CPU 모드 (기본)
docker compose -f docker-compose.yml up -d

# GPU 가속 모드 (NVIDIA GPU 있을 때)
sed -i '1i DEVICE=gpu' .env
docker compose -f docker-compose.yml up -d

# 5. 실행 확인
docker logs -f docker-ragflow-1
# "RAGFlow is started" 메시지 확인 후 접속

# 6. 브라우저 접속
# http://YOUR_SERVER_IP (포트 80, 생략 가능)
# 포트 충돌 시 (.env 수정)
SVR_HTTP_PORT=8080   # 80 → 8080으로 변경

# 재시작
docker compose -f docker-compose.yml restart

저장소 선택 — Elasticsearch vs Infinity

# 기본: Elasticsearch (안정적, 검증됨)
# docker/.env 에서 확인:
# DOC_ENGINE=elasticsearch  (기본값)

# Infinity로 전환 (더 빠름, x86 Linux만 지원):
# docker/.env 수정:
# DOC_ENGINE=infinity

# 주의: ARM64(Apple Silicon, Raspberry Pi) → Infinity 미지원
# ARM: Elasticsearch만 사용

실전 2 — LLM + 임베딩 모델 연결

[RAGFlow UI에서 설정]

1. 우상단 프로필 아이콘 → Model Providers
2. 사용할 LLM 클릭 → API Key 입력

지원 LLM 프로바이더:
→ OpenAI (GPT-5.4, GPT-4o 등)
→ Anthropic (Claude Sonnet 4.6, Opus 4.7)
→ Google (Gemini 3.5 Flash, Gemini 3.1 Pro)
→ DeepSeek
→ Ollama (로컬 모델 — 완전 오프라인)
→ Xinference, LocalAI
→ 기타 OpenAI 호환 엔드포인트

3. System Model Settings → 기본 모델 지정:
→ Chat Model: Claude Sonnet 4.6 (권장)
→ Embedding Model: text-embedding-3-small 또는 nomic-embed-text
→ Image-to-Text: 멀티모달 지원 모델 (스캔 문서용)
→ Rerank Model: 검색 정확도 향상 (선택)
→ Speech-to-Text: 오디오 문서용 (선택)
# service_conf.yaml.template — 기본 LLM 설정
user_default_llm:
  factory: "OpenAI"         # 또는 "Anthropic", "Google", "Ollama"
  api_key: "your-api-key"
  base_url: ""              # OpenAI 호환 커스텀 엔드포인트 시 입력

# Ollama 로컬 모델 (완전 오프라인):
user_default_llm:
  factory: "Ollama"
  api_key: ""
  base_url: "http://ollama:11434"  # Docker 네트워크 내

실전 3 — 지식 베이스(Knowledge Base) 구축

데이터셋 생성 + 문서 업로드

[UI 워크플로우]

1. Knowledge Base 탭 → + New Knowledge Base
2. 이름 입력 → 언어 선택 → Create

3. 업로드 지원 포맷:
   → PDF (표·이미지 포함 복잡한 레이아웃)
   → Word (.docx, .doc)
   → Excel (.xlsx, .xls)
   → PowerPoint (.pptx, .ppt)
   → 텍스트 (.txt, .md, .html, .csv)
   → 이미지 (JPG, PNG — OCR 처리)
   → 스캔 PDF (OCR 포함)
   → 웹 페이지 (URL 직접 입력)

4. 파일 드래그 앤 드롭 또는 URL 입력

5. 파싱 시작 → 진행률 표시
   (복잡한 PDF는 수 분 소요)

청킹 전략 선택 — 핵심 설정

[청킹 템플릿 — 문서 유형에 맞게 선택]

General (기본):
→ 일반 텍스트 문서
→ 단락 경계 기준 청킹
→ 대부분의 경우 시작점

Q&A:
→ 질문-답변 형태의 문서
→ FAQ, 고객 지원 문서에 적합
→ 질문-답변 쌍을 하나의 청크로

Resume (이력서):
→ 구조화된 이력서 파싱 특화

Paper:
→ 학술 논문 구조 인식
→ Abstract, Methodology, Results 섹션 구분

Book:
→ 긴 책·레포트
→ 챕터 단위 청킹

Laws:
→ 법률 문서 특화
→ 조항·항목 단위 청킹

Manual:
→ 기술 매뉴얼, 제품 문서
→ 단계별 절차 보존

Table:
→ 표 중심 문서 (Excel, 데이터 시트)
→ 행·열 관계 유지

[청킹 시각화]
→ 파싱 완료 후 "Chunk" 탭 클릭
→ 각 청크가 어떻게 잘렸는지 시각적 확인
→ 이상한 청크 발견 시:
   - 직접 편집 가능
   - 삭제 가능
   - 수동 추가 가능
→ 이것이 RAGFlow가 다른 RAG와 근본적으로 다른 점

실전 4 — 채팅 어시스턴트 생성

[Chat Assistant 생성]

1. Chat 탭 → + New Assistant
2. 이름 입력
3. 연결할 Knowledge Base 선택 (복수 선택 가능)
4. 모델 선택 (KB별 다른 모델 지정 가능)
5. 시스템 프롬프트 설정

핵심 설정:
→ Empty Response: "답변을 찾을 수 없습니다"
   (비워두면 할루시네이션 발생 가능 — 반드시 설정)
→ Top N: 검색할 청크 수 (기본 8)
→ Similarity Threshold: 유사도 임계값 (기본 0.2)
   높일수록 엄격한 검색 (할루시네이션 감소, 검색 누락 증가)
→ Keywords Similarity Weight: BM25 가중치 (0~1)
→ Reranking Model: 검색 정확도 향상 (선택)
[답변에서 인용 확인]

RAGFlow 채팅에서 답변 받으면:
→ 각 문장 옆에 인용 번호 표시 [1] [2]
→ 클릭 → 원본 문서의 정확한 위치로 이동
→ 어느 PDF 몇 페이지 어느 단락에서 왔는지 확인
→ "이게 맞는 정보인가?" → 원문 직접 검증 가능

이것이 엔터프라이즈에서 RAGFlow를 선택하는 이유:
→ 법률: "이 조항 어느 계약서 어디에 있나?"
→ 금융: "이 수치 어느 보고서에서 나왔나?"
→ 의료: "이 지침 어느 프로토콜 기반인가?"
→ 컴플라이언스: 모든 답변의 근거 추적 가능

실전 5 — API로 코드 통합

# RAGFlow REST API 사용
import httpx
import os

RAGFLOW_URL = "http://your-ragflow-server"
RAGFLOW_API_KEY = "your-api-key"  # UI에서 발급

headers = {
    "Authorization": f"Bearer {RAGFLOW_API_KEY}",
    "Content-Type": "application/json"
}

# ── 데이터셋 목록 조회 ────────────────────────────────
response = httpx.get(
    f"{RAGFLOW_URL}/api/v1/datasets",
    headers=headers
)
datasets = response.json()["data"]
print(f"데이터셋 수: {len(datasets)}")
for ds in datasets:
    print(f"  {ds['name']}: {ds['document_count']}개 문서")
# ── 문서 업로드 ───────────────────────────────────────
dataset_id = "your-dataset-id"

with open("company_policy.pdf", "rb") as f:
    response = httpx.post(
        f"{RAGFLOW_URL}/api/v1/datasets/{dataset_id}/documents",
        headers={"Authorization": f"Bearer {RAGFLOW_API_KEY}"},
        files={"file": ("company_policy.pdf", f, "application/pdf")}
    )
print(f"업로드 결과: {response.json()}")

# ── 파싱 시작 ─────────────────────────────────────────
doc_id = response.json()["data"]["id"]
httpx.post(
    f"{RAGFLOW_URL}/api/v1/datasets/{dataset_id}/chunks",
    headers=headers,
    json={"document_ids": [doc_id]}
)
# ── 채팅 (인용 포함) ──────────────────────────────────
import json

chat_id = "your-chat-assistant-id"

# 채팅 세션 생성
session = httpx.post(
    f"{RAGFLOW_URL}/api/v1/chats/{chat_id}/sessions",
    headers=headers,
    json={"name": "질의 세션"}
).json()["data"]

session_id = session["id"]

# 질문 + 스트리밍 응답
with httpx.stream(
    "POST",
    f"{RAGFLOW_URL}/api/v1/chats/{chat_id}/sessions/{session_id}/completion",
    headers=headers,
    json={
        "question": "2026년 출장비 정책은 어떻게 되나요?",
        "stream": True
    }
) as response:
    full_answer = ""
    references = []

    for line in response.iter_lines():
        if not line or line == "data: [DONE]":
            continue
        if line.startswith("data: "):
            data = json.loads(line[6:])
            # 스트리밍 텍스트
            if "answer" in data:
                chunk = data["answer"]
                print(chunk, end="", flush=True)
                full_answer += chunk
            # 인용 정보
            if "reference" in data:
                references = data["reference"].get("chunks", [])

    print("\n\n[출처 인용]")
    for i, ref in enumerate(references, 1):
        print(f"[{i}] 문서: {ref.get('document_name')}")
        print(f"     내용: {ref.get('content', '')[:100]}...")
# ── 직접 검색 (채팅 없이) ─────────────────────────────
response = httpx.post(
    f"{RAGFLOW_URL}/api/v1/retrieval",
    headers=headers,
    json={
        "question": "출장비 한도",
        "dataset_ids": [dataset_id],
        "top_k": 5,
        "similarity_threshold": 0.3,
        "keyword_similarity_weight": 0.3,  # BM25 가중치
        "rerank_id": "",  # 리랭크 모델 ID (선택)
    }
)

chunks = response.json()["data"]["chunks"]
for chunk in chunks:
    print(f"유사도: {chunk['similarity']:.3f}")
    print(f"문서: {chunk['document_name']}")
    print(f"내용: {chunk['content'][:200]}")
    print("---")

실전 6 — 에이전트 워크플로우 (2026년 신기능)

RAGFlow는 단순 RAG를 넘어 시각적 에이전트 워크플로우 빌더로 진화했습니다.

[에이전트 기능 — n8n과 비슷한 비주얼 빌더]

지원 노드:
→ RAGFlow Retrieval: 지식 베이스에서 검색
→ LLM: 모델 호출
→ Web Search: 실시간 웹 검색
→ Code Executor: Python 코드 실행 (샌드박스)
→ MCP Tools: 외부 MCP 서버 연결
→ Categorize: 입력 분류
→ Message: 사용자 메시지
→ Keyword Extract: 키워드 추출
→ Relevant: 관련성 판단

워크플로우 예시:
→ 사용자 질문 → 분류 → 법률 쿼리: 법률 KB 검색
                    → 재무 쿼리: 재무 KB 검색
                    → 실시간 정보: 웹 검색
                    → 코드 관련: Code Executor

OpenClaw 연동:
→ 2026년 3월 OpenClaw 공식 스킬 추가
→ OpenClaw 에이전트가 RAGFlow KB를 도구로 사용
→ 장기 실행 에이전트 + 신뢰할 수 있는 지식 베이스 결합
# MCP 서버 연결 (에이전트에서 외부 도구 사용)
# RAGFlow 에이전트가 MCP를 통해 Slack, GitHub, DB 연결 가능

# service_conf.yaml.template에 MCP 서버 추가
mcp_servers:
  - name: slack-mcp
    url: "http://slack-mcp-server:3000"
  - name: github-mcp
    url: "http://github-mcp-server:3000"

# 에이전트 워크플로우에서 MCP 노드 추가 후
# 에이전트가 Slack 메시지 전송, GitHub PR 생성 등 가능

실전 7 — Langfuse 옵저버빌리티 연동

[Langfuse 연동 설정 — UI에서]

1. RAGFlow UI → 우상단 프로필 → API Settings
2. Langfuse 섹션까지 스크롤
3. 입력:
   → Public Key: pk-lf-...
   → Secret Key: sk-lf-...
   → Host: https://cloud.langfuse.com
         또는 셀프호스팅 URL
4. Save

이후 모든 RAGFlow 실행이 Langfuse에 자동 트레이싱:
→ UI 인터랙티브 실행
→ 스케줄 작업
→ API 호출
모두 동일하게 Langfuse 대시보드에 기록됨

Langfuse 대시보드에서 확인:
→ 각 검색 쿼리의 청크 검색 결과
→ 리랭킹 스코어
→ LLM 입력/출력
→ 레이턴시, 토큰, 비용
→ 어느 청크가 최종 답변에 기여했는지

RAGFlow vs 다른 RAG 솔루션

[선택 기준]

LangChain RAG (DIY):
→ 최대 유연성, 코드 풀 컨트롤
→ 10개+ 라이브러리 직접 조합
→ 파싱·청킹·검색 전부 직접 구현
→ 엔지니어링 비용 높음
→ RAGFlow가 필요 없는 경우: 완전 커스텀 필요할 때

LlamaIndex:
→ 라이브러리 (RAGFlow처럼 완성된 앱 아님)
→ 코드 중심, UI 없음
→ LangChain보다 RAG 특화

RAGFlow:
→ 완성된 앱 + API 둘 다
→ UI로 비엔지니어도 KB 관리 가능
→ DeepDoc 파싱 + 인용 추적이 핵심 차별점
→ 에이전트·MCP 통합 내장
→ 언제 선택: 엔터프라이즈 KB, 컴플라이언스 요구, 팀 협업

Dify:
→ RAGFlow와 포지션 겹침
→ Dify: 더 넓은 앱 빌더 (RAG + 앱 전반)
→ RAGFlow: RAG + 에이전트에 더 특화
→ 파싱 품질은 RAGFlow가 우세 (DeepDoc)

Chroma / Qdrant + 직접 구현:
→ 벡터 DB만 — 파싱·청킹·UI 없음
→ RAGFlow의 컴포넌트 중 하나를 대체
→ RAGFlow가 내부에서 Elasticsearch/Infinity 사용

마무리

✅ RAGFlow가 맞는 경우
→ 복잡한 PDF·스캔 문서가 많은 환경 (DeepDoc 파싱 강점)
→ 컴플라이언스·법률·금융 — 모든 답변의 출처 추적 필수
→ 비엔지니어도 KB 관리해야 하는 팀 (UI 완성도)
→ 셀프호스팅으로 데이터 외부 유출 막아야 할 때
→ 단순 RAG → 에이전트 워크플로우로 점진적 확장 계획 시

❌ RAGFlow가 과할 수 있는 경우
→ 단순 마크다운·텍스트 파일만 처리 (가벼운 RAG로 충분)
→ 소규모 개인 프로젝트 (Docker 인프라 부담)
→ 코드 레벨 완전 커스텀이 필요한 경우 (LangChain이 더 유연)
→ ARM64 환경 (공식 지원 제한)

⚠️ 알고 시작할 것
→ Elasticsearch 때문에 vm.max_map_count 설정 필수
→ 첫 파싱은 DeepDoc 모델 다운로드로 시간 걸림
→ 복잡한 스캔 PDF는 GPU 있으면 훨씬 빠름
→ Empty Response 반드시 설정 (안 하면 할루시네이션 발생)

관련 글

 

 

반응형