남들은 이미 다 써봤다는 chatGPT를 나는 이제서야 사용하게 되었다. chatGPT를 활용해 만들어보고 싶은 프로젝트가 있어서 API를 가져와 사용하는 법을 공부해보았다.
npx create-next-app
리액트를 설치하는 과정과 비슷하다.
선택할 옵션이 엄청 많지만 이번 프로젝트에서는 대부분 필요 없는 옵션이다.
// index.js
import { useState } from 'react';
export default function Home() {
const [question, setQuestion] = useState('');
return (
<>
<form>
<input
type='text'
value={question}
onChange={(e) => setQuestion(e.target.value)}
/>
</form>
</>
);
}
useState
와 onChange
를 사용해 사용자의 질문을 입력 받는다.
// index.js
...
const handleSubmit = (e) => {
e.preventDefault();
setQuestion('');
};
return (
<>
...
<button type='submit'>질문하기</button>
</form>
...
버튼을 클릭하면 onSubmit
이 작동하여 입력한 폼을 제출하도록 한다.
form
의 특성상 제출하면 화면을 렌더링하기 때문에 이것을 방지하기 위한 코드도 추가한다.
// App.js
...
const [answer, setAnswer] = useState();
...
return (
<>
...
<div>{answer}</div>
</>
...
OpenAI를 통해 받은 응답을 출력하는 부분도 미리 만든다.
// App.js
...
const handleSubmit = async (e) => {
e.preventDefault();
// 1.
const response = await fetch('./api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ question: question }),
});
// 2.
const data = await response.json();
if (response.status !== 200) {
throw (
data.error || new Error(`request failed with status ${response.status}`)
);
}
// 3.
setAnswer(data.result);
setQuestion('');
};
...
질문을 제출하면 API를 패치한다.
만약 응답의 status가 200이 아닌, 즉 서버가 요청을 제대로 처리하지 못한 경우에는 에러를 던진다.
status가 200인 경우에는 answer
를 응답 결과로 바꾸고 질문 폼을 비운다.
// App.js
...
const handleSubmit = async (e) => {
e.preventDefault();
try {
...
// 위와 동일
} catch (error) {
console.error(error);
alert(error.message);
}
};
...
에러가 던져진 경우에는 콘솔에 에러를 반환하고 에러 메세지를 알림창으로 띄운다.
OpenAI 사이트에 회원가입을 하고 "프로필" > "View API keys" > "Create new secret key" 경로를 통해 key를 복사한다.
// .env
OPENAI_API_KEY="발급 받은 키"
package.json
과 같은 위치에 .env
라는 파일을 생성해 발급 받은 키를 저장한다.
https://platform.openai.com/examples
Examples 카테고리에서 여러 종류의 애플리케이션을 볼 수 있다. 이 중에서 "Q&A"라는 질의응답 애플리케이션을 가져다 쓸 것이다.
맨 하단에 "API request" 코드를 사용할 수 있다. 여러 언어 중 node.js를 선택해 복사한다.
// ./api/generate.js
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:
'I am a highly intelligent question answering bot...',
// prompt가 너무 길어서 생략
temperature: 0,
max_tokens: 100,
});
pages/api/generate.js
라는 파일을 생성하고 복사한 코드를 붙여넣는다.
npm install dotenv
dotenv
라이브러리를 설치한다. dotnev는 환경변수를 .env
에 저장하고 process.env
로 로드하는 의존성 모듈이다. 이것을 사용하면 민감한 정보를 안전하게 사용할 수 있다.
// ./api/generate.js
import * as dotenv from 'dotenv';
dotenv.config({ path: __dirname + '/.env' });
...
환경변수를 불러오고 다른 파일에 환경변수를 저장하기 위해 위와 같은 코드를 추가한다.
npm install openai
openai
라이브러리를 설치한다.
// ./api/generate.js
...
export default async function (req, res) {
const question = req.body.question || '';
const response = await openai.createCompletion({
model: 'text-davinci-003',
prompt: 'I am a highly intelligent question answering bot. If you ask me ${question} that is rooted in truth, I will give you the answer to Korean. If you ask me a question that is nonsense, trickery, or has no clear answer, I will respond with \"잘 모르겠습니다.\".\n',
temperature: 0,
max_tokens: 100,
});
res.status(200).json({ result: response.data.choices[0].text });
}
// ./api/generate.js
...
export default async function (req, res) {
if (!configuration.apiKey) {
res.status(500).json({
error: {
message: 'OpenAI API key not configured',
},
});
return;
}
...
configuration의 apiKey가 존재하지 않다면 status를 500, 즉 서버에 오류를 발생하여 요청을 수행할 수 없다는 상태로 만들고 에러 메세지를 작성하여 응답한다.
// index.js
import { useState } from 'react';
export default function Home() {
const [question, setQuestion] = useState('');
const [answer, setAnswer] = useState();
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch('./api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ question: question }),
});
const data = await response.json();
if (response.status !== 200) {
throw (
data.error ||
new Error(`request failed with status ${response.status}`)
);
}
setAnswer(data.result);
setQuestion('');
} catch (error) {
console.error(error);
alert(error.message);
}
};
return (
<>
<form onSubmit={handleSubmit}>
<input
type='text'
value={question}
onChange={(e) => setQuestion(e.target.value)}
/>
<button type='submit'>질문하기</button>
</form>
<div>{answer}</div>
</>
);
}
// ./api/generate
import * as dotenv from 'dotenv';
import { Configuration, OpenAIApi } from 'openai';
dotenv.config({ path: __dirname + '/.env' });
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
export default async function (req, res) {
if (!configuration.apiKey) {
res.status(500).json({
error: {
message: 'OpenAI API key not configured',
},
});
return;
}
const question = req.body.question || '';
const response = await openai.createCompletion({
model: 'text-davinci-003',
prompt: `I am a highly intelligent question answering bot. If you ask me ${question} that is rooted in truth, I will give you the answer to Korean. If you ask me a question that is nonsense, trickery, or has no clear answer, I will respond with \"잘 모르겠습니다.\".\n `,
temperature: 0,
max_tokens: 100,
});
res.status(200).json({ result: response.data.choices[0].text });
}