Skip to content

[Feat] #17 Layer A + MENTIONS + 8문서 일괄 적재 스크립트 (옵션 3 chunk_id prefix)#45

Merged
TaskerJang merged 15 commits into
devfrom
feat/17-batch-ingest
May 16, 2026
Merged

[Feat] #17 Layer A + MENTIONS + 8문서 일괄 적재 스크립트 (옵션 3 chunk_id prefix)#45
TaskerJang merged 15 commits into
devfrom
feat/17-batch-ingest

Conversation

@TaskerJang

Copy link
Copy Markdown
Owner

컨텍스트

이슈 #17 — 평가 셋 8문서 일괄 적재 + Layer A 추가. PR #44 (#24 Opik 1단계) 직후 진행 → 모든 함수에 자동 @track 적용된 상태에서 운영 데이터 가시화.

5/23 발표 자료의 § 운영 데이터 + § 포맷 다양성 슬라이드의 핵심 시드.

변경 사항

Layer A 적재 (신규)

  • kg/builder.pybuild_layer_a(document, client) 함수
    • Document / Section / Chunk / Table 노드 MERGE (doc-ontology.md §3 스키마 그대로)
    • HAS_SECTION / CONTAINS_CHUNK / CONTAINS_TABLE / NEXT 관계 (§4)
    • Layer A 4 노드의 id 유일 제약 추가
  • kg/builder.pylink_chunks_to_entities(linking, client) 함수
    • Layer A Chunk → Layer B Entity [:MENTIONS] 관계

Layer 책임 분리 (AGENTS.md 원칙)

  • build_layer_a 는 Layer A 노드만, build_layer_b 는 Layer B 만 적재
  • MENTIONS 연결은 별도 함수 link_chunks_to_entities 책임
  • 두 신규 함수 모두 @track(ignore_arguments=["client"]) — Opik trace 의 client 노이즈 회피 (5/16 박제)

옵션 3 — chunk_id global prefix (extractor)

  • kg/extractor.py — chunk dict 에 옵셔널 chunk_id 키 인식 → entity local_id + relation source/target 모두 {chunk_id}__ prefix 자동 부여
  • make_global_id / parse_global_id 헬퍼 — MENTIONS 매핑에서 청크 역추적
  • 5/10 1청크 sanity (chunk_id 없음) 와 호환 — 기존 동작 그대로

8문서 일괄 처리 스크립트

  • scripts/run_w3_batch.pyeval/dataset/documents/ 의 모든 파일 순회
    • 파이프라인: parse_documentextract (Kimi) → link_entities (bge-m3) → build_layer_abuild_layer_blink_chunks_to_entities
    • 문서별 stat 수집 → markdown 표 stdout (weekly-log 그대로 복사 가능)
    • 실패 graceful: 한 문서 실패해도 다음 문서 진행, error 박제
    • 첫 문서만 create_indexes=True (멱등이지만 절약)
    • 사용: uv run python -m scripts.run_w3_batch [--limit N] [--pattern '*.pdf']

단위 테스트 11개

  • tests/kg/test_builder_layer_a.py — mock Neo4jClient 로 client.write() 인자 검증
  • build_layer_a (7): 노드 수치 / Document params / Section label 직렬화 / Section label None / NEXT 경계 / CONTAINS_TABLE / MERGE 기반 idempotent
  • link_chunks_to_entities (4): 기본 / dedupe / 다중 청크 / 구식 local_id graceful drop
  • 로컬 검증 결과: 11/11 PASS

Weekly log

  • docs/weekly-log/2026-05-16-batch-ingest.md — DRAFT
    • 본인이 로컬 8문서 sanity 후 결과 표 + 시행착오 + 그래프 통계 채울 자리
    • 5/23 발표 자료 시드 매핑 표 포함

DoD

  • Layer A 적재 함수 (build_layer_a) 구현
  • MENTIONS 매핑 함수 (link_chunks_to_entities) 구현
  • 옵션 3 chunk_id prefix 자동 부여 (extractor)
  • 8문서 일괄 스크립트 (scripts/run_w3_batch.py)
  • 단위 테스트 11개 (Layer A + MENTIONS) — 11/11 PASS
  • Weekly log 템플릿 박제
  • 본인 로컬: 1문서 sanity 실행 — Layer A 가 Aura 에 적재되는지 확인
  • 본인 로컬: 8문서 풀 실행 — 결과 표를 weekly-log 에 박제
  • Aura console 그래프 통계 — Cypher 쿼리 실행 후 통계 박제
  • Opik UI trace 스크린샷 — 8문서 trace 페이지

후속

본 PR 머지 후 5/17 (일):

  1. 로컬에서 작은 문서 1개로 sanity → 잘 되면 8문서 풀 실행
  2. 발견되는 시행착오 (HWP 파서 / 큰 PDF 비용 / DOC 처리 등) 를 weekly-log 에 박제
  3. [Feat] W4 Layer A: Text2Cypher #18 W4 Text2Cypher Agent — 본 PR 로 적재된 풍성한 Layer A + Layer B 그래프 위에서 동작

비용 주의

8문서 × 평균 N청크 → Kimi 호출 200+ 번 추정. 첫 실행은 --limit 1 로 sanity 후 풀 실행 권장.

관련

TaskerJang added 11 commits May 16, 2026 11:27
청크 N개에서 각각 ent_001부터 부여되는 local_id 충돌 해결.

- chunk dict에 옵셔널 chunk_id 키 추가 → entity local_id + relation
  source/target 모두에 {chunk_id}__ prefix 부여
- make_global_id / parse_global_id 헬퍼 → MENTIONS 매핑에서 역추적
- _GLOBAL_ID_SEP = "__" (chunk_id 안의 단일 _ 충돌 회피)
- chunk_id 없는 호출은 기존 동작 그대로 (5/10 sanity 호환)
- extract() 로그에 global_id=ON/off 표시

옵션 3 채택 사유 (#17 이슈 코멘트 박제):
- 시그니처 호환 (chunk_id optional)
- 병렬성 유지 (Semaphore + asyncio.gather)
- 책임 분리 (호출자는 chunk_id만 박아주면 됨)
신규:
- build_layer_a(document, client) — Document/Section/Chunk/Table 노드
  + HAS_SECTION/CONTAINS_CHUNK/CONTAINS_TABLE/NEXT 관계 (doc-ontology.md §3-§4)
- link_chunks_to_entities(linking, client) — Chunk -[:MENTIONS]-> Entity
  parse_global_id 로 entity local_id 에서 chunk_id 역추적

인프라:
- Layer A 인덱스 4개 추가 (Document/Section/Chunk/Table id 유일 제약)
- LayerAIngestResult, MentionsResult dataclass 추가
- 두 신규 함수 모두 @track(ignore_arguments=["client"]) — span input 에서
  Neo4jClient 객체 노이즈 회피 (5/16 박제)

설계 원칙 (AGENTS.md):
- Layer 책임 섞지 않기: build_layer_a 는 Layer A 노드만, build_layer_b 는
  Layer B 만. MENTIONS 연결은 link_chunks_to_entities 별도 책임.
- MERGE 기반 idempotent — 8문서 재실행 시 중복 없음

기존 build_layer_b 변경 없음 (시그니처 호환).
local_id 매핑 코멘트만 갱신 (옵션 3 적용 반영).
사용: uv run python -m scripts.run_w3_batch [--limit N] [--pattern '*.pdf']

파이프라인:
  parse_document (Layer A) → extract (Kimi) → link_entities (bge-m3)
  → build_layer_a → build_layer_b → link_chunks_to_entities

- 문서별 stat 수집 → markdown 표 stdout (weekly-log 그대로 복사 가능)
- 실패 graceful: 한 문서 실패해도 다음 문서 진행, stat 에 error 박제
- 첫 문서만 create_indexes=True (멱등이지만 절약)
- 옵션 3 chunk_id 자동 박힘 → MENTIONS 매핑 동작

run_w3_pipeline.py (1청크 sanity) 는 그대로 유지 — 두 스크립트 책임 분리.
build_layer_a (7 tests):
- 노드 수치 검증 (Doc 1 + Sec 2 + Chunk 3 + Table 1)
- Document params (publisher / fiscal_year 등 메타 전달)
- Section label enum → string 직렬화
- Section label None 안전 (W3 미분류 청크 대응)
- NEXT 관계 Section 경계 안 넘음
- CONTAINS_TABLE 관계 올바른 Section 연결
- 모든 쿼리 MERGE 기반 (idempotent 검증)

link_chunks_to_entities (4 tests):
- 기본 동작 — chunk_id 추출 후 MENTIONS
- dedupe — 같은 청크 같은 그룹 1회만
- 다중 청크 — 각각 MENTIONS
- 구식 local_id graceful drop (5/10 sanity 호환)

mock Neo4jClient 사용 — 실제 DB 없이 client.write() 인자 검증.
로컬 검증 결과: 11/11 PASS.
본인이 로컬에서 8문서 sanity 후 다음을 채울 자리:
- 결과 표 (stdout markdown 그대로 복사)
- 시행착오 박제 (HWP/DOC/큰 PDF 예상 후보 미리 적어둠)
- 그래프 통계 (Cypher 쿼리 미리 박제)

5/23 발표 자료 시드 매핑 표 포함.
5/16 토요일 작업 마무리:
- ✅ 코드 + 단위 테스트 11/11 PASS
- ⚠️ 8문서 sanity 는 SemanticChunker 단계 hang 으로 이월
  (DS투자증권 PDF 1쪽으로 시도, bge-m3 초기화 후 5분+ 멈춤, CPU 0.1%)
- 별도 발견: MS Store python stub 이 PATH 에 있음 (uv run 은 영향 없음)

5/17 일요일 디버깅 순서 박제:
1. cmd 에서 uv run python -u 로 unbuffered 재시도
2. SemanticChunker 단독 검증
3. bge-m3 임베딩 단독 검증

PR #45 는 sanity 완료 후 머지.
5/16 두 번째 시도 (cmd -u + 새 Aura 9b57188f) 에서도 같은 자리 hang.

확정된 사항:
- Neo4j 문제 아님 (새 인스턴스에서도 동일)
- PyCharm 환경 문제 아님 (cmd -u 에서도 동일)
- 진짜 원인: SemanticChunker.split_documents 의 bge-m3 임베딩 (CPU)

5/17 옵션:
- A. langchain-huggingface 마이그레이션
- B. 단순 분할 fallback (sanity 빠르게, 발표 시드 우선) ← 권장
- C. 더 작은 임베딩 모델 (MiniLM)

단독 검증 명령 3개 박제: bge-m3 / SemanticChunker / parse_document
어느 단계에서 막히는지 정확히 짚고 옵션 선택.

부가 발견:
- Aura Free trial 만료 → 새 인스턴스 9b57188f 생성, .env 갱신 완료
- MS Store python stub PATH 이슈 (uv run 은 영향 없음)
5/16 디버깅 1.5시간 끝에 진짜 원인 확정:
- LangChain SemanticChunker = 임베딩 sequential 호출 (batch 안 씀)
- 측정값: 820자 / 31.3초 (비현실적)
- 추정: 8문서 전체 분할만 40분~1시간

해결: 옵션 B fallback 한 줄
- ingestion/chunker.py 의 _semantic_split() 에 USE_SIMPLE_CHUNKER 환경변수 분기
- RecursiveCharacterTextSplitter 로 즉시 분할
- semantic 분할은 발표 후 GPU/API 마련 후 다시

5/17 일요일 시작 순서를 최상단에 박제 (Step 0~6):
- chunker.py fix (5분) → sanity (2-5분) → 8문서 풀 실행 (10-20분)
  → 결과 박제 → PR #45 머지 → #18 W4 시작

발표 자료 시드 2개 추가:
- "SemanticChunker+bge-m3+CPU 본질적 비효율" 슬라이드
- "semantic vs 단순 분할 trade-off" 슬라이드
배경 (5/16 #17 디버깅 발견):
- LangChain SemanticChunker 가 임베딩을 문장 단위 sequential 호출 (batch 미사용)
- bge-m3 (568M) + CPU 환경: 820자 / 31.3초 측정
- 8문서 풀 실행 예상: 분할만 40분~1시간 → production 비현실적

해결:
- _semantic_split() 맨 위에 USE_SIMPLE_CHUNKER 환경변수 분기 추가
- ON 시 RecursiveCharacterTextSplitter (separators 한국어 친화) 사용
- 기본값 OFF — 회사 레포 (doc-summary-agent) 와 100% 동일 동작

정직성 원칙:
- chunk 정책 (size=700, overlap=200, min=50) 은 회사 레포 dev 와 동일 유지
- USE_SIMPLE_CHUNKER=1 fallback 은 CPU 환경의 escape hatch +
  발표 자료의 trade-off 슬라이드 증거 (선택지 3)

발표 메시지 (5/23):
"동일 chunker 로 비교하려 했으나, CPU 환경에서 SemanticChunker 가
 비현실적임을 발견. semantic 분할의 운영 비용 trade-off 를 정량화함."
5/16 디버깅 1.5시간 끝에 선택지 3 채택:
"디버깅 자체를 발표 메시지로 — trade-off 인사이트 최우선"

발표 구조 6슬라이드 박제:
1. 문제 정의 (회사 vs 본인 동일 환경 비교 시도)
2. 발견 (820자/31초 실측)
3. 분석 (SemanticChunker sequential 호출)
4. trade-off 표 (품질 vs 비용)
5. 결정 + fallback (USE_SIMPLE_CHUNKER, chunk 정책 동일)
6. 인사이트 (RAG 비교의 메타-변수)

선택지 3 채택 근거:
- SEOCHO 멘토링 원칙 일치 ("도구 중 하나" 메시지)
- 엔지니어링 사고력 증거 (단순 비교 → trade-off 분석)
- 발표 시간 안전 (8문서 fallback 10-20분)

"그래서 결국 성능 비교는?" 질문 대응 답변 박제.

코드는 commit 8f0bcb1 에서 fallback 추가 완료.
chunker 정책 (size=700, overlap=200) 은 회사 레포 동일 유지.
5/16 12:48~12:55 검증 결과:
- 1문서 sanity 성공: DS투자증권 PDF, 227.5초
- 113 raw → 55 group (NED compression 0.49)
- Layer A + Layer B + MENTIONS 108 모두 적재

Aura Cypher 통계 4종:
- 노드: Entity 55 / Chunk 52 / Section 6 / Document 1 = 114
- 관계: MENTIONS 108 / CONTAINS_CHUNK 52 / NEXT 46 / HAS_METRIC 13 / FACES_RISK 7 / HAS_SECTION 6 = 232
- Entity 타입: Company 14(68 mentions) / Risk 6(21) / Metric 4(14) / Outlook 3(5)
- Company 1개당 평균 4.86 청크 언급 — NED 효과 정량화

발표 슬라이드 시드 추가:
- 1쪽 시황분석 → 14 회사 + 6 리스크 + 4 지표 + 3 전망 추출
- VectorRAG 단순 유사도로는 못 얻는 다중 청크 entity 구조

5/17 일요일 시작 가이드 갱신:
- USE_SIMPLE_CHUNKER 이미 .env 추가됨 → 바로 8문서 풀 실행 가능
- 1문서당 4분 × 8 = 약 30분 예상
@TaskerJang TaskerJang mentioned this pull request May 16, 2026
8문서 실측 결과:
- 성공률 7/8 (DOC 파서만 실패)
- 전체 소요 4249초 (≈ 71분)
- 총 청크 228, raw entity 937 → group 791
- 총 relation 229, MENTIONS 929
- NED compression 0.84 (8문서 합산)

핵심 발견 박제:
- HWP 파서 성공 (농협 사업보고서, 11 청크 + 56 entity)
- DOC 파서 실패 (금감원, 예상대로) → "포맷 다양성 challenge" 슬라이드
- 미래에셋 4Q 최대 그래프: 68 청크 + 6 표 + 261 entity (Table 첫 적재!)
- 3Q/4Q parse 시간 폭증 (1500초+) → OCR 캐시 부재 신규 PDF 원인
- LLM 비결정성: DS투자증권 2회 추출 결과 차이 (113→55 vs 103→60)

발표 슬라이드 구조 6 → 10슬라이드로 강화:
- 운영 데이터 표 (8문서)
- 포맷 다양성 (PDF + HWP / DOC 실패)
- 미래에셋 4Q 깊이 (Table 적재)
- LLM 비결정성

5/17 일요일 시작 가이드 3단계로 간소화:
1. Aura Cypher 4종 재측정 (5분)
2. PR #45 머지 + 이슈 #17 close (1분)
3. #18 W4 Text2Cypher 시작
1차 (16:05~17:19, 71분): 7/8 성공
- DOC (금감원) 만 실패 — LibreOffice 미설치
2차 (17:28~17:33, 5분): DOC→DOCX 수동 변환 후 1/1 성공
- 49 청크 + 275→251 entity (compression 0.91)
- 260 MENTIONS, 26 relation
- doc_type 자동 분류: disclosure (보도자료) ⭐

진짜 최종 8/8 100% 총합:
- 처리 시간: 76분 (1차 71분 + 2차 5분)
- 청크 277, raw 1212 → group 1042 (compression 0.86)
- relation 255, MENTIONS 1189
- 포맷 다양성: PDF + HWP + DOCX
- doc_type 자동 분류 4가지: report / ir / filing / disclosure

발표 자료 보석 추가:
- LibreOffice 환경 의존성 발견 + 해소 사이클 (엔지니어링 무결성)
- production 배포 가이드 (Docker 이미지 libreoffice 추가)
- doc_type 자동 분류 4가지

발표 구조 10 → 11 슬라이드 (production 의존성 슬라이드 추가)
5/17 일요일 Aura Cypher 5종 (doc_type 분포 추가)
5/16 17:37 Aura console 측정 결과:
- Q1 노드: 610 (Chunk 277 / Entity 261 / Section 53 / Table 11 / Document 8)
- Q2 관계: 2,451 (MENTIONS 1553 / CONTAINS_CHUNK 277 / HAS_METRIC 270 등)
- Q3 Entity 타입: Company 186 (1220 mentions) / Metric 69 / Risk 24 / Outlook 7
  → Company 1개당 평균 6.56 청크 언급 (1문서 4.86보다 증가)
- Q4 미래에셋증권 NED: ⚠️ 빈 결과!
  → NED 한국어 회사명 표기 변형 challenge 발견 (발표 보석)
- Q5 doc_type: pdf+ir 4 / pdf+report 2 / hwp+filing 1 / docx+disclosure 1

핵심 발견 (NEW):
- NED 한국어 변형 challenge — '미래에셋증권' 이 분기별 다른 표기로 NED 분산
- production 환경 후처리 추가 필요성 정량 발견

발표 슬라이드 구조 11 → 12장:
- 9번째 슬라이드: 그래프 통계 (610 노드 / 2,451 관계 / 1,553 MENTIONS)
- 10번째 슬라이드: NED 한국어 변형 challenge ⭐ NEW

5/17 일요일 시작 가이드 갱신:
- Step 1: Q4 NED 추가 진단 (미래에셋 entity 표기 패턴 확인)
- Step 2: PR #45 머지
- Step 3: #18 W4 Text2Cypher (평가 셋 질문 5개 후보 박제)
5/16 17:39~17:43 진단 3종 결과:

진단 1: 미래에셋 4Q 문서 entity top 20
- 68청크에서 회사명 0개
- "공모발행액 23조 7,050억원" 이 Company 라벨 + 19 청크 언급
- LLM 이 entity 라벨을 잘못 부여 패턴 확인

진단 2: 영문/대명사 패턴 ("Mirae"/"당사"/"회사"/"그룹")
- 회사명 0건. 모두 금융 수치
- 대명사 entity 추출 안 됨

진단 3: 전체 Company top 20
- 1위 "총계 금액 전년동기대비..."
- 2위 "금융사", 3위 "일반기업"
- 7위 "한국예탁결제원" (유일한 진짜 기관명)
- 186 Company 중 진짜 회사명 거의 없음

핵심 발견 (진짜 발표 보석!):
- Entity 라벨 품질 challenge — Kimi 가 Company 라벨에 금융 metric 부여
- 자기 회사 보고서에 자기 회사명 entity 없음 (정직한 한계)
- VectorRAG (대명사 해소) ↔ GraphRAG (그래프 traversal) 보완 관계 정량 증명

발표 슬라이드 11 → 12장:
- 슬라이드 10: Entity 라벨 품질 challenge ⭐⭐⭐
- 슬라이드 11: VectorRAG ↔ GraphRAG 보완 관계 정량 증명 ⭐ NEW!

5/17 일요일 시작 가이드 간소화:
- Step 1: PR #45 머지
- Step 2: #18 W4 Text2Cypher (평가 셋 질문 5개 - Company 라벨 challenge 검증 포함)

미래 발견 추가:
- Entity 추출 프롬프트 엔지니어링 (Kimi Company 라벨 개선)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] [5/14 보너스] 8문서 일괄 적재

1 participant