-
Notifications
You must be signed in to change notification settings - Fork 1
Examples
Evidence Chunker를 실제로 사용하는 방법을 설명한다.
langchain/llamaindex extra는 둘 다 코어(chunk(), build_corpus())에 얹히는 얇은 export 레이어다. 프레임워크 연동 없이 표 추출만 필요하면 extra 없이 코어만 설치해도 된다.
| 용도 | 설치 명령 |
|---|---|
| 코어만 (표 추출) | pip install -e . |
| LangChain 연동까지 | pip install -e ".[langchain]" |
| LlamaIndex 연동까지 | pip install -e ".[llamaindex]" |
sim_threshold > 0 코사인 필터까지 |
pip install -e ".[similarity]" |
모든 표는 EvidenceChunker.chunk()의 반환값인 EvidenceUnit 인스턴스로 표현된다. 주요 필드는 다음과 같다.
| 필드 | 타입 | 설명 |
|---|---|---|
eu_id |
str |
"{doc_id}-p{page}-{idx}" 형식의 고유 식별자 |
caption_text |
str | None |
매핑된 캡션. 못 찾으면 None
|
caption_confidence |
"direct" | "inferred" | "none" |
캡션 매핑 신뢰도 |
table_html |
str | None |
표 원본 HTML. 렌더링 실패 시 None
|
context_before / context_after
|
list[str] |
표 위/아래에서 흡수한 인접 단락 |
text |
str (property) |
LLM 컨텍스트용 전체 텍스트(HTML 포함) |
retrieval_text |
str (property) |
임베딩 검색용 축약 텍스트(HTML 제외) |
retrieval_units |
list[str] (property) |
small-to-big 검색용 행 단위 조각 |
from evidence_chunker import EvidenceChunker
chunker = EvidenceChunker()
eus = chunker.chunk("report.pdf")
for eu in eus:
print(eu.eu_id, "|", eu.caption_text, "|", eu.caption_confidence)신뢰도가 낮은("inferred"/"none") EU만 따로 검수하고 싶을 때 caption_confidence로 필터링하면 된다. 단, bbox 거리·인접 페이지·병합 헤더 중 어떤 fallback으로 찾았는지는 "inferred" 하나로 뭉쳐 있어 이 필드만으로는 구분되지 않는다.
표만으로는 답이 안 되는 서술형 질문까지 커버하려면 build_corpus()로 표(EU)와 일반 본문 청크를 한 번에 받는다.
chunks = chunker.build_corpus("report.pdf")
tables = [c for c in chunks if not c.is_atomic] # EvidenceUnit
texts = [c for c in chunks if c.is_atomic] # 일반 본문(TextChunk)build_corpus()는 생성자에 커스텀 parser를 넘긴 경우 NotImplementedError를 던진다(일반 본문 청킹이 Docling DocumentConverter 산출물에 직접 결합돼 있기 때문). 커스텀 파서를 쓰면서 표만 필요하면 chunk()를 쓸 것.
자세한 필드·메서드 시그니처는 API Reference 참고.
from evidence_chunker.export.langchain import to_langchain, EvidenceRetriever
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_huggingface import HuggingFaceEmbeddings
chunks = chunker.build_corpus("report.pdf")
docs = to_langchain(chunks) # metadata["retrieval_text"]를 쓰고 싶으면 page_content 대신 직접 매핑할 것
vectorstore = InMemoryVectorStore.from_documents(docs, HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2"))
retriever = EvidenceRetriever(vectorstore, k=5) # max-pool dedupe 기본 적용
retriever.invoke("서울 지역 Q2 매출은?")표 안 특정 셀 값을 묻는 질의에 강하다. 같은 표의 다른 행 데이터에 유사도가 묻히는 문제를 줄인다.
from evidence_chunker.export.langchain import to_langchain_units
docs = to_langchain_units(chunks) # EU 하나가 retrieval_units개의 작은 Document로 쪼개짐
vectorstore = InMemoryVectorStore.from_documents(docs, HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2"))
# fetch_k는 자동으로 max(k*4, 20)
retriever = EvidenceRetriever(vectorstore, k=5)
results = retriever.get_relevant_documents("서울 Q1 매출은 얼마?")
# 실제 LLM에 넘길 땐 검색에 쓰인 작은 조각(page_content) 대신 표 전체 맥락을 사용
for doc in results:
context = doc.metadata["parent_text"]API 대응 관계는 LangChain과 동일하다: to_llamaindex() / to_llamaindex_units() / EvidenceRetriever(llamaindex 버전).
from evidence_chunker.export.llamaindex import to_llamaindex_units, EvidenceRetriever as LlamaEvidenceRetriever
from llama_index.core import VectorStoreIndex
nodes = to_llamaindex_units(chunks)
index = VectorStoreIndex(nodes)
base_retriever = index.as_retriever(similarity_top_k=20) # dedupe 전이라 넉넉하게
retriever = LlamaEvidenceRetriever(base_retriever, k=5)
retriever.retrieve("서울 Q2 매출은?")기본값(sim_threshold=0.0)은 bbox 거리만으로 인접 단락을 채택해 sentence-transformers를 아예 로드하지 않는다. 표 주변에 무관한 텍스트(예: 여러 단이 좁은 간격으로 배치된 논문)가 자주 섞여 들어온다면 코사인 필터를 추가로 켠다.
# pip install -e ".[similarity]" 로 sentence-transformers 설치 필요
chunker = EvidenceChunker(bbox_threshold=300.0, sim_threshold=0.35)
eus = chunker.chunk("dense_two_column_paper.pdf")기본값은 0.0이며, 근거가 되는 실험 로그는 Experiments 또는 Configuration 참고.
bbox_threshold(기본 300pt)는 표 위/아래로 몇 pt 이내의 단락까지 인접 문맥으로 볼지 결정한다. 표와 설명 단락 사이 여백이 유난히 넓은 보고서(예: 정부 공시 문서)라면 값을 올려야 문맥이 누락되지 않는다.
# 여백이 넓은 문서: 더 넓게 탐색
chunker_wide = EvidenceChunker(bbox_threshold=500.0)
# 표가 밀집된 문서: 옆 표의 캡션이 잘못 붙는 걸 방지하려면 좁게
chunker_tight = EvidenceChunker(bbox_threshold=150.0)300pt를 기본값으로 정한 근거는 Experiments 또는 Configuration 참고.
표 추출 및 청킹 알고리즘(caption.py, context.py 등)은 특정 PDF 분석 도구(예: Docling)에 묶여 있지 않고, ParsedDoc(parser.base.ParsedDoc)이라는 공통 양식(인터페이스)만 바라보고 동작하도록 설계하였다.
따라서 PdfParser 프로토콜(parse(path) -> ParsedDoc)만 구현하면 다른 PDF 파서로 교체할 수 있다.
from evidence_chunker.parser.base import PdfParser, ParsedDoc
class MyCustomParser:
def parse(self, path: str) -> ParsedDoc:
... # 다른 파서(예: MinerU)의 출력을 ParsedDoc으로 매핑
chunker = EvidenceChunker(parser=MyCustomParser())
eus = chunker.chunk("report.pdf") # OK — chunk()는 parser 교체를 지원
chunker.build_corpus("report.pdf") # NotImplementedError — 위 "핵심 모듈: Evidence Unit 생성" 참고현재 저장소에는 DoclingParser 구현체만 포함되어 있다.
doc_id를 지정하지 않으면 파일명 stem이 자동으로 쓰이지만, 여러 문서를 하나의 벡터스토어에 합칠 때는 충돌 방지를 위해 명시적으로 지정하는 걸 권장한다.
import glob
from pathlib import Path
chunker = EvidenceChunker()
all_chunks = []
for path in glob.glob("data/pdfs/*.pdf"):
doc_id = Path(path).stem
all_chunks.extend(chunker.build_corpus(path, doc_id=doc_id))
print(f"총 {len(all_chunks)}개 청크 (표 {sum(not c.is_atomic for c in all_chunks)}개)")EvidenceChunker는 Docling DocumentConverter를 첫 파싱 시점에 lazy하게 한 번만 생성해 재사용하므로, 같은 인스턴스로 여러 PDF를 반복 처리해도 매번 모델을 다시 로드하지 않는다.
EvidenceRetriever를 안 쓰고 벡터스토어 API를 직접 호출하는 기존 코드에 끼워 넣고 싶다면 dedupe_by_chunk_id()만 가져다 쓸 수 있다.
from evidence_chunker.export.langchain import dedupe_by_chunk_id
results = vectorstore.similarity_search_with_score(query, k=20) # k는 넉넉하게
top5 = dedupe_by_chunk_id(results, k=5)(Document, score) 튜플 리스트와 Document 리스트 둘 다 받는다. LlamaIndex는 evidence_chunker.export.llamaindex.dedupe_by_chunk_id가 동일하게 동작하며 NodeWithScore/TextNode를 구분 없이 처리한다.
-
캡션 없는 표:
direct(captions 참조) 매칭이 실패하면 bbox 거리 → 인접 페이지 → 병합 헤더 순으로 fallback한다. 넷 다 실패하면caption_text=None,caption_confidence="none"으로 남으니, 이 경우retrieval_text에 표 제목이 없다는 걸 감안해 상위 애플리케이션에서 별도 처리(예: 섹션 헤더로 대체)할 수 있다. -
목차(TOC)/그림 목차(LOF) 표 오탐: 텍스트가 표 형태로 파싱된 목차 페이지는
filters.is_toc_or_lof_decoy()로 자동 제외된다. 별도 설정 없이 기본 동작이다. -
동일 표 중복 인식: Docling이 표 하나를
TableItem2개로 중복 인식하는 경우, 더 세밀하게 구조화된 쪽만 남기고 캡션은 살아남은 쪽에 이전한다(filters.find_duplicate_tables()).
알고리즘 세부사항은 Architecture 참고.