2단계 파이프라인으로 중복을 걸러낸다

새 엣지가 들어올 때 무조건 LLM을 돌리면 비용이 폭발한다. 그래서 후보 검색(인덱스)과 모순 판단(LLM)을 나눠 쓴다.

1단계: 후보 검색 (LLM 아님)

새 엣지가 추출되면 semantic 유사도(의미 기반 유사도) + BM25(키워드 랭킹 알고리즘) + 그래프 순회로 기존 엣지 중 충돌 가능성이 있는 후보를 긁어온다. 인덱스 기반이라 빠르고 LLM 호출이 없다.

2단계: 모순 판단 (LLM)

여기서는 LLM이 필요하다. 새 엣지를 의미적으로 유사한 기존 엣지들과 비교해서 잠재적 모순을 식별한다. 시간적으로 겹치는 모순이 발견되면 기존 엣지의 t_invalid를 “무효화하는 엣지의 t_valid” 시점으로 설정한다.

즉 새 엣지마다 invalidation prompt(무효화 판단 프롬프트)로 LLM을 호출하고, 기존 유사 엣지들을 컨텍스트로 같이 넣어준다.


실제 동작 예시

기존 엣지: (화인)-[:WORKS_AT]->(A회사)  t_valid=2023-01, t_invalid=null (현재 유효)

새 엣지:   (화인)-[:WORKS_AT]->(B회사)  t_valid=2024-06

LLM이 “이 두 개 모순이다” 판단하면 아래처럼 바뀐다.

기존 엣지: (화인)-[:WORKS_AT]->(A회사)  t_valid=2023-01, t_invalid=2024-06  ← 여기가 바뀜
새 엣지:   (화인)-[:WORKS_AT]->(B회사)  t_valid=2024-06, t_invalid=null

핵심 규칙은 기존 엣지의 t_invalid = 새 엣지의 t_valid 로 채운다는 것이다. “새 사실이 유효해지는 시점 = 옛 사실이 무효화되는 시점”이니까 당연한 처리다.

삭제가 아니라 “2023-01부터 2024-06까지는 A회사가 맞았다”는 이력이 그대로 남는다. 이게 Graphiti 계열에서 시간축(bitemporal) 관리하는 방식이다.


후보를 몇 개나 가져오냐

벡터 검색 자체가 top-k(상위 k개) 기반이라 내부적으로 k개만 가져온다. 정확한 k 값은 설정에 따라 다르지만 이런 후보 검색은 보통 10~20개 수준.

필터링은 이중으로 건다.

  • threshold: 최소 유사도 컷오프
  • top-k: 개수 제한

둘을 같이 써야 “유사하지도 않은데 억지로 k개 맞추느라 쓰레기가 섞여 오는 문제”를 막을 수 있다.

한마디 요약

후보는 인덱스로 싸게 걸러내고, 모순 판단만 LLM에 맡기는 2단계 구조. 모순이 생기면 시간축(t_valid / t_invalid)으로 “덮어쓰지 않고 이력을 유지”한다.