AI Sparkup

복잡한 AI 세상을 읽는 힘 ⚡

15분만에 나만의 AI 검색엔진 만들기 – 실습 가이드

간단한 API 두 개만으로 Perplexity 스타일의 AI 검색엔진을 만들고, 선택적으로 메모리 기능까지 추가해보는 실용적 가이드입니다.

AI 검색엔진, 이제 직접 만들어보자

구글과 같은 전통적인 검색엔진은 키워드 매칭 방식에 의존합니다. 하지만 AI 기반 검색엔진은 질문의 의도를 이해하고, 여러 소스의 정보를 종합해 답변을 제공합니다. Perplexity, ChatGPT Search 같은 서비스가 바로 이런 방식으로 동작하죠.

이제 여러분도 15분만에 이런 AI 검색엔진을 만들 수 있습니다. 복잡한 인프라 없이, 두 개의 간단한 API만으로 말이죠.

필요한 핵심 도구

우리가 사용할 핵심 도구는 다음과 같습니다:

Brave Search API: 실시간 웹 검색 결과를 가져오는 API입니다. 빠르고 안정적이며, 개발자 친화적인 무료 플랜을 제공합니다.

OpenAI GPT API: 웹 검색 결과를 분석하고 사용자 질문에 맞는 답변을 생성하는 AI 모델입니다.

이 두 가지 도구만으로도 Perplexity의 핵심 기능을 모두 구현할 수 있습니다:

  1. 사용자 질문 접수 → 2. 웹에서 실시간 정보 수집 → 3. AI가 종합 답변 생성

추가 옵션 – 메모리 기능: 더 똑똑한 검색엔진을 원한다면 Supermemory 같은 메모리 시스템을 추가할 수 있습니다. 이전 대화를 기억하고 연관성 있는 답변을 제공하죠.

AI 검색엔진 구조도
우리가 만들 AI 검색엔진의 기본 구조

단계별 구현 과정

1단계: 환경 설정 (2분)

프로젝트 폴더를 생성하고 필요한 패키지를 설치합니다.

mkdir ai-search-engine
cd ai-search-engine
npm init -y
npm install express cors dotenv axios openai

각 패키지의 역할은 다음과 같습니다:

  • express: 백엔드 API 서버
  • cors: CORS 문제 해결
  • dotenv: API 키 관리
  • axios: HTTP 요청 처리
  • openai: AI 답변 생성

2단계: API 키 발급 (3분)

다음 두 곳에서 API 키를 발급받아야 합니다:

  1. Brave Search: Brave Search API 페이지에서 계정 생성 (카드 정보 필요하지만 무료 플랜 이용 가능)
  2. OpenAI: 플랫폼 문서를 참고해 API 키 생성

프로젝트 폴더에 .env 파일을 생성하고 키를 입력합니다:

OPENAI_API_KEY=your_openai_key
BRAVE_SEARCH_API_KEY=your_brave_key

3단계: 백엔드 구현 (7분)

server.js 파일을 생성하고 다음 코드를 입력합니다:

// server.js
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import axios from 'axios';
import OpenAI from 'openai';

dotenv.config();
const app = express();

// 미들웨어 설정
app.use(cors());
app.use(express.json());
app.use(express.static('public'));

// OpenAI 클라이언트 설정
const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

// Brave 검색 함수
async function braveSearch(query, count = 5) {
  const params = new URLSearchParams({
    q: query,
    count: count.toString(),
    country: 'US',
    search_lang: 'en',
    extra_snippets: 'true',
    text_decorations: 'false',
  });

  const url = `https://api.search.brave.com/res/v1/web/search?${params}`;

  const response = await axios.get(url, {
    headers: {
      'Accept': 'application/json',
      'Accept-Encoding': 'gzip',
      'X-Subscription-Token': process.env.BRAVE_SEARCH_API_KEY,
    },
    timeout: 5000,
  });

  return response.data.web?.results || [];
}

// 메인 검색 엔드포인트
app.post('/api/query', async (req, res) => {
  const { query } = req.body;

  if (!query || typeof query !== 'string') {
    return res.status(400).json({ error: '유효한 검색어를 입력해주세요.' });
  }

  try {
    // 1. 웹 검색 실행
    const webResults = await braveSearch(query, 5);

    // 2. 웹 결과 요약
    const webSummaries = webResults.map((r, i) => {
      const allSnippets = [r.description, ...(r.extraSnippets || [])];
      const bestSnippet = allSnippets.reduce((a, b) => 
        a.length > b.length ? a : b, allSnippets[0]);
      return `${i+1}. ${bestSnippet.trim()} — ${r.url}`;
    }).join('\n\n');

    // 3. AI 답변 생성
    const systemPrompt = `
    당신은 도움이 되는 AI 검색 어시스턴트입니다.
    웹 검색 결과를 바탕으로 사용자의 질문에 정확하고 유용한 답변을 제공하세요.
    답변은 간결하면서도 포괄적이어야 하며, 답변 끝에 참고할 만한 URL들을 나열해주세요.
    `.trim();

    const userPrompt = `
    질문: ${query}

    웹 검색 결과:
    ${webSummaries}

    위 검색 결과를 바탕으로 질문에 답변하고 관련 링크를 제시해주세요.
    `.trim();

    const chat = await openai.chat.completions.create({
      model: 'gpt-4o-mini',
      messages: [
        { role: 'system', content: systemPrompt },
        { role: 'user', content: userPrompt },
      ],
      temperature: 0.2,
    });

    const answer = chat.choices[0].message.content;

    // 4. 결과 반환
    res.json({ answer, searchResults: webResults });

  } catch (error) {
    console.error('검색 오류:', error);
    res.status(500).json({ error: '검색 중 오류가 발생했습니다.' });
  }
});

// 서버 시작
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`서버가 http://localhost:${PORT}에서 실행 중입니다`);
});

이 간단한 코드의 핵심 로직은 다음과 같습니다:

  1. 웹 검색: Brave API로 실시간 웹 검색 결과 수집
  2. 결과 정리: 가장 유용한 정보를 추출하고 정리
  3. AI 답변: OpenAI가 웹 결과를 분석해 최종 답변 생성

단 3단계만으로 Perplexity의 핵심 기능이 완성됩니다!

4단계: 프론트엔드 구현 (3분)

public 폴더를 생성하고 그 안에 index.html 파일을 만듭니다:

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>나만의 AI 검색엔진</title>

  <!-- 마크다운 렌더링을 위한 라이브러리 -->
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js"></script>

  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      max-width: 800px;
      margin: 2rem auto;
      padding: 0 1rem;
      line-height: 1.6;
      color: #333;
    }

    .search-container {
      margin-bottom: 2rem;
    }

    input {
      width: 70%;
      padding: 0.8rem;
      font-size: 1rem;
      border: 2px solid #ddd;
      border-radius: 8px;
      outline: none;
    }

    input:focus {
      border-color: #007acc;
    }

    button {
      padding: 0.8rem 1.5rem;
      font-size: 1rem;
      margin-left: 0.5rem;
      background: #007acc;
      color: white;
      border: none;
      border-radius: 8px;
      cursor: pointer;
    }

    button:hover {
      background: #005f99;
    }

    h2 {
      margin-top: 2rem;
      color: #2c3e50;
      border-bottom: 2px solid #ecf0f1;
      padding-bottom: 0.5rem;
    }

    #answer {
      background: #f8f9fa;
      padding: 1.5rem;
      border-radius: 8px;
      border-left: 4px solid #007acc;
      margin: 1rem 0;
      white-space: pre-wrap;
    }

    .results-list {
      list-style: none;
      padding: 0;
    }

    .results-list li {
      margin-bottom: 1rem;
      padding: 1rem;
      background: white;
      border: 1px solid #e1e8ed;
      border-radius: 8px;
    }

    .results-list a {
      color: #007acc;
      text-decoration: none;
      font-weight: 600;
      font-size: 1.1rem;
    }

    .results-list a:hover {
      text-decoration: underline;
    }

    .snippet {
      color: #666;
      margin-top: 0.5rem;
      font-size: 0.95rem;
    }

    .loading {
      color: #666;
      font-style: italic;
    }
  </style>
</head>
<body>
  <h1>🔍 나만의 AI 검색엔진</h1>

  <div class="search-container">
    <input id="query" placeholder="궁금한 것을 물어보세요..." />
    <button id="search">검색</button>
  </div>

  <h2>💡 AI 답변</h2>
  <div id="answer">검색하시면 AI가 생성한 답변이 여기에 표시됩니다.</div>

  <h2>🌐 웹 검색 결과</h2>
  <ul id="results" class="results-list"></ul>

  <script>
    const queryInput = document.getElementById('query');
    const searchButton = document.getElementById('search');
    const answerDiv = document.getElementById('answer');
    const resultsList = document.getElementById('results');

    // 엔터키로 검색 실행
    queryInput.addEventListener('keypress', (e) => {
      if (e.key === 'Enter') {
        searchButton.click();
      }
    });

    searchButton.addEventListener('click', async () => {
      const query = queryInput.value.trim();
      if (!query) {
        alert('검색어를 입력해주세요.');
        return;
      }

      // 로딩 상태 표시
      answerDiv.innerHTML = '<div class="loading">🤔 AI가 답변을 생성하고 있습니다...</div>';
      resultsList.innerHTML = '';
      searchButton.disabled = true;
      searchButton.textContent = '검색 중...';

      try {
        const response = await fetch('/api/query', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ query })
        });

        if (!response.ok) {
          throw new Error(`서버 오류: ${response.statusText}`);
        }

        const { answer, searchResults } = await response.json();

        // AI 답변 렌더링 (마크다운 지원)
        const rawHtml = marked.parse(answer);
        answerDiv.innerHTML = DOMPurify.sanitize(rawHtml);

        // 웹 검색 결과 렌더링
        searchResults.forEach(result => {
          const li = document.createElement('li');

          const link = document.createElement('a');
          link.href = result.url;
          link.target = '_blank';
          link.rel = 'noopener';
          link.textContent = result.title;

          const snippet = document.createElement('div');
          snippet.className = 'snippet';
          snippet.textContent = result.description;

          li.appendChild(link);
          li.appendChild(snippet);
          resultsList.appendChild(li);
        });

      } catch (error) {
        console.error('검색 오류:', error);
        answerDiv.innerHTML = '<div style="color: red;">❌ 검색 중 오류가 발생했습니다: ' + error.message + '</div>';
      } finally {
        searchButton.disabled = false;
        searchButton.textContent = '검색';
      }
    });
  </script>
</body>
</html>
완성된 AI 검색엔진 화면
완성된 AI 검색엔진의 사용자 인터페이스

실행 및 테스트

이제 모든 준비가 완료되었습니다. 터미널에서 다음 명령어로 서버를 실행하세요:

node server.js

브라우저에서 http://localhost:3000에 접속하면 여러분만의 AI 검색엔진을 사용할 수 있습니다.

테스트해볼 만한 질문들

  1. 기술 관련: “React와 Vue의 차이점은?”
  2. 일반 지식: “기후변화의 주요 원인은?”
  3. 최신 정보: “2024년 AI 트렌드는?”
  4. 복합 질문: “JavaScript로 API 호출하는 방법과 에러 처리 방법”

이제 Perplexity의 80% 기능을 가진 AI 검색엔진이 완성되었습니다!

한 단계 더: 메모리 기능 추가하기

기본 버전도 충분히 유용하지만, 이전 대화를 기억하는 메모리 기능을 추가하면 더욱 똑똑한 검색엔진이 됩니다.

간단한 메모리 (세션 기반)

가장 간단한 방법은 세션을 이용한 메모리입니다:

npm install express-session
import session from 'express-session';

app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true
}));

app.post('/api/query', async (req, res) => {
  const { query } = req.body;

  // 세션에 검색 기록 저장
  if (!req.session.searchHistory) {
    req.session.searchHistory = [];
  }

  // 관련 기록 찾기 (키워드 매칭)
  const relatedHistory = req.session.searchHistory
    .filter(item => 
      item.query.toLowerCase().includes(query.toLowerCase()) ||
      query.toLowerCase().includes(item.query.toLowerCase())
    )
    .slice(-3);

  // AI에게 이전 대화 맥락 제공
  const memoryContext = relatedHistory
    .map(item => `이전 질문: ${item.query}\n이전 답변: ${item.answer}`)
    .join('\n\n');

  // 웹 검색 및 AI 답변 생성
  // ... (기존 코드)

  // 현재 대화를 기록에 저장
  req.session.searchHistory.push({
    query,
    answer,
    timestamp: new Date()
  });
});

고급 메모리 (벡터 데이터베이스)

더 정교한 메모리 기능을 원한다면 다음 옵션들을 고려해보세요:

Supermemory: 가장 간단한 관리형 서비스

npm install supermemory

Chroma: 무료 오픈소스 벡터 DB

pip install chromadb

Pinecone: 사용하기 쉬운 클라우드 서비스

npm install @pinecone-database/pinecone

메모리 기능의 장점

메모리가 추가되면 다음과 같은 경험을 제공할 수 있습니다:

컨텍스트 유지: “그럼 React는 어떻게 설치하나요?” 같은 후속 질문에 정확한 답변

개인화: 사용자의 관심사와 질문 패턴을 학습해 맞춤형 답변 제공

지식 축적: 검색할수록 더 똑똑해지는 AI 어시스턴트

발전 방향과 추가 기능

기본 구현이 완료되었다면 다음과 같은 기능들을 추가해볼 수 있습니다:

다국어 지원: Brave Search API의 언어 설정을 조정해 한국어 검색 결과의 비중을 높일 수 있습니다.

이미지 검색: Brave API는 이미지 검색도 지원하므로 시각적 결과도 포함할 수 있습니다.

음성 인터페이스: Web Speech API를 활용해 음성 검색 기능을 추가할 수 있습니다.

결과 필터링: 사용자가 원하는 도메인이나 날짜 범위로 검색 결과를 필터링하는 기능을 추가할 수 있습니다.

실제 서비스로 발전시키기

15분 버전은 프로토타입이지만, 실제 서비스로 발전시키려면 다음 사항들을 고려해야 합니다:

확장성: 사용자 증가에 대비한 데이터베이스 분산과 캐싱 전략이 필요합니다.

보안: API 키 관리, 사용자 인증, 속도 제한 등의 보안 조치가 필요합니다.

비용 최적화: API 호출 비용을 절약하기 위한 결과 캐싱과 스마트 라우팅이 필요합니다.

품질 관리: 부적절한 콘텐츠 필터링과 답변 품질 모니터링이 필요합니다.

하지만 지금 만든 시스템도 충분히 실용적입니다. 개인 프로젝트나 소규모 팀에서 사용하기에는 전혀 문제없는 수준이죠.

AI 검색엔진 플로우차트
AI 검색엔진의 기본 동작 플로우

마무리

15분만에 Perplexity 수준의 AI 검색엔진을 만들어봤습니다. 복잡한 벡터 데이터베이스나 대규모 인프라 없이도 충분히 실용적인 시스템을 구축할 수 있다는 점이 인상적입니다.

기본 버전만으로도 웹 검색과 AI 답변의 조합으로 강력한 검색 경험을 제공할 수 있고, 필요에 따라 메모리 기능을 단계적으로 추가할 수 있습니다.

AI 기술이 빠르게 발전하면서 이런 고급 기능들이 점점 더 접근하기 쉬워지고 있습니다. 여러분도 오늘 만든 검색엔진을 바탕으로 더 창의적이고 혁신적인 서비스를 만들어보시기 바랍니다.


참고자료:


AI Sparkup 구독하기

구독을 신청하면 최신 게시물을 이메일로 받아볼 수 있습니다.

Comments