배포를 완료했는데 계속해서 중간에 코드가 실행이 안되는 문제가 생겼다.

.

이를 해결하기 위해 nginx 데몬을 내 파이썬 코드에 연동하여 실행하였다.

 

데몬(Daemon)

데몬은 사용자가 직접 실행하거나 조작하지 않아도 백그라운드에서 계속 실행되어 필요한 작업을 수행하는 프로그램

 

데몬의 예

  • 웹 서버 데몬:
    • nginx 또는 Apache와 같은 웹 서버는 데몬으로 실행되어 웹 요청을 처리.
  • 데이터베이스 데몬:
    • MySQL 또는 PostgreSQL과 같은 데이터베이스 서버는 데몬으로 실행되어 데이터베이스 요청을 처리.
  • cron 데몬:
    • cron은 일정한 시간 간격으로 특정 작업을 수행하는 데몬. 예를 들어, 매일 백업을 수행하거나 정기적으로 시스템 업데이트를 확인.

 

Python 스크립트를 데몬으로 실행 과정

nginx에 관한 블로그 글에서 이어 데몬을 활용해서 ec2 창을 닫아도 파이썬 코드가 실행되게 해보자!

 

systemd 서비스 파일 작성

Python 스크립트를 데몬으로 실행하기 위해 systemd 서비스 파일을 작성한다.

sudo nano /etc/systemd/system/openport.service

[Unit]
Description=Open Port Python Script
After=network.target

[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu
ExecStart=/usr/bin/python3 /home/ubuntu/open_port.py

[Install]
WantedBy=multi-user.target

 

 

서비스 파일을 저장한 후, systemd를 다시 로드한다

sudo systemctl daemon-reload
sudo systemctl enable openport.service
sudo systemctl start openport.service


##코드 실행 예시##

#서비스 시작:
sudo systemctl start 서비스이름

#서비스 중지:
sudo systemctl stop 서비스이름

#서비스 상태 확인:
sudo systemctl status 서비스이름

 

nginx 다시 시작

nginx 설정을 다시 로드합니다:

sudo systemctl restart nginx

 

systemctl 명령어를 사용하여 서비스 상태 확인

#nginx 상태 확인
sudo systemctl status nginx

#내 파이썬 코드가 실행 중인지 확인
sudo systemctl status openport.service

 

(출처:chat gpt)

1. 서브도메인 api.

 

도메인은 웹사이트를 식별해 주는 URL섹션

서브 도메인이란?
하위 도메인으로 웹사이트의 섹션을 구분하기 위해 도메인 이름에 추가되는 프리픽스

자료출처:https://www.google.com/url?sa=i&url=https%3A%2F%2Fko.wix.com%2Fblog%2Fpost%2Fwhat-is-a-subdomain&psig=AOvVaw0tu5qrRH6NI0AJYS15aKQ5&ust=1718554800953000&source=images&cd=vfe&opi=89978449&ved=0CBEQjRxqFwoTCKCo6dWB3oYDFQAAAAAdAAAAABAJ

 

 

2. api.도메인을 aws ip에 연결

 

아래 블로그 참고함

aws ec2 서버와 카페24 도메인 연결 - 진화하는공간 (tistory.com)

 

aws ec2 서버와 카페24 도메인 연결

aws 로그인 후 EC2 메뉴 클릭 연결할 서버 인스턴스 체크 -> 세부정보에 퍼블릭 IPv4 DNS를 복사합니다. 카페24 호스팅 센터 로그인 후 도메인 포워딩 관리 클릭 연결할 도메인 선택 후 변경 버튼 클릭

marshmello.tistory.com

 

도메인 포워딩: cafe24에서 구매한 도메인이 다른 웹사이트(aws인스턴스 퍼블릭 IPv4 DNS)로 이동 시켜주는 기능

네임서버 관리: 호스트ip 수정으로 aws public ip를 수정하고 서브 도메인으로 (api.내 도메인)으로 추가

 

#로컬호스트 말고 이제부터 사용할 내 서브 도메인을 활용해서 통신이 가능하게
#요청하는 프론트엔드 서버 코드 수정

const response = await axios.post('https://api.내 도메인/sendMessage', {
                user_input: inputValue
            });
            
            
            
 #https s!!주의 이거 빼먹어서 개고생함

 

3. aws에 Letsencypt사용해서 ssl설치하기

 

아래 블로그 참고

2-6 AWS에 Let's Encrypt로 HTTPS 적용하기 (tistory.com)

 

2-6 AWS에 Let's Encrypt로 HTTPS 적용하기

현재 AWS의 서버 환경은 다음 같습니다. 운영체제 : Ubuntu 18.04 (LTS) 웹서버 : Nginx BL : PHP DBMS : MySQL 웹 서버에 기본적인 부분을 구축했으니 이제 보안 부분을 신경 써보겠습니다. HTTP(Hypertext Transfer Pr

luminitworld.tistory.com

 

저번에 nginx 웹서버를 구축했으니 외부로부터 접속할 수 있도록 포트 포워딩을 해준다.

 

Let's Encrypt
Let's Encrypt는 무료의 TLS/SSL 인증서를 쉽게 가져오고 설치할 수 있는 방법을 제공하는 CA(인증 기관)으로, 웹 서버에서 암호화된 HTTPS를 사용할 수 있다.
이러한 방법은 사용자에게 Certbot라는 소프트웨어를 제공함으로써 구현할 수 있게 한다.

(1) Certbot 설치

$ sudo apt update
$ sudo apt upgrade
$ sudo apt-get install python3-certbot-nginx

 

(2) Nginx 설정 파일 수동 업데이트
Nginx 기본 설정 파일에 들어가서 인증서를 적용할 도메인 이름을 설정합니다.

sudo nano /etc/nginx/sites-available/default

server {
    listen 80;
    server_name api.내 도메인;

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 원래 클라이언트 IP 주소를 전달합니다.
        proxy_set_header X-Original-IP $remote_addr;
    }

    if ($scheme != "https") {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name api.내 도메인;

    ssl_certificate /etc/letsencrypt/live/api.westcold0035.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.westcold0035.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 원래 클라이언트 IP 주소를 전달합니다.
        proxy_set_header X-Original-IP $remote_addr;
    }
}

 

Chat GPT의 도움을 받아 전체 과정 요약

  1. 80번 포트에서 HTTP 요청 수신:
    • server_name api.내 도메인;에 도메인 이름을 설정한다.
    • location / 블록은 모든 요청을 내부 서버(로컬호스트)로 프록시한다.
    • proxy_set_header 지시어를 사용해 클라이언트의 원본 요청 정보를 보존한다.
    • if ($scheme != "https") { return 301 https://$host$request_uri; } 구문을 통해 HTTP 요청을 HTTPS로 리다이렉트한다.
  2. 443번 포트에서 HTTPS 요청 수신:
    • listen 443 ssl;을 통해 SSL을 사용하는 443 포트에서 요청을 수신한다.
    • ssl_certificate와 ssl_certificate_key 지시어를 사용해 Let's Encrypt SSL 인증서를 적용한다.
    • location / 블록은 HTTP와 동일하게 모든 요청을 내부 서버(로컬호스트)로 프록시한다.
    • proxy_set_header 지시어를 사용해 클라이언트의 원본 요청 정보를 보존한다.

상세 설명

HTTP 요청 처리 (80번 포트)

  • HTTP 요청 수신: 서버는 80번 포트에서 HTTP 요청을 수신한다.
  • 프록시 설정: 들어오는 모든 요청은 http://127.0.0.1:5000으로 프록시된다. 이는 로컬에서 동작하는 백엔드 애플리케이션을 의미한다.
  • 헤더 설정: proxy_set_header 지시어를 통해 원래의 호스트, 클라이언트 IP 주소, 전달된 포워드 정보를 백엔드로 전달한다.
  • HTTP -> HTTPS 리다이렉션: 요청이 HTTP인 경우, Nginx는 해당 요청을 HTTPS로 리다이렉트한다. 이는 모든 트래픽을 암호화된 HTTPS로 강제하여 보안을 강화한다.

HTTPS 요청 처리 (443번 포트)

  • HTTPS 요청 수신: 서버는 443번 포트에서 SSL을 사용하는 HTTPS 요청을 수신한다.
  • SSL 인증서 설정: Let's Encrypt에서 발급받은 SSL 인증서와 개인 키를 사용하여 연결을 암호화한다.
  • 프록시 설정: 들어오는 모든 HTTPS 요청은 http://127.0.0.1:5000으로 프록시된다.
  • 헤더 설정: HTTP와 동일하게 원래의 호스트, 클라이언트 IP 주소, 전달된 포워드 정보를 백엔드로 전달한다.

 

설정 파일을 적용하기 위해 Nginx를 다시 로드해줍니다.

sudo systemctl reload nginx

 

(3) SSL 인증서 받기
Certbot은 다양한 플러그인을 통해 SSL 인증서를 획득하는 다양한 방법을 제공
Nginx플러그인은 필요할 때마다 Nginx를 재구성하고 구성을 다시 로드
이제 원하는 도메인을 지정해서 Nginx플러그인을 통해 인증서를 획득
이후 알림설정이 나오는데 다 ok해줬다

 

$ sudo certbot --nginx
Please enter the domain name(s) you would like on your certificate (comma and/or space separated) (Enter 'c' to cancel): api.your-domain.com

 

(4)파이썬 실행

python3 "open_port.py"

 

https://api.도메인이 제대로 돌아가는지 결과 화면

 

참고한 블로그

서브 도메인 정의와 유용하게 사용하는 방법 (wix.com) 

 

서브 도메인 정의와 유용하게 사용하는 방법

서브 도메인(subdomain)은 하위 도메인으로 웹사이트의 섹션을 구분하기 위해 도메인 이름에 추가되는 프리픽스(Prefix)를 말합니다. 서브 도메인 사례와 만드는 법을 알려드립니다.

ko.wix.com

 

-프론트엔드 코드 Vercel에 배포하기

내가 원하는 도메인을 무료 버전으로 하려면 .vercel.app을 넣어줘야 한다

WEST_COLD (westcold.vercel.app)

 

WEST_COLD

west__cold Raclette knausgaard hella meggs normcore williamsburg enamel pin sartorial venmo tbh hot chicken gentrify portland. 안녕하세요, 저는 서찬이라고 합니다. 나날이 성장하고 발전하는 것을 즐기는 사람입니다. 언제

westcold.vercel.app

 

-도메인 구매하기

아래 영상 참고
카페24 쇼핑몰 도메인 구매부터 적용까지 (도메인 가격비교, 네임서버 변경 방법) (youtube.com)

 


-Nginx 가 무엇인지 검색하고  ec2 서비스에 설치하기 

출처:https://hue9010.github.io/images/Nginx/Nginx_%EA%B5%AC%EC%A1%B0.png


nginx는 내 컴퓨터가 서버가 되게 해주는 소프트웨어 
-클라이언트와 WAS사이에서 서빙 및 매니징을 담당하는 역할을 수행
-정적인 요소를 was에 넘어가기 전 미리 서빙해줌 

was란
서버의 동적인 요소를 전문적으로 처리해주는 서버.

Nginx를 사용하는 이유:
1. 리버스 프록시(보안 강화): Nginx는 보안을 강화하는 역할을 한다. 클라이언트와 직접 통신하는 대신 Nginx를 통해 요청을 받아들이면 서버의 IP 주소를 숨길 수 있어 보안을 강화할 수 있다(  한마디로 말하면 클라이언트는 가짜 서버에 요청(request)하면, 프록시 서버가 배후 서버(reverse server)로부터 데이터를 가져오는 역할을 한다 )

2. 로드 밸런싱(부하 분산): Nginx는 요청을 여러 백엔드 서버로 분산하여 부하를 분산할 수 있다. 이를 통해 서버의 안정성과 성능을 향상시킬 수 있다. 

3. 캐싱(정적 파일 서빙): Nginx는 정적 파일(HTML, CSS, JavaScript 등)을 효과적으로 서빙하는 데 사용된다. 이렇게 하면 백엔드 서버가 동적 콘텐츠에만 집중할 수 있고, 정적 파일 서빙은 Nginx가 처리하게 된다.(vercel로 프론트엔드 파일을 처리햇으니 3번은 굳이 지금은 필요없긴 하다) 
 
4.SSL지원: 클라이언트와 HTTPS로 통신하고, 백엔드 서버와 HTTP로 통신하여 보안을 강화하는 역활을 한다. 
 

*설치하기

ec2인스턴스 연결로 들어감 

nginx를 업데이트하고 설치함
sudo apt update
sudo apt install nginx -y 

nginx를 시작하고 부팅 시 자동으로 시작되도록 설정
sudo systemctl start nginx
sudo systemctl enable nginx 

nginx 상태를 확인
sudo systemctl status nginx

  

public ip로 접속해보기 

->aws ip주소 입력을 웹브라우저에 했을때 nginx 웰컴페이지가 떠야한다

 


-ec2에 파이썬 코드 업로드

git clone https://github.com/your-username/your-repo.git (나중에 업데이트는 git pull origin main) 

(github에 올라온 파이썬 코드 pull로 받아오기)

nano .env 

(.env파일 업로드)

sudo apt update
sudo apt install python3-pip python3-venv -y (파이썬 설치)

python3 -m venv venv(가상 환경 설정)
source venv/bin/activate(가상 환경 활성화)
pip install -r requirements.txt (ex openai같은 모듈 설치)

python your_script.py (코드 실행)



#나중에 가져올때
git init
git commit -m""
git pull origin main

파이썬 코드가 실행 되었다


-nginx에서 포트포워딩 설정(aws ip주소를 입력하면 80번 포트가 5000번 포트로 가게끔 처리) 

 

sudo nano /etc/nginx/sites-available/default


아래 처럼 수정해줌

server {
        listen 80 ;
        listen [::]:80 ;
        server_name _; 

        location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        }
Nginx 서비스를 재시작하여 변경 사항을 적용.
sudo systemctl restart nginx





openai api를 활용하면 토큰이 소모가 되는데 이게 사용량이 많으면 돈이 많이 깨진다
잘못하면 누가 마음 먹고 쓸모없는 채팅으로 내 토큰을 소모되게 할 수 있다
이를 해결하기 위해 여러 방법을 사용하여 보안을 구축해보았다.

1.검색어에 나의 대한 질문만 할 수 있게 설정

# 허용되는 키워드 목록
allowed_keywords = ["서찬", "찬", "너", "운영자"]

if any(keyword in user_input for keyword in allowed_keywords):
#생략
else:
    return jsonify({"description": "입력값에 '서찬', '찬', '너', '운영자' 중 하나라도 포함되어야만 실행됩니다."})

 


2.각 ip마다 질문의 갯수를 제한하여 관리(+하루가 지나면 다시 초기화)

# 제한할 요청 수
REQUEST_LIMIT = 1 # 테스트 목적으로 1로 설정 (원래는 10개)

# IP 주소별 요청 횟수를 저장하는 사전
request_counts = defaultdict(lambda: {'count': 0, 'last_request': None})

def reset_request_count(ip):
    request_counts[ip]['count'] = 0
    request_counts[ip]['last_request'] = datetime.now()

def is_new_day(last_request):
    return datetime.now() - last_request > timedelta(seconds=10) # 테스트 목적으로 10초로 설정 (원래는 days=1)



@app.route('/sendMessage', methods=['POST'])
def send_message():
    data = request.get_json()
    user_input = data.get('user_input')
    #사용자 ip 가져오기
    ip = request.remote_addr

    # 요청 횟수 가져오기
    request_data = request_counts[ip]
    count = request_data['count']
    last_request = request_data['last_request']

    # 하루가 지났는지 확인하여 요청 횟수 초기화
    if last_request is None or is_new_day(last_request):
        reset_request_count(ip)

    if count >= REQUEST_LIMIT:
        return jsonify({"description": "질문이 초과했습니다ㅠㅠ"})
    else:
        request_counts[ip]['count'] += 1
        request_counts[ip]['last_request'] = datetime.now()

-중간에 질문이 데이터 베이스에 저장하는데 쓰레드에 여러 질문이 들어와 저장이 안되고 또한 질문의 답 출력하는데 오류가 나길래 쓰레드를 각ip마다 주었다

# IP 주소별 스레드 ID를 저장하는 사전
thread_ids = {}

# 새로운 스레드 생성 함수
def create_new_thread():
    response = client.beta.threads.create()
    # response의 구조를 출력해보자
    print(response)
    # response 객체의 속성에 맞게 접근
    return response.id
    
# sendMessage 라우터
@app.route('/sendMessage', methods=['POST'])
def send_message():
    data = request.get_json()
    user_input = data.get('user_input')
    ip = request.remote_addr

    # 요청 횟수 가져오기
    request_data = request_counts[ip]
    
# IP 주소별 스레드 ID 가져오기
        if ip not in thread_ids:
            thread_id = create_new_thread()
            thread_ids[ip] = thread_id
        else:
            thread_id = thread_ids[ip]

        # AI 응답 가져오기
        ai_response = get_ai_response(user_input, thread_id)

 

질문이 초과되면 초과되었다는 출력값을 주었다

 

3. 잘못된 질문을 계속하면 차단하기

인스트럭션에 "서찬에 대한 질문이 아니면 "false" 라고만 대답해줘"라는 명령을 주어 false가 3번 이상 나오면 더 이상 opanai api가 실행되지 않게 설정해준다

 

request_counts = defaultdict(lambda: {'count': 0, 'last_request': None, 'false_count': 0})

def reset_request_count(ip):
    request_counts[ip]['count'] = 0
    request_counts[ip]['last_request'] = datetime.now()
    request_counts[ip]['false_count'] = 0
   
@app.route('/sendMessage', methods=['POST'])
def send_message():
    data = request.get_json()
    user_input = data.get('user_input')
    ip = request.remote_addr

    # 요청 횟수 가져오기
    request_data = request_counts[ip]
    count = request_data['count']
    last_request = request_data['last_request']
    false_count = request_data['false_count']
    
    # false_count가 3 이상이면 차단
    if false_count >= 3:
        return jsonify({"description": "너무 많은 잘못된 응답으로 인해 차단되었습니다."})
        
    else:
         # AI 응답 가져오기
        ai_response = get_ai_response(user_input, thread_id)

        # 'False' 응답 횟수 증가
        if ai_response.strip().lower() == 'false':
            request_counts[ip]['false_count'] += 1

 

4.데이터베이스에 사용자가 어떤 질문을 하는지와 어떤 사용자가 어떤 질문을 하는지 확인하게 사용자 ip를 저장하기

from openai import OpenAI
import time
from flask import Flask, request, jsonify
from flask_cors import CORS
import pymysql
from collections import defaultdict
from datetime import datetime, timedelta

# MySQL 데이터베이스 연결 설정 함수
def get_database_connection():
    return pymysql.connect(host="localhost", user="root", password="", db="", charset="")
    
    # 데이터베이스에 저장
            try:
                db_connection = get_database_connection()
                cursor = db_connection.cursor()
                query = "INSERT INTO portpolio.portpolio_input (ip, comments, answer) VALUES (%s, %s, %s)"
                cursor.execute(query, (str(ip), user_input, ai_response))
                db_connection.commit()
                return jsonify({"description": ai_response})

            except Exception as e:
                return jsonify({"description": "데이터 저장 중 오류가 발생하였습니다: " + str(e)})

            finally:
                cursor.close()
                db_connection.close()

-전체 코드

(검색어를 미리 설정 하는 건 비효율적이라는 피드백을 받아 수정->새로운 로직)

from openai import OpenAI
import time
from flask import Flask, request, jsonify
from flask_cors import CORS
import pymysql
from collections import defaultdict
from datetime import datetime, timedelta

app = Flask(__name__)
CORS(app)  # 모든 출처에서의 요청을 허용합니다.

# OpenAI API 키 설정
client = OpenAI(api_key="")

# 제한할 요청 수
REQUEST_LIMIT = 5  # 테스트 목적으로 1로 설정 (원래는 10개)

# IP 주소별 요청 횟수를 저장하는 사전
request_counts = defaultdict(lambda: {'count': 0, 'last_request': None, 'false_count': 0})

# IP 주소별 스레드 ID를 저장하는 사전
thread_ids = {}

def reset_request_count(ip):
    request_counts[ip]['count'] = 0
    request_counts[ip]['last_request'] = datetime.now()
    request_counts[ip]['false_count'] = 0

def is_new_day(last_request):
    return datetime.now() - last_request > timedelta(seconds=50)  # 테스트 목적으로 5초로 설정 (원래는 days=1)

# MySQL 데이터베이스 연결 설정 함수
def get_database_connection():
    return pymysql.connect(host="", user="t", password="", db="", charset="")

# 새로운 스레드 생성 함수
def create_new_thread():
    response = client.beta.threads.create()
    # response의 구조를 출력해보자
    print(response)
    # response 객체의 속성에 맞게 접근
    return response.id

# AI 응답을 가져오는 함수
def get_ai_response(user_input, thread_id):
    try:
        assistant_id = ""

        message = client.beta.threads.messages.create(
            thread_id,
            role="user",
            content=user_input,
        )

        run = client.beta.threads.runs.create(
            thread_id=thread_id,
            assistant_id=assistant_id,
        )

        run_id = run.id

        while True:
            run = client.beta.threads.runs.retrieve(
                thread_id=thread_id,
                run_id=run_id,
            )
            if run.status == "completed":
                break
            else:
                time.sleep(2)

        thread_messages = client.beta.threads.messages.list(thread_id)
        ai_response = thread_messages.data[0].content[0].text.value

        return ai_response

    except Exception as e:
        return str(e)

# sendMessage 라우터
@app.route('/sendMessage', methods=['POST'])
def send_message():
    data = request.get_json()
    user_input = data.get('user_input')
    ip = request.remote_addr

    # 요청 횟수 가져오기
    request_data = request_counts[ip]
    count = request_data['count']
    last_request = request_data['last_request']
    false_count = request_data['false_count']

    # 하루가 지났는지 확인하여 요청 횟수 초기화
    if last_request is None or is_new_day(last_request):
        reset_request_count(ip)

    # false_count가 3 이상이면 차단
    if false_count >= 3:
        return jsonify({"description": "너무 많은 잘못된 응답으로 인해 차단되었습니다."})


    # 요청 횟수 확인
    if count >= REQUEST_LIMIT:
        return jsonify({"description": "질문이 초과했습니다ㅠㅠ"})


    # 요청 횟수 증가
    request_counts[ip]['count'] += 1
    request_counts[ip]['last_request'] = datetime.now()

    # IP 주소별 스레드 ID 가져오기
    if ip not in thread_ids:
        thread_id = create_new_thread()
        thread_ids[ip] = thread_id
    else:
        thread_id = thread_ids[ip]

    # AI 응답 가져오기
    ai_response = get_ai_response(user_input, thread_id)

    # 'False' 응답 횟수 증가
    if ai_response.strip().lower() == 'false':
        request_counts[ip]['false_count'] += 1

    # 데이터베이스에 저장
    try:
        db_connection = get_database_connection()
        cursor = db_connection.cursor()
        query = "INSERT INTO portpolio.portpolio_input (ip, comments, answer) VALUES (%s, %s, %s)"
        cursor.execute(query, (str(ip), user_input, ai_response))
        db_connection.commit()
        return jsonify({"description": ai_response})

    except Exception as e:
        return jsonify({"description": "데이터 저장 중 오류가 발생하였습니다: " + str(e)})

    finally:
        cursor.close()
        db_connection.close()

if __name__ == '__main__':
    print("Flask 애플리케이션이 실행되었습니다.")
    app.run(debug=True)

로직의 순서

하루마다 검색어 초기화->false 3번 했는지->ip마다 요청횟수->ip마다 쓰레드 만들기->ai에게 질문

 


이번 과제를 하며 느낀점
-사실 코딩 할 때 chatgpt한테 물어보는 경우가 많은데 아무리 편하다해도 조금 복잡한 로직은 오류가 많이 나서 시간이 오래 걸렸다. 그러나 한번 천천히 코드 읽어가면서 로직?을 이해하며 수정하니 30분이면 끝나는 경우도 많았다. 이를 통해 chatgpt에게 물어보기 전 먼저 내가 그 로직을 구상 + 코드가 나오면 로직을 읽는 버릇이 필요하다 느낌

과제
Open AI 어시스턴스 만들기
어시스턴트와 관련 된 기본용어 찾아서 정리해보기, 어시스턴트id,스레드id
어시스턴트에 간단하게 이력서샘플 만들어서 업로드
어시스턴트 api로 바꾸기


Assistants API 구조

출처:https://cdn.openai.com/API/docs/images/diagram-assistant.webp


기본요소


*assistant=목적에 맞는 로봇
 

from openai import OpenAI
import time


# OpenAI API 키 설정
client = OpenAI(api_key="")

#assistant 생성
assistant = client.beta.assistants.create(
     name="Math Tutor",
     instructions="You are a personal math tutor. Answer questions briefly, in a sentence or less.",
     model="gpt-3.5-turbo",
 )
# # 생성된 챗봇의 정보를 JSON 형태로 출력합니다.
print(assistant)

assistant_id=""


*thread=카톡 방,하나의 thread 안에 여러개의 메세지 존재

# # # 새로운 스레드를 생성합니다.
thread = client.beta.threads.create()

print(thread)

#resume thread
thread_id = ""

 
 

*message=thread 안에 있는 대화들
 
종류
-system:어시스턴트가 참고할 만한 전역조건
-user: 사용자의 질문
-assistant: ai의 답변
 
thread는 여러개의 메세지를 가지고 있을 수 있다
 
message{
role:user//user 메세지
content는 물어보고 싶은 거 담으면 된다
}

message = client.beta.threads.messages.create(
    thread_id,
    role="user",
    content="서찬의 전화번호 알고 싶습니다",
)

print(message)

 

*run:어시스턴트가 정해진 대화를 통해 객채를 만듬, 실행시점을 관리

어시스턴트와 쓰레드는 독립(수학 과학 관련된 로봇과 관련없는 수학과 과학 관련된 카톡방)
일관성을 위해 하나의 주제에 맞는 어시스턴트에 하나의 주제에 맞는 쓰레드가 좋다

run을 사용할 때는 쓰레드와 어시스턴트가 독립적이기에 각각 아이디를 받아온다

status
queued 대기중-비동기처리로 단계관리하기 위해->원하는 시점에 데이터를 받아 올 수 있다
completed 질문 답변 완료

list로 답변 확인
 

# 실행할 Run 을 생성합니다.
# Thread ID 와 Assistant ID 를 지정합니다.
run = client.beta.threads.runs.create(
  thread_id="",
  assistant_id="",
)

print(run)

run_id = run.id

while True:
  run = client.beta.threads.runs.retrieve(
    thread_id=thread_id,
    run_id= run_id,
  )
  if run.status == "completed":
    break
  else:
    time.sleep(2)
  # print(run)


thread_messages = client.beta.threads.messages.list(thread_id)
print(thread_messages.data[0].content[0].text.value)





Assistants API

->chatgpt의 텍스트 형식의 도구의 제약을 해소하기 위해 탄생

code interpreter
-디버깅과 다른 느낌
-토큰 제한수를 넘는 데이터를 위해 쿼리문을 작성해서 토큰을 효율적이게 사용
-쉽게 말해 엄청난 양의 데이터 분석 가능

retrieval
-검색
-우리의 자료로 결과를 낼 수 있다
-문서를 gpt에게 학습을 시켜 그 문서를 토대로 답변을 해준다


function
-내가 만든 함수를 기반으로 어시스턴트가 사용할 수 있게 만들어준다(예를들어 검색으로 학습한 문서를 기반으로 함수를 만들 수 있다)

 

Retreieval을 활용하여 이력서 학습을 시켜 나만의 챗봇 만들기

1. openai api 사이트에서 내가 만든 어시스턴트에 내 이력서 파일을 넣어준다(+추가로 instructions도 수정 가능)
 

어시스턴트에 간단하게 이력서샘플 만들어서 업로드

 
2. 프로트엔드 서버에서 입력값 받아와 출력하기
 
아래 블로그에서 참고
Front & Back 서버 연결(axios 실습) :: West_Cold (tistory.com)

 

Front & Back 서버 연결(axios 실습)

기술과제 • Python Falsk 가 무엇인지 • Axios가 무엇인지 (Fetch함수는 무엇인지) 코딩과제 • 본인 포트폴리오 코드 Github에 올리기 • Varcel에 본인 코드 배포하기 • 본인이 만든 파이썬코드 내용

eatitstory.tistory.com

 

from openai import OpenAI
import time
from flask import Flask, request, jsonify
from flask_cors import CORS

app = Flask(__name__)
CORS(app)  # 모든 출처에서의 요청을 허용합니다.

# OpenAI API 키 설정
client = OpenAI(api_key="")


@app.route('/sendMessage', methods=['POST'])
def send_message():
    data = request.get_json()
    user_input = data.get('user_input')

    try:
        assistant_id=""

        #resume thread
        thread_id = ""

        message = client.beta.threads.messages.create(
            thread_id,
            role="user",
            content=user_input,
        )

        # 실행할 Run 을 생성합니다.
        # Thread ID 와 Assistant ID 를 지정합니다.
        run = client.beta.threads.runs.create(
          thread_id=thread_id,
          assistant_id=assistant_id,
        )

        run_id = run.id

        while True:
          run = client.beta.threads.runs.retrieve(
            thread_id=thread_id,
            run_id= run_id,
          )
          if run.status == "completed":
            break
          else:
            time.sleep(2)


        thread_messages = client.beta.threads.messages.list(thread_id)
        ai_response = thread_messages.data[0].content[0].text.value

        return jsonify({"description": ai_response})

    except Exception as e:
        return jsonify({"error": str(e)})

if __name__ == '__main__':
    print("Flask 애플리케이션이 실행되었습니다.")
    app.run(debug=True)

 
3. 결과


참고영상

https://youtu.be/kKhWkG5Di5s?si=Pvi8JbR_vmqZcdpt

https://youtu.be/-Wne4a-8RlY?si=bpn6IyXQIQBXal86

 

기술과제

• Python Falsk 가 무엇인지

• Axios가 무엇인지 (Fetch함수는 무엇인지)

코딩과제

• 본인 포트폴리오 코드 Github에 올리기

• Varcel에 본인 코드 배포하기

• 본인이 만든 파이썬코드 내용을 Flask에 이동시키기

• localhost:5000/sendMessage API 만들기

• API호출, Next.js에서 사용자가 입력한값을 보내기 (Axios요청)

• 개발자도구 콘솔창에 응답값을 console.log가지고 찍어보기


 

코딩과제

 

• 본인 포트폴리오 코드 Github에 올리기

• Varcel에 본인 코드 배포하기

 

WEST_COLD (westcold.vercel.app)

 

WEST_COLD

west__cold Raclette knausgaard hella meggs normcore williamsburg enamel pin sartorial venmo tbh hot chicken gentrify portland. 안녕하세요, 저는 서찬이라고 합니다. 나날이 성장하고 발전하는 것을 즐기는 사람입니다. 언제

westcold.vercel.app

 

 

 



flask
-백엔드의 전반적인 역활을 해준다
-웹페이지를 인터넷에 띄우고, 사용자들의 접속인 트래픽을 감당하고, 회원들의 정보와 게시판 정보를 관리하는 역활을 도와준다
- API 서버를 만드는 데에 특화 되어있는 Python Web Framework 
-URL을 파이썬 코드의 함수나 메서드에 매핑할 수 있는 라우팅 메커니즘을 제공

 

flask 기본 세팅

pip install Flask

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'he'

if __name__ == '__main__':
    app.run(debug=True)

 

기본세팅 결과물

 

• 본인이 만든 파이썬코드 내용을 Flask에 이동시키기

from flask import Flask, request, jsonify
from flask_cors import CORS
from openai import OpenAI
import requests
import json

app = Flask(__name__)
CORS(app)  # 모든 출처에서의 요청을 허용합니다.

# OpenAI API 키 설정
client = OpenAI(api_key="")

@app.route('/sendMessage', methods=['POST'])
def send_message():
    data = request.get_json()
    user_input = data.get('user_input')

    try:
        # OpenAI에 사용자 입력 전달하여 응답 받기
        completion = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[
                {"role": "user", "content": user_input}
            ]
        )
        ai_response = completion.choices[0].message.content.strip()

        return jsonify({"description": ai_response})

    except Exception as e:
        return jsonify({"error": str(e)})


if __name__ == '__main__':
    print("Flask 애플리케이션이 실행되었습니다.")
    app.run(debug=True)

 

• localhost:5000/sendMessage API 만들기

-위 코드는 '/sendMessage' 엔드포인트에 POST 요청을 받으면 요청의 JSON 데이터에서 'message' 값을 읽어와 성공 응답을 반환한다.

 

뒤에서 계속

https://eatitstory.tistory.com/23

 

프론트와 백 서버 연결(axios)

기술과제• Python Falsk 가 무엇인지• Axios가 무엇인지 (Fetch함수는 무엇인지)코딩과제• 본인 포트폴리오 코드 Github에 올리기• Varcel에 본인 코드 배포하기• 본인이 만든 파이썬코드 내용을 Flas

eatitstory.tistory.com

 

 4주차
기술과제
-API 토큰이 필요한 이유
코딩과제
-openai API 계정 생성
-파이썬에서 Openai 모듈 설치
-질문하고 답변 받아온거 print해서 테스트 해보기
-크롤링 한 데이터값을 GPT한테 주고 받은 답변을 Mysql에 저장하기


기술과제
*API 토큰이 필요한 이유
-API 토큰은 API를 사용할 때 인증 및 권한 부여를 위해 사용된다.

-이 토큰은 API를 호출할 때 서버가 클라이언트를 식별하고, 클라이언트가 특정 작업을 수행할 수 있는 권한이 있는지 확인하는 데 사용된다.

-따라서 API 토큰이 없으면 API를 사용할 수 없다.

-또한 API 토큰을 통해 API 사용량을 추적하고 제한할 수도 있다.

-이는 보안과 사용자 경험을 보장하기 위한 조치이다.

코딩과제
*openai API 계정 생성
*파이썬에서 Openai 모듈 설치

결제 안하면 작동 안된다

*질문하고 답변 받아온거 print해서 테스트 해보기

from openai import OpenAI
import re

client = OpenAI(api_key=".....")

# 사용자로부터 질문 입력 받기
user_input = input("질문을 입력하세요: ")

# 질문에 대한 대화 완성 생성
completion = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[
        {"role": "user", "content": user_input}
    ]
)

# 답변 내용 가져오기
answer = completion.choices[0].message.content

# 문장으로 분리
sentences = re.split(r'(?<=[.!?]) +', answer)

# 4~5문장까지 선택하여 출력
print("답변:")
for sentence in sentences[:5]:
    print(sentence)

 

 

 


*크롤링 한 데이터값을 GPT한테 주고 받은 답변을 Mysql에 저장하기

import pymysql
from bs4 import BeautifulSoup
import requests
from openai import OpenAI

# OpenAI API 키 설정
client = OpenAI(api_key="....")

# MySQL 데이터베이스 연결 설정
db_connection = pymysql.connect(host="localhost", user="root", password="....", db="rs", charset="utf8")

# MySQL 커서 생성
cursor = db_connection.cursor()

# 데이터 크롤링
data = requests.get('https://sports.news.naver.com/wfootball/index')
soup = BeautifulSoup(data.text, 'html.parser')
soccer_rank = soup.select('#_team_rank_epl > table > tbody > tr')

# 데이터를 담을 리스트 초기화
team_data = []

for tr in soccer_rank:
    team_name = tr.select_one("td > div > div.info > span")
    if team_name:
        team_name = team_name.text.strip()
        # GPT 모델에 전달하여 팀 설명 얻기
        completion = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[
                {"role": "user", "content": team_name}
            ]
        )
        team_description = completion.choices[0].message.content.strip()
        team_data.append((team_name, team_description))

try:
    # 축구팀 이름과 설명을 MySQL에 저장
    query = "INSERT INTO my_schema.football_content2 (name, content) VALUES (%s, %s)"
    cursor.executemany(query, team_data)

    # 변경사항 저장
    db_connection.commit()

    print("데이터가 성공적으로 저장되었습니다.")

except Exception as e:
    print("데이터 저장 중 오류가 발생하였습니다:", e)

finally:
    # 연결 종료
    cursor.close()
    db_connection.close()
 

웹사이트 구성하는 방법

*mpa

-페이지가 여러개
-스마트폰 나오기 전에 많이 사용
-화면이 깜빡인 후에 로딩이 된다

/list
/detail
/write
/edit
위와 관련된 html을 서버가 준비 해뒀다가 클라이언트가 요청시 보내주는 게 mpa
->서버는 html페이지만 준비하고만 있으면 된다

mpa에 사용되는 언어: node js, pyhon, java, php, asp

*node.js가 무엇인가?

-웹서버 백엔드 구현을 하기 위해서 사용
-자바스크립트 영역이 서버쪽으로 확대되어 클라이언트와 서버 양쪽의 프로그램을 모두 개발이 가능해졌다.

백앤드 언어를 사용하는 이유:
-언어와 데이터베이스를 연동을 시켜논다
-서버에서 필요한 데이터를 데이터베이스에서 가져와 html형식에 맞게 바꿔준다

 

*spa

-페이지가 하나
-페이지를 이동하지 않고 안에있는 컨텐츠만 바뀌어 깜빡임이 없다
-클라이언트가 서버에게 페이지 요청을 하면 빈 html파일을 준다+js소스파일을 준다(버튼을 누르면 js파일에 있는 소스 파일이 실행이되어 컨테츠만 바꿔준다)




*api

-약속
-버튼을 누르면(행동) 어떠한 동작이 되게하는 약속(ex자판기, 컴퓨터 자판...)
-키보드 A누르면 A가 나오는 동작도 api
-어떤 행위를 원할 때 이 버튼을 누르면 서버가 내가 원하는 동작을 해주는 약속
-spa에선 거의 API를 사용한다


-spa는 백앤드와 프로트앤드 서버가 따로 있다
-유지보수 측면에서 편하다
-백앤드는 API만 존재한다
-프론트엔드 서버한테 html과 js코드를 받아온다 그래서 데이터를 백엔드에 요청함
-장점:서버는 API만 제공하고 서버는 다양한 어플리케이션으로 사용할 수 있다.(HTTP만 가능하면)




*코드정리


app.js

const express = require('express');
const mysql = require('mysql');
const app = express();
const port = 4000;

const cors = require('cors');
app.use(cors());


const conn = {
  host : 'localhost',  
  port : '3306',
  user : 'root',
  password : '----',
  database : 'my_schema'
};

let connection = mysql.createConnection(conn);
connection.connect(); // db접속


app.engine('html', require('ejs').renderFile);
app.set('view engine', 'ejs');


app.get('/', (req, res) => {
  res.render('index.html');
});

app.get('/list', (req, res) => {
  connection.query('SELECT * from board', function(error, results, fields) {
    if (error) throw error;
    console.log(results);
    res.render('list',{'data':results})
  });
 });

 app.get('/api/list', (req, res) => {
  connection.query('SELECT * from board', function(error, results, fields) {
    if (error) throw error;
    console.log(results);
    res.send(results)
  });
 });

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

 
index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=\, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    블로그에 오신 걸 환영<br>
    <a href="/list">게시글 보기</a>
</body>
</html>

 
list.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <table>
        <tr>
            <td>id</td>
            <td>writer</td>
            <td>title</td>
        </tr>
        <% for (let i=0; i<data.length; i++) { %>
        <tr>
            <td><%=data[i]['id']%></td>
            <td><%=data[i]['writer']%></td>
            <td><%=data[i]['title']%></td>
        </tr>
        <% } %>
    </table>
</body>
</html>

 
index.html-frontend

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
       
    </div>
</body>
</html>

<script src="index.js"></script>

 
index.js-frontend

document.getElementById("app").innerHTML = '<a onclick="ShowList()">리스트로 이동</a>';

const ShowList = () => {
//api요청
fetch('http://localhost:4000/api/list')
  .then((response) => response.json())
  .then((data) => {
    console.log(data);
    document.getElementById("app").innerHTML = `
            <table>
                <tr>
                    <td>
                        id
                    </td>
                    <td>
                        writer
                    </td>
                    <td>
                        title
                    </td>
                </tr>
                <tr>
                    <td>
                        ${data[0]['id']}
                    </td>
                    <td>
                        ${data[0]['writer']}
                    </td>
                    <td>
                        ${data[0]['title']}
                    </td>
                </tr>
                <tr>
                    <td>
                        ${data[1]['id']}
                    </td>
                    <td>
                        ${data[1]['writer']}
                    </td>
                    <td>
                        ${data[1]['title']}
                    </td>
                </tr>
            </table>
            `;
  })
  .catch((error) => console.log("error:", error))

   
}


//</head> spa 쉽게 구형 ->react, vue....

+ Recent posts