-
Python: 카카오 챗봇 서버 FastAPI로 만들기컴퓨터/파이썬 2021. 1. 17. 23:10728x90반응형
예제 결과물
결과 챗봇을 보고 싶으면 @참고
더 빠르게 Go언어로 만들고 싶으면 @참고
더욱 빠르게 Rust언어로 만들고 싶으면 @참고
코드만 보고 싶으면 아래 참고
1. 카카오 챗봇 만들기
준비물
- 카카오 i 챗봇 사용 허락 @링크
- AWS EC2나 구름 IDE 환경 준비하기 @AWS EC2 만들기 링크
- AWS RDS (MySQL), AWS S3
위 준비를 다했으면 학교 일정을 MySQL에 저장해서,
사용자가 "달력", "일정"... 이란 발화 문을 제시하면 MySQL에서 일정을 불러와서 보여줄 것이다.
2. 시나리오 만들기
발화 문 지정
엔티티를 만들어서, "일정", "달력"과 같은 부분을 만들어서 엔티티를 받아서 하거나
단순 내가 만든 발화 문에 있는 문구와 일치할 시 원하는 request를 줄 수 있다.
3. 스킬 서버 만들기
스킬 서버는 request를 받고 보낼 http 서버이다.
일정을 보여주기 위해 사용할 end-point는 /schedule이다.
스킬 서버를 만든 후 시나리오로 다시 가서 서버를 선택하고 스킬 데이터를 사용함을 체크한다.
스킬 데이터 사용을 봇 응답으로 설정하면, 직접 응답 메시지를 json으로 보내주어야 한다.
3-1. 메시지 스타일 종류
json으로 직접 return 할 경우 최대 출력 크기를 맞춰야 정상적으로 메시지가 보인다.
SimpleText
SimpleImage
간단 이미지만 보내기
BasicCard
Carousel에서도 쓰이는 기본 카드형 메시지이다.
CommerceCard
BasicCard에서 제품을 판매할 수 있는 스타일이다.
ListCard
헤더와 총 5개의 목록을 만들어서 보낼 수 있는 카드이다.
Carousel
BasicCard를 10개 가로로 보여줄 수 있는 메시지이다.
4. MySQL과 FastAPI
SQLAlchemy로 db를 사용하고, uvicorn으로 서버를 실행할 것이다.
(FastAPI 용 SQLAchemy를 사용해도 된다.) @링크
pip install fastapi uvicorn SQLAlchemy
폴더를 하나 만들어서 db 코드를 모아준다. @예제 Github 링크
db_model (db 코드 폴더) └---- __init__.py (모듈화) └---- crud.py (db CRUD 커맨드 모음) └---- database.py (db 초기화) └---- models.py (db 테이블 python으로 표시)
models.py
MySQL ajou_sched 테이블
위 콜럼들을 그대로 파이썬으로 옮겨준다.
from sqlalchemy import Column, Integer, String from .database import Base class Schedules(Base): __tablename__ = "ajou_sched" id = Column(Integer, primary_key=True) content = Column(String(50)) start_date = Column(String(12)) end_date = Column(String(12))
crud.py
db.query(models.Schedules).all() == "SELECT * FROM ajou_sched"과 같음
from sqlalchemy.orm import Session from . import models def get_all_sched(db: Session): scheds = db.query(models.Schedules).all() return scheds
database.py
서버를 직접 노출시키기 보단, 환경 변수에 담아서 불러온다.
KAKAO_DB = AWS RDS 서버 링크
SQLAlchemy를 사용할 때, MySQL 기본 타임아웃이 8시간이라
장시간 사용하지 않다가 다시 사용하면 MySQL timeout error가 발생할 수 있다.
그래서 항상 다른 pool로 지정(NullPool)하고, pool_recycle 기본 값은 -1이기 때문에 적당히 바꿔준다.
(MySQL timeout > pool_recycle)
import os from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from sqlalchemy.pool import NullPool SQLALCHEMY_DATABASE_URL = os.environ["KAKAO_DB"] engine = create_engine(SQLALCHEMY_DATABASE_URL, pool_recycle=3600, poolclass=NullPool) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base()
FastAPI server.py
from random import choice from typing import Dict import db_model.crud import db_model.database import db_model.models import db_model.schemas import uvicorn from fastapi import Depends, FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from sqlalchemy.orm import Session # db 생성 db_model.models.Base.metadata.create_all(bind=db_model.database.engine) application = FastAPI( title="Ajou notices server", description="for Kakao Chatbot", version="1.0.0" ) application.add_middleware( # 미들웨어 CORS CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], allow_credentials=True, ) # SQL START # Dependency def get_db(): db = db_model.database.SessionLocal() try: yield db finally: db.close() def getSchedule(db: Session): return db_model.crud.get_all_sched(db=db) # SQL END # Carousel 카드를 만든다. def makeCarouselCard(title, desc): card_imgs = ["ajou_carousel", "ajou_carousel_1", "ajou_carousel_2"] # 랜덤 이미지 card = { "title": title, "description": desc, "thumbnail": { "imageUrl": f"https://raw.githubusercontent.com/Alfex4936/kakaoChatbot-Ajou/main/imgs/{choice(card_imgs)}.png" }, # "buttons": [ optional # { # "action": "message", # "label": "열어보기", # "messageText": "짜잔! 우리가 찾던 보물입니다" # }, # ] } return card @application.get("/") def hello(): return "Welcome, the server is running well." # http링크/schedule로 스킬 서버를 연결 @application.post("/schedule") def schedule(content: Dict, db: Session = Depends(get_db)): # 자동으로 db 연결 """MySQL DB 학사일정 불러오기 | 메시지 type: Carousel BasicCards """ user_id = content["userRequest"]["user"]["id"] # user Id print(">>> /schedule") cards = [] append = cards.append scheds = getSchedule(db=db) for sched in scheds: # 각 Row가 담겨있다. iterable append( makeCarouselCard(sched.content, f"{sched.start_date} ~ {sched.end_date}") ) # 모든 스케쥴을 Carousel BasicCard로 바꿔준다. content = { "version": "2.0", "template": { "outputs": [{"carousel": {"type": "basicCard", "items": cards[:10]}}], }, # 최대 10개이기 때문에 [:10]로 에러 방지 } return JSONResponse(content=content) if __name__ == "__main__": uvicorn.run(application, host="0.0.0.0", port=8000, log_level="info")
pandatic BaseModel을 사용한다면 아래처럼 만들면 될 것이다.
class KakaoUser(BaseModel): id: str properties: Dict type: str class KakaoUserRequest(BaseModel): block: Dict lang: Optional[str] params: Dict timezone: str user: KakaoUser utterance: str class KakaoAction(BaseModel): clientExtra: Optional[Dict] detailParams: Dict id: str name: str params: Dict class KakaoAPI(BaseModel): """Main Kakao JSON""" action: KakaoAction bot: Dict contexts: Optional[List] intent: Dict userRequest: KakaoUserRequest @application.post("/route", response_model=KakaoAPI) async def message(content: KakaoAPI): ...
또는 kakao-json (자가제작) 라이브러리 이용
pip install kakao-json
from dataclasses import dataclass from typing import Dict, Union import uvicorn from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from kakao_json import Button, Kakao, ListItem app = FastAPI(title="FastAPI kakao-py example", version="1.0.0") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["POST"], allow_headers=["*"], allow_credentials=True, ) @app.get("/") def read_root(): return {"Hello": "World"} @app.post("/list_card") def read_item(req: Dict): @dataclass class Item: name: str number: int # Make your python objects items = [Item(f"I {i}", i + 2) for i in range(5)] append = items.append # kakao-json part k = Kakao() k.add_qr("오늘", "카톡 발화문1") k.add_qr("어제") # label becomes also messageText list_card = k.init_list_card().set_header("리스트 카드 제목") list_card.add_button(Button("그냥 텍스트 버튼", "message")) list_card.add_button(k.init_button("link label").set_link("https://google.com")) list_card.add_button( k.init_button("share label").set_action_share().set_msg("카톡에 보이는 메시지") ) list_card.add_button(k.init_button("call label").set_number("010-1234-5678")) list_card.add_item( ListItem("title").set_desc("description").set_link("https://naver.com") ) carousel = k.init_carousel() for i in range(5): basic_card = k.init_basic_card() basic_card.set_title(f"Hey {i}").set_image("https://kakao") carousel.add_card(basic_card) k.add_output(list_card) k.add_output(carousel) # import json # print(json.loads(k.to_json())) return k.to_json() if __name__ == "__main__": uvicorn.run(app)
AWS EC2 인스턴스
배포를 하고 이제 서버를 실행시킨다.
graphic interface가 없기 때문에, s3에 저장해서 불러와서 사용 중이다.
ubuntu:~$ virtualenv server ubuntu:~$ source ~/server/bin/activate (server) ubuntu:~$ pip install fastapi uvicorn SQLAlchemy (server) ubuntu:~$ aws s3 cp s3://bucket/kakao.py . (server) ubuntu:~$ aws s3 sync s3://bucket/db_model . (server) ubuntu:~$ python kakao.py INFO: Started server process [id] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
풀 소스 @Github
라이브러리
728x90'컴퓨터 > 파이썬' 카테고리의 다른 글
Python: Apache Spark 공부 예제 (pyspark) (0) 2021.02.02 Python selectolax: Modest 엔진 HTML parser (0) 2021.01.16 Python 3.10 미리보기 (switch case) (0) 2021.01.11