RAG 시스템의 컨텍스트 윈도우 최적화 전략
문제 상황
회사 내부 문서 검색 RAG 시스템에서 복잡한 질의에 대한 응답 품질이 낮았다. 관련 문서를 충분히 찾아내지만, LLM에 전달되는 컨텍스트가 128k 토큰 제한을 초과하면서 중요한 정보가 잘려나가는 문제였다.
접근 방법
1. 청크 재정렬 로직 구현
단순 유사도 점수 기반 정렬 대신, 다단계 스코어링을 도입했다.
interface ChunkScore {
semanticScore: number;
recencyScore: number;
diversityScore: number;
finalScore: number;
}
function reranKChunks(chunks: Chunk[], query: string): Chunk[] {
const scored = chunks.map(chunk => {
const semantic = cosineSimilarity(query, chunk.embedding);
const recency = calculateRecencyScore(chunk.updatedAt);
const diversity = calculateDiversityScore(chunk, chunks);
return {
...chunk,
finalScore: semantic * 0.6 + recency * 0.2 + diversity * 0.2
};
});
return scored.sort((a, b) => b.finalScore - a.finalScore);
}
2. 동적 컨텍스트 압축
토큰 제한에 도달하면, 덜 중요한 청크를 요약하여 압축했다.
async function optimizeContext(
chunks: Chunk[],
maxTokens: number
): Promise<string> {
let totalTokens = 0;
const context: string[] = [];
for (let i = 0; i < chunks.length; i++) {
const chunkTokens = estimateTokens(chunks[i].content);
if (totalTokens + chunkTokens <= maxTokens) {
context.push(chunks[i].content);
totalTokens += chunkTokens;
} else if (i < 5) {
// 상위 5개는 압축하여 포함
const summarized = await summarizeChunk(chunks[i].content);
context.push(summarized);
totalTokens += estimateTokens(summarized);
}
}
return context.join('\n\n');
}
3. 계층적 검색 전략
먼저 문서 레벨에서 필터링 후, 해당 문서 내에서만 상세 청크를 검색하도록 변경했다.
결과
- 평균 응답 품질(LLM-as-judge 기준) 72% → 89%
- 컨텍스트 토큰 사용량 평균 30% 감소
- 응답 지연시간은 청크 재정렬로 인해 200ms 증가했으나 허용 범위
교훈
RAG는 단순히 벡터 검색만의 문제가 아니다. 검색된 정보를 어떻게 구조화하고 우선순위를 매기느냐가 최종 품질을 결정한다. 특히 컨텍스트 윈도우가 충분히 크더라도, 관련 없는 정보가 섞이면 LLM의 성능이 저하되는 것을 확인했다.