ChatGPT API 를 활용한 기능 개발하기

D uuu·2025년 2월 25일
0

Next.js14 프로젝트

목록 보기
17/17
post-thumbnail

잇핏 프로젝트에서는 사용자가 목표 몸무게에 도달할 수 있도록 방향성을 제시하고, 목표 달성 가능성을 분석하는 기능을 구현하고자 했다. 마치 퍼스널 트레이너처럼 사용자의 건강 데이터를 바탕으로 피드백을 제공하는 것이 핵심이었는데, 고민 끝에 AI가 이러한 역할을 수행할 수 있겠다는 생각이 들었고, OpenAI 를 활용하여 기능을 개발해보기로 했다.

💡 실제 구현 화면

ChatGPT API 이용하기

  1. OpenAI 페이지에 접속해 API Key 를 먼저 발급받아야 한다.

https://openai.com/index/openai-api/

로그인 후에 오른쪽 상단에 프로필을 클릭해서 Your Profile > API Keys 에서 key 를 생성해주면 된다. 이때 발급받은 키는 .env 에 저장해준다.

근데 User API keys 가 프로젝트 API 키로 대체되었다고 한다. 사용법은 마찬가지이니 새로 발급할땐 PROJECT > API keys 에서 발급받으면 될듯싶다.

  1. OpenAI는 선불 충전 방식으로 최소 $5부터 충전 가능하다. Billing 에서 Payment mothod 에 들어가 결제 카드를 등록 한 뒤에 원하는 금액을 충전하면 된다.

사용하는 모델에 따라서 비용이 달라지는데 gpt-4o-mini 모델은 상대적으로 경량화된 모델로 비용이 낮은 편이다. 테스트를 위해 꽤 많은 요청을 보냈지만, $5에서 $4.88 로 줄어든 정도로 생각보다 합리적인 비용 구조를 보였다.

ChatGPT API 호출 방법

ChatGPT API를 호출하는 방법에는 SDK를 이용하는 방법과 REST API를 직접 호출하는 방법이 있다. 이번 구현에서는 REST API를 활용하는 방법을 사용했다. 기본적으로 다른 API 호출과 유사하게 URL과 API Key를 포함한 요청을 보내면 된다.

const apiKey =NEXT_PUBLIC_GPT_API_KEY;
const url = 'https://api.openai.com/v1/chat/completions';

const response = await fetch(url, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
        model: 'gpt-4',
        messages: [
            { role: 'system', content: 'You are a helpful assistant.' },
            { role: 'user', content: 'Write a haiku about recursion in programming.' },
        ],
    }),
});

const data = await response.json();
console.log(data.choices[0].message.content);

ChatGPT 메시지 객체 구성

ChatGPT API는 messages 배열을 사용하여 대화의 흐름을 정의한다. 각 객체는 role(역할)과 content(내용)으로 구성되며, 세 가지 주요 역할이 있다.

1. system
AI의 성격과 응답 스타일을 설정하는 메시지로, 특정 역할을 부여할 수 있다.

{
  "role": "system",
  "content": "너는 전문 셰프야. 항상 요리에 대한 정확한 정보를 제공해야 해."
}

2. user
사용자의 질문이나 요청을 담는 메시지이다. 모델에게 질문이나 요청을 보낼 때 사용된다. content 부분에 유저의 입력값을 변수로 받아서 넣어줄 수 있다.

{
  "role": "user",
  "content": "까르보나라를 맛있게 만드는 레시피를 알려줘."
}

3. assistant
모델이 제공하는 응답 메시지이다. 실제 결과가 이 속성에 담기는데 data.choices[0].message.content 으로 AI 응답을 가져올 수 있다.

  {
    "role": "assistant",
    "content": "까르보나라를 만들려면 먼저 베이컨을 볶고, 달걀과 치즈를 섞은 소스를 준비하세요."
  }

역할 설정하기

messages 객체를 활용해서 ai 의 역할을 구체적으로 지정해주고 어떤 응답을 받을지에 대해서도 세세하게 커스텀할 수 있다.

나의 경우에는 헬스케어 전문가로 역할을 설정했다. "사용자의 진행 상황에 따라 건강 관련 분석과 조언을 제공하고, 이를 한국어로 피드백 달라" 는 식으로 구체적으로 작성할수록 ai 가 정확한 답변을 줄 가능성이 높다. 그리고 원하는 응답 형식에 맞게(JSON) 작성해야 하며, 다음과 같은 필드가 포함되어 있어야 한다는 식으로 응답 형식까지 구체적으로 지정해주었다.

const messages = [
    {
        role: 'system',
        content: `
            You are a health expert. Analyze the user's progress based on the provided data and provide actionable advice.
            Respond in Korean.
        `,
    },
    {
        role: 'system',
        content: `
            - Assess the likelihood of achieving the goal based on the current progress.
            - Analyze the balance between calories burned and consumed.
            - Provide 3 practical tips to improve progress.
            - Write a motivational message for the user.

            **Respond in the format below:**
            {
                "possibility": "likelihood of goal achievement",
                "evaluation": "habit analysis",
                "tips": ["tip1", "tip2", "tip3"],
                "motivation": "motivational message"
            }
        `,
    },

]

그리고 이런식으로 body 에 messages 배열을 담아서 요청을 보내면 된다.

const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
        Content-Type': 'application/json',
       'Authorization': `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
        model: 'gpt-4o-mini',
        messages,
        temperature: 0.7,
      }),
   });

const responseData = await response.json();
const assistantMessage = responseData.choices[0].message.content;

실제로 이런 식으로 응답이 내려오기 때문에 사용하는 UI 에 맞게 응답을 가공할 수 있다는 부분이 편리했다.

{
 "cheering":"목표 체중에 도달하기 위해 꾸준히 노력하는 모습이 정말 멋집니다!,
 "evaluates":"사용자의 주간 체중 변화를 보면, 62kg에서 60kg으로 감소한 것을 확인할 수 있습니다. 그러나 목표 체중인 57kg에 도달하기 위해서는 더 많은 체중 감량이 필요합니다. 현재 진행률인 10%와 목표 기간을 고려했을 때, 체중 감량의 가능성은 약 30%로 평가됩니다.",
 "tips":["주 3회 이상 유산소 운동(걷기, 자전거 타기 등)을 추가하여 칼로리 소모를 늘리세요."~~],
 "possibility":"30%",
 "createdAt":"2025-02-25",
 "deadline":"2025-03-04"
 }

비용 절감 및 최적화 작업

openAI API 호출은 유료 서비스이기 때문에, 불필요한 요청을 최소화하여 비용을 절감하는 것이 중요한 과제였다. 특히, 사용자의 데이터가 변하지 않았음에도 동일한 요청을 반복적으로 보내는 것은 비효율적일 뿐만 아니라, 응답의 일관성을 저해하는 문제를 야기했다. AI 모델은 매번 약간씩 다른 답변을 생성할 수 있기 때문에, 동일한 입력값에 대해 다른 결과가 반환되면 사용자 경험이 저하될 가능성도 있었다. 따라서, 요청의 빈도를 최적화하고 불필요한 호출을 줄이면서도, 안정적인 응답을 유지할 수 있도록 개선이 필요했다.

⭐️ 개선 요청
1. open AI 요청을 최적화해서 비용 절감
2. 유저 정보 업데이트 주기를 고려하여 요청 빈도를 조절

캐시 적용하기

먼저, AI 요청의 빈도를 줄이기 위해 브라우저 캐싱 전략을 도입했다. 사용자의 체중 변화 및 기타 유의미한 데이터가 업데이트되는 주기를 일주일 단위로 설정하고, 같은 데이터로 반복적인 AI 요청이 발생하지 않도록 제한하는 방식이다.

사용자가 AI 분석을 요청하면, 먼저 브라우저 쿠키를 확인한다. 쿠키에 데이터가 존재하고 설정된 만료 기한이 지나지 않았다면, 기존 데이터를 그대로 반환한다. 캐시 데이터가 존재하지 않는 경우엔 새로운 AI 요청을 수행하고 그 결과를 다시 쿠키에 저장한다.

이 방법을 적용하면서, 단기적인 중복 요청을 효과적으로 방지할 수 있었다. 하지만 브라우저 쿠키는 사용자가 직접 삭제할 수도 있으며, 기기나 브라우저 환경에 따라 유지되지 않을 가능성이 있었다. 따라서, 더 안정적인 저장 방식이 필요했다.

서버 단위 캐싱 (DB 활용)

브라우저 캐시만으로는 충분하지 않다고 판단하여, AI 응답을 데이터베이스에 저장하는 방식을 추가로 도입했다.

사용자가 AI 분석을 요청하면, 먼저 브라우저에 쿠키를 확인한다. 쿠키에 데이터가 없거나 만료된 경우, 서버(DB)에서 기존 응답 데이터를 조회한다. DB에서 최신 데이터가 존재하면, AI 요청 없이 기존 데이터를 반환한다. 브라우저 캐시와 DB 모두에 유효한 데이터가 없을 경우, 새로운 AI 요청을 수행하고, 그 결과를 DB와 쿠키에 저장한다.

이렇게 하면 1차적으로는 클라이언트에서 요청을 필터링하고, 2차적으로는 서버에서 AI 호출을 최적화하는 이중 캐싱 구조를 구축할 수 있다.

또 기존에는 브라우저에서 AI 에 요청을 보내는 로직이 작성되어 있었다면 서버를 통해 중계하는 구조로 변경하여 API 키가 유출될 위험성도 줄일 수 있었다. 기본적으로 AI API 호출을 최적화하는 것이 목표였지만, 단순히 비용 절감뿐만 아니라 서비스의 성능, 안정성, 보안성까지 함께 개선할 수 있는 작업이었다고 생각한다.

profile
배우고 느낀 걸 기록하는 공간

0개의 댓글

관련 채용 정보