본문으로 건너뛰기

RAG 기반 AI Guide 설계

단계별 구성

  1. 1차 버전은 search-index.json과 브라우저 키워드 점수 검색으로 관련 문서를 미리 보여준다.
  2. 2차 버전은 Cloudflare Pages Function에서 Gemini 답변을 생성한다.
  3. 현재 3차 버전은 문서 chunk embedding을 vector-index.json에 미리 저장하고, /api/ask에서 질문 embedding과 cosine similarity를 계산해 실제 답변 근거 Top 3를 선택한다.

화면의 관련 문서 찾기는 빠른 키워드 미리보기이고, AI 답변 생성은 서버의 임베딩 검색 결과를 사용한다.

요청 흐름

보안과 운영

  • Cloudflare Pages Functions가 서버 측에서 환경변수를 읽으므로 API Key나 비밀번호를 프론트 번들에 포함하지 않는다.
  • RAG_PASSWORD가 설정된 환경에서는 요청 비밀번호가 일치해야 한다.
  • Gemini API Key는 Pages Functions의 GEMINI_API_KEY 환경변수에서만 읽는다.
  • 질문 embedding은 gemini-embedding-001QUESTION_ANSWERING, 문서 chunk는 RETRIEVAL_DOCUMENT task type으로 생성한다.
  • 임베딩은 768차원으로 축소하고 정규화해 정적 JSON 크기와 검색 계산량을 줄인다.
  • cosine similarity가 0.35 이상인 chunk 중 상위 3개를 답변 근거로 선택한다.
  • Gemini 답변 생성에는 임베딩 검색으로 선택한 관련 chunk 최대 3개, chunk당 최대 2,500자만 전달한다.
  • 공개 사이트에서는 비용과 남용 방지를 위해 비밀번호 외에도 호출 횟수 제한, 요청 크기 제한, 로그와 모니터링을 고려한다.
  • 같은 도메인의 /api/ask를 호출하며 광범위한 CORS 허용 헤더를 추가하지 않는다.

Vector DB를 사용하지 않은 이유

현재 문서 수가 적은 포트폴리오 프로젝트이므로 별도 Vector DB 운영 비용과 배포 복잡도를 늘리지 않는다. 정적 JSON 전체 순회는 구조를 이해하기 쉽고 RAG 검색 흐름을 시연하기에 충분하다. 문서와 chunk 수가 크게 증가하면 검색 시간, Function 메모리, 정적 파일 크기를 다시 평가해야 한다.

vector index 생성

문서를 변경한 뒤 다음 명령을 수동 실행한다.

npm run generate:vector-index

스크립트는 .env 또는 .dev.varsGEMINI_API_KEY를 읽고 docs의 MD/MDX 문서를 heading과 1,500~2,500자 내외 기준으로 나눈다. 생성 결과는 static/vector-index.json이며 배포 후 /vector-index.json으로 제공된다.

Embedding 요청은 기본 5초 간격으로 순차 실행한다. 간격은 밀리초 단위 환경변수로 조절할 수 있다.

VECTOR_EMBEDDING_DELAY_MS=5000

429 quota 응답은 5초, 15초, 30초, 60초 exponential backoff로 재시도한다. 성공한 chunk는 매번 static/vector-index.partial.json에 저장하므로 중단 후 같은 명령을 실행하면 이어서 진행한다. 기존 vector-index.json 또는 partial 파일에서 id와 content hash가 같은 embedding은 재사용한다. partial 파일은 Git에서 제외되고 전체 완료 시 자동 삭제된다.

Embedding API 비용과 쿼터를 불필요하게 사용하지 않도록 prebuild에는 연결하지 않았다. 문서가 변경됐을 때만 수동으로 재생성하고 변경된 JSON을 함께 커밋한다.

Gemini provider

functions/providers/geminiProvider.tsgemini-2.5-flashgenerateContent REST API를 호출한다. 응답 성공 시 /api/askprovider: "gemini"와 답변, 참고 문서 목록을 반환한다.

GEMINI_API_KEY가 설정되지 않은 환경에서는 개발 편의를 위해 프론트 키워드 검색 문서를 사용하는 mockProvider로 대체하며, 응답에 mock 사용 사실을 표시한다. Gemini API 호출 자체가 실패하면 내부 원인은 서버 로그에만 남기고 사용자에게는 일반적인 재시도 메시지를 반환한다.

vector-index.json이 아직 배포되지 않았거나 embedding 검색 단계에 일시적인 문제가 있으면 기존 프론트 키워드 Top 3를 Gemini 답변 근거로 사용하는 keyword-client-fallback으로 기존 기능을 유지한다. 벡터 검색이 정상 동작하면 응답의 retrieval.typeembedding-static-index다.

로컬 환경변수

프로젝트 루트에 Git으로 추적되지 않는 .dev.vars 파일을 만들고 다음 값을 설정한다.

RAG_PASSWORD=테스트비밀번호
GEMINI_API_KEY=Gemini API Key

.dev.vars.dev.vars.*.gitignore에 포함되어 있다.

추가 개선

답변 provider는 공통 AnswerProvider 인터페이스를 사용하므로 향후 OpenAI 등 다른 구현으로 교체할 수 있다.

문서가 많아지면 빌드 시 chunk 단위로 본문을 나누고 문서 ID, 원본 경로, heading, category, embedding vector, 문서 버전을 함께 저장하는 방식을 권장한다.

로컬 실행

npm run generate:search-index
npm run generate:vector-index
npm run build
npx wrangler pages dev build

Docusaurus 개발 서버만 실행하면 키워드 문서 검색을 확인할 수 있다. /api/ask까지 포함한 로컬 테스트는 Cloudflare Wrangler의 Pages 개발 서버로 빌드 결과와 functions 디렉터리를 함께 실행한다.

향후 Vector DB 확장

문서 규모가 커지면 functions/retrieval/vectorIndex.ts의 정적 JSON 로딩과 functions/utils/vectorSearch.ts의 전체 순회 검색을 다음 저장소의 query 호출로 교체할 수 있다.

  • Cloudflare Vectorize
  • PostgreSQL pgvector
  • Supabase pgvector
  • 전용 Vector DB

문서 chunk 생성과 Gemini embedding provider, 답변 provider 경계는 그대로 재사용할 수 있다.