05 파이썬 챗봇 만들기
전체 구조 이해하기
이 교안은 아래의 기술을 사용하여 AI 챗봇을 만드는 방법을 단계별로 설명한다.
| 역할 | 기술 | 설명 |
|---|---|---|
| 프론트엔드(front-end) | React(리액트) + Vanilla CSS | 사용자 화면 |
| 백엔드(back-end) | Python(파이썬) + FastAPI(패스트에이피아이) | 서버 로직 |
| AI 모델 | HuggingFace(허깅페이스) | 무료 AI API |
| 배포 | Render(렌더) | 무료 호스팅 |
정보
HuggingFace(허깅페이스) 란? AI 모델을 누구나 무료로 사용할 수 있도록 제공하는 플랫폼이다. 회원가입만 하면 API(에이피아이) 키를 무료로 받을 수 있다.
1단계. 개발 환경 준비
1.1. 프로젝트 폴더 만들기
- 폴더 구조
- Terminal(터미널)
1my-chatbot/2├── backend/ ← 파이썬 서버3│ ├── main.py4│ └── requirements.txt5└── frontend/ ← 리액트 화면6 ├── src/7 │ ├── App.jsx8 │ └── App.css9 └── package.jsonbackend/: 파이썬 코드가 들어가는 폴더이다.frontend/: 리액트 코드가 들어가는 폴더이다.- 두 폴더는 서로 다른 역할을 하며, 나중에 각각 배포한다.
1mkdir my-chatbot2cd my-chatbot3mkdir backend4mkdir frontendmkdir(메이크 디렉토리): 폴더를 만드는 명령어이다.cd(체인지 디렉토리): 폴더 안으로 이동하는 명령어이다.
1.2. HuggingFace(허깅페이스) API(에이피아이) 키 발급
요약
API(에이피아이) 키란? 내 신분증처럼, 허깅페이스 서버에 “나는 회원이에요”라고 증명하는 비밀 문자열이다.
- https://huggingface.co 에 접속하여 회원가입을 한다.
- 우측 상단 프로필 → Settings(설팅스) → Access Tokens(액세스 토큰스) 를 클릭한다.
- New token(뉴 토큰) 버튼을 클릭하고 이름을 입력한 뒤 Read 권한으로 생성한다.
- 생성된 키(예:
hf_xxxxxxxxxxxxxxxx)를 복사하여 안전한 곳에 저장한다.
주의
API(에이피아이) 키는 절대로 GitHub(깃허브)에 올리면 안 된다. 타인이 내 키를 무단으로 사용할 수 있다.
2단계. 파이썬 백엔드(back-end) 만들기
2.1. 가상환경 설정
- Windows(윈도우)
- Mac/Linux(맥/리눅스)
1cd backend2python -m venv .venv3.venv\Scripts\activatepython -m venv .venv: 가상환경 폴더를 만든다..venv\Scripts\activate: 가상환경을 실행(활성화)한다.- 활성화되면 터미널 앞에
(.venv)표시가 나타난다.
1cd backend2python3 -m venv .venv3source .venv/bin/activatesource .venv/bin/activate: Mac/Linux에서 가상환경을 활성화하는 명령어이다.
2.2. 라이브러리(library) 설치
1pip install fastapi uvicorn requests python-dotenv| 라이브러리 | 발음 | 역할 |
|---|---|---|
| fastapi | 패스트에이피아이 | 파이썬 웹 서버 프레임워크 |
| uvicorn | 유비콘 | 서버 실행기 |
| requests | 리퀘스츠 | 외부 API 호출 도구 |
| python-dotenv | 파이썬 닷엔브 | 환경변수 관리 |
2.3. 환경변수(environment variable) 파일 만들기
backend/ 폴더 안에 .env 파일을 만들고 아래 내용을 입력한다.
1HF_TOKEN=hf_여기에_내_키를_붙여넣기참고
.env (닷엔브) 파일이란? 비밀 정보를 코드 밖에 보관하는 파일이다. 코드에 직접 키를 쓰지 않아도 되므로 안전하다.
2.4. 메인 서버 파일 작성
backend/main.py 파일을 만들고 아래 코드를 작성한다.
1from fastapi import FastAPI # 012from fastapi.middleware.cors import CORSMiddleware # 023from pydantic import BaseModel # 034import requests, os # 045from dotenv import load_dotenv # 056
7load_dotenv() # 068app = FastAPI() # 079
10app.add_middleware( # 0811 CORSMiddleware, # 0912 allow_origins=["*"], # 1013 allow_methods=["*"], # 1114 allow_headers=["*"], # 1215) # 1316
17class Msg(BaseModel): # 1418 text: str # 1519
20HF_URL = "https://router.huggingface.co/v1/chat/completions" # 1621HF_MODEL = "Qwen/Qwen2.5-72B-Instruct" # 1722
23def ask_ai(q: str) -> str: # 1824 token = os.getenv("HF_TOKEN") # 1925 headers = {"Authorization": f"Bearer {token}"} # 2026 payload = { # 2127 "model": HF_MODEL, # 2228 "messages": [{"role": "user", "content": q}], # 2329 "max_tokens": 300 # 2430 } # 2531 res = requests.post(HF_URL, headers=headers, json=payload) # 2632 data = res.json() # 2733 return data["choices"][0]["message"]["content"] # 2834
35@app.post("/chat") # 2936def chat(msg: Msg): # 3037 reply = ask_ai(msg.text) # 3138 return {"reply": reply} # 32| 줄 | 설명 |
|---|---|
| 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~32 | /chat 주소로 POST 요청이 오면 ask_ai를 실행하고 결과를 반환한다. |
2.5. 서버 실행 테스트
1uvicorn main:app --reloaduvicorn(유비콘): 파이썬 서버 실행 도구이다.main:app:main.py파일의app객체를 실행한다는 뜻이다.--reload: 코드 수정 시 자동으로 서버를 재시작한다.
브라우저에서 http://localhost:8000/docs 에 접속하면 자동 생성된 API(에이피아이) 문서를 확인할 수 있다.
요약
/docs (닥스) 페이지에서 /chat 엔드포인트를 직접 테스트해볼 수 있다. Try it out(트라이 잇 아웃) 버튼을 클릭하여 {"text": "안녕?"} 을 입력해 보자.
2.6. requirements.txt 작성
배포 시 필요한 파일이다. backend/requirements.txt 를 만들고 아래 내용을 입력한다.
1fastapi2uvicorn3requests4python-dotenv3단계. 리액트 프론트엔드(front-end) 만들기
3.1. 리액트 프로젝트 생성
1cd ../frontend2npm create vite@latest . -- --template react3npm installcd ../frontend: 백엔드 폴더에서 나와 프론트엔드 폴더로 이동한다.npm create vite@latest .: 현재 폴더에 Vite(비트) 프로젝트를 생성한다.-- --template react: 리액트 템플릿(template)으로 생성한다.npm install: 필요한 패키지(package)들을 설치한다.
3.2. 채팅 화면 만들기
frontend/src/App.jsx 파일을 아래 코드로 교체한다.
1import { useState } from "react"; // 012import "./App.css"; // 023
4const API = "http://localhost:8000/chat"; // 035
6export default function App() { // 047 const [msgs, setMsgs] = useState([]); // 058 const [input, setInput] = useState(""); // 069 const [loading, setLoading] = useState(false); // 0710
11 const send = async () => { // 0812 if (!input.trim()) return; // 0913 const userMsg = { role: "user", text: input }; // 1014 setMsgs(prev => [...prev, userMsg]); // 1115 setInput(""); // 1216 setLoading(true); // 1317
18 const res = await fetch(API, { // 1419 method: "POST", // 1520 headers: { "Content-Type": "application/json" }, // 1621 body: JSON.stringify({ text: input }) // 1722 }); // 1823 const data = await res.json(); // 1924 const botMsg = { role: "bot", text: data.reply }; // 2025 setMsgs(prev => [...prev, botMsg]); // 2126 setLoading(false); // 2227 }; // 2328
29 const onKey = (e) => { // 2430 if (e.key === "Enter") send(); // 2531 }; // 2632
33 return ( // 2734 <div className="wrap"> // 2835 <h1>🤖 AI 챗봇</h1> // 2936 <div className="box"> // 3037 {msgs.map((m, i) => ( // 3138 <div key={i} className={m.role}> // 3239 <span>{m.role === "user" ? "🧑" : "🤖"}</span> // 3340 <p>{m.text}</p> // 3441 </div> // 3542 ))} // 3643 {loading && <p className="loading">생각 중...</p>} // 3744 </div> // 3845 <div className="input-row"> // 3946 <input // 4047 value={input} // 4148 onChange={e => setInput(e.target.value)} // 4249 onKeyDown={onKey} // 4350 placeholder="메시지를 입력하세요" // 4451 /> // 4552 <button onClick={send}>전송</button> // 4653 </div> // 4754 </div> // 4855 ); // 4956} // 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 | 입력창과 전송 버튼 영역이다. |
3.3. CSS(씨에스에스) 스타일 작성
frontend/src/App.css 파일을 아래 코드로 교체한다.
1.wrap { max-width: 600px; margin: 40px auto; font-family: sans-serif; }2.box { border: 1px solid #ccc; height: 400px; overflow-y: auto; padding: 16px; }3.user { text-align: right; margin: 8px 0; }4.bot { text-align: left; margin: 8px 0; }5.user p, .bot p { display: inline-block; padding: 8px 12px; border-radius: 8px; }6.user p { background: #d1e7ff; }7.bot p { background: #f0f0f0; }8.loading { color: #888; font-style: italic; }9.input-row { display: flex; gap: 8px; margin-top: 8px; }10.input-row input { flex: 1; padding: 8px; }11.input-row button { padding: 8px 16px; cursor: pointer; }- 최소한의 스타일만 적용하여 채팅 레이아웃을 구성한다.
.user와.bot으로 사용자와 봇 메시지를 좌우로 구분한다.
3.4. 프론트엔드 실행 테스트
1npm run dev브라우저에서 http://localhost:5173 에 접속하여 챗봇이 동작하는지 확인한다.
요약
파이썬 서버(uvicorn)와 리액트 개발 서버(npm run dev)를 동시에 실행해야 한다. 터미널 창을 두 개 열어서 각각 실행한다.
4단계. Render(렌더)에 배포하기
4.1. GitHub(깃허브)에 코드 올리기
참고
GitHub(깃허브)란? 코드를 온라인에 저장하고 공유하는 서비스이다. Render(렌더)는 깃허브 저장소를 연결하여 자동으로 배포한다.
- https://github.com 에서 새 저장소(repository, 리포지토리)를 만든다.
- 프로젝트 루트(
my-chatbot/)에.gitignore파일을 만들고 아래 내용을 입력한다.
1backend/.env2backend/.venv/3frontend/node_modules/- 터미널에서 아래 명령어를 실행하여 코드를 깃허브에 올린다.
1git init2git add .3git commit -m "first commit"4git remote add origin https://github.com/내아이디/my-chatbot.git5git push -u origin main4.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형태의 주소가 생성된다.
4.3. 리액트 프론트엔드 배포 전 수정
frontend/src/App.jsx 의 API 상수를 Render(렌더) 주소로 변경한다.
1const API = "https://my-chatbot-xxxx.onrender.com/chat";localhost:8000을 배포된 파이썬 서버 주소로 바꾼다.- 이 작업 후 깃허브에 다시
push(푸시)한다.
4.4. 리액트 프론트엔드 배포
- Render(렌더)에서 New(뉴) → Static Site(스태틱 사이트) 를 클릭한다.
- 같은 저장소를 연결하고 아래와 같이 설정한다.
| 항목 | 입력값 |
|---|---|
| Root Directory(루트 디렉토리) | frontend |
| Build Command(빌드 커맨드) | npm install && npm run build |
| Publish Directory(퍼블리시 디렉토리) | dist |
- Create Static Site(크리에이트 스태틱 사이트) 버튼을 클릭한다.
- 배포 완료 후 생성된 주소로 접속하면 챗봇을 사용할 수 있다.
요약
Render(렌더) 무료 플랜은 15분간 요청이 없으면 서버가 절전 모드로 전환된다. 처음 요청 시 30초~1분 정도 응답이 느릴 수 있다. 이는 정상 동작이다.
4.5. 배포 오류 해결
4.5.1. Conflicting peer dependency
- 배포중 아래와 같이 오류 발생시

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

5단계. 응용하기
5.1. 다른 AI 모델로 바꾸기
main.py의 HF_URL 을 변경하면 다른 AI 모델을 사용할 수 있다.
- 추천 모델 목록
- 변경 방법
| 모델명 | 특징 |
|---|---|
Qwen/Qwen2.5-72B-Instruct | ✅ 기본 추천, 한국어 응답 우수 |
meta-llama/Llama-3.1-8B-Instruct | 가볍고 빠른 응답 |
mistralai/Mistral-Nemo-Instruct-2407 | 균형 잡힌 성능 |
주의
2025년 기준 api-inference.huggingface.co 엔드포인트는 폐지되었다. 반드시 router.huggingface.co/v1 을 사용해야 한다.
1# HF_MODEL 변수의 모델명만 교체한다2HF_MODEL = "meta-llama/Llama-3.1-8B-Instruct"HF_MODEL변수의 값만 바꾸면 된다.- 모든 모델이 동일한 OpenAI(오픈에이아이) 호환 형식을 사용하므로 다른 코드는 수정하지 않아도 된다.
5.2. 캐릭터 챗봇으로 만들기
AI에게 역할을 부여하면 특정 캐릭터처럼 대화할 수 있다. ask_ai 함수의 messages 부분을 수정한다.
1def ask_ai(q: str) -> str:2 token = os.getenv("HF_TOKEN")3 headers = {"Authorization": f"Bearer {token}"}4
5 # 캐릭터 역할 설정 (system 메시지로 전달)6 system = "You are a helpful Korean cooking teacher. Answer only about Korean food."7
8 payload = {9 "model": HF_MODEL,10 "messages": [11 {"role": "system", "content": system}, # 역할 설정12 {"role": "user", "content": q} # 사용자 질문13 ],14 "max_tokens": 30015 }16 res = requests.post(HF_URL, headers=headers, json=payload)17 data = res.json()18 return data["choices"][0]["message"]["content"]system역할의 메시지를messages배열 맨 앞에 추가하면 AI가 해당 캐릭터처럼 행동한다.system내용을 바꾸면 요리 선생님, 영어 튜터, 게임 캐릭터 등 다양한 챗봇을 만들 수 있다.
참고
system 역할 메시지는 영어로 작성할 때 더 정확하게 동작한다. 한국어 지시도 가능하지만 영어를 권장한다.
전체 흐름 요약
1사용자 입력2 ↓3리액트 (fetch POST /chat)4 ↓5파이썬 FastAPI (/chat 엔드포인트)6 ↓7허깅페이스 API (AI 모델 호출)8 ↓9AI 응답 반환10 ↓11리액트 화면에 메시지 출력요약
막히는 부분이 있으면 각 단계의 console.log(리액트) 또는 print(파이썬)로 데이터 흐름을 확인하는 것이 디버깅(debugging, 디버깅)의 기본이다.