Skip to content

RAG Pipeline

donghoong2 edited this page Oct 28, 2026 · 1 revision

약관 데이터와 검색 파이프라인

dev a9974a0 (2026-10-12) 기준

리포트가 근거로 쓰는 약관·판례·분쟁조정사례·장해분류표가 어떻게 수집되고, 청크가 되고, 검색되는지를 정리한다. 수집(Notion → S3)은 이 레포의 corpus_worker가, 구조 복원·청킹·임베딩은 오프라인 파이프라인(Policy-Chunker, 별도 저장소)이, 검색은 이 레포의 공용 모듈 src/rag가 맡는다.

PDF 파싱(OCR·VLM) → 구조 복원(특약 경계·목차 대조) → 청킹(조 단위·500토큰 병합) → 메타데이터·검증(대표쪽·표 분리) → 임베딩·인덱싱(pgvector + tsvector) → 검색(BM25 + 벡터 → RRF → Reranker)

약관 PDF가 검색 가능한 청크가 되기까지. 앞 다섯 칸은 오프라인, 마지막 칸(검색)은 리포트·챗봇이 함수로 호출한다

1. 수집: Notion → S3

corpus_worker 흐름: sync → PostgreSQL 미러 → drain(SKIP LOCKED) → 스트리밍 다운로드 + SHA256 → HeadObject dedup → S3 → mark done → embed_worker 폴링

팀이 Notion에 쌓는 약관 PDF를 PG에 미러링하고, 우선순위대로 S3에 sha256 키로 올린다

결정 이유
큐 이벤트가 아니라 폴링 데몬 Notion은 변경을 푸시하지 않고, 우선순위는 시간이 지나며 계속 바뀐다
sync(증분 하루 1회 + 6일 주기 전수)와 drain을 한 프로세스의 독립 루프로 삭제 반영은 비싼 전수에서만. 편집 중 첨부가 잠깐 비는 문서를 지우지 않도록 "페이지 존재(observed)"와 "첨부까지 존재(seen)"를 구분
FOR UPDATE SKIP LOCKED claim + 지수 백오프 + 좀비 회수 여러 drain 태스크가 같은 문서를 잡지 않게, 레이트리밋 같은 일시 오류에서 시도 예산을 태우지 않게
내용주소 키 + HeadObject dedup, DB 문서 키는 비유니크 여러 약관이 같은 첨부(공통 특약·별표)를 공유한다
PDF 전용 → 코퍼스 스테이징 기록
우선순위 = 카테고리 지배항 + 출처(P0~P3) + 수요도 + 긴급도(지수 반감기) 선형 감쇠는 오래된 문서에서 음수가 나와 점수가 꼬였다

2. 구조 복원과 청킹 (오프라인)

약관 PDF가 청크가 되기까지 7단계: 본문·표 추출, 지점 찾기(제N조), 구조 경계 설정(특약·장·조·항·호), 조문 범위 복원, 단위 청킹, 임베딩 & 색인, 검색 + 리랭킹

약관 청킹 흐름. 텍스트로 바꾸면 사라지는 제목/본문 구분을 조판 정보로 되살리는 것이 핵심이다

  • 구조 복원: 폰트 크기·굵기·배치·문단 간격을 점수화해 특약·조문 경계를 찾고, 목차·본문 패턴으로 잘못된 후보를 보정한다. 폰트 크기 하나만 보면 목차나 상품 설명까지 특약 제목으로 잡는다. 보험사마다 조판이 달라, 문서의 목차를 기준으로 복원 결과를 자동 평가한다.
  • 청킹: 특약 → 조 → 항 → 호 계층. 기본은 조 하나가 청크 하나이고, 면책 조항처럼 한 조에 조건이 길게 나열되면 항·호로 더 나눈다. 긴 조문 때문에 특정 조건의 검색 신호가 묻히는 것을 막는다.
  • 조항 분류: 복원된 조항을 9종(보장·면책·정의·특별조항·의무·청구·해지·별표·일반)으로 분류한다. 정규식 규칙이 먼저 판정하고, 실패한 조항만 LLM으로, 그것도 실패하면 키워드 규칙으로 복원한다.

조항 분류 정확도: gemma4 26b 0.633, qwen3.6 35b-a3b 0.933, A.X-4.0-Light 0.583, 키워드 규칙 0.617 (전체 60건). 의견이 갈린 40건에서는 gemma4·qwen3.6 각 0.775, 키워드 규칙 0.725

조항 분류 모델 비교(주요 보험사 4곳 × 약관 3개, 60건). 전체 정확도는 qwen3.6:35b-a3b가 0.933으로 가장 높아 채택했다. 모델끼리 의견이 갈린 40건에서는 qwen3.6과 gemma4가 같았다(0.775)

판례·분쟁조정사례 임베딩·적재
판례·금감원 조정사례 → 5블록 분할 → 사건번호·법원·선고일·쟁점 태깅 → 저장 → 검색·판례 참조 청크 + 메타데이터 → 임베딩 생성 → pgvector 저장 → 검색 인덱스 구성
사건 단위로 5블록 분할하고 사건번호·법원·선고일·쟁점을 태깅한다. 리포트 워커가 판례를 사건 단위로 dedup하고 표시 정보를 복원할 때 이 태그를 쓴다 1024차원 임베딩(qwen3:embedding, 폴백 BGE-M3)을 halfvec(1024)로 저장한다

보험사별 목차 항목 대비 검출 섹션과 점수: 0.987→0.987, 0.607→0.866, 0.832→0.851

보험사별 구조 복원 점수(목차 항목 대비 검출). 조판이 특이한 보험사에서 0.607 → 0.866으로 가장 크게 올랐다. 평가 배치 전체 평균은 0.528 → 0.651, 약관 100개에서 청크 111,791개, 최대 1,250페이지 약관까지 처리했다(팀 중간보고서)

3. 저장과 검색 (src/rag)

사용자 질문 + 사고 정보 → 검색 질의 생성 → 키워드 기반 검색 / 의미 기반 검색 → 검색 결과 통합 → 관련도 재정렬 → 상위 조항 복원·메타데이터 결합 → AI 분석 컨텍스트 → 보험 리포트

하이브리드 검색. "상해후유장해"·"외모추상"처럼 정확히 일치해야 하는 용어와 표현이 달라도 뜻이 같은 질의가 공존해 두 검색을 함께 쓴다

결정 이유 상태
전용 벡터 DB(Pinecone·Qdrant) 대신 PostgreSQL + pgvector 보험사·상품·세대·시행일·문서 종류가 복합 필터로 걸린다. 인제스트 중 버전이 섞이지 않게 트랜잭션이 필요하다. 필터·트랜잭션·벡터·키워드를 한 DB에서 채택
HNSW (IVFFlat 아님) 속도보다 정밀도. 잘못된 조항 하나가 리포트 전체를 틀리게 한다. 문서가 계속 추가돼도 재구성 부담이 적다 채택
halfvec(1024) (float32 vector 아님) 인덱스 RAM이 절반. 1024차원에서 정확도 손실은 무시할 수준으로 판단 채택
키워드 검색: pg_search(BM25) + trigram 설계 단계 선택. BM25 랭킹 대체됨 → 현재 코드는 Kiwi 형태소 토큰 + tsvector(GIN) + trigram 오타 보정. 전환 사유는 기록 없음
Elasticsearch 검색 기능은 가장 강력하지만 별도 서버와 동기화 파이프라인이 필요 폐기
오타 보정: search_terms(Kiwi 추출 용어 + trigram) 사용자 입력을 바로 쓰지 않고 가장 가까운 용어로 보정한 뒤 검색. 갱신은 인제스트 중이 아니라 배치로(동시 인제스트 충돌 방지) 채택
통합: RRF (k=60) 두 순위를 점수 스케일 없이 합칠 수 있다 채택
리랭커 (bge-reranker-v2-m3) 하이브리드로 좁힌 후보만 다시 평가해 처리량을 관리하면서 정확도를 올린다 오프라인 평가 파이프라인 채택. 이 레포의 src/rag에는 아직 없음

검색 방식별 R@1/R@5. 현재 청크셋: BM25 0.489/0.766, 임베딩 0.553/0.830, RRF 0.553/0.809, RRF+리랭커 0.723/0.894

검색 방식별 재현율(현재 청크셋). RRF만으로는 임베딩 단독과 R@1이 같았지만, 리랭커를 붙이자 R@1 0.553 → 0.723, R@5 0.809 → 0.894로 올랐다

4. 약관 자동 수집 (설계·구현 중)

보험증권 업로드 → OCR·문서 분석 → 보험사·상품명·가입시점 추출 → 보험사 공시실 자동 검색 → 현행·과거 약관 후보 수집 → 상품명 유사도 + 시행일 기준 선별 → 약관 자동 다운로드 및 원본 검증 → Policy-Chunker → 구조 복원·청킹·임베딩 → RAG 적재

사용자 약관이 코퍼스에 없을 때의 경로. 증권에서 보험사·상품명·가입 시점을 뽑아 공시실에서 가입 당시 판본을 찾는다. 판매 중지된 과거 약관도 대상이다

  • 보험사마다 공시실 구조가 달라 보험사별 어댑터를 둔다(5개사).
  • 받은 약관은 PDF 형식·페이지 수·조문 존재·SHA-256을 검증한 뒤에만 청킹으로 넘긴다.
  • 리포트 워커의 "약관 미보유 → 약관 파싱" 분기는 아직 빈 자리다(리포트 워커 구성).

알려진 과제 (오프라인 청킹 파이프라인)

팀 설계 문서 "청크 파이프라인 최적화" 기준. 그 시점 규모는 약관 20개·청크 21,791개, 문서당 처리 시간 p50 251.9초·p95 378.9초(n=11)였다.

항목 유형 상태
런타임 관측이 없어 사이클 실패를 알 방법이 없음. 감지까지 최대 7일(사이클 주기) 탐지 사이클 결과 DB 기록 + 알림 + 헬스체크 제안
청크가 0개인 문서가 매 주기 무한 재처리 개선 미착수
임베딩 배치를 for 문으로 순차 호출해 Ollama 대기가 그대로 누적 개선 미착수
SLO 후보: 인덱스 신선도(마지막 성공 적재 경과 시간), 멱등 스킵률, 처리량 탐지 미착수

참고

  • 팀 설계 문서 "임베딩 전략", "corpus_worker 파이프라인 설계 문서", "보험 약관 청킹 파이프라인 설계 문서", 팀 중간보고서
  • src/rag/, src/corpus_worker/, migrations/corpus/001_policy_chunks.sql, docs/architecture/rag.md

Clone this wiki locally