파이썬 챗봇 만들기
코드 블록의 Try it Yourself 버튼으로 직접 실행할 수 있다.
구문
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(에이피아이) 키란? 내 신분증처럼, 허깅페이스 서버에 "나는 회원이에요"라고 증명하는 비밀 문자열이다.
- https://huggingface.co 에 접속하여 회원가입을 한다.
- 우측 상단 프로필 → Settings(설팅스) → Access Tokens(액세스 토큰스) 를 클릭한다.
- New token(뉴 토큰) 버튼을 클릭하고 이름을 입력한 뒤 Read 권한으로 생성한다.
- 생성된 키(예:
hf_xxxxxxxxxxxxxxxx)를 복사하여 안전한 곳에 저장한다.
Warning: API(에이피아이) 키는 절대로 GitHub(깃허브)에 올리면 안 된다. 타인이 내 키를 무단으로 사용할 수 있다.
3. 2단계. 파이썬 백엔드(back-end) 만들기
3.1. 가상환경 설정
Windows(윈도우)
cd backend
python -m venv .venv
.venv\Scripts\activate
python -m venv .venv: 가상환경 폴더를 만든다..venv\Scripts\activate: 가상환경을 실행(활성화)한다.- 활성화되면 터미널 앞에
(.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(렌더)는 깃허브 저장소를 연결하여 자동으로 배포한다.
- https://github.com 에서 새 저장소(repository, 리포지토리)를 만든다.
- 프로젝트 루트(
my-chatbot/)에.gitignore파일을 만들고 아래 내용을 입력한다.
backend/.env
backend/.venv/
frontend/node_modules/
- 터미널에서 아래 명령어를 실행하여 코드를 깃허브에 올린다.
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. 파이썬 백엔드 배포
- https://render.com 에 접속하여 GitHub(깃허브) 계정으로 로그인한다.
- New(뉴) → Web Service(웹 서비스) 를 클릭한다.
- 깃허브 저장소를 연결하고 아래와 같이 설정한다.
| 항목 | 입력값 |
|---|---|
| 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 |
- Environment(환경변수) 탭에서 아래 값을 입력한다.
| Key(키) | Value(값) |
|---|---|
HF_TOKEN | 허깅페이스에서 발급받은 API(에이피아이) 키 |
- Create Web Service(크리에이트 웹 서비스) 버튼을 클릭하면 배포가 시작된다.
- 배포 완료 후
https://my-chatbot-xxxx.onrender.com형태의 주소가 생성된다.
5.3. 리액트 프론트엔드 배포 전 수정
frontend/src/App.jsx 의 API 상수를 Render(렌더) 주소로 변경한다.
const API = "https://my-chatbot-xxxx.onrender.com/chat";
localhost:8000을 배포된 파이썬 서버 주소로 바꾼다.- 이 작업 후 깃허브에 다시
push(푸시)한다.
5.4. 리액트 프론트엔드 배포
- Render(렌더)에서 New(뉴) → Static Site(스태틱 사이트) 를 클릭한다.
- 같은 저장소를 연결하고 아래와 같이 설정한다.
| 항목 | 입력값 |
|---|---|
| Root Directory(루트 디렉토리) | frontend |
| Build Command(빌드 커맨드) | npm install && npm run build |
| Publish Directory(퍼블리시 디렉토리) | dist |
- Create Static Site(크리에이트 스태틱 사이트) 버튼을 클릭한다.
- 배포 완료 후 생성된 주소로 접속하면 챗봇을 사용할 수 있다.
Tip: Render(렌더) 무료 플랜은 15분간 요청이 없으면 서버가 절전 모드로 전환된다. 처음 요청 시 30초~1분 정도 응답이 느릴 수 있다. 이는 정상 동작이다.
5.4.1. 배포 오류 해결
5.4.1.1. Conflicting peer dependency
- 배포중 아래와 같이 오류 발생시

- 에러 로그에
npm error ...,Conflicting peer dependency가 포함되어 있을 경우 의존성 버전이 안맞아 실패한 것이다. - 배포 프로젝트의 루트 폴더에
.npmrc파일을 생성하고 아래의 코드를 추가한다.
legacy-peer-deps=true
4. 깃허브에 푸쉬한다.
5. 렌더에 재배포 한다.

6. 5단계. 응용하기
6.1. 다른 AI 모델로 바꾸기
main.py의 HF_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(리액트) 또는