OPEN HYPER STEP
← 목록으로 (ai-startup)
AI-STARTUP · 30 / 100
ai-startup
CHAPTER 30 / 100
읽기 약 2
FUNCTION

RAG 구현: 문서 업로드 → AI 검색


핵심 개념

embedding·vector DB·retrieval·답변 생성 — 자체 데이터 AI 챗봇.

본문

RAG (Retrieval-Augmented Generation) 흐름

📋 코드 (8줄)
1. 문서 업로드 (PDF·Markdown·웹)
2. Chunking (의미 단위 분할)
3. Embedding (벡터 변환)
4. Vector DB 저장
5. 사용자 질문 → embedding
6. 유사도 검색 → 관련 chunk
7. LLM에 컨텍스트 + 질문
8. 답변 생성

벡터 DB 옵션

📋 코드 (14줄)
[Supabase pgvector] (권장)
- PostgreSQL 확장
- 무료 (Supabase plan)
- 추가 인프라 X

[Pinecone] $0~$70/mo
- 전용 vector DB
- 빠름

[Weaviate] 셀프호스트 무료
- 오픈소스

[Chroma] 무료
- 가벼운 옵션

Supabase pgvector 셋업

SQL📋 코드 (35줄)
-- Extension 활성
CREATE EXTENSION vector;

CREATE TABLE documents (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL,
  title TEXT,
  content TEXT,
  embedding vector(1536),  -- OpenAI ada-002
  metadata JSONB DEFAULT '{}',
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops);


-- 검색 함수
CREATE OR REPLACE FUNCTION match_documents(
  query_embedding vector(1536),
  match_threshold float,
  match_count int,
  user_id_filter UUID
)
RETURNS TABLE (id UUID, content TEXT, similarity float)
LANGUAGE plpgsql AS $$
BEGIN
  RETURN QUERY
  SELECT id, content, 1 - (embedding <=> query_embedding) AS similarity
  FROM documents
  WHERE user_id = user_id_filter
    AND 1 - (embedding <=> query_embedding) > match_threshold
  ORDER BY embedding <=> query_embedding
  LIMIT match_count;
END;
$$;

문서 업로드 + 임베딩

TYPESCRIPT📋 코드 (44줄)
import { embed, embedMany } from 'ai';
import { openai } from '@ai-sdk/openai';

async function indexDocument(userId: string, content: string, title: string) {
  // 1. Chunking
  const chunks = chunkText(content, { maxTokens: 500, overlap: 50 });

  // 2. Embedding (배치)
  const { embeddings } = await embedMany({
    model: openai.embedding('text-embedding-3-small'),
    values: chunks,
  });

  // 3. DB 저장
  const supabase = createClient();
  await supabase.from('documents').insert(
    chunks.map((content, i) => ({
      user_id: userId,
      title,
      content,
      embedding: embeddings[i],
      metadata: { chunk_index: i, total_chunks: chunks.length },
    })),
  );
}


function chunkText(text: string, opts: { maxTokens: number; overlap: number }) {
  // 간단 버전: 문단 단위
  const paragraphs = text.split(/\n\n+/);
  const chunks = [];
  let current = '';

  for (const p of paragraphs) {
    if ((current + p).length > opts.maxTokens * 4) {  // ~4 chars per token
      chunks.push(current);
      current = p;
    } else {
      current += '\n\n' + p;
    }
  }
  if (current) chunks.push(current);
  return chunks;
}

RAG 질의

TYPESCRIPT📋 코드 (44줄)
// app/api/rag/route.ts
import { streamText, embed } from 'ai';
import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';

export async function POST(req: Request) {
  const { question, userId } = await req.json();

  // 1. 질문 embedding
  const { embedding } = await embed({
    model: openai.embedding('text-embedding-3-small'),
    value: question,
  });

  // 2. 유사 문서 검색
  const supabase = await createClient();
  const { data: matches } = await supabase.rpc('match_documents', {
    query_embedding: embedding,
    match_threshold: 0.7,
    match_count: 5,
    user_id_filter: userId,
  });

  // 3. 컨텍스트 구성
  const context = matches!.map(m => m.content).join('\n\n---\n\n');

  // 4. LLM 답변
  const result = streamText({
    model: anthropic('claude-sonnet-4-6'),
    messages: [
      {
        role: 'system',
        content: `Answer the user's question using ONLY the provided context.
If the answer isn't in the context, say so.

Context:
${context}`,
      },
      { role: 'user', content: question },
    ],
  });

  return result.toDataStreamResponse();
}

PDF 처리

BASH📋 코드 (1줄)
pnpm add pdf-parse
TYPESCRIPT📋 코드 (11줄)
import pdfParse from 'pdf-parse';

async function extractPDF(file: File) {
  const buffer = Buffer.from(await file.arrayBuffer());
  const { text } = await pdfParse(buffer);
  return text;
}


// 또는 Llama Cloud (더 정확, 표·이미지 인식)
// https://cloud.llamaindex.ai

비용 (1000 페이지 = 약 500K 토큰)

📋 코드 (10줄)
Embedding 1회:
- text-embedding-3-small: $0.01
- text-embedding-3-large: $0.07

Chat 100 질문/일:
- Claude Sonnet: 일 $0.5~2
- 월 $15~60

→ MVP는 small embedding + Sonnet
→ 정확도 우선 large embedding + Opus

다음 챕터

CH.31 "스트리밍 응답: 실시간 AI 출력".


AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
무료

월 $0 — 검증·시작 단계

RAG 구현을 무료 도구만으로
시작하는 방법을 알려줘.
소자본

월 $20~50 — MVP·초기 운영

월 $20~50 예산으로 RAG 구현을
검증·MVP 단계까지 진행하는 전략은?
프로덕션

월 $200~500 — 성장 단계

RAG 구현을 프로덕션 단계로
확장할 때 필요한 도구·운영 체계는?
스택

풀스택 — 도구 조합 분석

2026년 RAG 구현 관련 도구 5개를
조합한 추천 스택을 알려줘.

⭐ 이것만 기억하세요
RAG 구현: 문서 업로드 → AI 검색 이 3가지만 확실히 잡으세요
1.Supabase pgvector = 무료 + 추가 인프라 X
2.Chunking + embedding + 유사도 검색 = 자체 데이터 챗봇
3.embedding-3-small + Sonnet 4.6 = 비용 효율 좋음


공유하기
진행도 30 / 100