홈으로 돌아가기

Legal RAG ARLC 2026: 0.791까지 17 iterations

기사에서 법률 챌린지 ARLC 2026을 위한 RAG 파이프라인 분석: grounding 0.05인 첫 제출에서 0.791까지. Page-level 인덱싱, hybrid RRF search, cross-encoder reranking 및 typed responses를 위한 fast-paths를 포함한 아키텍처 설명. 30개에서 300개 문서로 스케일링 시 성능 하락 분석.

법률 PDF에서의 RAG: 실패에서 ARLC 최고 점수까지
Advertisement 728x90

ARLC 2026 법적 RAG: 17회 반복에서 0.034에서 0.791로, 확장성 분석과 함께

RAG 개발자들에게 주는 교훈: 검색 최적화보다 먼저 제출 형식 검증이 우선되어야 한다. (doc_id, page_number)에 정확히 매핑되지 않으면 완벽한 재정렬도 의미가 없다.

파이프라인 아키텍처: 페이지 수준 인덱싱 및 하이브리드 검색

파이프라인은 문서 전체 페이지를 인덱싱하며 청크화를 피한다. 이는 페이지 수준의 정합성 검증을 간단하게 하지만, LLM 처리 전에 컨텍스트 정제가 필요하다.

OCR 보완을 통한 PDF 파싱

def extract_pages(pdf_path, min_chars=50, ocr_dpi=300):
    pages = []
    doc = pdfplumber.open(pdf_path)
    for i, page in enumerate(doc.pages):
        text = page.extract_text() or ""
        if len(text.strip()) < min_chars:
            text = ocr_page(pdf_path, i + 1, dpi=ocr_dpi)
        pages.append({
            "doc_id": pdf_path.stem,      # .pdf 없이!
            "page_number": i + 1,
            "text": text
        })
    return pages

코드는 추출된 텍스트가 적을 경우 스캔된 PDF에 대해 OCR을 활용한다. doc_id는 확장자 없이 파일명의 스테임을 사용하며, page_number는 1부터 시작된다.

Google AdInline article slot

RRF를 활용한 하이브리드 검색

정확한 일치를 위해 BM25와 의미적 유사성을 위해 임베딩을 결합하는 Reciprocal Rank Fusion(RRF, k=60) 방식을 사용:

def hybrid_search(query, bm25_index, embedding_index, pages, top_k=20, rrf_k=60):
    bm25_scores = bm25_index.get_scores(tokenize(query))
    bm25_ranked = sorted(range(len(pages)), key=lambda i: -bm25_scores[i])

    q_emb = embed_model.encode([query[:512]], normalize_embeddings=True)
    sim_scores = (embedding_index @ q_emb.T).flatten()
    emb_ranked = sorted(range(len(pages)), key=lambda i: -sim_scores[i])

    combined = {}
    for rank, idx in enumerate(bm25_ranked[:top_k * 3]):
        combined[idx] = combined.get(idx, 0) + 1.0 / (rrf_k + rank)
    for rank, idx in enumerate(emb_ranked[:top_k * 3]):
        combined[idx] = combined.get(idx, 0) + 1.0 / (rrf_k + rank)

    return sorted(combined, key=lambda i: -combined[i])[:top_k]

임베딩 모델: all-MiniLM-L6-v2 (22M 파라미터). RRF는 스케일 정규화 없이도 직접 순위에 작동한다.

재정렬 및 우선순위 태그 지정

상위 30개 페이지를 cross-encoder(ms-marco-MiniLM-L-6-v2)로 재정렬한다. 하드캡 30개로 설정해 O(N²) TTFT 폭발을 막는다.

Google AdInline article slot
def rerank(pages, query, top_k=5):
    HARD_CAP = 30
    priority = [p for p in pages if p.get("_priority")]
    non_priority = pages[:HARD_CAP - len(priority)]

    all_pages = priority + non_priority
    pairs = [(query, p["text"][:512]) for p in all_pages]
    scores = cross_encoder.predict(pairs)

    for i, p in enumerate(all_pages):
        if p.get("_priority"):
            scores[i] = 1000.0

    ranked = sorted(zip(scores, all_pages), key=lambda x: -x[0])
    return [p for _, p in ranked[:top_k]]

우선순위 페이지(표지, 조항 등)는 점수 1000을 부여해 컨텍스트에 반드시 포함되도록 보장한다.

비교 질문을 위한 문서 라우팅

"CFI 001/2020 vs CFI 002/2021에서 누가 먼저 임명되었는가" 같은 질문에는 표지 페이지 인덱스를 구축한다.

  • 패턴: CFI 010/2024 → doc_id
  • 라운드 로빈 방식으로 컨텍스트를 번갈아 가며 쿼리 실행

타입 기반 결정론적 빠른 경로

타입: 숫자(±1%), 불리언, 이름(정확), 날짜(YYYY-MM-DD), 이름(자카르드), 자유 텍스트(LLM-판단)

Google AdInline article slot

정규식 추출 예시:

  • 법률 번호: re.search(r"(?:DIFC\s+)?Law\s+No\.\s*(\d+)")
  • 발행일: Date of Issue[:\s]+(\w+ \d+,?\s+\d{4})로 첫 페이지 파싱
  • 금액: case_ref를 통해 금융 청구액 추출

빠른 경로는 Haiku 호출 시 700ms 절약, 답변과 정합성 정보 동시 반환.

점수 공식 및 TTFT 최적화

Total = (0.7 × S_det + 0.3 × S_asst) × G × T × F
  • G (F-beta, β=2.5): 페이지 수준 정합성 — 핵심 곱계수
  • F: <1000ms → +5%, >3000ms → -15%

| TTFT (ms) | 배율 |

|-----------|------|

| <1000 | 1.05 |

| <2000 | 1.02 |

| <3000 | 1.00 |

| >3000 | 0.85–0.99 |

확장성 장벽: 워밍업 vs 최종 결과

30개 문서(워밍업)에서는 점수가 0.791이었으나, 300개(최종)에서는 42% 하락했다. 원인:

  • 더 큰 코퍼스로 인한 검색 품질 저하
  • TTFT는 크기에 비례해 증가
  • 300페이지 이상 재정렬 시 F 성능 악화

최종 최적화: 임베딩 캐싱, cross-encoder 배치 처리, 하이브리드 검색에서 조기 종료 적용.

무엇이 중요한가?

  • 페이지 수준 인덱싱: 정합성 검증을 간단하게 하지만, 컨텍스트 정제 필요.
  • RRF(k=60): 정규화 없이도 BM25 + 임베딩의 최적 융합 가능.
  • 우선순위 태그: 핵심 페이지가 항상 컨텍스트에 포함되도록 보장.
  • 빠른 경로: 타입 기반 정규식으로 80%의 질의 처리, TTFT 크게 감소.
  • TTFT 중심 접근: 동일한 품질에서도 속도로 +5% 보너스 제공.

— Editorial Team

Advertisement 728x90

다음 읽기