파이썬 챗봇 만들기

구현화면

1. 전체 구조 이해하기

이 교안은 아래의 기술을 사용하여 AI 챗봇을 만드는 방법을 단계별로 설명한다.

역할 기술 설명
프론트엔드(front-end) React(리액트) + Vanilla CSS 사용자 화면
백엔드(back-end) Python(파이썬) + FastAPI(패스트에이피아이) 서버 로직
AI 모델 HuggingFace(허깅페이스) 무료 AI API
배포 Render(렌더) 무료 호스팅

Info: HuggingFace(허깅페이스) 란? AI 모델을 누구나 무료로 사용할 수 있도록 제공하는 플랫폼이다. 회원가입만 하면 API(에이피아이) 키를 무료로 받을 수 있다.


2. 1단계. 개발 환경 준비

2.1. 프로젝트 폴더 만들기

폴더 구조

my-chatbot/
├── backend/        ← 파이썬 서버
│   ├── main.py
│   └── requirements.txt
└── frontend/       ← 리액트 화면
    ├── src/
    │   ├── App.jsx
    │   └── App.css
    └── package.json
  • backend/ : 파이썬 코드가 들어가는 폴더이다.
  • frontend/ : 리액트 코드가 들어가는 폴더이다.
  • 두 폴더는 서로 다른 역할을 하며, 나중에 각각 배포한다.

Terminal(터미널)

mkdir my-chatbot
cd my-chatbot
mkdir backend
mkdir frontend
  • mkdir (메이크 디렉토리): 폴더를 만드는 명령어이다.
  • cd (체인지 디렉토리): 폴더 안으로 이동하는 명령어이다.

2.2. HuggingFace(허깅페이스) API(에이피아이) 키 발급

Tip: API(에이피아이) 키란? 내 신분증처럼, 허깅페이스 서버에 "나는 회원이에요"라고 증명하는 비밀 문자열이다.

  1. https://huggingface.co 에 접속하여 회원가입을 한다.
  2. 우측 상단 프로필 → Settings(설팅스)Access Tokens(액세스 토큰스) 를 클릭한다.
  3. New token(뉴 토큰) 버튼을 클릭하고 이름을 입력한 뒤 Read 권한으로 생성한다.
  4. 생성된 키(예: hf_xxxxxxxxxxxxxxxx)를 복사하여 안전한 곳에 저장한다.

Warning: API(에이피아이) 키는 절대로 GitHub(깃허브)에 올리면 안 된다. 타인이 내 키를 무단으로 사용할 수 있다.


3. 2단계. 파이썬 백엔드(back-end) 만들기

3.1. 가상환경 설정

Windows(윈도우)

cd backend
python -m venv .venv
.venvScriptsactivate
  • python -m venv .venv : 가상환경 폴더를 만든다.
  • .venvScriptsactivate : 가상환경을 실행(활성화)한다.
  • 활성화되면 터미널 앞에 (.venv) 표시가 나타난다.

Mac/Linux(맥/리눅스)

cd backend
python3 -m venv .venv
source .venv/bin/activate
  • source .venv/bin/activate : Mac/Linux에서 가상환경을 활성화하는 명령어이다.

3.2. 라이브러리(library) 설치

pip install fastapi uvicorn requests python-dotenv
라이브러리 발음 역할
fastapi 패스트에이피아이 파이썬 웹 서버 프레임워크
uvicorn 유비콘 서버 실행기
requests 리퀘스츠 외부 API 호출 도구
python-dotenv 파이썬 닷엔브 환경변수 관리

3.3. 환경변수(environment variable) 파일 만들기

backend/ 폴더 안에 .env 파일을 만들고 아래 내용을 입력한다.

HF_TOKEN=hf_여기에_내_키를_붙여넣기

Note: .env (닷엔브) 파일이란? 비밀 정보를 코드 밖에 보관하는 파일이다. 코드에 직접 키를 쓰지 않아도 되므로 안전하다.


3.4. 메인 서버 파일 작성

backend/main.py 파일을 만들고 아래 코드를 작성한다.

from fastapi import FastAPI                         # 01
from fastapi.middleware.cors import CORSMiddleware  # 02
from pydantic import BaseModel                      # 03
import requests, os                                 # 04
from dotenv import load_dotenv                      # 05

load_dotenv()                                       # 06
app = FastAPI()                                     # 07

app.add_middleware(                                 # 08
    CORSMiddleware,                                 # 09
    allow_origins=["*"],                            # 10
    allow_methods=["*"],                            # 11
    allow_headers=["*"],                            # 12
)                                                   # 13

class Msg(BaseModel):                               # 14
    text: str                                       # 15

HF_URL = "https://router.huggingface.co/v1/chat/completions" # 16
HF_MODEL = "Qwen/Qwen2.5-72B-Instruct"             # 17

def ask_ai(q: str) -> str:                          # 18
    token = os.getenv("HF_TOKEN")                   # 19
    headers = {"Authorization": f"Bearer {token}"}  # 20
    payload = {                                     # 21
        "model": HF_MODEL,                          # 22
        "messages": [{"role": "user", "content": q}], # 23
        "max_tokens": 300                           # 24
    }                                               # 25
    res = requests.post(HF_URL, headers=headers, json=payload) # 26
    data = res.json()                               # 27
    return data["choices"][0]["message"]["content"] # 28

@app.post("/chat")                                  # 27
def chat(msg: Msg):                                 # 28
    reply = ask_ai(msg.text)                        # 29
    return {"reply": reply}                         # 30


| 줄 | 설명 |
| :—- | :————————————————————————————————————————- |
| 01 | FastAPI(패스트에이피아이) 라이브러리에서 `FastAPI` 클래스를 불러온다. |
| 02 | CORS(코어스) 미들웨어를 불러온다. 리액트와 파이썬이 서로 통신할 수 있게 해준다. |
| 03 | 데이터 형태를 검사해 주는 `BaseModel`(베이스모델)을 불러온다. |
| 04 | `requests`(리퀘스츠)는 외부 서버에 요청을 보내는 도구, `os`(오에스)는 환경변수를 읽는 도구이다. |
| 05 | `.env` 파일을 읽어오는 `load_dotenv`(로드 닷엔브)를 불러온다. |
| 06 | `.env` 파일을 실제로 불러온다. |
| 07 | FastAPI 앱 객체를 만든다. 이 `app`이 우리 서버의 중심이다. |
| 08~13 | CORS(코어스) 설정이다. 리액트(포트 5173)에서 파이썬(포트 8000)으로 요청을 허용한다. `”*”`는 모든 주소를 허용한다는 뜻이다. |
| 14~15 | 요청으로 받을 데이터 형태를 정의한다. `text`라는 문자열 필드 하나를 갖는다. |
| 16 | 허깅페이스의 새 Router(라우터) API 주소이다. 2025년 기준 공식 엔드포인트이다. |
| 17 | 사용할 AI 모델 이름이다. Qwen2.5-72B(큰 용량, 고성능)를 기본으로 사용한다. |
| 18 | AI에게 질문을 보내고 답변을 받는 함수를 정의한다. |
| 19 | `.env` 파일에서 `HF_TOKEN` 값을 가져온다. |
| 20 | 허깅페이스 서버에 내 키를 전달하는 헤더(header)를 만든다. |
| 21~25 | AI에게 보낼 데이터(payload, 페이로드)를 만든다. OpenAI(오픈에이아이)와 동일한 메시지 형식을 사용한다. |
| 26 | 허깅페이스 서버에 POST(포스트) 요청을 보낸다. |
| 27 | 서버의 응답을 JSON(제이슨) 형식으로 변환한다. |
| 28 | `choices[0].message.content` 경로에서 AI의 답변 텍스트를 꺼내 반환한다. |
| 29~30 | `/chat` 주소로 POST 요청이 오면 `ask_ai`를 실행하고 결과를 반환한다. |


3.5. 서버 실행 테스트

uvicorn main:app --reload
  • uvicorn (유비콘): 파이썬 서버 실행 도구이다.
  • main:app : main.py 파일의 app 객체를 실행한다는 뜻이다.
  • --reload : 코드 수정 시 자동으로 서버를 재시작한다.

브라우저에서 http://localhost:8000/docs 에 접속하면 자동 생성된 API(에이피아이) 문서를 확인할 수 있다.

Tip: /docs (닥스) 페이지에서 /chat 엔드포인트를 직접 테스트해볼 수 있다. Try it out(트라이 잇 아웃) 버튼을 클릭하여 {"text": "안녕?"} 을 입력해 보자.


3.6. requirements.txt 작성

배포 시 필요한 파일이다. backend/requirements.txt 를 만들고 아래 내용을 입력한다.

fastapi
uvicorn
requests
python-dotenv

4. 3단계. 리액트 프론트엔드(front-end) 만들기

4.1. 리액트 프로젝트 생성

cd ../frontend
npm create vite@latest . -- --template react
npm install
  • cd ../frontend : 백엔드 폴더에서 나와 프론트엔드 폴더로 이동한다.
  • npm create vite@latest . : 현재 폴더에 Vite(비트) 프로젝트를 생성한다.
  • -- --template react : 리액트 템플릿(template)으로 생성한다.
  • npm install : 필요한 패키지(package)들을 설치한다.

4.2. 채팅 화면 만들기

frontend/src/App.jsx 파일을 아래 코드로 교체한다.

import { useState } from "react";          // 01
import "./App.css";                         // 02

const API = "http://localhost:8000/chat";  // 03

export default function App() {            // 04
  const [msgs, setMsgs] = useState([]);   // 05
  const [input, setInput] = useState(""); // 06
  const [loading, setLoading] = useState(false); // 07

  const send = async () => {              // 08
    if (!input.trim()) return;            // 09
    const userMsg = { role: "user", text: input }; // 10
    setMsgs(prev => [...prev, userMsg]);  // 11
    setInput("");                         // 12
    setLoading(true);                     // 13

    const res = await fetch(API, {        // 14
      method: "POST",                     // 15
      headers: { "Content-Type": "application/json" }, // 16
      body: JSON.stringify({ text: input }) // 17
    });                                   // 18
    const data = await res.json();        // 19
    const botMsg = { role: "bot", text: data.reply }; // 20
    setMsgs(prev => [...prev, botMsg]);   // 21
    setLoading(false);                    // 22
  };                                      // 23

  const onKey = (e) => {                  // 24
    if (e.key === "Enter") send();        // 25
  };                                      // 26

  return (                                // 27
    <div className="wrap">               // 28
      <h1>🤖 AI 챗봇</h1>               // 29
      <div className="box">              // 30
        {msgs.map((m, i) => (            // 31
          <div key={i} className={m.role}> // 32
            <span>{m.role === "user" ? "🧑" : "🤖"}</span> // 33
            <p>{m.text}</p>              // 34
          </div>                         // 35
        ))}                              // 36
        {loading && <p className="loading">생각 중...</p>} // 37
      </div>                             // 38
      <div className="input-row">        // 39
        <input                           // 40
          value={input}                  // 41
          onChange={e => setInput(e.target.value)} // 42
          onKeyDown={onKey}              // 43
          placeholder="메시지를 입력하세요" // 44
        />                               // 45
        <button onClick={send}>전송</button> // 46
      </div>                             // 47
    </div>                               // 48
  );                                     // 49
}                                        // 50


| 줄 | 설명 |
| :—- | :———————————————————————————— |
| 01 | 리액트의 `useState`(유즈스테이트) 훅을 불러온다. 화면의 상태(데이터)를 관리한다. |
| 02 | CSS(씨에스에스) 스타일 파일을 불러온다. |
| 03 | 파이썬 서버의 주소를 상수로 저장한다. 나중에 배포 주소로 바꿔야 한다. |
| 04 | `App` 컴포넌트(component)를 기본(default) 내보내기로 정의한다. |
| 05 | `msgs`(메시지 목록) 상태를 빈 배열로 초기화한다. |
| 06 | `input`(입력창) 상태를 빈 문자열로 초기화한다. |
| 07 | `loading`(로딩) 상태를 `false`로 초기화한다. AI가 답변 중일 때 `true`가 된다. |
| 08 | `send`(전송) 함수를 비동기(async, 에이싱크)로 정의한다. |
| 09 | 입력값이 비어있으면 함수를 종료한다. |
| 10 | 사용자가 보낸 메시지 객체를 만든다. `role`(롤)은 “user”이다. |
| 11 | 기존 메시지 목록에 사용자 메시지를 추가한다. `…prev`는 기존 목록을 펼치는 문법이다. |
| 12 | 메시지 전송 후 입력창을 비운다. |
| 13 | 로딩 상태를 `true`로 바꿔 “생각 중…” 문구를 표시한다. |
| 14~18 | 파이썬 서버에 POST(포스트) 요청을 보낸다. `body`에 사용자 메시지를 담는다. |
| 19 | 서버 응답을 JSON(제이슨) 형식으로 변환한다. |
| 20 | AI 봇의 메시지 객체를 만든다. `role`은 “bot”이다. |
| 21 | 메시지 목록에 봇 메시지를 추가한다. |
| 22 | 로딩 상태를 `false`로 바꿔 “생각 중…” 문구를 숨긴다. |
| 24~26 | 키보드 Enter(엔터) 키를 누르면 `send` 함수를 실행한다. |
| 27~49 | 화면에 그릴 JSX(제이에스엑스) 코드이다. |
| 30~38 | 메시지 목록을 표시하는 영역이다. `msgs.map`으로 각 메시지를 하나씩 그린다. |
| 32 | `className`(클래스네임)에 `role` 값(“user” 또는 “bot”)을 넣어 스타일을 구분한다. |
| 37 | `loading`이 `true`일 때만 “생각 중…” 문구를 표시한다. |
| 39~47 | 입력창과 전송 버튼 영역이다. |


4.3. CSS(씨에스에스) 스타일 작성

frontend/src/App.css 파일을 아래 코드로 교체한다.

.wrap { max-width: 600px; margin: 40px auto; font-family: sans-serif; }
.box  { border: 1px solid #ccc; height: 400px; overflow-y: auto; padding: 16px; }
.user { text-align: right; margin: 8px 0; }
.bot  { text-align: left;  margin: 8px 0; }
.user p, .bot p { display: inline-block; padding: 8px 12px; border-radius: 8px; }
.user p  { background: #d1e7ff; }
.bot  p  { background: #f0f0f0; }
.loading { color: #888; font-style: italic; }
.input-row { display: flex; gap: 8px; margin-top: 8px; }
.input-row input  { flex: 1; padding: 8px; }
.input-row button { padding: 8px 16px; cursor: pointer; }
  • 최소한의 스타일만 적용하여 채팅 레이아웃을 구성한다.
  • .user.bot으로 사용자와 봇 메시지를 좌우로 구분한다.

4.4. 프론트엔드 실행 테스트

npm run dev

브라우저에서 http://localhost:5173 에 접속하여 챗봇이 동작하는지 확인한다.

Tip: 파이썬 서버(uvicorn)와 리액트 개발 서버(npm run dev)를 동시에 실행해야 한다. 터미널 창을 두 개 열어서 각각 실행한다.


5. 4단계. Render(렌더)에 배포하기

5.1. GitHub(깃허브)에 코드 올리기

Note: GitHub(깃허브)란? 코드를 온라인에 저장하고 공유하는 서비스이다. Render(렌더)는 깃허브 저장소를 연결하여 자동으로 배포한다.

  1. https://github.com 에서 새 저장소(repository, 리포지토리)를 만든다.
  2. 프로젝트 루트(my-chatbot/)에 .gitignore 파일을 만들고 아래 내용을 입력한다.
backend/.env
backend/.venv/
frontend/node_modules/
  1. 터미널에서 아래 명령어를 실행하여 코드를 깃허브에 올린다.
git init
git add .
git commit -m "first commit"
git remote add origin https://github.com/내아이디/my-chatbot.git
git push -u origin main

5.2. 파이썬 백엔드 배포

  1. https://render.com 에 접속하여 GitHub(깃허브) 계정으로 로그인한다.
  2. New(뉴)Web Service(웹 서비스) 를 클릭한다.
  3. 깃허브 저장소를 연결하고 아래와 같이 설정한다.
항목 입력값
Root Directory(루트 디렉토리) backend
Runtime(런타임) Python 3
Build Command(빌드 커맨드) pip install -r requirements.txt
Start Command(스타트 커맨드) uvicorn main:app --host 0.0.0.0 --port 10000
  1. Environment(환경변수) 탭에서 아래 값을 입력한다.
Key(키) Value(값)
HF_TOKEN 허깅페이스에서 발급받은 API(에이피아이) 키
  1. Create Web Service(크리에이트 웹 서비스) 버튼을 클릭하면 배포가 시작된다.
  2. 배포 완료 후 https://my-chatbot-xxxx.onrender.com 형태의 주소가 생성된다.

5.3. 리액트 프론트엔드 배포 전 수정

frontend/src/App.jsxAPI 상수를 Render(렌더) 주소로 변경한다.

const API = "https://my-chatbot-xxxx.onrender.com/chat";
  • localhost:8000 을 배포된 파이썬 서버 주소로 바꾼다.
  • 이 작업 후 깃허브에 다시 push(푸시)한다.

5.4. 리액트 프론트엔드 배포

  1. Render(렌더)에서 New(뉴)Static Site(스태틱 사이트) 를 클릭한다.
  2. 같은 저장소를 연결하고 아래와 같이 설정한다.
항목 입력값
Root Directory(루트 디렉토리) frontend
Build Command(빌드 커맨드) npm install && npm run build
Publish Directory(퍼블리시 디렉토리) dist
  1. Create Static Site(크리에이트 스태틱 사이트) 버튼을 클릭한다.
  2. 배포 완료 후 생성된 주소로 접속하면 챗봇을 사용할 수 있다.

Tip: Render(렌더) 무료 플랜은 15분간 요청이 없으면 서버가 절전 모드로 전환된다. 처음 요청 시 30초~1분 정도 응답이 느릴 수 있다. 이는 정상 동작이다.


5.4.1. 배포 오류 해결

5.4.1.1. Conflicting peer dependency
  1. 배포중 아래와 같이 오류 발생시
    alt
  2. 에러 로그에 npm error ..., Conflicting peer dependency 가 포함되어 있을 경우 의존성 버전이 안맞아 실패한 것이다.
  3. 배포 프로젝트의 루트 폴더에 .npmrc 파일을 생성하고 아래의 코드를 추가한다.
  legacy-peer-deps=true

alt
4. 깃허브에 푸쉬한다.
5. 렌더에 재배포 한다.
alt

6. 5단계. 응용하기

6.1. 다른 AI 모델로 바꾸기

main.pyHF_URL 을 변경하면 다른 AI 모델을 사용할 수 있다.

추천 모델 목록

모델명 특징
Qwen/Qwen2.5-72B-Instruct ✅ 기본 추천, 한국어 응답 우수
meta-llama/Llama-3.1-8B-Instruct 가볍고 빠른 응답
mistralai/Mistral-Nemo-Instruct-2407 균형 잡힌 성능

Warning: 2025년 기준 api-inference.huggingface.co 엔드포인트는 폐지되었다. 반드시 router.huggingface.co/v1 을 사용해야 한다.

변경 방법

# HF_MODEL 변수의 모델명만 교체한다
HF_MODEL = "meta-llama/Llama-3.1-8B-Instruct"
  • HF_MODEL 변수의 값만 바꾸면 된다.
  • 모든 모델이 동일한 OpenAI(오픈에이아이) 호환 형식을 사용하므로 다른 코드는 수정하지 않아도 된다.

6.2. 캐릭터 챗봇으로 만들기

AI에게 역할을 부여하면 특정 캐릭터처럼 대화할 수 있다. ask_ai 함수의 messages 부분을 수정한다.

def ask_ai(q: str) -> str:
    token = os.getenv("HF_TOKEN")
    headers = {"Authorization": f"Bearer {token}"}

    # 캐릭터 역할 설정 (system 메시지로 전달)
    system = "You are a helpful Korean cooking teacher. Answer only about Korean food."

    payload = {
        "model": HF_MODEL,
        "messages": [
            {"role": "system", "content": system}, # 역할 설정
            {"role": "user",   "content": q}        # 사용자 질문
        ],
        "max_tokens": 300
    }
    res = requests.post(HF_URL, headers=headers, json=payload)
    data = res.json()
    return data["choices"][0]["message"]["content"]
  • system 역할의 메시지를 messages 배열 맨 앞에 추가하면 AI가 해당 캐릭터처럼 행동한다.
  • system 내용을 바꾸면 요리 선생님, 영어 튜터, 게임 캐릭터 등 다양한 챗봇을 만들 수 있다.

Note: system 역할 메시지는 영어로 작성할 때 더 정확하게 동작한다. 한국어 지시도 가능하지만 영어를 권장한다.


7. 전체 흐름 요약

사용자 입력
    ↓
리액트 (fetch POST /chat)
    ↓
파이썬 FastAPI (/chat 엔드포인트)
    ↓
허깅페이스 API (AI 모델 호출)
    ↓
AI 응답 반환
    ↓
리액트 화면에 메시지 출력

Tip: 막히는 부분이 있으면 각 단계의 console.log(리액트) 또는 print(파이썬)로 데이터 흐름을 확인하는 것이 디버깅(debugging, 디버깅)의 기본이다.

댓글 남기기