지난달 OpenAI가 GPT 모델을 API로 공개하며 생성 AI를 활용한 프로덕트가 쏟아져 나오고 있습니다. character.ai나 YOU.com, 국내에는 뤼튼(wrtn) 같은 스타트업부터 기업 비즈니스에는 기존 프로덕트에 AI 챗봇을 추가하는 등 다양한 방식으로 활용되고, 토이 프로젝트에도 정말 많이 활용하고 있습니다. 얼마 전에는 생성 AI 활용 프로젝트를 모아 놓은 웹사이트까지 등장했습니다👀
GPTForge🔽
저 역시 OpenAI API에 흥미가 생겼습니다. 사실상 복잡한 AI 로직을 자연어로 처리해주는 거대한 서버가 생긴 것과 다름없었고, 코드를 뜯어보니 클라이언트에서 npm 패키지를 설치해 API Key를 넣고 fetch만 하면 되는 매우 간단한 로직이었습니다.
openai-node🔽
시작은 공식문서부터
Quickstart🔽
OpenAI 공식문서의 Quickstart 가이드는 GPT의 동작과 관련된 핵심 개념들 (token / probability / temperature)에 대해 알기 쉽게 설명해줍니다.
GPT는 대규모 언어 모델(LLM)이며, 주어진 문장을 token 단위로 나누고, 뒤에 이어질 가장 자연스러운 (probability가 높은) token을 생성합니다. token은 단순한 단어의 개념과 약간 다릅니다.
What are tokens and how to count them? 🔽
만약 모델이 가장 probability가 높은 다음 token만을 선택한다면, 응답이 다소 일관되고 창의성이 없다고 생각될 수 있습니다. 그래서 등장하는 개념이 temperature입니다. temperature는 0에서 1 사이의 숫자로, 1에 가까울수록 GPT 모델이 위험을 감수하고 probability가 낮은 다음 token을 선택할 가능성이 높아집니다. 한 마디로 보다 창의적인 답변이 가능해지며, 이를 서비스의 성격에 따라 조정할 수 있습니다🔥
공식문서에서는 애완동물의 이름을 생성하는 서비스를 예시로 코드를 설명합니다.
const completion = await openai.createCompletion({
model: "text-davinci-003",
prompt: generatePrompt(req.body.animal),
temperature: 0.6,
});
이처럼 model과 prompt, temperature를 지정해 요청을 보낼 수 있습니다.
function generatePrompt(animal) {
const capitalizedAnimal = animal[0].toUpperCase() + animal.slice(1).toLowerCase();
return `Suggest three names for an animal that is a superhero.
Animal: Cat
Names: Captain Sharpclaw, Agent Fluffball, The Incredible Feline
Animal: Dog
Names: Ruff the Protector, Wonder Canine, Sir Barks-a-Lot
Animal: ${capitalizedAnimal}
Names:`;
}
이렇게 대화 맥락을 제시한다면 특정 목적으로 보다 정확한 응답을 얻을 수 있습니다.
Openai-quickstart-node🔽
OpenAI에서 Quickstart 레포지토리를 제공하지만 몇 가지 불편한 부분이 있었습니다.
- GPT 모델이 이전 대화를 기억하지 못하고 일회성 응답만 함
- TypeScript나 CSS-in-JS 라이브러리 세팅을 새로 해줘야 함
- UI, 폴더 구조, 로직, 변수명 등이 새로 커스텀하기 불편함
그럼 직접 만들어보자!
지금 당장 생성 AI 활용 프로덕트 아이디어가 있는 것은 아니었지만, 다른 사람들이 보다 간편하게 프로젝트를 시작할 수 있도록 Quickstart 레포지토리를 개조해 보일러플레이트를 만들어 보기로 마음먹었습니다. 기반은 Vercel에서 제공하는 CNA 템플릿 중 Next.js + TypeScript + styled-components 조합을 선택했습니다.
일주일 정도 틈틈이 개발해 다음과 같은 점들을 개선했습니다.
- TypeScript와 styled-components 지원
- GPT 모델이 일회성 응답이 아닌, 대화 맥락을 기억
- 더 직관적인 UI와 커스텀에 용이하도록 구조 변경
Next.js를 사용하는 이유는 API Key 보안과 관련 있습니다. 브라우저에서 바로 API Key를 호출하면 네트워크 탭이나 로드되는 자바스크립트에서 유출될 수 있어 환경변수로 불러와 server-side API route에 적용해 주어야 합니다. 혹시 몰라서 이슈를 올리니 외국인 고수 분이 친절하게 답변해 주셨습니다👍
OpenAI API Key Security in Next.js🔽
이전 대화를 기억하게 하는 것은 조금 무식한 방법을 사용했는데, 입력과 결과 값들을 배열에 저장해서 요청을 보낼 때마다 대화 맥락을 문자열로 앞에 추가했습니다.
[pages/index.tsx]
const [input, setInput] = useState("");
const [result, setResult] = useState("");
const [conversation, setConversation] = useState([]);
const [isLoading, setIsLoading] = useState(false);
async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
setIsLoading(true);
try {
const response = await fetch("/api/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
conversation: conversation
.map((sentence, i) => (i % 2 === 0 ? `Me: ${sentence}` : `You: ${sentence}`))
.join("\n"),
input: input,
}),
});
const data = await response.json();
if (response.status !== 200) {
throw data.error || new Error(`Request failed with status ${response.status}`);
}
setResult(data.result);
setConversation([...conversation, input, data.result.replace(/^\s+|\s+$|\n+/g, "")]);
setInput("");
setIsLoading(false);
} catch (error) {
// Consider implementing your own error handling logic here
console.error(error);
alert(error.message);
setIsLoading(false);
}
}
구현한 함수는 위와 같습니다.
[pages/api/generate.ts]
function generatePrompt(conversation: string, input: string) {
return `
${SETTING.PREFIX}
${conversation}
Me: ${input}
You:
`;
}
프롬프트는 위와 같이 생성합니다. PREFIX는 모든 요청에 포함할 사전 값이라고 볼 수 있습니다.
[setting.ts]
export const SETTING = Object.freeze({
PREFIX: "",
MODEL: "text-davinci-003",
TEMPERATURE: 0.6,
MAX_TOKENS: 3000,
});
모델 설정과 관련된 변수들을 따로 파일로 분리했습니다.
- PREFIX: 모든 요청의 앞에 포함할 기본 설정입니다. 언어, 역할, 어조 등이 해당될 수 있습니다
- MODEL: 모델에 따라 성능과 비용이 조금씩 다릅니다. 사용 가능한 모델을 선택할 수 있습니다
- TEMPERATURE: 앞서 설명한 모델을 fine-tuning할 수 있는 0에서 1 사이 숫자 값입니다
- MAX_TOKEN: 요청에 포함할 수 있는 최대 토큰 수입니다. 모델마다 최대값이 다릅니다
토큰 제한 안에서 PREFIX를 설정해 모델을 특정 목적으로 개조할 수 있습니다.
좋은 예시 (샘플) 수십개 + 질문 자동화 = Jasper, 뤼튼(wrtn)과 같은 서비스
Models🔽
완성된 보일러플레이트🔽
clone해서 바로 사용해 볼 수 있습니다! (실행 방법은 README 문서 참고)
긴 글 읽어 주셔서 감사합니다!! 🙇
재미있게 보셨다면 블로그나 글또 Slack에 감상을 남겨주세요!
보일러플레이트가 도움이 되었다면 깃헙 Star⭐도 부탁드립니다😊
'Generative AI > Conversation' 카테고리의 다른 글
GPTs/GPT Store 정리 (0) | 2024.01.16 |
---|---|
ChatGPT 활용 팁 정리 (2023.03) (0) | 2023.04.12 |