Post

[U+Pick #1] Function Calling 기반 결합 할인 챗봇 구축

[U+Pick #1] Function Calling 기반 결합 할인 챗봇 구축

✅ 오늘의 목표

  • GPT-4.1-mini Function Calling으로 가족결합 할인 계산 기능 구현
  • 프론트 React + 백엔드 Express 기반 챗봇 서비스 구축
  • 결합 할인 금액 계산 로직 모듈화 및 Function Schema 설계

🧩 문제 상황 / 배경

  • LG U+ 요금제에 대한 상담/추천 기능을 챗봇 형태로 제공하고 싶었음
  • 가족결합 할인 계산은 단순 텍스트 답변으로 제공하기에는 정확도가 낮고 복잡한 규칙을 포함
  • GPT Function Calling 기능을 사용하면 실제 계산 로직과 GPT의 자연어 처리를 유연하게 조합할 수 있다고 판단
  • React 기반의 심플한 챗 UI로 사용자 경험을 테스트하고자 함

🛠️ 해결 과정 / 코드 정리

🔸 프로젝트 구조

1
2
3
4
5
6
7
8
9
10
11
12
backend/
  ├── server.js
  ├── openai 
    ├── client.js
  ├── controllers
    ├── gptChat.js
  ├── functions
    ├── gptFunctions.js
    ├── calculateFamilyBundle.js
frontend/
  ├── components
    ├── ChatBox.jsx

🔸 가족결합 할인 계산 로직 모듈화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// backend/functions/calculateFamilyBundle.js
export function calculateFamilyBundle(prices) {
  const phoneCount = prices.length;
  const discounts = prices.map((price) => {
    if (phoneCount >= 4) {
      if (price >= 88000) return 8800;
      if (price >= 69000) return 6600;
      return 4400;
    }
    if (phoneCount === 3) {
      if (price >= 88000) return 6600;
      if (price >= 69000) return 5500;
      return 3300;
    }
    if (phoneCount === 2) {
      if (price >= 88000) return 4400;
      if (price >= 69000) return 3300;
      return 2200;
    }
    return 0;
  });

  const total = discounts.reduce((sum, d) => sum + d, 0);
  return { phoneCount, discounts, totalDiscount: total };
}

✅ 가격별로 할인율을 계산하는 함수를 별도로 분리
✅ 할인 총액, 인원수를 함께 리턴


🔸 Function Calling Schema 설계 - backend/functions/gptFunctions.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export const calculateFamilyBundleDiscountSchema = {
  name: "calculateFamilyBundleDiscount",
  description: "가족 결합 할인 계산",
  parameters: {
    type: "object",
    properties: {
      planNames: {
        type: "array",
        items: { type: "string" },
        description: "결합할 요금제 이름 리스트",
      },
      planPrices: {
        type: "array",
        items: { type: "number" },
        description: "결합할 요금제 가격 리스트",
      },
    },
  },
};

✅ GPT가 어떤 매개변수를 보내야 하는지 명확히 정의 (planNames, planPrices)
✅ ChatGPT가 상황에 따라 자동으로 함수 호출을 시도할 수 있도록 설계


🔸 GPT Chat 컨트롤러 구성 및 Function Calling 처리 흐름

이 서비스의 가장 핵심 부분은 gptChatHandler

  1. 사용자가 입력한 메시지를 GPT-4.1-mini로 전달
  2. 상황에 따라 Function Calling을 유도
  3. 실제 할인 계산 로직과 연계
  4. 최종 결과를 자연어 답변으로 변환 후 응답

✅ 코드 흐름

1
사용자 입력 → 키워드 룰 검사 → GPT 호출 → Function 호출 → 할인 계산 → 최종 답변

✅ systemPrompt 역할

1
2
3
4
5
const systemPrompt = `
당신은 친절하고 전문적인 LG U+ 통신 요금제 추천 상담사입니다.
...
- 고객이 요금제명 또는 요금제 가격을 입력한 경우, 반드시 calculateFamilyBundleDiscount 함수를 호출하세요.
`;

GPT가 어떤 어조로, 어떤 기능을 써야 하는지 명확하게 유도하는 역할
→ Function Calling을 자동으로 유도하기 위해 “반드시 함수를 호출하세요” 문구 사용


✅ 키워드 기반 선처리 (Rule-based fallback)

1
2
3
4
5
6
7
8
9
10
11
const keywordRules = [
  {
    pattern: /지인\s?결합/,
    response:
      "지인 결합 할인에 대한 정보는 저희 서비스에서 확인할 수 있어요\n`U+Pick url`",
  },
  {
    pattern: /어떤\s?결합.*(할인|가능|받을|있나|알려)/,
    response: "지인 결합과 가족 결합 중 어떤 결합으로 알려드릴까요?",
  },
];

→ GPT 호출 전에 패턴 매칭으로 즉시 응답이 가능한 경우 처리 → 챗봇 UX의 반응속도와 응답 일관성 확보 → 아주 단순한 FAQ 성 패턴들은 빠르게 대응 (API 비용 절감 효과도 있음)

? 정규식 코드 의미


✅ Function 호출 흐름

1
2
3
4
5
6
const response = await openai.chat.completions.create({
  model: "gpt-4.1-mini",
  messages: systemMessages,
  functions,
  function_call: "auto",
});

→ GPT가 상황을 보고 알아서 calculateFamilyBundleDiscount 를 호출할지 말지 선택하도록 구성 → 자동으로 사용자 입력과 함수 호출 흐름을 자연스럽게 연결


✅ 함수 호출 여부 판단

1
2
3
4
5
const funcCall = choice.message.function_call;

if (!funcCall) {
  return res.send(choice.message.content);
}

→ GPT가 함수 호출이 필요 없다고 판단한 경우, 바로 자연어 응답을 리턴 → 반면, 호출한 경우에는 arguments 를 파싱해서 할인 계산 단계로 넘어감


✅ 요금제명 or 가격 매핑 처리

1
2
3
4
5
6
7
8
9
10
if (args.planNames && Array.isArray(args.planNames)) {
  prices = args.planNames
    .map(findClosestPlanPrice) // testPlans 에서 price 가져옴
    .filter((p) => p !== null);
}
else if (args.planPrices && Array.isArray(args.planPrices)) {
  prices = args.planPrices
    .map((p) => parseInt(p, 10))
    .filter((p) => !isNaN(p));
}

→ GPT가 planNames 로 요금제명을 주는 경우도 있고, planPrices 로 숫자를 주는 경우도 있음 → 두 가지 모두 대응할 수 있도록 분기 처리


findClosestPlanPrice() 함수 설계 의도

1
2
3
4
5
6
7
8
9
function findClosestPlanPrice(planName) {
  const exactMatch = testPlans.find((p) => planName.includes(p.name));
  ...
  const aliasMatch = testPlans.find((p) =>
    p.alias?.some((alias) =>
      lowerInput.includes(alias.toLowerCase().replace(/\s/g, ""))
    )
  );
}

→ 사용자가 입력한 요금제명이 정확하지 않거나 띄어쓰기 오류가 있어도 → 유사한 alias 매칭으로 최대한 가격을 찾아낼 수 있도록 설계 → 실제 상담 시 사용자가 입력하는 텍스트가 깨끗하지 않은 경우가 많기 때문에 robust한 매칭 로직 필요


✅ 할인 계산 후 최종 응답 흐름

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const result = calculateFamilyBundle(prices);

const finalResponse = await openai.chat.completions.create({
  model: "gpt-4.1-mini",
  messages: [
    ...messages,
    {
      role: "function",
      name: "calculateFamilyBundleDiscount",
      content: JSON.stringify(result),
    },
  ],
});
res.send(finalResponse.choices[0].message.content);

→ 할인 계산 후, function 응답을 GPT에 다시 넘겨서 자연어로 표현하게 구성 → GPT가 최종적으로 사용자가 읽기 쉬운 형태로 응답을 완성 → ex) “3회선 기준 총 17,600원의 할인이 적용됩니다!”


✅ 전체 설계 포인트

포인트설명
Function Calling정확한 계산이 필요한 부분과 GPT의 자유로운 응답을 적절히 분리
Robust 매칭사용자의 입력 변형에 대응하는 유사도 매칭 로직
선처리 룰패턴 매칭으로 간단한 질문은 빠르게 대응
Modular 구조할인 로직/스키마/API 요청부가 명확히 모듈화됨
대화 기록 유지이전 messages 전체 history를 유지하며 컨텍스트 반영

✅ 개인적인 느낀 점

  • gpt-4.1-mini 모델이라도 Function Calling이 잘 동작해서 규칙 기반 계산이 매우 안정적으로 연결 가능
  • 기존 룰 기반 챗봇보다 훨씬 자연스러운 UX 구현 가능
  • Function arguments 매핑에서 GPT가 가끔 불완전한 arguments를 주는 경우가 있어서, fallback이나 validation 로직을 좀 더 강화하면 좋겠다는 개선 포인트도 발견

🔸 프론트엔드 챗 UI 구성 - frontend/components/ChatBox.jsx

1
2
3
4
5
6
7
8
const [messages, setMessages] = useState([]);
const res = await fetch("http://localhost:5001/api/gpt/chat", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ messages: newMessages }),
});
const data = await res.text();
setMessages([...newMessages, { role: "assistant", content: data }]);

✅ React에서 간단한 chat history 구성
✅ 사용자가 메시지 입력 시 서버에 기존 messages 배열과 함께 전달
✅ 서버 응답을 받아 chat 창에 append
✅ loading 표시 추가


🖼️ 작업 결과물


✅ 결과 및 느낀 점

  • GPT Function Calling을 활용하면 규칙 기반 계산과 자연어 응답을 효과적으로 결합할 수 있다는 점을 경험
  • 기존 단순 텍스트 프롬프트 기반보다는 신뢰도 높은 할인 계산이 가능
  • React 간단한 챗 UI를 통해 빠르게 프로토타이핑 가능
  • GPT-4.1-mini의 응답 속도가 생각보다 빨라 실시간 상담형 UX에도 적용 가능성을 확인

Reference


END

This post is licensed under CC BY 4.0 by the author.