개발 공부를 하다가 급 무료해지기도 하고, 뭔가 리프레시가 될 수 있는 재밌는 게 없을까 ! 하다가 이 강의를 발견했다.
마침 내가 약하다고 생각했던 API 데이터 왔다갔다 부분이기도 했고, ChatGPT 라는 용어에 홀리듯이 강의를 결제하고 시작해버렸다 !
만들어야하는 기능은 다음과 같았다.
입력창에 동물에 관한 설명을 입력하고, Generate name 버튼을 누르거나 엔터키를 입력하면 동물의 이름이 촤라란~ 하고 뜨는 기능이다. 강의에 나온 기능만 정리하자면 (이것저것 추가해볼 예정)
NextJS 로 개발하는 강의였고, 간단한 앱이라서 고대로 진행했다 !
client side 에서 개발해야 하는 기능은 아무리도 입력창이 있기 때문에 대부분이 state 관련 기능이었다.
useState 로 사용자 입력을 담는 animalInput 을 만들어주고, 화면을 참고해서 아래처럼 form 을 구성하기
const [animalInput, setAnimalInput] = useState('');
// 컴포넌트가 form 이 전부라서 요고만 기록..!
<form>
<input
type = 'text'
name = 'animal'
value = {animalInput}
onChange = {(e)=>{setAnimalInput(e.target.value);}}
placeholder = 'Enter an animal'
/>
<input
type = 'submit'
value = 'Generate names'
onClick = {handleSubmit} // handleSubmit : 나중에 animalInput POST 하게 될 함수
/>
</form>
요기까지 하고 enter 키 기능은 onKeyPress 메서드를 사용해야 하나? 하고 고민하고 있었다.
근데 강의에서 유용한 정보를 배울 수 있었다.
<form onSubmit={handleSubmit}>
// ...
</form>
이렇게 form 컴포넌트에 onSubmit 프로퍼티로 클릭과 엔터키 이벤트 모두를 제어할 수 있다는 것!!
다음에도 활용해야지 싶었다 너무 좋은 정보 !
그리고 임시로 result state 도 만들어준 후, result 를 화면에 뿌리는 영역을 작성해줬다.
const [result, setResult] = useState('...')
<div>
{result}
</div>
jsx 언어는 화면구성하다가 갑자기 '아 잠만잠만 변수 넣을래 나' 하고 묻지도 따지지도 않고 넣을 수 있어서 재밌따. 아무튼 result 를 이렇게 넣어준다.
그다음에는 아래처럼 임시로 작성해놨던 handleSubmit 함수를 수정해줬다.
const handleSubmit = (e) => {
console.log(animalInput)
}
const handleSubmit = async(e) => {
e.preventDefault(); // 자동 전송 막아주는 메서드
try{
// app\api\generate.js 에 api 가 작성될 예정!
const response = await fetch('./api/generate', {
method: 'POST',
headers: {
"Content-Type" : "application/json"
},
// animalInput 보내기
body: JSON.stringify({animal: animalInput})
})
const data = await response.json(); // http 통신 응답 data 에 저장
if(response.status !== 200){ // 200 은 http 요청이 성공적으로 되었다는 응답코드이다.
// status 로 http 요청의 응답코드를 알 수 있따.
throw data.error
}
setResult(data.result);
setAnimalInput('');
}
catch(error){ // try 에서 throw 한 error 를 catch
console.error(error)
alert(error.message) // 프롬프트로 error 알려주기
}
}
[ 새로 배운 것들 & 원래 알던 내용 복기 ]
try{
// 처리하고 싶은 코드
// 이상한 응답은 던져버리기
}
catch(err){
// try 에서 던져진 친구 err 로 catch 하기
}
당연히 여기까지만 하면 빈 껍데기였다. 강의를 들으면서 천천히 server side 개발을 따라갔다.
Server-side 에서는, animalInput을 포함한 요청이 오면 openAI API 를 통해 result 를 만들어야한다.
프로젝트를 처음 생성하면 생기는 generate.js 를 api 폴더 안에 넣어서 기능을 작성했다.
일단 OpenAI 친구를 모셔와야 한다. OpenAI 에 들어가면, 여기서 사용할 product name generator 말고도 다양한 기능의 친구들이 있다.
여기로 들어가면 키워드뽑기, Q&A, 텍스트를 컬러로 바꾸기, 등등 신기한 것들이 많이 있으니 꼭꼭 구경하기!
구경하고 있으면 다양한 생각이 마구마구 샘솟는 것 같다. 아직은 생각뿐..
product name generator 를 찾아서 눌러주면 아래와 같은 화면이 나온다.
이거 보고 이름짓기? 이게 동물이름을 지을 수 있나? 했는데 Prompt 에 적혀있는 정보들을 참고해서 response 를 만들어내는 AI 이다. Prompt 에 suggest pet name 이런 식의 명령을 입력해주면 되었다. 각종 실험은 우측 상단 버튼 Open In Playground 를 해보면 알 수 있다.
다 확인했다면 API request 에 나와있는 코드 복사하기 !
그리고 늘 그랬던 것 처럼 API key 를 발급받아야 한다.
가입 후, 우측상단 프로필을 누르면 나오는 드롭다운 메뉴에서 view API Keys 로 들어가면 엄청 쉽게 발급받을 수 있다.
마지막으로 프로젝트에 openai 패키지를 추가해주면 준비물은 다 챙겼다.
(npm i openai 또는 yarn add openai)
API key 는 매우 중요하고 중요한 암호라고 했다.. 따라서 숨겨야한다.
OPENAI_API_KEY = " 발급받은 키 복사해서 붙여넣기 "
dotenv 패키지 설치
dotenv 는 .env 안의 정보들을 환경변수로 설정해주는 패키지이다.
.env 안에 API key 를 넣어줬기 때문에, generate 에서 바로 사용할 수 있다.
generate.js 에서 불러오기
import * as dotenv from 'dotenv';
dotenv.config({path:__dirname + '../../.env'})
// process.env.OPENAI_API_KEY 로 key 에 접근할 수 있다.
이제 본격적으로 기능을 구현하는 단계 ! 복사해놓은 API request 샘플 코드를 generate.js 에 붙여넣기 했다.
강의에서는 이 프로젝트에 top_p, frequency_penalty, presence_penalty 는 필요없다구 하셔서 지워줬다.
그리고 원래 엄청 길게 되어있었던 프롬프트를 목적에 맞게 바꿨다 !
const { Configuration, OpenAIApi } = require("openai");
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
const response = await openai.createCompletion({
model: "text-davinci-003",
//원래 prompt : "Product description: A home milkshake maker\nSeed words: fast, healthy, compact.\nProduct ..."
prompt: `suggest three pet names for the follow ${animal}`,
temperature: 0.8,
max_tokens: 60
});
근데 여기서, prompt에 들어가는 animal 은 client-side 에서 요청할때 넘겨준 animalInput 이어야 한다.
response 전체를 비동기 처리 함수 안에 다시 넣어주어야 했다 !
export default async function(req, res){ // request, response
// ...
const animal = req.body.animal || '';
try{
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: `suggest three pet names for the follow ${animal}`,
temperature: 0.8,
max_tokens: 60
});
res.status(200).json({result : response.data.choices[0].text })
}
catch(error){
// ...
}
}
request 에 들어있는 animal 을 저장해주고, openai prompt 에 넣어 명령하는 방식이다.
response 를 openai api 에서 받아와주면 원하는 result 를 보내줄 수 있다 !
[ 새로 배운 것 ]
사전 발생 에러 처리
위의 코드에서는 생략되어 있지만, try-catch 로 openai 로 가기 전에 일어날 수 있는 에러 처리를 강사님이 많이 해주셨다. animal 문자열 변수를 trim 해서 빈 문자열이라면 error 를 response 한 후 return 해주었고, configuration 의 api key 가 잘못되었다면 마찬가지로 에러처리를 해주었다.
api request 과정 에러 처리
catch문 안에서도 두가지 분기로 에러를 처리했다.
error.response 라면 openai 안에서 에러가 난 것이고, 그게 아니라면 request 과정에서 에러가 난 것이기 때문에 이 두개를 따로 처리했다.
평소 api 를 가져다가 쓸 때, 이런식으로 에러처리를 꼼꼼하게 안해서 어디서 에러가 나는지 한참 찾았었던 기억이 있다. 이 강의를 들으면서 에러 처리의 중요성을 너무 배웠다 ㅠㅠ 그냥 요청이 성공한다고 해서 다가 아니었다!!
처음에는 재밌는 게 하고싶어서 무작정 시작했는데, 하다보니 정말 많은 것을 배울 수 있었다 !
특히 에러 처리 과정은 다른 프로젝트에도 적용하고 싶을 만큼 깔끔했다. request 를 하기 전 발생할 수 있는 에러들을 방지하는 습관을 들여야겠다.
그리고 개발하면서 개선하고 싶은 부분들이 많이 생겼다.
얘네들은 2탄으로 진행할 수 있,,겠,,지 ?