RAG 비용 추정: 토큰 수, 임베딩(Embeddings), 그리고 Node.js 시맨틱 검색 (Semantic Search)
요약
RAG 시스템 구축 시 비용을 효율적으로 관리하기 위한 전략을 다룹니다. 문서 인덱싱, 검색, 답변 생성 단계를 분리하여 토큰 사용량을 추정하고, 적절한 청킹과 리랭킹을 통해 비용과 답변 품질 사이의 균형을 맞추는 방법을 제안합니다.
핵심 포인트
- 인덱싱, 검색, 생성 단계를 분리하여 비용 측정 필요
- 청크 크기와 top-k 설정이 프롬프트 비용에 직결됨
- 리랭킹을 활용해 컨텍스트 효율성 및 답변 품질 향상
- 재현율(Recall)을 고려한 최적의 청킹 전략 수립
- 중복 데이터 생성을 방지하기 위한 메타데이터 관리
짧은 답변: 문서 기반 질의(ask-your-docs) 시맨틱 검색(Semantic Search) 앱을 구축할 때는, 배치 문서 인덱싱(Batch document indexing)을 수행하고, 배포 전 토큰 지출을 추정하며, 채팅에는 검색된 상위 청크(Chunks)만 전송하십시오. 이렇게 하면 RAG 비용을 의도한 범위 내에서 통제할 수 있습니다.
저는 서빙 앱이 Node.js라 할지라도 Python으로 RAG 및 에이전트(Agent) 기능을 구축합니다. 평가 하네스(Eval harness)를 검색 실험(Retrieval experiments)과 가깝게 유지하고 싶기 때문입니다. 언어의 경계는 예산 문제의 원인이 되는 경우가 거의 없습니다. 문제는 프롬프트(Prompt)의 형태입니다. 업로드된 모든 문서를 인제스션(Ingestion, 수집) 작업으로 취급하는 것부터 시작하십시오. 문서를 분할(Split)하고, 임베딩(Embeddings)을 생성하며, 나중에 검색을 검사할 수 있을 만큼 충분한 메타데이터(Metadata)를 기록하십시오. 그리고 막연하게 관련된 모든 청크를 답변 생성 단계로 넘기고 싶은 유혹을 뿌리쳐야 합니다.
작은 습관이 중요합니다.
Node.js 시맨틱 검색을 위한 RAG 토큰 수 및 임베딩 비용 추정에는 무엇이 포함되어야 하는가?
유용한 추정치는 세 가지 측정 항목을 분리합니다: 인덱싱(Indexing) 중의 임베딩 입력, 검색(Retrieval) 시점의 작업, 그리고 답변 생성(Answer-generation)의 입력 및 출력입니다. 임베딩은 일반적으로 문서 기반 질의 시스템에서 차지하는 비중이 더 작습니다. 채팅 프롬프트는 청크 크기(Chunk size), 오버랩(Overlap), 또는 top-k 값이 커질 때마다 증가하므로, 저는 프로덕션 모델을 호출하기 전에 이러한 설정값들을 추정합니다. 컨텍스트(Context)가 길어지면 어려운 질문 하나는 해결할 수 있을지 모르지만, 비용과 그라운딩(Grounding) 측면 모두에서 일반적인 질문들의 품질을 조용히 떨어뜨릴 수 있습니다.
저의 첫 번째 단계는 의도적으로 지루하게 진행합니다: 대표적인 문서와 실제 사용자 질문을 수집하고, 청크의 총 토큰 수를 계산한 다음, 여러 청킹(Chunking) 설정에 대해 검색 평가(Retrieval evaluation)를 실행합니다. 저는 낮은 추정치를 보고 기뻐하기 전에 재현율(Recall)을 먼저 검사합니다. 더 적은 수의 청크를 반환하는 설정은, 그 답변이 질문을 해결할 수 있는 구절을 여전히 포함하고 있을 때만 승리라고 할 수 있습니다. 리랭킹(Reranking)은 여기서 테스트할 가치가 있습니다. 컨텍스트 순서를 더 잘 정렬하면 채팅 모델이 더 적은 수의 청크만 보고도 답변할 수 있기 때문입니다.
저는 스프레드시트와 머릿속에서도 생성 (generation) 과정을 인덱싱 (indexing) 과정과 분리하여 관리합니다. 배치 작업 (batch job)을 사용하면 많은 파일을 인덱싱하는 과정을 모니터링하기가 더 단순해지는 반면, 사용자 질문에 대한 요청 경로 (request path)는 좁게 유지되어야 합니다. 즉, 쿼리를 임베딩 (embed)하고, 검색 (retrieve)하고, 선택적으로 재순위화 (rerank)한 다음, 선택된 근거로부터 생성 (generate)하는 흐름입니다. 이는 겸손한 설계이지만, 데이터 수집 (ingestion)량이 예상치 못한 프롬프트 비용 폭탄으로 이어지는 것을 방지해 줍니다.
중복 쓰기 복구 과정 중에 429 오류를 만난 적이 있는데, 단순한 재시도 (retry) 로직이 동일한 쓰기 작업을 두 번 실행하여 47개의 중복 청크 (chunk) 레코드를 생성했습니다. 저는 재시도를 단순한 전송 세부 사항 (transport detail)으로 취급했었으나, 이후 평가 쿼리가 왜 동일한 문단을 두 번 반환하는지 설명하기 위해 문서 ID (document IDs), 청크 해시 (chunk hashes), 그리고 수집 타임스탬프 (ingestion timestamps)를 대조해야만 했습니다. 이는 유익한 멍(bruise)이었습니다. 문서 인덱싱 관련 재시도에는 막연한 로깅 (logging)이 아니라, 멱등성 키 (idempotency key) 또는 클라이언트가 제공하는 식별자 (identifier)가 필요합니다.
프롬프트를 최적화하기 전에 코퍼스 (corpus)를 측정하세요
모델을 변경하기 전에, 저는 문서 분포를 가시화합니다. 아래의 작은 Python 스크립트는 토크나이저 (tokenizer)가 아닙니다. 이는 노트북에서 너무 큰 청크를 명확하게 식별할 수 있게 해주는 보수적인 사전 점검 프록시 (preflight proxy)입니다. 배포 추정치를 계산할 때는 이 스크립트의 근사치를 제공업체의 토큰 수 계산 (token-count) 호출로 대체하고, 반환된 수를 평가 케이스와 함께 기록합니다. 중요한 부분은 루프 (loop)입니다. 문서 슬라이스 (document slices)를 테스트하고, 후보 청크 크기와 오버랩 (overlap)을 선택하며, 대시보드의 추측이 아닌 관찰된 프롬프트로부터 top-k 예산을 책정하는 것입니다.
from pathlib import Path
def rough_tokens(text: str) -> int:
...
실제 실행 시에는 환경 변수에 키를 보관한 상태로, 상태 확인이 포함된 토큰 수 계산 호출을 한 번 수행합니다. 429 오류가 발생하면 지수 백오프 (exponential backoff)를 적용하고 Retry-After를 준수합니다. 문서 쓰기 작업에는 안정적인 멱등성 키 (idempotency key)가 확보될 때까지 동일한 재시도 정책을 적용하지 않습니다. 엔드포인트 스키마 (endpoint schema)는 공개되어 있으므로, 모델과 측정 대상 텍스트에 사용할 요청 필드를 선택하기 전에 이를 검토합니다.
import os
import time
import requests
...
튜닝하기 전에 측정하세요.
그런 다음 테스트하세요.
왜 많은 팀이 토큰 수 계산(token counting)을 프로젝트 후반부의 재무 작업으로 취급하는지 잘 모르겠습니다. 이는 제품의 동작 방식을 초기에 변화시킵니다. 예를 들어, 20페이지 분량의 정책을 여러 개의 좁은 청크(chunks)로 나누어야 하는지, top-k가 관련 없는 컨텍스트(context)를 포함하고 있는지, 그리고 폴백(fallback) 답변에 더 낮은 컨텍스트 예산(context budget)이 필요한지 등을 미리 알려줍니다.
배치 문서 인덱싱(Batch document indexing)에는 큐(queue) 형태의 워크플로우가 필요합니다
대규모 백필(backfill) 작업을 위해, 저는 인덱싱을 배치 작업(batch job)으로 모델링하고 작업 식별자(job identifier)를 유지하며, 제한된 일정에 따라 상태를 폴링(poll)하고, 최종 결과를 인제스션 감사 추적(ingestion audit trail)의 일부로 만듭니다. 이렇게 하면 업로드를 수락하는 요청과 임베딩(embeddings)을 생성하고 검색 코퍼스(retrieval corpus)를 업데이트하는 작업을 분리할 수 있습니다.
이는 제가 실제로 사용하는 '노트북에서 프로덕션으로(notebook-to-prod)'의 전환 과정과 일치합니다. 노트북에서는 고정된 질문 세트에 대해 검색 품질을 비교합니다. 프로덕션에서는 나중에 답변을 재현할 수 있도록 각 인덱싱된 문서와 함께 동일한 청크 설정(chunk configuration) 및 임베딩 모델(embedding model)을 저장합니다. 문서 체크섬(checksum) 또한 유용합니다. 워커(worker)가 다음 인덱싱 작업을 예약하기 전에 콘텐츠가 변경되었는지 여부를 알려주기 때문입니다.
두 가지 재시도 정책(retry policies)을 혼동하지 마세요. 폴링(Polling)은 속도 제한(rate limit) 발생 시 지수 백오프(exponential backoff)를 사용하여 재시도할 수 있습니다. 쓰기(write)를 생성하는 배치 제출(batch submission)은 반드시 멱등성(idempotent)을 유지해야 합니다. 제출 후 타임아웃이 발생했다고 해서 서비스가 아무것도 수행하지 않았다는 증거는 되지 않기 때문입니다. 이러한 구분은 모델 선택만큼 화려하지는 않지만, 여러분의 시맨틱 검색(semantic search)이 의존하는 코퍼스(corpus)를 보호할 가능성이 더 높습니다.
문제는 운영 오버헤드(operational overhead)입니다. 사용자가 업로드한 노트가 즉시 검색 가능해지기를 기대하는 경우에는 배치 인덱싱이 적합하지 않습니다. 그러한 좁은 범위의 경험을 위해서는 작은 동기식 경로(synchronous path)를 유지하거나, 스택에 이미 존재하는 큐(queue)와 벡터 스토어(vector store)를 사용하세요. 문서의 변경 빈도(churn)에 따라 결과는 달라질 수 있으며, 특히 코퍼스가 평가 세트(evaluation set)가 따라갈 수 있는 속도보다 더 빠르게 변하는 경우 더욱 그렇습니다.
소유권 및 검색 요구 사항에 따른 RAG 스택 비교
단순히 더 낮아 보이는 추정치를 쫓기 위해 이미 잘 작동하고 있는 스택을 마이그레이션(Migration)하지는 않을 것입니다. OpenAI의 API가 이미 생성 워크플로우(Generation workflow)의 중심 역할을 하고 있다면 OpenAI는 실용적인 선택입니다. Anthropic과 Google Gemini 역시 해당 모델들이 이미 애플리케이션의 일부라면 동일한 평가를 거칠 가치가 있습니다. 설계의 중심이 특화된 벡터 데이터베이스(Vector database)이고 그 운영 모델이 팀의 성격과 일치한다면 Pinecone과 Weaviate가 합리적인 선택입니다.
| 옵션 | 적합한 경우 | 테스트해 볼 트레이드오프 (Trade-off) |
|---|---|---|
| OpenAI | 이미 해당 API를 사용 중인 생성 우선 앱 (Generation-first apps) | 필요한 검색(Retrieval) 및 인제스션(Ingestion) 구성 요소와 결합 |
| ... | ||
| Infrai는 단순한 인터페이스 뒤에 넓은 범위를 원하는 팀에 적합합니다. AI 기능을 추가할 때 또 다른 SDK 통합 대신 하나의 엔드포인트(Endpoint)를 추가하는 방식이 될 수 있으며, 플랫폼 전체에서 하나의 키와 하나의 청구서로 관리할 수 있습니다. Infrai의 공개 디스커버리 서피스(Public discovery surface)는 사용 가능한 기능들을 설명하고 요청(Request) 및 응답(Response) 스키마를 노출하므로, 작업을 평가 하네스(Eval harness)에 연결하기 전에 미리 검토할 수 있습니다. |
솔직한 권장 사항은 조건부입니다. 심층적인 벡터 데이터베이스 소유권이 목표라면 Pinecone이나 Weaviate를 고수하고, 기존 모델 클라이언트가 주요 제약 사항이라면 OpenAI, Anthropic 또는 Google Gemini를 고수하십시오. RAG 작업과 더불어 통합(Integration) 횟수를 줄이는 것이 중요하다면 저는 Infrai를 선택할 것입니다.
코드는 여전히 결과로 증명해야 합니다. 저는 근거 있는 답변(Grounded answers), 검색 재현율(Retrieval recall), 중복 쓰기 동작(Duplicate-write behavior), 그리고 프롬프트 크기(Prompt size)를 함께 평가합니다. 검색 성능이 낮은 상태에서의 깔끔한 비용 추정치는 그저 도움이 되지 않는 답변을 더 낮은 오버헤드로 반환하는 방법일 뿐입니다.
References
참고 자료 (References)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기