검색(Retrieval)이 놓친 것: FalkorDB 기반의 회사 지식 베이스를 1,203개의 미사용 질문으로 테스트하다
요약
본 글은 FalkorDB 기반의 'inherit-brain'을 구축하여 회사 지식 베이스를 개선한 사례입니다. 기존 검색(Retrieval) 방식이 놓치던 정보를 에이전트가 여러 읽기 전용 도구 호출과 그래프 구조 분석을 통해 성공적으로 복구하는 과정을 보여줍니다.
핵심 포인트
- 그래프 DB와 에이전트를 결합하여 회사 지식 베이스를 구축했습니다.
- 에이전트는 모델이 쿼리를 작성하지 않고도 고정된 Cypher 쿼리 도구를 사용합니다.
- 기존 검색 방식 대비 정확도가 높아져 누락된 정보를 찾아냅니다.
저는 제 에이전트에게 제품 기술 사양의 작성자와 핵심 검토자를 요청했습니다. 그러자 이 모델은 모든 10명의 직원 ID를 반환했습니다. 같은 모델을 사용한 검색(Retrieval) 방식으로는 단 한 명만 반환되었습니다. 그 사람들 중 두 명은 Alice Davis라는 이름이었습니다.
저는 Graph Hacks의 Track 3: Company Brain을 위해 inherit-brain을 구축했습니다. 저는 Salesforce의 HERB 벤치마크를 사용했는데, 이는 30개의 제품을 가진 시뮬레이션된 회사입니다. 저는 6개 제품에 대해 개발하고 나머지 24개 제품으로 테스트했습니다. 저는 그래프 도구들이 무엇을 복구하는지, 검색(retrieval)이 무엇을 놓쳤는지, 그리고 둘 다 어디에서 실패했는지를 확인하고 싶었습니다.
답변과 도구 추적 기록을 포함한 데모 영상: https://zeyusuntt.github.io/inherit-brain/
코드: https://github.com/zeyusuntt/inherit-brain
작동 방식
inherit-brain은 제품의 기록들을 제품별 하나의 FalkorDB 그래프로 변환합니다. 이 그래프에는 사람, 문서, 회의, Slack 메시지, 풀 리퀘스트(pull requests), 버그 및 그 이벤트 히스토리, 기능, 고객, 그리고 누가 무엇을 누구에게 했는지에 대한 정보가 포함됩니다. 에이전트는 9개의 읽기 전용 도구(read-only tools)를 호출하여 질문에 답변합니다. 각 도구는 고정된 매개변수화된 Cypher를 실행하며, 모델은 절대로 쿼리를 작성하지 않습니다.
저는 평범한 코드를 사용하여 구조를 추출하고, 이름을 해결하며, 규칙을 적용합니다. 모델 호출은 버그 히스토리와 검토 제안과 같은 의미적 관계(semantic relations)를 추출합니다. 저는 이러한 사실들을 FalkorDB에 로드하고 엣지(edges) 상에 원본 기록 ID를 유지합니다.
한 제품인 SupportForce의 사양 질문에 대해, 에이전트는 하나의 도구 호출(doc_people)을 수행했고, 이 도구는 21.9ms 만에 세 개의 읽기 전용 쿼리를 실행했습니다. 그 결과 열 명의 사람이 반환되었으며, 각 사람에게는 그들이 존재하게 된 기록이 함께 제공되었습니다. 두 Alice Davis는 eid_6e2f3e2b와 eid_e5e36dc1에 의해 식별되며, doc_people은 문서 버전 전반에 걸쳐 직원 ID를 통해 사람들을 중복 제거(deduplicate)합니다.
이 답변을 위해, 데모의 다이어그램은 사양의 세 가지 버전과 열 명의 사람들 사이에 저장된 AUTHORED 및 SUGGESTED 엣지를 보여줍니다:
FalkorDB의 역할
저는 herb:<product>라는 이름의 30개 제품 그래프를 로드했으며, 이에는 16개의 노드 레이블과 24개의 엣지 타입에 걸쳐 총 56,708개의 노드와 164,698개의 엣지가 포함되어 있습니다. 각 엣지는 src라는 필드를 저장하며, 이는 도구들이 결과와 함께 반환할 수 있는 소스 레코드 ID 목록입니다. 점수화된 실행(scored run)에서, 답변 가능한 질문에 대한 659개 답변 중 46개가 어떤 기록도 인용하지 않았습니다.
이 에이전트는 제품별로 읽기 전용 사용자(agent_<product>)로 연결됩니다. 이 사용자는 다음 명령어로 생성되었습니다:
ACL SETUSER agent_<p> reset on ><pw> (~herb:<p> +GRAPH.RO_QUERY)
따라서 에이전트는 자신의 제품 그래프에 대해서만 GRAPH.RO_QUERY를 실행할 수 있으며, 다른 것은 할 수 없습니다. 한 제품에 대한 질문은 다른 제품을 읽을 수 없습니다.
보류된(held-out) 질문 중 하나는 고객이 나중에 재개한 SecurityForce의 버그 수정 사항을 가장 많이 작성한 사람이 누구인지 묻는 것이었습니다. 에이전트는 먼저 유효하지 않은 고객 재개 인수(customer-reopen) 인수를 사용하여 pr_board를 시도했습니다. 이 호출은 거부되었습니다. 두 번째 pr_board 호출은 행을 반환하지 않았습니다. 세 번째 호출에서는 bug_board를 사용했고, 이는 다음 조인을 실행했습니다:
MATCH (b:Bug) WHERE b.reopened = true
AND ($components IS NULL OR b.component IN $components)
AND ($companies IS NULL OR any(x IN b.companies WHERE x IN $companies))
...
Cypher는 버그, 해당 수정 PR(Pull Request), 작성자, 그리고 재개방(reopening)으로 이어지는 해결책을 연결합니다. Python은 PR당 고유한 링크 수를 작가별로 계산하는데, 이는 하나의 PR이 여러 쌍의 해결 및 재개방 이벤트를 일치시킬 수 있기 때문이며, 모든 사람을 최대값에 묶어둡니다. Julia Miller는 네 개의 매칭된 버그에서 두 개의 수정 PR을 가지고 있었고, 이것이 HERB의 골드 답변이 되었습니다. 검색(Retrieval)은 다른 네 명의 엔지니어를 언급했습니다. 전체 답변은 네 번의 모델 호출에 걸쳐 9.43초가 소요되었으며, 주요 연결 작업은 이 기록에서 13.0ms가 소요되었습니다.
FalkorDB Browser에서 Julia의 결과를 검사하기 위해 그녀의 직원 ID(eid_c38fe0e7)로 필터링했고, p, op, pr, fx, b, h, ro를 반환받았습니다. 이는 그녀의 두 PR을 버그 및 재개방 이벤트에 연결하는 일곱 개의 노드와 여섯 개의 엣지입니다. 점수화된 쿼리에서 사용된 해결책 이벤트는 표시되지 않습니다.

resolve_entity는 버그, 기능(features), 문서 및 경쟁업체를 위한 폴백으로 전체 텍스트 검색(db.idx.fulltext.queryNodes)을 사용합니다. 사람 해결(Person resolution)은 직원 ID와 정규화된 이름을 일치시키고, 발표자에게는 회의 증거를 사용합니다. herb_v1.statusAt(events, $as_of) UDF는 특정 시점까지 기록된 이벤트를 통해 상태를 계산합니다. 저의 bridges 도구는 협업 엣지에 대한 방향성 사이의 매개 중심성(directed betweenness)을 순위화하며, 그 순위는 방향성에 의존하고 있으며, 어떤 제외 질문도 이를 호출하지 않았습니다.

모든 것은 FalkorDB 4.22.1과 6.0.2를 기반으로 작동하며, 도구 테스트는 이 두 버전을 모두 대상으로 실행되었습니다.
스스로 속일 수 없도록 테스트하기
Salesforce AI Research의 HERB는 정답을 제공하고 기록(records)에서 답변할 수 없는 질문도 포함합니다. 제가 따로 분리한 세트에는 659개의 답변 가능한 질문과 544개의 답변 불가능한 질문이 담겨 있습니다.
분리된 테스트를 진행하기 전에 저는 코드를 동결하고, 프롬프트와 그래프(각각 해시 처리)를 기록했으며, 예상 결과를 적고 모든 결과에 대해 할 말을 작성했습니다. 그리고 계획의 sha256 값을 공개적으로 게시했습니다(gist와 Wayback 스냅샷). 사전 점검(preflight)은 해시가 변경된 실행을 거부하도록 했습니다.
저는 Nebius에서 Qwen3-30B-A3B-Instruct-2507을 사용하여 여덟 가지 방법을 비교했습니다. 네 가지 방법은 1,203개 질문 전체를 다루었습니다: inherit-brain, 공유 루프 내 HERB의 검색 도구(retrieval tools), 전체 제품 프롬프팅(whole-product prompting), 그리고 HERB의 하이브리드 검색(hybrid retrieval). 나머지 네 가지는 실행 전에 고정된 더 작은 질문 세트에서 다른 도구 디자인을 테스트했습니다: depth-1 카드, 체인형 카드 접근(chained card access), 카드를 통한 검색(retrieval over cards), 그리고 자동 온톨로지(automatic ontology)를 가진 GraphRAG-SDK. 전체 결과에서 각 방법의 커버리지를 확인할 수 있습니다. 추출 및 채점(grading)을 포함한 측정된 Nebius 호출은 제 50 USD 한도 대비 42.72 USD가 소요되었습니다.
| Same model | Structured-answer score | Median input tokens per answerable question |
|---|---|---|
| inherit-brain | 80.0 | 10,117 |
| ... | ||
| I scored 478 questions with structured answers using HERB의 set F1을 곱하여 100으로 점수화했습니다. 저는 사람(people), 회사(companies), 아티팩트(artifacts) 세 그룹을 동일하게 평균화했는데, 여기에는 PR 및 URL 질문이 결합되어 있습니다. 토큰 중앙값은 모든 659개의 답변 가능한 질문을 다룹니다. |
두 에이전트는 각각 12회의 호출 제한과 질문당 60,000 토큰의 입력 예산을 가졌습니다. 동일한 모델과 루프를 사용했을 때, inherit-brain은 HERB의 검색 도구보다 40.8점(95% 신뢰 구간 35.7~46.2; 제품 클러스터 부트스트랩) 앞섰습니다. 검색(Retrieval) 방식은 질문의 44.5%에서 입력 예산을 소진했고, inherit-brain은 1.3%에서 소진했습니다. 이는 도구와 추출된 표현을 함께 변경합니다. 데이터베이스 엔진 자체를 비교하는 것은 아닙니다.

제가 배운 점 (What I learned)
모델에 그래프 접근 권한을 주는 것만으로는 충분하지 않았습니다. 5개의 보류된 제품(held-out products)에서, 동일 엔티티 카드에 대한 깊이-1(depth-1) 유형의 접근은 해당 카드를 통한 검색(retrieval)보다 13.8점 낮았습니다. 280개 질문을 처리한 과정 전반에 걸쳐, 이는 질문의 68.9%에서 입력 예산을 소진했습니다. 별도의 버전에서는 모델이 카드들을 통해 호출 체인(chain calls)을 만들도록 했습니다. 제 전체 도구 세트(full tools)는 동일한 5개 제품에서 이 버전보다 33.5점 앞섰습니다. 저는 완전한 조인(join)을 하나의 도구로 패키징한 다음, 모델에게 한 단계씩 조립하도록 요청할 것입니다. 재개된 버그 수정 도구(reopened-bug tool)에서는 Cypher가 조인을 수행하고 Python이 고유한 PR 링크를 계산합니다. 이러한 비교는 단순히 탐색(traversal) 능력만을 측정하는 것이 아니라 전체 도구 세트(whole toolkits)를 측정합니다.
제가 가진 도구들은 제가 본 질문들에 적합했습니다. 보류된(held-out) 질문 610개 중에는 개발 질문과 단어 구성이 유사한 것들(제품 이름만 바뀜)이 있었습니다. 이 경우, inherit-brain은 검색 성능을 57.9점 향상시켰습니다. 한 번도 본 적 없는(never seen) 593개의 질문에서는 20.1점(12.9점에서 26.5점으로) 향상을 보였습니다. 이는 여전히 명확한 우위이며, 크기는 약 3분의 1 정도이고, 결과 페이지에서도 헤드라인 옆에 표시됩니다. 이 분할은 질문 구성도 변화시킵니다: 답변 가능한 미사용 질문 243개 중 135개가 PR(Product Requirement) 관련 질문인 반면, 본 적 있는 질문 416개 중에는 10개만이 PR 관련 질문이었습니다. 저는 더 작은 우위를 단지 생소한 문구 때문이라고만 돌릴 수는 없습니다.
제 추출 과정은 감사 라운드에서 다섯 번 실패했고 여섯 번째에 통과했습니다. 두 가지 실패 사례가 있었습니다: 하나의 과제는 버그를 전달하는 메시지에 대한 이벤트가 필요했고, 다른 하나는 승인(acknowledgement)이 리뷰가 아니었습니다. Codex와 Claude 에이전트가 원시 기록을 바탕으로 샘플링된 사실들을 판단했으며, 세 번째 Codex 통과 과정에서 의견 불일치들이 해결되었습니다. 라운드 사이에는 추출 규칙과 감사 기준을 변경했습니다. 네 번째 라운드 이후에는 검토자 재현율(reviewer recall)을 버전별 문서 유형을 대상으로 하도록 수정하고 저자 승인(author acknowledgements)은 제외하도록 했습니다. 모든 라운드는 PREREG section 12에 요약되어 있습니다. 이는 인간의 검증이 아니라 모델 감사자들의 샘플 승인이었습니다.
제가 예상하지 못했던 FalkorDB 관련 세부 사항 몇 가지:
%R~herb:pACL 규칙은 그래프 명령어에 대해 작동하지 않습니다:GRAPH.RO_QUERY는 키를 읽기-쓰기로 선언하므로, 읽기 전용 키 규칙이 절대 일치하지 않습니다. 명령어 범위의 선택자((~herb:p +GRAPH.RO_QUERY))가 이를 수행합니다.GRAPH.COPYACL은 소스(source)와 목적지(destination)를 구별할 수 없습니다. 제 'what-if' 사용자는 자신의 제품 그래프와 스크래치 네임스페이스 간에 복사할 수 있습니다. 이 사용자에게 활성화되어 있는 동안 제품 그래프는 존재해야 합니다: 기존의 목적지는 그 이름으로 다시 복사되는 것을 방지합니다. 테스트는 이 경계를 확인하지만, 운영상의 조건을 강제하지는 않습니다.- 서버의
TIMEOUT이 1000 ms였기 때문에, 전체 그래프 식별 읽기(freeze를 위한 그래프 해싱)가 시간 초과되어, 제가 쿼리당 타임아웃을 설정할 때까지 기다려야 했습니다. - 방향성 체인
a -> b -> c에서,samplingSize:10, samplingSeed:1은 4.22.1 버전에서는b에 대해 0의 중간성 점수(betweenness score)를, 6.0.2 버전에서는 1을 부여했습니다. 두 개의 작은 방향성 테스트 케이스 전반에 걸쳐, 6.0.2는 다섯 가지 검사를 모두 통과했고 4.22.1은 두 가지를 통과했습니다. 저는 4.22.1에서 Python 폴백(fallback)을 사용합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
