RAG 필터가 너무 늦게 실행되는 문제: TypeScript로 테넌트 안전 리트리버 구축하기
요약
RAG 시스템에서 테넌트 필터가 검색 과정의 후반부에 실행되면, 사용자가 접근 가능한 유용한 문서들이 누락되는 '검색 버그' 또는 '데이터 경계 버그'가 발생할 수 있습니다. 이 글은 인증된 사용자 기반으로 적격 문서를 먼저 식별하고 랭킹을 수행하는 올바른 순서와 방법을 제시합니다.
핵심 포인트
- 테넌트 필터는 프롬프트 지침이 아닌, 검색 가능한 문서에 대한 술어(predicate)입니다.
- 필터링은 반드시 '인증된 사용자' 기반으로 가장 먼저 실행되어야 합니다.
- Azure AI Search 등 데이터베이스별로 프리필터링과 포스트필터링의 차이를 이해해야 합니다.
- 전역 top-k를 찾고 나중에 필터링하는 방식은 결과가 0일 수 있습니다.
당신의 RAG 시스템은 가장 점수가 높은 두 개의 문서를 찾습니다.
그런 다음 사용자가 그 문서들을 읽을 수 있는지 확인합니다.
두 문서 모두 다른 테넌트에 속해 있습니다. 따라서 이 문서들은 버려지고 사용자에게 답변이 없다고 알려줍니다.
하지만 사용자가 읽을 수 있는 유용한 문서가 세 개 있었습니다. 그것들은 후보 목록에 포함되지도 않았습니다.
이것이 바로 검색(retrieval) 버그입니다. 만약 그 첫 두 문서가 당신이 버리기 전에 외부 리랭커(reranker)에 도달했다면, 이것은 데이터 경계(data-boundary) 버그이기도 합니다.
두 가지 실수를 모두 눈에 띄게 만드는 가장 작은 버전의 코드를 만들어 봅시다.
질문은 접근 확인이 어디서 실행되느냐이다
테넌트 필터는 프롬프트 지침(prompt instruction)이 아닙니다.
그것은 애플리케이션이 이 인증된 사용자를 위해 검색할 수 있도록 허용하는 문서에 대한 술어(predicate)입니다. 테넌트 멤버십은 그 일부일 뿐입니다. 그룹, 문서 ACL(Access Control List), 그리고 권한 변경 사항도 중요합니다.
우리가 원하는 순서는 다음과 같습니다:
verified principal
-> eligible documents
-> ranking and top-k
...
모든 데이터베이스가 필터링을 같은 방식으로 구현하지는 않습니다. Microsoft의 Azure AI Search documentation은 그래프 순회(graph traversal) 중의 프리필터링(prefiltering)과 검색 후의 포스트필터링(postfiltering)을 구분합니다. 또한 필터링되지 않은 전역 top-k를 필터링하는 별도의 엄격한 포스트필터 모드도 문서화하고 있는데, 이 방식은 적격 일치 항목이 존재함에도 불구하고 결과가 0일 수 있습니다. 선택적 프리필터는 순회 비용과 지연 시간(latency)을 증가시킬 수 있습니다.
아래 배열 예시는 전역 top-k를 먼저 찾고 나서 필터링하는 실수를 나타냅니다. 이는 Azure의 샤딩된 postFilter 알고리즘 구현이 아닙니다.
여섯 개의 문서, 하나의 쿼리, 두 개의 테넌트
쿼리는 '환불 정책(refund policy)'입니다. 우리의 인증된 가상 사용자(fixture user)는 acme 테넌트와 support 그룹에 속해 있습니다.
| Document | Tenant | Allowed group | Synthetic score |
|---|---|---|---|
| b1 | beta | support | 0.99 |
| ... | |||
| These scores are invented inputs. There is no embedding model hiding behind them. |
The exact authorized top two는 a1과 a2입니다. 우리의 fixture recall은 파이프라인이 반환하는 두 항목 중 몇 개인지, 이를 2로 나눈 값입니다.
전역적으로 볼 때, b1과 b2가 가장 높은 순위를 차지합니다. 이 목록을 필터링하면 아무것도 얻지 못합니다. k를 늘리는 것은 이 작은 코퍼스에서 적격한 히트(eligible hits)를 복구할 수는 있지만, 권한 경계(authorization boundary)를 설정하지는 않습니다.
빌드 실행하기
public GitHub repository에 소스 코드, fixture 데이터, README, 다이어그램 및 예상 출력이 포함되어 있습니다.
Node.js 22.18 이상이 필요합니다. Node documents native TypeScript stripping; 이는 별도의 컴파일러 없이 이 제거 가능한 구문(erasable syntax)을 실행합니다. 파일에 대해 타입 검사(type-check)를 수행하지는 않습니다.
git clone https://github.com/bobbyhalljr/tenant-safe-rag.git
cd tenant-safe-rag
node --experimental-strip-types rag.ts
설치, API 키 또는 모델 호출이 필요하지 않습니다.
다음은 전체 학습 빌드입니다:
import assert from 'node:assert/strict';
type Principal = { tenantId: string; groups: readonly string[] };
...
중요한 줄은 의도적으로 지루합니다:
return rank(corpus.filter(doc => allowed(doc, scope)), request, k);
이 요청에는 쿼리가 포함되어 있습니다. 이는 신뢰할 수 있는 범위(trusted scope)를 선택하지 않습니다. fixture는 심지어 위조된 tenantId와 groups 필드를 전송합니다. 검색 함수는 이를 무시하고 서버의 principal을 계속 사용합니다.
이것은 서버 principal이 신뢰할 만한 경우에만 도움이 됩니다. 이 예제는 그것을 로컬에서 구성합니다. 인증(authentication)을 구현하지는 않습니다.
정확하게 테스트된 출력
Node.js 22.20.0으로 실행:
post-filter: returned=0, authorized top-2 recall=0/2
pre-filter: ids=a1,a2, authorized top-2 recall=2/2
late access check: foreign texts sent to reranker=2
...
지연 확인(late-check) 기능은 재랭커 경계에서 두 개의 Beta 문서 텍스트를 기록합니다. 이들을 최종 답변에서 제거하더라도, 이미 해당 내용을 수신한 서비스에서는 여전히 남아 있습니다.
폐기(revocation) 기능은 a1의 지원 접근 권한을 HR 접근 권한으로 변경합니다. 새로운 검색(retrieval)을 수행하면 a2와 a3가 반환됩니다. 캐시된 답변은 자체적인 무효화 또는 새로운 인증 확인이 필요할 것입니다.
실제 경계를 데이터 계층으로 이동하기
메모리 내 필터는 순서를 보여줍니다. 이는 프로덕션 설계가 아닙니다.
검증된 세션 컨텍스트와 현재 권한 기록을 기반으로 접근 술어(access predicate)를 구축하세요. 이 술어를 검색 계층(retrieval layer)을 통과시켜 승인되지 않은 텍스트가 신뢰 저장소(trusted store)를 벗어나기 전에 막아야 합니다. 키워드 및 벡터 브랜치, 재랭킹(reranking), 인용(citations), 그리고 캐시된 답변에 일관되게 적용하세요.
PostgreSQL을 사용한다면, 행 보안 정책(row security policies)를 사용하여 역할이 볼 수 있는 행을 제한할 수 있습니다. 행 보안이 활성화되면, 적용 가능한 정책의 부재는 기본적으로 거부(default deny)입니다. 소유자들은 일반적으로 이행하도록 강제되지 않는 한 행 보안을 우회합니다. 슈퍼유저와 BYPASSRLS 권한을 가진 역할도 마찬가지로 우회합니다. 실제 애플리케이션 역할을 사용하여 접근 테스트를 수행하세요.
애플리케이션 필터를 추가하거나 데이터베이스 기능을 활성화하는 것이 전체 파이프라인이 격리되었음을 증명한다고 주장하지 마세요.
검색 평가(retrieval evals)의 경우, 두 가지 측정을 분리하여 유지해야 합니다:
- 권한 위반(Authorization violations): 부적격 문서 텍스트가 보호 경계를 넘었는가?
- 승인된 검색 품질(Authorized retrieval quality): 사용자의 적격 코퍼스에서 유용한 문서를 복구했는가?
문서가 0개 반환되는 파이프라인은 노출된 문서도 0개이고 검색 품질도 형편없을 수 있습니다. 다른 테넌트의 관련 텍스트를 복구하는 파이프라인은 인상적인 관련성을 가질 수 있지만, 접근 경계는 깨져 있을 수 있습니다.
이것이 증명하지 못하는 것
수동으로 설정한 점수를 가진 6개 문서에서 8개의 단언(assertions)이 통과했습니다.
이는 ANN 검색(recall), 지연 시간(latency), 임베딩 품질 또는 모델 정확도를 측정하지 못합니다. 라이브 리랭커, ACL 데이터베이스, 동시 권한 변경, 분산 캐시 또는 인증 서비스는 없습니다. 또한 사용자가 읽을 수 있도록 허용된 문서 내부의 프롬프트 주입(prompt injection)도 방지할 수 없습니다.
유용한 결과는 더 좁습니다: 필터 배치와 신뢰 범위는 답변을 평가하기 전에 검색 과정에서 테스트 가능한 부분입니다.
저는 실제 업무를 수행하는 AI 직원들을 중심으로 Roster를 구축하고 있습니다. 이 직원들은 유용한 컨텍스트가 필요하며, 자신이 근무하는 사람의 접근 경계 내에서 그것을 받아야 합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
