Skip to content

About

[팀프로젝트] Text & Image Mulitmodal RAG Project

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

176 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

개요

애널리스트가 쓴 산업·섹터 투자의견 리포트(PDF)를 넣으면, 페이지를 유형별로 자동 분류 → 유형에 맞는 도구로 텍스트/표/이미지 각각 핵심만 추출 → 근거를 하나의 DB로 통합 → 질의에 대해 근거 기반 투자 인사이트를 생성하는 멀티모달 RAG 시스템입니다.

리포트 한 페이지에 텍스트·표·이미지(차트)가 섞여 있어 하나의 도구로 전부 처리하면 정보 손실이 크고(특히 표·차트), 반대로 모든 페이지에 무거운 모델(VLM, 표 구조 인식 모델)을 매번 돌리면 시간·비용이 낭비된다는 문제에서 출발했습니다. 그래서 쉬운 건 싸게, 필요한 곳만 비싸게 처리하도록 유형별 최적 도구를 배치했습니다.

전체 파이프라인

flowchart TD

%% ---------------- STYLE DEFINITIONS ----------------
classDef inputFill fill:#E1F5FE,stroke:#0288D1,stroke-width:2px,color:#01579B
classDef branchFill fill:#FFF3E0,stroke:#F57C00,stroke-width:2px,color:#E65100
classDef processFill fill:#F3E5F5,stroke:#8E24AA,stroke-width:2px,color:#4A148C
classDef visionFill fill:#E8F5E9,stroke:#388E3C,stroke-width:2px,color:#1B5E20
classDef tableFill fill:#FFF8E1,stroke:#FFA000,stroke-width:2px,color:#FF6F00
classDef textFill fill:#E0F2F1,stroke:#00897B,stroke-width:2px,color:#004D40
classDef dbFill fill:#EDE7F6,stroke:#5E35B1,stroke-width:2px,color:#311B92
classDef outputFill fill:#FFEBEE,stroke:#E53935,stroke-width:2px,color:#B71C1C

%% ---------------- NODES & LINKS ----------------
A["애널리스트 산업 섹터 투자의견 보고서 PDF"]:::inputFill

A --> B["페이지 분류"]:::processFill

B --> C["PyMuPDF 메타데이터 신속검사"]:::processFill

C --> D{"PDF 난이도"}:::branchFill

D -->|쉬운 PDF| E["YOLOv11n Layout Detection"]:::processFill
D -->|어려운 PDF| F["MinerU"]:::processFill

E --> G
F --> G

G{"Page Type"}:::branchFill

%% ---------------- IMAGE ----------------
G --> TAB
G --> TXT
G --> IMG["이미지"]:::visionFill

IMG --> CHART{"Chart Classification"}:::branchFill

CHART -->|Yes| VLM["VLM"]:::visionFill

CHART -->|No| IMGOCR["이미지 이해<br/>(OCR + Vision)"]:::visionFill

VLM --> IMGSO["Structured Output"]:::visionFill

IMGOCR --> IMGSO

%% ---------------- TABLE ----------------
G --> TAB["테이블"]:::tableFill

TAB --> TATR["TATR (Table Transformer)"]:::tableFill

TATR --> PDFPL["pdfplumber"]:::tableFill

PDFPL --> RECON["Table Reconstruction"]:::tableFill

RECON --> TABSO["Structured Output"]:::tableFill

%% ---------------- TEXT ----------------
G --> TXT["텍스트"]:::textFill

TXT --> TEXT["PyMuPDF Text"]:::textFill

TEXT --> SO["Structured Output"]:::textFill

TEXT --> CHUNK["Rule-based 계층적 청킹"]:::textFill

CHUNK --> EMB["Dense Embedding<br/>+ BM25 Index"]:::textFill

EMB --> TMP["임시 DB (Supabase)"]:::dbFill

%% ---------------- ENTITY ----------------
IMGSO --> FUSION["Entity Fusion"]:::processFill

TABSO --> FUSION

SO --> FUSION

FUSION --> META["기업 메타데이터 DB<br/>(Supabase)"]:::dbFill

%% ---------------- SEARCH ----------------
QUERY["사용자 Query + 타입"]:::inputFill --> SEARCH

TMP --> SEARCH["Hybrid Search"]:::processFill

SEARCH --> RRF["Rank Fusion"]:::processFill

%% ---------------- NEWS ----------------
NEWS["관련 뉴스<br/>Sentiment Analysis"]:::inputFill --> META

%% ---------------- FINAL ----------------
META --> LLM["LLM 투자 Insight"]:::outputFill

RRF --> LLM
Loading

노드별 상세 — 어떤 모델을 왜 썼는지

페이지 분류 / 난이도 라우팅

노드 모델·기법 채택 근거
PyMuPDF 메타데이터 신속검사 규칙 기반(모델 없음) find_tables() + 래스터/벡터 카운트로 1차 스캔, 161.8ms/page — 대부분 문서는 이 단계에서 이미 처리 가능 여부가 갈림
PDF 난이도 판정 규칙 기반(assess_page_difficulty) 컬럼이 기하학적으로 겹쳐도 원문 추출 순서가 컬럼별로 잘 그룹돼 있으면 문제없다는 관찰 반영 — material_overlaps AND interleaving_excess≥2를 동시 만족할 때만 hard 판정
YOLOv11n Layout Detection YOLOv11n(Armaggheddon/yolo11-document-layout, plain crop) baseline(pdfplumber+PyMuPDF, 83.3%) 대비 라벨 정확도 100%. YOLOv26n과 정확도는 동률이지만, 벤치마크 mAP와 달리 실제 표 크롭 개수(14 vs 13)·다운스트림 표 인식 성능에서 YOLOv11n이 더 우수해 채택
스캔본 감지 규칙 기반(detect_scanned_pages) 텍스트 레이어 유무 + 이미지가 페이지를 덮는 비율만으로 판정(순수 PyMuPDF, 모델 없음)

텍스트 브랜치

노드 모델·기법 채택 근거
PyMuPDF Text get_textbox(bbox)(YOLO 박스 단위) get_text("blocks")는 시각적 줄 단위로 잘려 문단 경계가 깨짐 — YOLO 박스=문단/불릿 경계로 교체
정제 PUA 글리프 제거, 헤더/푸터 제거(좌표+반복빈도), Boilerplate(Compliance Notice) 탐지 Boilerplate 탐지는 BGE-M3 문장 임베딩 유사도 기반 채택(SimHash/MinHash는 증권사 간 패러프레이즈를 못 잡아 기각) — 정밀도 100%, 재현율 83%
Rule-based 계층적 청킹 규칙 기반(chunk_hierarchical, LLM 미사용) Title/Section-header를 그대로 청크 경계로 쓰고 계층 경로를 메타데이터로 부착. Qwen 로컬 VLM 청킹(entity_recall 89.3%, 101.1s) 및 OpenAI API 청킹(59.3%, 55.0s)과 비교해 entity_recall 96.0%로 가장 높으면서 LLM 호출 0건이라 가장 빠름(57.3s) — 유료/로컬 LLM 어느 쪽도 이득이 없어 규칙 기반 채택
Dense Embedding dragonkue/BGE-m3-ko BGE-M3(다국어 베이스) vs OpenAI text-embedding-3-small과 3종 비교(Recall@1/@3, MRR) — BGE-m3-ko가 Recall@3 100%(유일), MRR 0.922(최고), 임베딩 속도도 최속이라 트레이드오프 없이 채택
Sparse Index BM25Okapi(한글 어절 토크나이저) Dense 단독 대비 하이브리드가 recall@1 86.7%→93.3%로 개선

테이블 브랜치

노드 모델·기법 채택 근거
TATR (Table Transformer) TATR로 표 내부 row bounding box만 구조적으로 탐지 Docling(TableFormer, OCR+구조+텍스트 전부)과 동일 품질(228행)을 6.2배 빠르게(20.24s→3.25s) 달성. Docling이 OCR 실패로 0행 처리한 표를 TATR+pdfplumber는 7행 복원했고, 반대로 YOLO가 차트를 표로 오탐한 영역에서 Docling은 의미 없는 가짜 행 11개를 만든 반면 TATR+pdfplumber는 정직하게 0행 반환
pdfplumber row 좌표를 pdfplumber에 넘겨 행 단위로 독립 추출 표 전체를 한 번에 읽지 않고 행 단위로 쪼개 처리 — 겹친 텍스트(글자 간격 손상)로 인한 엔티티 누락 문제(예: "우리은행") 해결. Recall 90%→100%, F1 92.2%→96.6%
크롭 전처리 Adaptive padding + 300dpi 재렌더링 (padding/dpi) 9가지 조합 탐색 결과 최고(242행) — 상단 +35px·좌우/하단 +12px + 표 크롭만 300dpi로 재렌더링
표 유형 라우팅 재무제표 → 규칙 기반(정규식), 계약/일반 → LLM 구조화 출력 재무제표는 회사명이 거의 안 나온다는 특성을 이용해 LLM 호출 없이 처리 — 총 처리시간 1097.5s→462.8s(-57.8%), Precision 81.2%→94.4%
SIMPLE/COMPLEX 게이팅 fill_ratio·컬럼 일관성·빈 셀 비율로 판정 SIMPLE 표는 pdfplumber만으로 충분해 TATR을 아예 안 태움(하이브리드 게이트, [JAEIL v5])
엔티티 보강 행 content(라벨+셀)에서 KOSPI200 199개 기업명 결정적 매칭 LLM 호출 없이 표 행에 structured_metadata.entities를 채워, 질의 시점 entity_count 계산이 LLM 폴백으로 새지 않게 함(§8.4)

이미지 브랜치

노드 모델·기법 채택 근거
문서 파싱 MinerU(레이아웃 탐지 + 내장 OCR, PP-OCR) chart/image/table 블록 탐지와 크롭, 페이지당 ~0.5s OCR
규칙 필터 크기·종횡비 기반(모델 없음) 명백한 junk(로고·아이콘)를 VLM 호출 전에 선컷
세부 분류(선택) DocumentFigureClassifier-v2.5(EfficientNet-B0, 4.08M) 26종 세부 라벨로 MinerU 판정과 상호검증만 함(판정 자체는 안 바꿈), 장당 ~10-20ms로 사실상 무료
Chart Classification → VLM MinerU2.5-Pro-2605-1.2B(내장 VLM, 차트→데이터 표 근사 추출) 자유 지시를 따르지 않는 특화 모델이라 프롬프트 커스터마이즈는 무의미 — 차트 판독(수치 추출)에서 Docling(VLM 켜도 환각만 생성, 0%)·MinerU 기본모드(차트 미판독, 0%) 대비 유일하게 100% 달성
서술형 해석(선택) qwen3:8b(Ollama, 텍스트 전용 LLM) 4a가 뽑은 표를 애널리스트 문체로 재서술. 4a(VLM)가 성공하면 표 자체가 이미 검색 가능한 형태로 저장되므로 4b가 실패해도 핵심 데이터는 유지됨
부호/축눈금 안전장치 정규식 기반(_normalize_chart_card_signs) 한국 증권 리포트 관례(괄호=음수) 정규화 + 등간격 숫자를 축 눈금으로 오인해 시계열로 오독하는 걸 막는 경고 삽입 — VLM 없이도 동일 효과

엔티티 합성 · 인덱싱

  • entity_fusion.py가 3브랜치 출력을 하나의 document_evidence(Supabase/pgvector)로 모으고, 소스 타입별 신뢰도 가중치(표 canonical 매칭 1.3 / 표 미매칭 0.6 / 이미지 1.15 / 텍스트 1.0)를 부여.
  • load_evidence_from_db()로 재파싱 없이 재질의 가능(프로세스 메모리 캐시, TTL 60초).
  • 구조화 출력(다이어그램의 Structured Output 3노드, 선택 기능·기본 비활성): structured_output.py (gpt-4o-mini)가 텍스트/표/이미지 각 청크에서 entities/sector_mentioned/sentiment 등을 정형 추출하는 경로가 구현돼 있으나, add_structured_metadata=False가 기본값이라 지금 배포된 앱에서는 호출되지 않는다(유료 API 호출 추가 비용 때문에 off). 표 브랜치의 엔티티만 위 결정적 매칭(무료)으로 별도 보강돼 있어, 구조화 출력을 안 켜도 표 엔티티는 채워짐.

검색 — 두 경로

이 앱은 질의 경로가 두 갈래다: 종목 상세 페이지(PDF 없이 KOSPI200 199개 종목 전체에서 항상 가능)와 PDF+질문 업로드 페이지(그 문서에 한정된 근거 기반 답변).

종목 상세 페이지 (answer_question()):

  • 기본 근거: financial_chunks/company_profile_chunks 밀집 검색 — **OpenAI text-embedding-3-small**로 사전 임베딩(PDF 브랜치가 쓰는 BGE-m3-ko와는 별개 모델). 199개 종목 전체에 미리 적재돼 있어 PDF 업로드 없이 항상 응답 가능.
  • 이 종목에 실제 애널리스트 PDF가 적재돼 있으면(document_evidence에 행 존재) BM25+BGE-m3-ko 하이브리드 검색(weighted_hybrid_search)까지 근거에 추가.
  • 배당스코어·뉴스감성·Layer1~4 시그널(아래 별도 항목)도 병렬 조회해 프롬프트에 합류.

PDF+질문 업로드 페이지 (search_and_generate()):

  • 질의 분해: decompose_query()(gpt-4o)로 복합 질의를 성격별 하위질의로 분해, 여러 개면 ThreadPoolExecutor로 병렬 검색(예전엔 순차라 지연이 sum이었음). 질의 성격 분류 (classify_query_type_llm/_classify_and_expand)도 정확도가 중요해 gpt-4o를 쓰지만, 분류 뒤 실제 HyDE 가상문단·MQE 하위질의 생성 자체는 저비용 gpt-4o-mini로 내려서 처리 — 판단이 중요한 단계만 상위 모델을 쓰는 구조.
  • keyword_specific → hybrid_search(fusion="weighted_sum") — dense+BM25 선형가중(0.7/0.3). RRF보다 검증된 우세(ndcg 0.815 vs 0.71~0.73)로 교체됨.
  • abstract → 문서 entity_count로 재분기: 단일기업(≤1) → HyDE(가상 답변 문단 생성 후 임베딩), 다기업(>1) → MQE(하위질의 생성 후 RRF 융합) — 하위질의를 5개 측면으로 명시 분리(①거시경제/정책/지정학 — 반드시 구체적 예시 단어(전쟁·분쟁, 금리 인상 등) 사용, 카테고리명만 쓰면 임베딩이 못 잡는다는 걸 실측으로 확인 ②수급동향(기관/외국인/연기금) ③실적/이벤트 ④목표주가/투자의견 ⑤개별기업리스크). abstract 경로는 표/이미지 소스가중치도 끔(keyword용으로 검증된 가중치가 요약형 질의의 서술 문단을 부당하게 역전시키던 문제 수정) — 이 개선으로 추상형 검색 품질이 ndcg 0.271 → 약 0.70~0.72로 개선됨.
  • 하위질의별 결과는 라운드로빈 인터리빙으로 병합(순위 합산 아님 — abstract가 keyword_specific의 1위를 밀어내는 걸 방지).
  • 기업 엔티티 링킹(위와 병렬): find_mentioned_companies()(KOSPI200 199개 정확 문자열 매칭, LLM/임베딩 불필요) → fetch_company_db_context()(재무제표·프로필·배당스코어 v2·뉴스감성· Layer1~4 실시간 시그널을 DB에서 조회해 프롬프트에 합류).
  • 리랭커(cross-encoder, BAAI/bge-reranker-v2-m3): 도입 여부를 실측 검증 — 근거가 이미 강한 질의(ndcg>0.96)에는 MQE/HyDE가 의도적으로 만든 근거 다양성을 오히려 획일화시켜 해가 되고, 근거가 약한 질의에만 도움이 되는 혼재된 결과라 보류(문서가 늘어나 약한 근거 사례가 반복되면 재검토 조건부).

뉴스 감성 · 실시간 시그널

  • 뉴스 감성 (news_sentiment_link.py, src/finance/layer3_*): 네이버 실시간 뉴스 검색 → 규칙 기반 4요소 가중 랭킹으로 후보 선정 → Qwen3-0.6B(로컬, HF transformers, MPS)로 최종 top_n 재검증(reasoning) → snunlp/KR-FinBert-SC(한국어 금융뉴스 감성 파인튜닝)로 positive/neutral/negative 분류 → Qwen3-1.7B로 감성 판단 근거 서술문 생성(원래 4B였으나 개발 환경의 통합메모리 경합으로 축소). 읽기-통과 캐시로 재수집을 최소화.
  • 배당 스코어 · Layer1~4 시그널: 재무(Layer1)/기술적지표(Layer2, yfinance 실시간)/뉴스 (Layer3, 위 모델)를 규칙 기반 가중융합(Layer4)으로 계산 — Layer1/2/4는 전부 통계·규칙 기반이며 별도 ML 모델 없음. 사전 배치 산출물이 아니라 질의 시점 실시간 계산(과거 배치 결과는 답변 생성에 전혀 도달하지 못해 폐기하고 실시간 계산으로 교체).

생성

  • gpt-4.1 채택 — RAGAS 실측(9질의)으로 gpt-4o-mini 대비 faithfulness(환각 없음) 0.740→0.893, 논리 루브릭 2.22→3.00(둘 다 우세), 대가는 출력 토큰 2.7배·지연 1.3배.
  • citation_check.py — 답변 속 숫자가 실제 컨텍스트에 있는지 대조해 없으면 재생성. 소수점 스코어 (감성/융합 시그널 등 절댓값<1인 값)까지 검증 대상에 포함하도록 보강했고, 재시도해도 미해결 숫자가 줄지 않으면 조기 중단해 불필요한 재생성 낭비를 없앰. 여러 기업이 언급된 문서에서는 다른 기업 수치를 섞어 쓰지 말 것, 날짜/분기 표기를 원문 그대로 유지할 것도 프롬프트에 명시.

파이프라인 비교 (근거)

"기존 도구로 충분한가"에 대한 답 — Docling/MinerU vs 우리 파이프라인(텍스트/표 7문항 + 차트 16개 기준):

지표 Docling(VLM 미사용) Docling(VLM 사용) MinerU 우리 파이프라인
지연(전체) 6.9s ~900s 25.6s 12.9s
텍스트/표 추출 Recall 100% 100% 100% 100%
차트 판독률(유의미한 수치 추출) 0% 0%(환각만 생성) 0% 100%

Docling에 우리와 같은 일(차트 이해)을 시키면 35배 느려지면서 오히려 틀린 값을 만들어내고, 안 켜면 빠르지만 차트를 아예 못 읽는다. "차트까지 이해하는 파서" 카테고리에서 속도·정확도 둘 다 우위.

생성 모델 선택 근거 — RAGAS(9질의, 심판=gpt-4o):

지표 gpt-4o-mini gpt-4.1 승자
Faithfulness(환각 없음) 0.740 0.893 gpt-4.1 (+21%)
Factual Correctness 0.332 0.409 gpt-4.1 (+23%)
논리 루브릭(1~5) 2.22 3.00 gpt-4.1 (+35%)
지연 / 출력 토큰 4.71s / 304 6.04s / 814 gpt-4o-mini

자세한 배경

각 단계 디렉토리의 실험.md(pdf_pipeline/text_processing/실험.md, pdf_pipeline/table_processing/실험.md, pdf_pipeline/page_classification/실험.md 등)와 pdf_pipeline/파이프라인_최종정리_핸드오프.md, pdf_pipeline/final/검색퓨전_라우팅_생성품질_실험.md에 실험 전체 기록이 남아있습니다. 이미지 파이프라인 고도화판(VLM 캐시, pHash 중복제거, ChartQA A/B 등)은 pipeline/README.md 참고.

About

[팀프로젝트] Text & Image Mulitmodal RAG Project

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages