RAG (Retrievel Augmented Generation)
사내 정보시스템으로 LLM 과 조합됩니다.
사내에 있는 수천~수만개의 pdf, xls, doc, ppt 등의 문서파일에서 '답변' 을 추출해줍니다.
현재 기본 얼개는 갖춰졌는데, 하드웨어가 좀 받쳐줘야 합니다.
하지만 고가의 gpu 까지는 필요없고 맥미니 M4 32GB 정도면 필요충분합니다.
RAG 에서 부하가 크게 걸리는 부위는 아래와 같습니다
** Ingestion 단계 (문서를 vector 화하는 작업)
-- Marker 모듈 (여기서 문서 레이아웃 분석 & OCR 작업을 all-in-one 으로 해줍니다)
## cpu 로는 20page 문서 하나 작업에 8 vcpu 준 상태에서 2시간 넘게 걸립니다
-- Figure/Image/Schematic 인식 & 캡션 : cpu 로는 불가능하다 보심 됩니다.
-- GraphRAG : 각 문장, 키워들간에 '관계' 를 추출해서 별도로 저장합니다. cpu 로 불가
** Search 단계 (사용자의 query 를 바탕으로 vector / graph db 에서 검색후 답변 생성)
-- HyDE : 사용자의 '모호한 query' 를 LLM 을 통해 가상답변을 만들고 이를 검색 query 로 활용하여 답변품질 up
-- 답변생성 : 12B 급 모델정도면 기본적인 reasoning 은 됩니다. (cpu 로 돌리는 4B 급 에서도 기본논리는 갖춰주더라구요)
-- SelfRAG : 사용자의 최초 query 와 위의 '답변' 을 비교후 제대로 된 답변인지 2차 검토 (by LLM)
위 과정들을 보시면 아시겠지만 DL 모델들 범벅, LLM 범벅입니다.
구축하실분들 참조하실 수 있게 guide MD 문서 2개 올립니다.
<< guide_ingestion.md >>
# Ingestion Pipeline Guide
본 문서는 문서(PDF, Office 등)가 시스템에 업로드되어 벡터 데이터베이스(Qdrant)에 저장되기까지의 **Ingestion(데이터 적재)** 과정을 상세 기술합니다.
## 1. 기술 스택 및 라이브러리 (Technologies)
각 기술의 선정 이유와 장단점은 다음과 같습니다.
| 기술/라이브러리 | 사용 목적 | 장점 | 단점 |
| :--- | :--- | :--- | :--- |
| **PyMuPDF (fitz)** | PDF 기본 파싱, 이미지 렌더링 | 속도가 매우 빠르고 가벼움. | 복잡한 레이아웃(다단, 표) 인식에 한계가 있음. |
| **Marker** | PDF 레이아웃 분석 및 OCR | Suraya/Tesseract 등을 통합하여 정확한 레이아웃 분석 및 수식/코드 감지 우수. | 처리가 무겁고 시간이 오래 걸림 (GPU 권장). |
| **GMFT (Griffin's Magic Format Tool)** | PDF 내 테이블 감지 및 포맷팅 | PyPDFium2 기반으로 테이블을 감지하고 Markdown/CSV로 변환하는 성능이 우수함. | 완벽하지 않으며, 광학적 인식이 아닌 구조적 인식에 의존할 때가 있음. |
| **LangChain (Text Splitters)** | 텍스트 청킹 (Parent-Child) | RecursiveCharacterTextSplitter 등 검증된 분할 알고리즘 제공. | - |
| **FastEmbed / Ollama** | 임베딩 벡터 생성 | 로컬(FastEmbed)은 빠르고 가볍음. Ollama는 고성능 모델 사용 가능. | 로컬 모델은 한국어 성능이 다소 떨어질 수 있음. |
| **Qdrant** | 벡터 데이터베이스 | 고성능, 필터링 기능 강력, Rust 기반 안정성. | 설정 및 운영이 필요함 (Docker). |
| **Redis** | 작업 상태 관리 및 중복 방지 | 인메모리 키-값 저장소로 빠른 상태 조회 및 락(Lock) 관리. | 데이터 영속성 설정이 없으면 재부팅 시 데이터 유실 가능. |
| **Celery** | 비동기 작업 큐 | 긴 Ingestion 작업을 백그라운드에서 안정적으로 처리. | 브로커(Redis/RabbitMQ) 필요, 설정 복잡. |
## 2. 서브 모듈 및 역할 (Sub-modules)
특정 상황에서 사용되는 주요 모듈 설명입니다.
* **`app/api/endpoints/ingest.py`**: Ingestion API 엔드포인트. 파일 업로드 수신, 해시 중복 체크, Celery 태스크 생성 및 작업 상태 조회(Polling)를 담당합니다.
* **`app/services/pdf_processor.py`**: **핵심 모듈**. Marker와 GMFT를 사용하여 PDF를 심층 분석하고, 텍스트/표/이미지를 추출한 뒤 Parent-Child 전략으로 청킹합니다.
* **`app/services/office_processor.py`**: Office 문서(docx, pptx 등)가 들어올 경우 PDF로 변환하여 `pdf_processor`로 넘겨주는 전처리 역할을 합니다.
* **`app/services/translation_service.py`**: 다국어 처리를 담당. 한국어 텍스트가 들어오면 검색 정확도를 위해 영어로 번역(Search용)하고, 원본(Context용)은 유지합니다.
## 3. Ingestion 파이프라인 상세 (Pipeline Detail)
전체 과정은 **비동기(Async)**로 처리되며, 사용자는 `task_id`를 통해 진행률을 확인합니다.
1. **Validaton & Hashing (동기)**
* 파일 업로드 시 MD5 해시를 계산합니다.
* Redis `completed_hashes`와 `processing_hashes`를 확인하여 중복/진행 중 파일을 차단합니다.
* 파일을 `UPLOAD_DIR`에 저장하고 Celery Task를 시작합니다.
2. **PDF Analysis & Extraction (비동기 - Marker)**
* **도구**: Marker Pipeline (별도 프로세스 실행)
* **Layout Analysis**: 문서의 구조(헤더, 푸터, 본문, 다단 등)를 분석합니다.
* **OCR**: 텍스트 레이어가 없는 이미지형 PDF의 경우 OCR을 수행합니다.
* **Element Extraction**:
* **Text**: 일반 텍스트 블록 추출.
* **Table**: GMFT를 사용하여 테이블 감지 -> Markdown 변환.
* **Figure**: 이미지를 추출하여 `static/images`에 저장하고 캡션을 찾습니다.
3. **Chunking (Parent-Child Strategy)**
* 검색 정확도(작은 청크)와 문맥 이해(큰 청크)를 동시에 잡기 위해 **Parent-Child Splitting**을 사용합니다.
* **Parent Chunk**: 약 1800 토큰. LLM에게 제공되는 문맥(Context) 단위.
* **Child Chunk**: 약 400 토큰. 벡터 검색(Embedding)의 대상 단위.
* **Special Chunk**: Table과 Figure는 그 자체로 Parent이자 Child가 됩니다.
4. **Translation (Optional)**
* `detect_language` 수행.
* 한국어일 경우:
* **Child Text** -> 영어로 번역 (벡터 검색 매칭률 향상 목적).
* **Parent Text** -> 영어로 번역 (선택적, LLM 이해도 향상 목적).
5. **Embedding & Storage**
* 번역된 Child Text를 임베딩 모델(Local/Ollama)을 통해 벡터화합니다.
* Qdrant에 데이터(Payload)와 벡터를 업로드(Upsert)합니다.
## 4. 임베딩 및 저장 구조 (Embedding & Storage Structure)
### Embedding Process
* **Target**: `child_text_en` (번역된 Child Chunk 텍스트)
* **Model**: 설정에 따라 Local(HuggingFace) 또는 Ollama API 사용.
* **Normalization**: 벡터는 L2 정규화(Normalize)되어 저장(Cosine Distance 사용 최적화).
### Qdrant Data Structure (Payload)
Qdrant의 Point는 다음과 같은 Payload 구조를 가집니다.
```json
{
"chunk_id": "filename_p1_parent_0_child_0", // 유니크 ID
"parent_id": "filename_p1_parent_0", // 부모 청크 ID (Context 그룹핑용)
"text": "...", // [원본] Child Text (한국어 등)
"text_en": "...", // [번역] Child Text (검색용, 영어)
"text_ko": "...", // [소나기] Keyword 검색용 필드
"parent_text": "...", // [원본] Parent Text (LLM Context용)
"parent_text_en": "...", // [번역] Parent Text
"filename": "guide.pdf", // 출처 파일명
"page": 1, // 페이지 번호 (0-based)
"type": "text", // text | table | figure
"image_path": "/static/...", // type=figure일 경우 이미지 경로
"image_caption": "..." // type=figure일 경우 캡션
}
```
## 5. 특수 데이터 처리 (Table & Image)
### Table (표)
* **감지**: Marker 혹은 GMFT(Griffin's Magic Format Tool)가 PDF 내 표 영역을 감지합니다.
* **처리**: 감지된 표는 `Pandas DataFrame`을 거쳐 **Markdown Format** 텍스트로 변환됩니다.
* **저장**: `type: "table"`로 저장되며, Markdown 텍스트 자체가 `text` 필드에 들어갑니다. 이렇게 하면 LLM이 표 구조를 텍스트로 이해할 수 있습니다.
### Figure/Image (이미지)
* **감지**: Marker Layout Analysis가 그림/도표 영역을 식별합니다.
* **추출**: 해당 영역을 이미지 파일(.png)로 크롭하여 서버의 `static/images/{filename}/` 폴더에 저장합니다.
* **Captioning**: 이미지 주변의 텍스트(예: "Fig 1. Architecture")를 Regex 및 거리 기반으로 탐색하여 캡션으로 연결합니다.
* **저장**: `type: "figure"`로 저장됩니다.
* `image_path`: 웹 접근 가능한 이미지 URL.
* `text`: "Figure/Image: {caption}" 형태의 대체 텍스트.
<< guide_rag_search.md >>
# RAG Search Pipeline Guide
본 문서는 사용자의 질문(Query)을 받아 벡터 데이터베이스에서 관련 정보를 검색하고, LLM을 통해 최종 답변을 생성하는 **RAG(Retrieval-Augmented Generation) Search** 과정을 상세 기술합니다.
## 1. 기술 스택 및 라이브러리 (Technologies)
각 기술의 선정 이유와 장단점은 다음과 같습니다.
| 기술/라이브러리 | 사용 목적 | 장점 | 단점 |
| :--- | :--- | :--- | :--- |
| **Hybrid Search (Dense + Sparse)** | 검색 정확도 향상 | 의미 기반(Dense) 검색과 키워드(BM25/Sparse) 검색을 결합하여 다양한 쿼리 유형에 대응. | 두 가지 검색 로직을 모두 구현해야 하므로 복잡도 증가. |
| **Cross-Encoder (Reranker)** | 검색 결과 재정렬 | Bi-Encoder보다 훨씬 높은 정확도로 문맥적 관련성을 평가. | 계산 비용이 비싸(느림) 전체 검색 대상에 적용 불가 (Top-N 재정렬용). |
| **RRF (Reciprocal Rank Fusion)** | 검색 결과 병합 | 서로 다른 점수 체계(Cosine Similarity vs BM25)를 가진 검색 결과를 공정하게 합침. | K값 튜닝 필요. |
| **Ollama (LLM)** | 답변 생성 및 번역 | 로컬에서 대규모 언어 모델(LLaMA 3, Mistral 등)을 쉽게 구동. | GPU 메모리 요구량이 큼. |
| **FastAPI StreamingResponse** | 실시간 응답 | 긴 생성 과정을 단계별(Status -> Result)로 스트리밍하여 UX 개선. | 클라이언트 측에서 NDJSON 파싱 필요. |
## 2. 서브 모듈 및 역할 (Sub-modules)
특정 상황에서 사용되는 주요 모듈 설명입니다.
* **`app/api/endpoints/search.py`**: **핵심 모듈**. 검색 요청 처리, 파이프라인 제어, Reranking, 프롬프트 조립, LLM 호출 등RAG의 전 과정을 주관합니다.
* **`app/services/translation_service.py`**:
* **Query Translation**: 한국어 질문이 들어오면 영어로 번역하여 영문 문서(Dense Index) 검색에 활용합니다.
* **Answer Translation**: 영문 LLM이 생성한 답변을 최종적으로 한국어로 번역하여 사용자에게 제공합니다.
* **`app/domain_glossary.json`**: 도메인 특화 용어집. 번역 과정에서 기술 용어가 잘못 번역되는 것을 방지합니다.
## 3. RAG Search 파이프라인 상세 (Pipeline Detail)
검색 과정은 **스트리밍(Streaming)**으로 처리되어 사용자가 각 단계의 진행 상황을 실시간으로 확인할 수 있습니다.
1. **Language Detection & Translation**
* 사용자 질문(`Query`)의 언어를 감지합니다.
* **한국어 질문**: 영어로 번역(`Query_EN`)하여 Dense Search에 사용하고, 원본(`Query_KO`)은 Keyword Search에 사용합니다.
* **영어 질문**: 그대로 사용합니다.
2. **Hybrid Search (Dense + Sparse)**
* **Dense Search (Vector)**: `Query_EN`을 임베딩하여 Qdrant에서 코사인 유사도 기반으로 상위 50개 청크를 검색합니다. (의미적 유사성)
* **Keyword Search (BM25)**: `Query_KO`에서 명사/키워드를 추출하여 Qdrant의 `text_ko` 필드와 매칭합니다. (단어 일치)
3. **Result Fusion (RRF)**
* Dense 결과와 Keyword 결과를 **Reciprocal Rank Fusion(RRF)** 알고리즘으로 병합합니다.
* 공식: `Score = 1 / (k + rank)`
* 두 검색에서 모두 상위권에 랭크된 문서가 더 높은 최종 점수를 받게 됩니다.
4. **Reranking (Cross-Encoder)**
* 통합된 상위 50개 후보군에 대해 **Cross-Encoder** 모델을 사용하여 정밀 재순위(Reranking)를 수행합니다.
* 단순 유사도가 아닌, "이 문서가 이 질문에 대한 정답을 포함하고 있는가?"를 직접 추론합니다.
5. **Context Assembly**
* Reranking된 상위 15개 청크를 선택합니다.
* **Parent Document Retrieval**: 검색된 청크(Child)의 `parent_id`를 사용하여 더 넓은 문맥(Parent)을 가져옵니다.
* 토큰 제한(예: 20000 토큰) 내에서 Context를 조립합니다.
* **Figure Reference**: 이미지 청크가 포함될 경우 `[FIG:이미지 경로]` 태그를 삽입합니다.
6. **Answer Generation (LLM)**
* **System Prompt**: "기술 문서 분석가" 페르소나를 부여하고, "Context에 없는 내용은 답변하지 말 것", "이미지를 참조할 것" 등의 제약 조건을 설정합니다.
* **User Prompt**: 조립된 Context와 Query를 LLM(Ollama)에 전달합니다.
* LLM이 답변(`Answer_EN`)을 생성합니다.
7. **Finalization (Translation & Glossary)**
* `Answer_EN`을 한국어(`Answer_KO`)로 번역합니다.
* `domain_glossary.json`을 사용하여 도메인 용어를 표준화/교정합니다.
* 최종 결과(답변, 근거 문서 목록, 이미지 경로 등)를 JSON 형태로 반환합니다.
## 4. Table, Figure, Image 표시 (Display Logic)
검색 결과에서 표와 이미지가 어떻게 처리되고 표시되는지에 대한 설명입니다.
### Table (표)
* **Markdown Rendering**: 표 데이터는 Ingestion 시 Markdown Text로 변환되어 저장되었습니다.
* **LLM 인식**: LLM은 Markdown 표를 텍스트로 인식하고 해석하여, 답변에 표의 내용을 자연스럽게 포함시키거나 재구성하여 설명합니다.
* **UI 표시**: 근거 문서(Source) 탭에서 해당 청크를 클릭하면 원본 PDF 페이지를 통해 표의 원형을 확인할 수 있습니다.
### Figure/Image (이미지)
* **Context 삽입**: 문맥 조립 시, 이미지 청크가 선택되면 텍스트 사이에 `[FIG:/static/images/filename.png]` 형태의 태그가 삽입됩니다.
* **LLM 참조**: LLM은 이 태그를 인식하여 "그림 1을 참조하면..."과 같이 답변에 인용할 수 있습니다.
* **UI 표시**:
1. **답변 내 이미지**: 답변 텍스트 내에 이미지 링크/태그가 있을 경우 렌더링됩니다.
2. **Source Chips**: 검색 결과 하단에 `[이미지 아이콘] 파일명 (p.5)` 형태의 칩으로 표시됩니다.
3. **Preview Modal**: 칩을 클릭하면 `image_path`에 저장된 원본 크롭 이미지가 모달 창으로 뜹니다.
* **Page Rendering**: 텍스트 청크의 경우에도, 사용자가 원본을 보고 싶어할 때 `/documents/{filename}/pages/{page}` 엔드포인트를 통해 해당 PDF 페이지 전체를 이미지로 렌더링하여 보여줍니다.
댓글
댓글 쓰기직원이 많지는 않아서 문서중앙화 까지는 생각 못했구요.