Skip to content

[Feat] W4 Layer B Local Retriever + L1~L5 평가 셋 (#19)#49

Merged
TaskerJang merged 10 commits into
devfrom
feat/19-local-retriever
May 17, 2026
Merged

[Feat] W4 Layer B Local Retriever + L1~L5 평가 셋 (#19)#49
TaskerJang merged 10 commits into
devfrom
feat/19-local-retriever

Conversation

@TaskerJang

Copy link
Copy Markdown
Owner

요약

W4 두 번째 retrieval — Local Retriever (Layer B) 구현. text2cypher.py (#18, PR #48 머지 완료) 패턴 그대로 재활용하되, LLM 이 Cypher 를 자유 생성하는 대신 entity 만 식별하고 subgraph 는 결정적 1-hop Cypher 템플릿으로 추출. "관계/연관" 질의에 강함.

Closes #19 (정성 검증 결과 박제 후 PR 머지)

동작 흐름

자연어 질문
   ↓ _identify_entities (LLM JSON 응답, kg/extractor.py 패턴)
entity 후보 [{name, label?}, ...]
   ↓ _match_entities (Neo4j name/aliases CONTAINS — fulltext index 없을 때 대비)
매칭된 Entity 노드들
   ↓ _expand_subgraph (1-hop Layer B + MENTIONS 청크, parameterized Cypher)
subgraph (entities, relationships, chunks)
   ↓ _generate_answer (LLM 2단계, 라벨 품질 challenge 정직 보고)
자연어 답변

Text2Cypher (Q*) vs Local Retriever (L*) 차이

특성 Q (Text2Cypher) L (Local Retriever)
LLM 역할 Cypher 자유 생성 entity 만 식별
Cypher LLM 생성 (다양) 결정적 1-hop 템플릿
강점 factual / topN / 정량 관계 / 연관 / 의미
약점 다 entity 교집합 글로벌 / 통계 질의
안전장치 read-only 강제 + LIMIT parameterized (사용자 입력 escape)

변경 사항

신규 파일

  • retrieval/local_retriever.py (540 lines) — Local Retriever 메인
  • retrieval/prompts/local_retriever_entity_v1.md — entity 식별 system prompt (5 few-shot)
  • retrieval/prompts/local_retriever_answer_v1.md — 답변 생성 system prompt (4 few-shot)
  • docs/weekly-log/2026-05-17-local-retriever.md — 작업 일지 (정성 검증 결과 박제 자리)

수정 파일

  • retrieval/__init__.pylocal_retrieve, LocalRetrieverResult, LocalRetrieverError export
  • retrieval/eval_set.md — L1~L5 섹션 추가 (~150 lines)
  • scripts/run_w4_eval.py — L 케이스 5개 + --suite local 필터 + Local Retriever 평가 로직

평가 셋 L1~L5

ID 카테고리 질문 검증 포인트 슬라이드
L1 relation 두산밥캣과 함께 언급된 리스크는? Q4와 답변 패턴 차이 11
L2 two-entity 한화와 두산밥캣은 어떻게 관련? 두 entity 교집합 13 (NEW)
L3 self-company 미래에셋증권 지표/전망은? ⭐ 자기 회사명 미추출 10
L4 label-quality 공모발행액 23조 위험은? ⭐ 라벨 품질 정직 보고 10
L5 global 전체 8문서 트렌드는? Layer C 라우팅 명분 14

안전장치

  1. read-only: Cypher 템플릿이 결정적, 사용자 입력은 모두 parameterized (CONTAINS 매칭).
  2. 결과 크기 제한: MAX_ENTITIES_TO_EXPAND=5, MAX_RELATED_PER_ENTITY=15, MAX_CHUNKS_PER_ENTITY=3, CHUNK_TEXT_TRUNCATE=300 — 토큰 폭발 방지.
  3. graceful fallback: entity 식별 0개 (L5) / 매칭 0개 (L3) / 빈 subgraph / LLM 실패 모두 자연어 안내.

DoD (#19)

  • 관계 질의 (L1, L2) 에서 VectorRAG / Text2Cypher 와 다른 답변 패턴 (subgraph 활용) → 본인 정성 확인
  • 평균 응답 시간 < 5초 → 본인 측정 후 weekly-log 박제
  • graceful fallback (L3 매칭 0개, L4 라벨 challenge, L5 글로벌)

검증 방법

:: Local Retriever 만 (L1~L5)
uv run python -m scripts.run_w4_eval --suite local

:: 1개씩 디버깅
uv run python -m scripts.run_w4_eval --case L3

:: JSON 박제 (weekly-log 에 결과 표 박제용)
uv run python -m scripts.run_w4_eval --suite local --json eval/w4_local_2026-05-17.json

:: 전체 (Q* + F* + L*) 일관성 확인
uv run python -m scripts.run_w4_eval

레퍼런스

  • Microsoft GraphRAG — Local Search: entity 를 graph 진입점 (access point) 으로 활용
  • Tomaz Bratanic (Neo4j Developer Blog) — Local-to-Global GraphRAG with Neo4j
  • 회사 레포 doc-summary-agent — LLM 호출 / JSON 강제 / 재시도 패턴 (kg/extractor.py 정합)

발표 슬라이드 연결 (5/23 멘토링 D-6)

  • 슬라이드 10 (Entity 라벨 품질) ← L3 (자기 회사명 미추출), L4 (metric 라벨 혼재) 보강
  • 슬라이드 11 (보완 관계) ← L1 (Q4와 답변 패턴 차이)
  • 슬라이드 13 (NEW: Local 데모) ← L1 / L2 시연
  • 슬라이드 14 (Routing 명분) ← L5 글로벌 fallback

의존 / 후속

Merge 전 체크리스트

  • 로컬에서 uv run python -m scripts.run_w4_eval --suite local 실행
  • L1~L5 정성 결과를 docs/weekly-log/2026-05-17-local-retriever.md 의 TBD 표에 박제
  • 평균 응답 시간 측정값 박제 (DoD: < 5초)
  • (선택) JSON 결과를 eval/w4_local_2026-05-17.json 으로 박제

🤖 작업 어시스턴트: 코드 push & PR 작성 (5/17 일요일 코드 트랙)

TaskerJang added 10 commits May 17, 2026 08:51
W4 두 번째 retrieval. text2cypher.py (#18) 패턴 그대로 재활용하되, LLM 이 Cypher 를
자유 생성하는 대신 entity 만 식별하고 1-hop subgraph 는 결정적 Cypher 템플릿으로
추출. "관계/연관" 질의에 강함.

흐름:
  자연어 질문
     ↓ _identify_entities (LLM JSON 응답, kg/extractor.py 패턴)
  entity 후보 [{name, label?}, ...]
     ↓ _match_entities (Neo4j name/aliases CONTAINS — fulltext index 없을 때 대비)
  매칭된 Entity 노드들
     ↓ _expand_subgraph (1-hop Layer B + MENTIONS 청크, parameterized Cypher)
  subgraph (entities, relationships, chunks)
     ↓ _generate_answer (LLM 2단계, 라벨 품질 challenge 정직 보고)
  자연어 답변

신규 파일:
- retrieval/local_retriever.py (540 lines) — Local Retriever 메인 모듈
- retrieval/prompts/local_retriever_entity_v1.md — entity 식별 system prompt
- retrieval/prompts/local_retriever_answer_v1.md — 답변 생성 system prompt
- docs/weekly-log/2026-05-17-local-retriever.md — 작업 일지 (정성 검증 결과 박제 대기)

수정 파일:
- retrieval/__init__.py — local_retrieve / LocalRetrieverResult export 추가
- retrieval/eval_set.md — L1~L5 섹션 추가 (Layer B 평가 셋)
- scripts/run_w4_eval.py — L 케이스 분기 + --suite local 필터 + Local Retriever 평가 로직

평가 셋 L1~L5:
- L1 [relation]   두산밥캣과 함께 언급된 리스크 — Q4 와 답변 패턴 차이 (subgraph 활용)
- L2 [two-entity] 한화와 두산밥캣의 관계 — 두 entity 교집합 (Text2Cypher 어려운 케이스)
- L3 [self-company]   미래에셋증권 지표/전망 — ⭐ 자기 회사명 미추출 graceful (슬라이드 10)
- L4 [label-quality]  공모발행액 23조 위험 — ⭐ 라벨 품질 challenge 정직 보고 (슬라이드 10)
- L5 [global]     전체 8문서 트렌드 — Layer C 라우팅 명분 (슬라이드 14)

안전장치:
1. read-only: Cypher 템플릿이 결정적, 사용자 입력은 parameterized (CONTAINS 매칭).
2. 결과 크기 제한: MAX_ENTITIES_TO_EXPAND=5, MAX_RELATED_PER_ENTITY=15,
   MAX_CHUNKS_PER_ENTITY=3, CHUNK_TEXT_TRUNCATE=300 — 토큰 폭발 방지.
3. graceful fallback: entity 식별 0개 / 매칭 0개 / 빈 subgraph / LLM 실패 모두 자연어 안내.

레퍼런스:
- Microsoft GraphRAG Local Search — entity 를 graph 진입점 (access point) 으로 활용
- Tomaz Bratanic (Neo4j Developer Blog) — Local-to-Global GraphRAG with Neo4j
- 회사 레포 doc-summary-agent — LLM 호출 / JSON 강제 / 재시도 패턴 (kg/extractor.py 정합)

DoD (#19):
- 관계 질의에서 VectorRAG 와 다른 답변 패턴 (subgraph 정보 활용) → L1, L2 검증
- 평균 응답 시간 < 5초 → 정성 검증 시 측정

발표 슬라이드 연결 (5/23):
- 슬라이드 10 (라벨 품질) ← L3, L4 보강
- 슬라이드 11 (보완 관계) ← L1 (Q4 와 패턴 차이)
- 슬라이드 13 (NEW Local 데모) ← L1 / L2 시연
- 슬라이드 14 (Routing 명분) ← L5 글로벌 fallback

후속:
- #21 Routing Agent (5/18) — 키워드 분기: "관계/관련/영향" → Local
- 정성 검증 실행 → weekly-log 결과 박제 → PR 머지

Closes #19 (정성 검증 결과 박제 후 PR 머지)
기존 Q1~Q5 + F1~F3 (Text2Cypher) 섹션은 그대로 유지하고, 뒤에 L1~L5 (Local Retriever) 섹션 추가.

L 시리즈:
- L1 [relation]      두산밥캣과 함께 언급된 리스크 — Q4와 답변 패턴 차이 검증
- L2 [two-entity]    한화/두산밥캣 관계 — 두 entity 교집합
- L3 [self-company]  미래에셋증권 지표/전망 — ⭐ 자기 회사명 미추출 (슬라이드 10)
- L4 [label-quality] 공모발행액 23조 위험 — ⭐ 라벨 품질 정직 보고 (슬라이드 10)
- L5 [global]        전체 8문서 트렌드 — Layer C 라우팅 명분 (슬라이드 14)

차이점 표 (Q vs L), 동작 흐름, Cypher 템플릿 (entity matching + subgraph expansion) 박제.
기존 Q1~Q5 + F1~F3 (Text2Cypher) 케이스는 그대로 유지하고 다음을 추가:

1. EvalCase 에 suite 필드 추가 ("t2c" | "local") — case_id 가 Q/F/L 인지에 따라 retriever 분기
2. L1~L5 EvalCase 5개 추가 (Local Retriever 평가 셋)
3. expect_empty_entities (L5), expect_empty_matches (L3) 필드 추가 — graceful fallback 검증
4. _evaluate_t2c_case + _evaluate_local_case 분리 — 각 retriever 특성에 맞는 verdict 로직
5. CaseEvalResult 에 local 전용 필드 (identified_entities, matched_entity_count, subgraph_*) 추가
6. _print_summary_table: t2c / local 별도 표 + Local 평균 응답 시간 표시 (DoD < 5초 마크)
7. CLI: --suite local / --suite t2c 필터 추가

사용 예:
  uv run python -m scripts.run_w4_eval --suite local         # L* 만
  uv run python -m scripts.run_w4_eval --case L3              # L3 만
  uv run python -m scripts.run_w4_eval --json eval_w4.json   # raw JSON 박제

#19 DoD 검증:
- L1, L2: 매칭 ≥ 1 + 답변 + hint 절반 → PASS
- L3 (자기 회사명): 매칭 0개 + 안내 답변 → PASS
- L4 (라벨 품질): 매칭 ≥ 1 + 답변에 challenge 언급 hint → PASS
- L5 (글로벌): 식별 0개 + Layer C 안내 → PASS
- 평균 응답 시간 < 5초 자동 측정 + 마크 표시
DoD 체크리스트 + L1~L5 TBD 표 (본인 정성 검증 실행 후 결과 박제할 자리).
PR 머지 직전 결과 채우면 됨.
…tax 에러 수정 (#19)

5/17 정성 검증 시 L1~L4 모두 Cypher SyntaxError 42I63 발생:
  "ORDER BY, SKIP and LIMIT can only be used in this order in RETURN.
   (line 10, column 1) RETURN qname, ..."

원인: 이전 패턴 `WITH ... ORDER BY ... WITH collect(e)[..3]` 가 Neo4j 5.x 파서에서
모호하게 해석됨 — ORDER BY 후속 LIMIT 이 RETURN 절 밖에 위치한 형태로 잡힘.

해결: CALL 서브쿼리로 ORDER BY+LIMIT 을 RETURN 절 안에 격리.
  UNWIND $names AS qname
  CALL {
    WITH qname
    MATCH (e:Entity) WHERE ...
    RETURN e ORDER BY size(e.name) ASC LIMIT 3
  }
  RETURN qname, elementId(e) AS id, ...

이 패턴은 Neo4j 5.x 표준이고 Aura 에서 안정적으로 동작.

5/17 정성 검증 결과 (수정 전):
- L1~L4: PARTIAL (모두 매칭 0개로 fallback — Cypher 에러 graceful 처리 동작 검증)
- L5: PASS (글로벌 질의 entity 식별 0개 패턴 정상)
- 평균 응답: 5.4s ⚠️ (DoD 5초 0.4초 초과 — LLM 재시도 영향)

graceful fallback 자체는 완벽 동작 — Cypher 에러 → 매칭 0개 → 안내 답변 → 답변에
hint 단어 포함 → 모두 PARTIAL 이상 등급. 즉 에러 핸들링은 검증됐고 Cypher 만 fix.

수정 후 기대 결과:
- L1, L2: 매칭 ≥ 1개 → 1-hop subgraph 추출 → PASS (Q4 와 답변 패턴 차이 검증)
- L3: 매칭 0개 expected (자기 회사명 '미래에셋증권' 미추출) → 슬라이드 10 보강
- L4: 매칭 ≥ 1 (Company 라벨로 잘못 분류된 metric) → 답변에 라벨 challenge 언급
- L5: 변동 없음 (식별 0개 패턴 그대로)

수정 사항: _MATCH_ENTITIES_CYPHER 만 교체. 나머지 (_EXPAND_SUBGRAPH_CYPHER, 함수 시그니처) 무변경.
5/17 정성 검증 v2 실행 시 두 가지 warning 발견 (동작 영향 없음, cleanup):

1. CALL 서브쿼리 deprecation (Neo4j 5.x):
   기존 `CALL { WITH qname ... }` → `CALL (qname) { ... }`
   variable scope clause 명시로 5.x 권고 패턴 일치. 결과 동일.

2. Property warning `c2.page` does not exist:
   8문서 적재된 일부 청크에 page property 가 없음 (chunker 단계에서
   페이지 메타데이터 누락). 동작에는 영향 없으나 매 쿼리마다 warn 로그.
   → `properties(c2).page` 로 안전 접근, 없으면 null 반환.

두 fix 모두 cosmetic — 5/17 v2 결과의 verdict 에는 영향 없음.

연관 5/17 v2 정성 검증 결과:
- L1, L2, L3: PARTIAL (매칭 0개 — Cypher 에러 아니라 entity 자체 부재 발견)
- L4: PASS (matched=1, relations=15, chunks=3 — 라벨 품질 challenge 직접 데모)
- L5: PASS (글로벌 질의 entity 식별 0개 → Layer C 안내)
- 평균: 5.7s (L4 의 9.2s 가 끌어올림 — subgraph 풍부 케이스 trade-off)

L1/L2 entity 자체 부재는 entity 추출 (W3 영역) 이슈로 별도 트래킹 예정.
PR #49 머지 직전 결과 박제:

정성 검증 결과 표:
| ID | category | verdict | identified | matched | relations | chunks | elapsed |
| L1 | relation       | PARTIAL | 1 | 0 | 0  | 0 | 5.8s |
| L2 | two-entity     | PARTIAL | 2 | 0 | 0  | 0 | 4.6s |
| L3 | self-company   | PARTIAL | 1 | 0 | 0  | 0 | 5.0s |
| L4 | label-quality  | PASS    | 1 | 1 | 15 | 3 | 9.2s |
| L5 | global         | PASS    | 0 | 0 | 0  | 0 | 3.9s |

총 5 케이스 · PASS 2 · PARTIAL 3 · FAIL 0
평균 응답: 5.7s (L4 제외 4.8s — 일반 케이스는 DoD 통과)

DoD #19 충족도:
- ✅ 관계 질의 subgraph 활용 (L4 답변에 FACES_RISK 명시)
- ⚠️ 응답 시간 (일반 케이스 통과, L4 만 trade-off)
- ✅ graceful fallback (L1~L3, L5 모두)

4가지 시행착오 / 발견 박제:
1. Bug — Cypher 5.x SyntaxError 42I63 (CALL 서브쿼리로 해결)
2. Bug — c2.page property 부재 (properties() 안전 접근)
3. Discovery — Company 라벨 challenge 가 5/16 진단보다 한 단계 더 심각
   (CONTAINS 매칭으로도 회사명 못 찾음 → 슬라이드 10 보석)
4. Discovery — Local Retriever 의 subgraph↔응답시간 trade-off

발표 슬라이드 연결 update:
- 10 ← L4 PASS 답변 직접 인용 + Discovery 3
- 13 ← L4 데모 (relations=15)
- 14 ← L5 PASS

후속 트래킹:
- #21 Routing Agent (5/18 작업)
- #신규 — Entity 추출 LLM 한계 별도 이슈로 분리 (5/24+ W5 영역)
별도 이슈 #50 생성 후 weekly-log 의 placeholder 를 실제 이슈 번호로 교체.
@TaskerJang

Copy link
Copy Markdown
Owner Author

5/17 정성 검증 완료 — 머지 준비 OK

결과 요약

ID category verdict identified matched relations chunks elapsed
L1 relation PARTIAL 1 0 0 0 5.8s
L2 two-entity PARTIAL 2 0 0 0 4.6s
L3 self-company PARTIAL 1 0 0 0 5.0s
L4 label-quality PASS 1 1 15 3 9.2s
L5 global PASS 0 0 0 0 3.9s

총 5 케이스 · PASS 2 · PARTIAL 3 · FAIL 0 · 평균 5.7s

DoD 충족도

  • 관계 질의 subgraph 활용 — L4 답변에 'FACES_RISK 관계로 연결' 명시
  • ⚠️ 평균 응답 < 5초 — 평균 5.7s 초과. 단, L4 (subgraph 풍부) 제외 평균 4.8s 로 DoD 통과. trade-off 박제.
  • graceful fallback — L1~L3 매칭 0개 + L5 식별 0개 모두 친절 안내 정상

5/17 의 핵심 발견 (Discovery 3)

5/16 진단 3 ("Company 라벨 상위 5개가 모두 metric") 이 상한값이 아니라 평균값에 가까운 사실임이 확정됨.

L1 "두산밥캣", L2 "한화", L3 "미래에셋증권" 모두 CONTAINS 매칭으로도 0개 — entity name 자체에 회사명이 안 담겨 있음. Kimi LLM entity 추출의 한계가 5/16 진단보다 한 단계 더 심각.

별도 이슈 #50 으로 트래킹 (5/24+ W5 영역, 5/23 발표 전 해결 불요).
본 발견 자체가 발표 슬라이드 10 의 보석 이 됨 (라벨 품질 challenge 의 가장 정직한 증거).

추가 commit (검증 중 발견된 cosmetic fix)

  • ba806b2c — Cypher 5.x SyntaxError 42I63 fix (CALL 서브쿼리 격리)
  • 814d4c52 — CALL deprecation 해소 + c2.page property null-safe 접근
  • f0a4ec24 — weekly-log 결과 박제 + 4가지 발견 정리
  • c21fdb4b — #X → #50 이슈 번호 박제

Merge 후 다음 단계

  1. ✅ #50 (Entity 추출 한계) — 5/24+ W5 영역
  2. 5/18: [Feat] W4 Layer C: Community stub (시나리오 A) #20 Community stub + [Feat] W4 Routing Agent (rule-based) #21 Routing Agent
  3. 5/23: 멘토링 발표 (슬라이드 10 보강, 13/14 데이터 확보 완료)

PR 머지하시면 #19 자동 close 됩니다. 🚀

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] W4 Layer B: Local Retriever (Entity + Subgraph)

1 participant