티스토리 뷰

반응형

실서비스급 NestJS + Next.js 프로젝트에 AI 기능 붙이기

(LLM + RAG + 벡터DB + 이미지 임베딩까지 실제 앱에 통합하는 방법)


이제 우리는 프론트/백엔드/인프라/배포 자동화/Cloudflare/AWS/Terraform/K8s/GitOps까지
풀스택 SaaS 운영 체인을 완성했다.

다음 단계는 무엇일까?
지금부터는 “기능 확장”이다.

그중에서도 2025년 SaaS에서 가장 중요한 건 바로 AI 기능 통합이다.

이번 글에서는 실제 서비스에 적용 가능한 형태로
LLM + RAG + VDB(Vector Database) + 이미지 임베딩 기능을 NestJS + Next.js에 붙이는 방법을 정리한다.

이 글은 “OpenAI API를 단순 호출하는 수준”을 넘는다.
실제 SaaS에서 사용하는 구조 그대로 구성한다.


🧭 이번 목표

구성 목적

LLM API(OpenAI, HuggingFace, Ollama) 모델 선택/호출 로직 구축
RAG(Document + VectorDB) 검색·요약·QA 기능
Vector Database (Pinecone / Qdrant / Milvus) 문서 임베딩 저장소
이미지 임베딩(CLIP 계열) 이미지 태그/검색 기능
NestJS 서비스화 API 엔드포인트로 제공
Next.js UI 구현 RAG·챗 UI·이미지 검색 페이지 구현

1️⃣ NestJS에 LLM 모듈 추가하기

우선 OpenAI 기반으로 시작하고,
나중에 HuggingFace·Ollama·Llama·Gemini 등 멀티 모델 전략으로 확장할 수 있게 설계한다.

설치

npm install openai

NestJS LLM 서비스

src/ai/ai.service.ts

import { Injectable } from '@nestjs/common';
import OpenAI from 'openai';

@Injectable()
export class AiService {
  private client = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY,
  });

  async chat(prompt: string) {
    const completion = await this.client.chat.completions.create({
      model: "gpt-4o-mini",
      messages: [{ role: "user", content: prompt }],
    });

    return completion.choices[0].message.content;
  }
}

컨트롤러

import { Controller, Post, Body } from '@nestjs/common';
import { AiService } from './ai.service';

@Controller('ai')
export class AiController {
  constructor(private readonly ai: AiService) {}

  @Post('chat')
  async chat(@Body('prompt') prompt: string) {
    return this.ai.chat(prompt);
  }
}

API 호출:

POST /ai/chat
{ "prompt": "오늘 할 일 요약해줘" }

2️⃣ RAG 구성 – 문서 → 임베딩 → VectorDB 저장 → 검색

반응형

실제 SaaS에서는 "검색형 AI 기능"이 필수다.
(고객센터 FAQ, 문서 검색, 노트 검색, 유저 매뉴얼 등)

선택 가능한 VectorDB

DB 장점

Qdrant (무료, Rust 기반 빠름) 쉬운 도커 설치, NestJS에서 쓰기 편함
Pinecone 상용, 고성능
Milvus 대규모 배포용
Weaviate 서버 없는 클라우드 옵션

여기서는 Qdrant 사용.


2.1 Qdrant Docker 설치

docker-compose.yml

services:
  qdrant:
    image: qdrant/qdrant
    ports:
      - "6333:6333"
    volumes:
      - qdrant_data:/qdrant/storage

2.2 NestJS에서 Qdrant SDK 설치

npm install @qdrant/js-client-rest

2.3 문서 임베딩 → 저장

embedding.service.ts

import { Injectable } from '@nestjs/common';
import OpenAI from 'openai';
import { QdrantClient } from '@qdrant/js-client-rest';

@Injectable()
export class EmbeddingService {
  private openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
  private qdrant = new QdrantClient({ url: process.env.QDRANT_URL });

  async embedDocument(id: string, text: string) {
    const embedding = await this.openai.embeddings.create({
      model: "text-embedding-3-small",
      input: text,
    });

    await this.qdrant.upsert("documents", {
      points: [
        {
          id,
          vector: embedding.data[0].embedding,
          payload: { text },
        },
      ],
    });

    return { id, ok: true };
  }
}

2.4 검색 (RAG Retrieval)

async search(query: string) {
  const embedding = await this.openai.embeddings.create({
    model: "text-embedding-3-small",
    input: query,
  });

  const result = await this.qdrant.search("documents", {
    vector: embedding.data[0].embedding,
    limit: 5,
  });

  return result;
}

3️⃣ RAG 기반 LLM 응답 만들기

async ragAnswer(query: string) {
  const hits = await this.search(query);

  const context = hits
    .map((h) => h.payload.text)
    .join("\n------------------\n");

  const finalPrompt = `
다음 문서들을 참고해서 질문에 답변해줘:

문서:
${context}

질문:
${query}
  `;

  return this.chat(finalPrompt);
}

NestJS API:

POST /ai/rag
{
  "query": "반려식물 관리법 요약해줘"
}

4️⃣ 이미지 임베딩(CLIP 기반) 검색 기능

텍스트 검색만 하면 허전하다.
요즘 서비스들은 거의 다 “이미지 임베딩 검색”을 넣는다.

예:

  • 식물 사진 업로드 → 비슷한 식물 찾기
  • 상품 사진 업로드 → 유사 아이템 추천
  • 여행 사진 업로드 → 장소 자동 태깅

OpenAI Vision Embedding

const res = await openai.embeddings.create({
  model: "clip",
  input: fileBuffer,
});

Qdrant에 저장:

await qdrant.upsert("images", {
  points: [
    {
      id,
      vector: res.data[0].embedding,
      payload: { url: imageUrl },
    },
  ],
});

검색:

async searchImage(file: Buffer) {
  const embedding = await openai.embeddings.create({
    model: "clip",
    input: file,
  });

  return await qdrant.search("images", {
    vector: embedding.data[0].embedding,
    limit: 10,
  });
}

5️⃣ Next.js UI 구현

검색 → 상세 → 챗 → 이미지 업로드까지 하나의 프론트에 통합한다.

문서 검색 UI

'use client';
import useSWR from 'swr';
import axios from 'axios';
import { useState } from 'react';

export default function RagSearch() {
  const [query, setQuery] = useState('');
  const { data, mutate } = useSWR(
    query ? `/api/rag?query=${query}` : null,
    url => axios.get(url).then(r => r.data)
  );

  return (
    <div>
      <input 
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="검색어 입력"
      />
      <button onClick={() => mutate()}>검색</button>

      {data && data.hits && (
        <pre>{JSON.stringify(data.hits, null, 2)}</pre>
      )}
    </div>
  );
}

6️⃣ 최종 아키텍처 (AI 포함)

 Next.js (Cloudflare CDN SSR)
        │
        ▼
   NestJS API (K8s)
        │
        ├── LLM (OpenAI / Local / HuggingFace)
        ├── VectorDB (Qdrant)
        ├── R2 이미지 저장
        └── Redis 캐싱

7️⃣ 이번 편 정리

기능 구현

LLM 텍스트 응답 ✔️
RAG 검색 ✔️
Vector Database ✔️
이미지 임베딩 검색 ✔️
NestJS API로 통합 ✔️
Next.js UI 연동 ✔️

이제 완전한 AI SaaS 기능이 붙었다.


🔮 다음 편 예고

이제 마지막 단계다.

다음 글은
👉 “사용자 인증 + 결제(Stripe) + 구독 상태에 따라 AI 기능 제한”
편으로 들어간다.

즉, 실서비스에서 쓰이는
AI 기능 + 유료 구독 모델(Subscription SaaS) 완성편 이다.


 

NestJS, Next.js, RAG, VectorDB, Qdrant, LLM, 이미지임베딩, AI검색, OpenAI, SaaS, DevOps

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함
반응형