OpenAI๋ก ์๋น์ค ์ฌ์ฉ์ฑ ๊ฐ์ ํ๊ธฐ
๊ฐ์
์ต๊ทผ์ ์ฑ์ ์ถ์ํ๋ต๋๋ค.โญ๏ธ
Tidify ๋ ๋งํฌ ์์นด์ด๋น ์๋น์ค๋ก, ๋งํฌ๋ฅผ ๋ณต์ฌํ๊ฑฐ๋ Share Extension ์ ์ฌ์ฉํ์ฌ ๋ถ๋งํฌ๋ฅผ ์๋์ผ๋ก ์์ฑํด์ฃผ๋ ์๋น์ค์
๋๋ค. ์ฌ์ฉ์ ํธ์์ฑ์ ์ํด ๋งํฌ ์
๋ ฅ ํ ๋ถ๋งํฌ์ ์ด๋ฆ์ ์ง์ ํ์ง ์์ผ๋ฉด, url(๋งํฌ)์ ๋ถ๋งํฌ์ ์ด๋ฆ์ผ๋ก ์๋ ์ ์ฅํฉ๋๋ค. ํ์ง๋ง ํธ์์ฑ์ ์ํ ์ด ๊ธฐ๋ฅ์ด ์ด๋๋ ์๋ฌ๋ฅผ ๋ด๋ฒ๋ฆฌ๋๋ฐ…
(์์ง ๋ฒ ํ๋ฒ์ ์ด๋ผ ๋ณธ๊ฒฉ์ ์ธ ํ๋ณด๋ ํ์ง ์์์ง๋ง ๐์ฑ์คํ ์ด ์์ ๋ค์ด๋ฐ์ ์ ์์ต๋๋ค)
Open AI ๋์ ๋ฐฐ๊ฒฝ
ํน์ ๋งํฌ(url)์ ๊ธธ์ด๊ฐ 300์๋ฅผ ๋์ ๊ฒฝ์ฐ DB table ์ name column ์์ Too long for column ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค. ๋น์์๋ ๋จ์ํ ์ปฌ๋ผ์ ์ต๋ ๊ธธ์ด๋ฅผ 500์๋ก ๋๋ ค ๋์ฒํ์ง๋ง, ๊ทผ๋ณธ์ ์ธ ๋์ฒ ๋ฐฉ๋ฒ์ ์๋๋ผ ์๊ฐํ์ต๋๋ค. ํน์ ์ผ์ด์ค๋ง์ ์ํด name column ์ 500์๋ก ๋๋ฆฌ๋ ๊ฑด ์ค๋ฐ๋ผ ์๊ฐํ๊ฑฐ๋ ์.
com.mysql.cj.jdbc.exceptions.MysqlDataTruncation:
Data truncation: Data too long for column 'name' at row 1
DB ์์ Table column ์ค์ ์ ๋ฐ๊พธ๊ธฐ ๋ณด๋จ, ๋งํฌ์ ์ ์ ํ name ์ ์์ฑํ ์ ์๋ ๋ฐฉ๋ฒ์ ๊ตฌ์ํ์ต๋๋ค.
์ฒ์์ 2๊ฐ์ง ์ ๊ทผ์ ์๊ฐํ์ต๋๋ค. url ์ ํ์ฑํ์ฌ 1) ๋๋ฉ์ธ๋ง ์ ์ฅํ๊ฑฐ๋ 2) ์ ์ฒด url ๋์ , ํน์ ๊ธธ์ด๊น์ง๋ง slicing ํ์ฌ ์ ์ฅํ๋ ๋ฐฉ์์ ์๊ฐํ์ต๋๋ค.
1. ํ์ง๋ง ๋๋ฉ์ธ์ ํ์ฑํ ๊ฒฝ์ฐ, ์ฌ๋ฌ url ๋ง๋ค ๋๋ฉ์ธ ๋ค์์ ์์น๊ฐ ๋ถ๊ท์นํ๋ค๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
2. ํน์ ๊ธธ์ด๊น์ง slicing ํ๋ ๋ฐฉ๋ฒ์, ์ฌ์ฉ์์๊ฒ ์ข์ง ์์ UI ๋ผ๋ ๋์์ด๋์ ํผ๋๋ฐฑ์ด ์์์ต๋๋ค. ๊ฐ๋ น ์๋์ฒ๋ผ ๊ธธ์ด๊ฐ ๊ธด url ์ ํน์ ๊ธธ์ด๊น์ง slicing ํ์ฌ ์ ์ฅํ๋ฉด ์ด์ค๊ฐํ ์ด๋ฆ์ด ์์ฑ๋๋ฒ๋ฆฝ๋๋ค.
before: https://docs.google.com/presentation/d/1XnBMiHbgZmclXT4dLfI6Q7fnNnXQwfo165458o3Qzv4/mobilepresent?slide=id.p
after : https://docs.google.com/presen
์ด๋ ๊ฒ ์งค๋ ค๋ฒ๋ฆด์ง๋ ๋ชจ๋ฅด๋ ์ผ์ด๋๊น์. ์ฑ UI ์์ ์ด๋ ๊ฒ ์ ์ฅ๋๊ฒ ์ฃ .
์๋ค๋ฅธ ์ ๊ทผ์ด ํ์ํ์ต๋๋ค. ๋ถ๋งํฌ ์ด๋ฆ ์ ๋ ฅ์ ๊ฐ์ ํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๊ณ ์ถ์ง ์์์ต๋๋ค. ๊ทธ๋ฐ ์์ผ๋ก ํด๊ฒฐํ๋ฉด ์ฐ์ ์ ๋ถํฐ ์ฑ์ ์ฌ์ฉํ์ง ์์ ๊ฒ ๊ฐ์ผ๋๊น์.
url ์ ๋ํํ ์ ์๋ ๋ถ๋งํฌ ์ด๋ฆ์ ์๋์ผ๋ก ์์ฑํ๋ ๊ฒ์ด ๋ฌธ์ ์ ๊ด๊ฑด์ธ๋ฐ, ์ด ๋ ๋ ์ค๋ฅด๋ ๊ทธ ์ด๋ฆ ๊ทธ๋ ์ต๋๋ค. ๊ทธ๋ถ์ด ๋ฑ์ฅํ ๋ ์ ๋๋ค.
GPT ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด url ๋ง์ผ๋ก ๊ทธ์ ๋ง๋ ๋ถ๋งํฌ ์ด๋ฆ์ ์์ฑํ ์ ์์ผ๋ฆฌ๋ผ ์๊ฐํ์ต๋๋ค.
ํ๋กฌํํธ์ url ๊ณผ ๊ทธ์ ๋ง๋ ์ด๋ฆ์ ๋ฏธ๋ฆฌ ์์๋ก ์ฃผ๊ณ , ์๋ก์ด url ์ ์ ๋ ฅํ์ฌ ์ํ๋ ๋ฐฉ์์ผ๋ก ์ด๋ฆ์ด ์์ฑ๋๋์ง ํ ์คํธ ํ์ต๋๋ค. ์ด ๊ณผ์ ์์ ๋คผํผ์ ์ฌ์ฉํ์ต๋๋ค.
์๊ฐ๋ณด๋ค ๊ฒฐ๊ณผ๊ฐ ์๋์์ ๋๋์ต๋๋ค. ์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ํ๋กฌํํธ๋ฅผ ์ธํ ํ ์ ์๋ค๋ฉด ์ถฉ๋ถํ url ์ ์ด๋ฆ์ ์๋์ผ๋ก ์์ฑํ ์ ์์ ๊ฒ์ด๋ผ ์๊ฐํ์ต๋๋ค. ์ง์ฒด์์ด ์ฝ๋๋ฅผ ์ง๋ณด๊ธฐ๋ก ํ์ต๋๋ค.
์ฌ์ฉ ๊ธฐ์ ๊ณผ ์ ์ ๋ฐฐ๊ฒฝ
Tidify ์ ํ์ฌ ์๋ฒ๋ Java, Springboot ์ผ๋ก ๊ฐ๋ฐ๋์ด ์์ต๋๋ค. OpenAI API ๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ์ ๋ ๋ ์ค๋ฅธ ์ต์ ์ 2๊ฐ์ง ์์ต๋๋ค.
- ๊ธฐ์กด Springboot ์๋ฒ์ OpenAI API Dependency ์ถ๊ฐ.
- ๋ณ๋์ FastAPI ์๋ฒ์ OpenAI๋ฅผ Import.
๋จ์ํ OpenAI API ๋ฅผ ํธ์ถํ๊ธฐ ์ํด์๋ผ๋ฉด ๋ณ๋์ ์๋ฒ ์ฆ์ค ์์ด 1) Springboot ๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ๊ฒ ์ง๋ง, ๋ฌธ์์ด ํ์ฑ๊ณผ ํน์ ๋ฐ์ดํฐ ์ (csv)์ ํ๋ จ์ํค๊ธฐ ์ํด์ , pandas ๋ฅผ ํ์ฉํ ์ ์๋ ํ์ด์ฌ ํ๊ฒฝ์ด ๋ ์ ๋ฆฌํ๋ค๊ณ ์๊ฐํด 2) FastAPI ๋ฅผ ์ ํํ์ต๋๋ค. ๋น๋๊ธฐ ํธ์ถ์ด ์ง์๋๋ ์ ๋ FastAPI ๋ฅผ ์ ํํ ์ด์ ์ค ํ๋์์ต๋๋ค. (๋ฌผ๋ก ์คํ๋ง๋ถํธ์์๋ ๋น๋๊ธฐ ์ง์์ ๊ฐ๋ฅํฉ๋๋ค) ํ ๋จ๊ณ์์ ๊ฐ๋จํ ํ๋กฌํํธ๋ฅผ ์ธํ ํด, Url ์ ๋ง๋ ๋ถ๋งํฌ ์ด๋ฆ์ ์ถ์ฒ ๋ฐ๊ธฐ๋ก ํ์ต๋๋ค. (Pandas ๋ ๋ค์ ๋ฒ์ ๋ถํฐ ์ ์ฉํ๊ธฐ๋ก ใ )
๊ฐ๋ฐ
๊ฐ๋ฐ ํ๊ฒฝ์ ์๋์ ๊ฐ์ต๋๋ค.
- python 3.9
- fastapi 0.95
- openai 0.27.7
- uvicorn 0.22.0
Main.py (Set up) ์๋ํฌ์ธํธ ๊ฐ์ค์ ์ํด FastAPI()๋ฅผ ํธ์ถํ๊ณ Prompt ํด๋์ค๋ฅผ ์์ฑํฉ๋๋ค.
app = FastAPI()
openai.api_key = OPEN_AI_API_KEY
class Prompt(BaseModel):
question: str
Main.py (End point)
open.Completion.create() ํจ์๋ฅผ ์์ฑํ๊ณ engine, prompt, max_tokens, n, temperature ์ธ์๋ฅผ ๋ฐ์ต๋๋ค.
- engine : ์ฌ์ฉํ GPT ๋ชจ๋ธ์ ์ด๋ฆ์ ๋๋ค. ์ ๊ฐ ์ฌ์ฉํ ‘text-davinci-002’ ๋ OpenAi ๊ฐ๋ฐํ GPT ๋ชจ๋ธ ์ค ํ๋์ ๋๋ค.
- prompt : ๋ง ๊ทธ๋๋ก ํ๋กฌํํธ ์ ๋๋ค. GPT engine ์ ์ง๋ฌธํ ๋ด์ฉ์ ํด๋น๋ฉ๋๋ค.
- max_tokens : ๊ฒฐ๊ณผ์ ์ฌ์ฉ๋ ์ต๋ ํ ํฐ ์ ์ ๋๋ค. ํ ํฐ์ ๋จ์ด๋ ๊ตฌ๋์ ๋ถํธ๋ก ์๊ฐํ ์ ์์ต๋๋ค.
- n : ํ๋กฌํํธ์ ์์ฑ๋ ์๋ต์ ์ ์ ๋๋ค. 1 ๋ก ์ง์ ํ ๊ฒฝ์ฐ, ํ ๊ฐ์ง์ ํ ์คํธ ์๋ต์ด ์์ฑ๋ฉ๋๋ค.
- temperature : ์ํ๋ง ๋ฐฉ๋ฒ์ผ๋ก ๋ฌด์์์ฑ์ ์ ์ดํฉ๋๋ค. temperature ๊ฐ์ด ๋์ ์๋ก ๋ชจ๋ธ์ ๋ค์ํ๊ณ ์ฐฝ์์ฑ ๋์ ๋ฌธ์ฅ์ ์์ฑํ๋ฉฐ, ๊ฐ์ด ๋ฎ์ ๊ฒฝ์ฐ ์ผ๊ด์ ์ด๊ณ ์์ ๊ฐ๋ฅํ ๋ฌธ์ฅ์ ์์ฑํฉ๋๋ค.
@app.post("/ask_openai")
async def ask_openai(prompt: Prompt) -> str:
response = openai.Completion.create(
engine="text-davinci-002",
prompt=preset_prompt,
max_tokens=100,
n=1,
temperature=0.5,
)
semi_answer: str = response.choices[0].text.strip()
return semi_answer.split('\n')[0]
๊ทธ๋ฆฌ๊ณ prompt ์ ์ฌ์ฉ๋ preset_prompt๋ฅผ ์์ฑํฉ๋๋ค. ํ๋กฌํํธ๋ฅผ ์ด๋ป๊ฒ ์์ฑํ๋์ ๋ฐ๋ผ ์ป์ ์ ์๋ ๊ฒฐ๊ณผ๊ฐ ๋ค๋ฆ
๋๋ค. ๋ฒ์ ๋ณ๋ก ํ๋กฌํํธ๋ฅผ ์์ฑํ๋ฉฐ ์ด๋ค ์๋ต์ ์ป๋์ง ๋น๊ตํด๋ณด๊ฒ ์ต๋๋ค.
Version.1
prompt
preset_prompt = f"""
generate simple Korean name of this input URL: {prompt.question} \n
"""
๊ฒฐ๊ณผ
์ ํ ๊น๋ํ์ง ์์ Answer ์ด ๋์์์ต๋๋ค. ์ ๊ฐ ์ํ ๊ฑด ์ต์ `OOO ํ
ํฌ ๋ธ๋ก๊ทธ` ์ ๋์๋๋ฐ, OpenAI ๋ `์น์ฌ์ดํธ: https://techblog.woowahan.com/12044`๋ผ๊ณ ๋ตํ์ต๋๋ค. ์์ง ํ๋ จ์ด ํ์ํ ์น๊ตฌ๋ ํ๋กฌํํธ๋ฅผ ์ข ๋ ์์ธํ ์์ฑํ๊ฒ ์ต๋๋ค.
Input URL : https://techblog.woowahan.com/12044
OpenAI Answer : ์น์ฌ์ดํธ: https://techblog.woowahan.com/12044
Version.2
Version.1 ๋ณด๋ค ์์ธํ ํ๋กฌํํธ๋ฅผ ์์ฑํ์ต๋๋ค.
- ์ฐ์ ์ด๋ค ๋ฐฉ์์ผ๋ก url ์ name ์ ์์ฑํ๋ฉด ์ข์์ง ์์๋ฅผ ๋ค์ด์คฌ์ต๋๋ค.
- input URL ์ ์ ์ํ๊ณ , ํด๋น url ์ name ์ ์ง์ ์ ๋ ฅํ์ต๋๋ค.
- ๊ทธ๋ฆฌํ์ฌ random URL ์ด ์ ๋ ฅ๋๋๋ผ๋ ์ ๊ฐ ์ํ๋ ํ์์ name ์ ์ป์ ์ ์๋๋ก ํ์ต๋๋ค.
- ๋๋ฒ์งธ๋ก ์์ฑ๋ name ์ด single line ์ด์ด์ผ ํ๋ค๊ณ ์ฌ์ฐจ ๊ฐ์กฐํ์ต๋๋ค. Tidify ์๋น์ค์ ์ฌ์ฉ๋ ๋ถ๋งํฌ name ์ ๋๊ฒ ํ์ค๋ก ์ ์ฅ๋๊ธฐ ๋๋ฌธ์ ์ด๋ฐ ์ ์ฝ ์กฐ๊ฑด์ ์คฌ์ต๋๋ค.
prompt
preset_prompt = f"""
make a simple Korean name of the url base on examples.
It's important to generate a result in Korean words as a single line,
not multiple line sentences.
the result will be used as simple bookmark,
so it's important to generate a result as simple korean words.
- example 1:
input URL: https://google.com
generated name: ๊ตฌ๊ธ
- example 2:
input URL: https://medium.com/29cm/backend/home
generated name: 29CM ๊ธฐ์ ๋ธ๋ก๊ทธ
- example 3:
input URL: https://ios-development.tistory.com/945
generated name: iOS ๊ฐ๋ฐ ๋ธ๋ก๊ทธ
- example 4:
input URL: https://29cm.career.greetinghr.com/o/77203
generated name: 29cm ์ฑ์ฉ ๊ณต๊ณ ํ์ด์ง
- example 5:
input URL: https://www.linkedin.com/posts/catalin-patrascu_swift-concatenation-
generated name: LinkedIn Swift Concatenation ํฌ์คํธ
- example 6:
input URL: https://wrtn.ai/
generated name: ๋คผํผ
- example 7:
input URL: https://jh-bk.tistory.com/23
generated name: jh-bk ์ ํฐ์คํ ๋ฆฌ
- example 8:
input URL: https://docs.google.com/presentation/d/1XnBMiHbgZmclXT4dLfI6Q7fnNnXQwfo165458o3Qzv4/mobilepresent?slide=id.p
generated name: Google Presentation ์ฌ๋ผ์ด๋
Generate simple Korean name of this input URL: {prompt.question} \n
"""
๊ฒฐ๊ณผ
Version.1๊ณผ ๋์ผํ url ๋ก ํ ์คํธ ํด๋ณด๊ฒ ์ต๋๋ค.
ํ๋กฌํํธ๋ง ๋ฐ๊ฟจ์ ๋ฟ์ธ๋ฐ ์ฑ๋ฅ(?)์ด ๋น์ฝ์ ์ผ๋ก ์ข์์ง ๊ฑธ ํ์ธํ ์ ์์ต๋๋ค.
Input URL : https://techblog.woowahan.com/12044
OpenAI Answer : ์ฐ์ํ ๊ธฐ์ ๋ธ๋ก๊ทธ
๋ ๋ค๋ฅธ url ๋ก ํ ์คํธ ํด๋ณด๊ฒ ์ต๋๋ค.
Version.1
Input URL : https://docs.google.com/forms/d/e/1FAIpQLSciq7dwuIV7t0g9cITjCnf1vq1xRjj6dTfAT7hDLiwcfUn17w/viewform
OpenAI Answer : ์ด๋ฆ:
Version.2
Input URL : https://docs.google.com/forms/d/e/1FAIpQLSciq7dwuIV7t0g9cITjCnf1vq1xRjj6dTfAT7hDLiwcfUn17w/viewform
OpenAI Answer : Goolge Form ์ค๋ฌธ์กฐ์ฌ
๋ํดํ url ์ด๋๋ผ๋ Prompt ๋ฅผ ์ด๋ป๊ฒ ์ธํ
ํ๋์ ๋ฐ๋ผ ์๋ต ๊ฒฐ๊ณผ๊ฐ ์ฒ์ฐจ๋ง๋ณ์ธ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
Main.py (์ ์ฒด ์ฝ๋)
์ ์ฒด ์ฝ๋๋ ๊ณต์ ํ๊ฒ ์ต๋๋ค. ์ฝ๋๋ฅผ ๊น๋ํ ๋ณด๊ธฐ ์ํด Version2 ์์ ํ์ฉํ prompt ๋ ๋ณ๋์ ํ์ผ(secret.py)์ ๋ถ๋ฆฌํ์ฌ import ํ์ต๋๋ค.
import openai
from fastapi import FastAPI
from pydantic import BaseModel
from secret import OPEN_AI_API_KEY, PRE_PROMPT
app = FastAPI()
openai.api_key = OPEN_AI_API_KEY
class Prompt(BaseModel):
question: str
@app.post("/ask_openai")
async def ask_openai(prompt: Prompt) -> str:
preset_prompt = f"""
{PRE_PROMPT}
generate simple Korean name of this input URL: {prompt.question} \n
"""
response = openai.Completion.create(
engine="text-davinci-002",
prompt=preset_prompt,
max_tokens=100,
n=1,
temperature=0.5
)
semi_answer: str = response.choices[0].text.strip()
return semi_answer.split('\n')[0]
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
๋ง๋ฌด๋ฆฌ
As-Is ์ To-Be ๋ฅผ ๋น๊ตํ๋ฉฐ ๋ง๋ฌด๋ฆฌ ์ง๊ฒ ์ต๋๋ค.
As-Is
๊ธฐ์กด์ ๋ถ๋งํฌ ์ด๋ฆ์ ์ง์ ํ์ง ์์ผ๋ฉด input url ๊ณผ ๋์ผํ ๊ฐ์ผ๋ก ์๋ฒ์์ ์ธํ
ํด์ค๋๋ค. ์์ ์ธ๊ธํ๋ฏ url ์ด ๋๋ฌด ๊ธธ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด name column ๋ url column ๊ณผ ๋์ผํ ๊ธธ์ด๋ก ์ธํ
ํด์ค์ผ ํฉ๋๋ค.
To-Be
OpenAI ๋ฅผ ํ์ฉํด ์ด๋ฆ์ ์ธํ ํ๋ ๊ฒฝ์ฐ์ ๋๋ค.
url ์ ๋ ฅ ํ ๋ถ๋งํฌ ์ด๋ฆ์ ๋ฐ๋ก ์ง์ ํ์ง ์์ ๊ฒฝ์ฐ, OpenAI ์์ ์๋ต๋ฐ์ ์ถ์ฒ ์ด๋ฆ ์ ์ฑ์ ๋ฆฌํดํฉ๋๋ค. ํ๋กฌํํธ์์ ๊ฒฐ๊ณผ๋ฅผ single line ์ผ๋ก ์ง์ ํ์ฌ name ์ด ๊ธธ์ด์ง๋ ๋ฌธ์ ๋ ํด๊ฒฐํ ์ ์์ต๋๋ค. ์ถ๊ฐ์ ์ผ๋ก ask_openai() ๋ฉ์๋ return ๋ถ๋ถ์์ split('\n') ํ์ฒ๋ฆฌ๋ ํ๊ธฐ ๋๋ฌธ์ Data too long for column 'name' at row 1 ๊ฐ์ ๋ฌธ์ ๋ ๋ฏธ์ฐ์ ๋ฐฉ์งํ ์ ์๊ฒ ๋์ต๋๋ค.
์ด๋ ๊ฒ ์ ๊ฐ ์ด์์ค์ธ ์๋น์ค์ OpenAI API ๋ฅผ ์ ์ฉํด๋ณด์์ต๋๋ค. ์ฌ์ฉ์ ํธ์์ฑ์ ์ํด ์ ์ฉํ ๊ธฐ๋ฅ์ด ๋๋ ค ์ฅ์ ๋ฅผ ์ผ์ผํจ ์ผ์ด์ค์์ง๋ง, ์ด๋ฅผ AI ๊ธฐ์ ์ ํ์ฉํด ํด๊ฒฐํ ๊ฒฝํ์ด์์ต๋๋ค.
Tidify Ver.2 ๋ถํด URL ์ ๊ธฐ๋ฐ์ผ๋ก ์ ํฉํ ๋ถ๋งํฌ ์ด๋ฆ์ ์ง์ ํ ์ ์์ต๋๋ค.
๊ทธ๋ผ ์ Version.2 ์ค๋นํ๋ฌ 20000