s00jin 님의 블로그

6. [FastAPI + OpenAI API] 사전 공부 | OpenAI API 입력에 따른 함수 호출(Function Calling) 본문

프로젝트/AI 분석 가계부

6. [FastAPI + OpenAI API] 사전 공부 | OpenAI API 입력에 따른 함수 호출(Function Calling)

s00jin 2025. 6. 26. 11:16

api를 설계하면서 llm 쪽을 어떻게 설계할 지 고민했다.

우리의 챗봇 기능에는 크게

  • 사용자 입력을 받아, 소비 내역으로 저장
  • 사용자가 피드백을 원하면 피드백 반환

이렇게 있다.

 

처음에는 저 2개의 기능의 api 따로 설계했다. 하지만 생각해보니

챗봇 인터페이스에서 사용자와 대화하며 넘어오는 데이터들인데…이걸 따로 분리하는게 맞나..?라는 생각이 들었다.

 

그래서 사용자 입력

→ llm이 1차적으로 소비 내역인지 피드백 반환인지 구분
소비 내역이라면 함수 호출로 구조화 된 출력 반환 / 피드백이면 피드백 반환

위 흐름으로 다시 구현해봤다.

 

구현 코드

from openai import OpenAI
import os
import json
from datetime import datetime
from typing import Union
from pydantic import BaseModel
from fastapi import FastAPI, HTTPException

client = OpenAI(
    api_key=os.environ.get("OPENAI_API_KEY")
)

class ChatRequest(BaseModel):
    userId: int
    message: str

class ExpenseData(BaseModel):
    category: str
    amount: int
    date: str

class ChatResponse(BaseModel):
    type: str
    reply: str
    data: Union[ExpenseData, None] = None

tools = [{
    "type": "function",
    "function": {
    "name": "save_expense",
    "description": "사용자의 소비 내역을 파싱하여 금액과 카테고리, 날짜를 저장",
    "parameters": {
        "type": "object",
        "properties": {
            "category": {
            "type": "string",
            "enum": ["카페", "식비", "교통", "의류", "문화", "공과금", "기타"],
            "description": "정해진 소비 카테고리 중 하나"
            },
            "amount": {
                "type": "integer",
                "description": "금액 (숫자만, 원단위)"
            },
            "date": {
                "type": "string",
                "description": "소비한 날짜"
            }
        },
        "required": ["category", "amount", "date"],
        "additionalProperties": False
    }
    },
    "strict": True
}]

def handle_message(message: str):

    today = datetime.today().strftime("%Y-%m-%d")

    response = client.chat.completions.create(
        model="gpt-4.1-nano",
        messages=[
            {"role": "system", "content": f"""너는 사용자 소비 내역을 정리해주는 가계부 어시스턴트야. 
            오늘 날짜는 {today}야.
            만약 사용자가 '오늘', '어제', '그제' 같은 말을 하면 이를 기준으로 정확한 날짜(YYY-MM-DD)로 바꿔줘.
            소비 내역을 말하면 카테고리와 금액, 날짜를 파싱해서 JSON 형태로 반환해주고, 
            일반적인 질문이나 조언 요청이면 텍스트로 피드백을 반환해줘."""},
            {"role": "user", "content": message}
        ],
        tools=tools,
        tool_choice="auto"
    )

    msg = response.choices[0].message

    # 함수 호출이 있는 경우
    if msg.tool_calls:
        args = json.loads(msg.tool_calls[0].function.arguments)
        return {
            "type":"expense",
            "reply": f"{args['category']} 카테고리로 {args['amount']}원을 기록했어요.",
            "data": {
                "category": args["category"],
                "amount": args["amount"],
                "date": args["date"]
            }
        }
    else:
        return {
            "type":"feedback",
            "reply": msg.content
        }
    

# # 함수 코드 실행 - 모델의 응답을 구문 분석하고 함수 호출을 처리
# tool_call = response.choices[0].message.tool_calls[0]
# args = json.loads(tool_call.function.arguments)

# print(args)

 

API 테스트

결과가 잘 나온다.

(피드백 결과가 나온 스크린샷을 실수로 삭제했다...ㅎ)