AI & ML/사용하기

[ChatGPT] OpenAI의 function_call활용해서, 반말 챗봇 만들기

Ssul 2024. 1. 11. 00:08

 

0. 챗봇의 기본구조

class ChatLogCreate(generics.CreateAPIView):
    queryset = ChatLog.objects.all()
    serializer_class = ChatLogSerializer
    permission_classes = [AllowAny]

    def post(self, request, *args, **kwargs):
        client_id = request.data.get('client')
        client = Client.objects.get(id=client_id)

        if client.chat_counter >= 20:
            return Response({"detail": "대화 횟수가 20회를 초과하였습니다."}, status=status.HTTP_400_BAD_REQUEST)
        serializer = ChatLogSerializer(data=request.data)
        if serializer.is_valid():
            # client의 대화 저장
            client_chat_log = ChatLog(client=client, content=serializer.validated_data['content'], is_ai=False)
            client_chat_log.save()

            client.chat_counter += 1
            client.save()

            # AI 대답 생성 및 저장
            ai_response = generate_ai_response2(serializer.validated_data['content'], client_id)
            print(ai_response)
            # modify_ai_response = json.loads(ai_response)
            # print(modify_ai_response)
            ai_chat_log = ChatLog(client=client, content=ai_response, is_ai=True)
            ai_chat_log.save()

            return Response({'content': ai_chat_log.content}, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

- ChatLogCreate class를 만든다

- 사용자가 채팅을 입력하면, ChatLogCreate에서 post를 호출

- if client.chat_counter >= 20: 사용자 정보를 가져와서, 채팅을 몇번했는지 체크(20번 이상이면 호출불가)

- if serializer.is_valid(): 입력데이터를 시리얼라이져를 통하여 유효성 검사를 하고,

- client_chat_log = ChatLog(client=client, content=serializer.validated_data['content'], is_ai=False): 유효성 검사 통과하면, 입력된 채팅 db에 저장(사용자 입력데이터이니, is_ai=False로)

- 채팅 카운터 증가시키고

- ai_response = generate_ai_response(serializer.validated_data['content'], client_id): 사용자 입력을 openai의 api로 전달

generate_ai_reponse는 일반 존댓말 채팅 api 호출

generate_ai_reponse2는 function_call을 활용한 반말 api 호출

- ai_chat_log = ChatLog(client=client, content=ai_response, is_ai=True): 응답받은 gpt응답 db에 저장

- Response({'content': ai_chat_log.content}, status=status.HTTP_201_CREATED): 클라이언트에 gpt응답 전달

 

1. langchain을 활용한 일반(존댓말) 챗봇(generate_ai_reponse)

def generate_ai_response(user_message, client_id):
    # 지역 변수로 messages 초기화
    messages = []

    # 사용자 주호소문제 가져오기
    instructions = GptSystemMessage.objects.get(message_type='first').message_text
    messages.append(SystemMessage(content=instructions))
    # print(instructions)

    # 오늘의 날짜와 시간 가져오기
    today = timezone.now().date()

    # 오늘 생성된 대화 이력만 불러오기
    chat_logs = ChatLog.objects.filter(client_id=client_id, created_at__date=today).order_by('-created_at')[:4]
    chat_logs = reversed(chat_logs)
    # print(chat_logs)

    for log in chat_logs:
        if log.is_ai:
            messages.append(AIMessage(content=log.content))
        else:
            messages.append(HumanMessage(content=log.content))

    # print('messages------------------')
    print(messages)

    # chat_llm = ChatOpenAI(model_name="gpt-4")
    chat_llm = ChatOpenAI(model_name="gpt-3.5-turbo-1106")
    result = chat_llm(messages)
    # print('result---------------------')
    # print(result)

    return result.content

- messages=[]: 앞으로 이뤄질 메세지를 저장할 배열 선언

- instructions = GptSystemMessage.objects.get(message_type='first').message_text: 시스템 메세지를 매번 코드로 수정하기 불편하니, db에 넣어놓고, admin에서 수정 가능하도록 함

- 시스템 메세지 넣어주고,

- chat_logs = ChatLog.objects.filter(client_id=client_id, created_at__date=today).order_by('-created_at')[:4]: 지금까지 대화내용을 가져오는데, 토큰비용이 무서우니, 오늘 날짜 기준, 최근대화 4개만 가져오기

- for log in chat_logs: 최근 4개의 대화내용을 ai메세지와 user메세지를 구분해서 messages 넣어줌

- chat_llm = ChatOpenAI(model_name="gpt-3.5-turbo-1106"): langchain의 ChatOpenAI를 활용해서 사용 모델 설정

- result = chat_llm(messages): 시스템메세지 + 최근대화4개(user입력채팅포함)를 gpt api에 호출하여 응답가져옴

- 해당응답을 ai_response으로 리턴

존댓말을 잘하는 상담사 친구완성

 

2. function_call을 활용하여 반말(친근한)하는 상담챗봇 만들기

def generate_ai_response2(user_message, client_id):
    # 지역 변수로 messages 초기화
    messages = []

    # 사용자 주호소문제 가져오기
    instructions = GptSystemMessage.objects.get(message_type='first').message_text
    # 시스템 메세지 입력
    system_content = { "role": "system", "content": instructions }
    messages.append(system_content)

    # 오늘의 날짜와 시간 가져오기
    today = timezone.now().date()

    # 오늘 생성된 대화 이력만 불러오기
    chat_logs = ChatLog.objects.filter(client_id=client_id, created_at__date=today).order_by('-created_at')[:4]
    chat_logs = reversed(chat_logs)

    for log in chat_logs:
        if log.is_ai:
            messages.append({"role": "assistant", "content": log.content})
        else:
            messages.append({"role": "user", "content": log.content})

    print(messages)

    # OpenAI API에 요청 보내기
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-1106",
        messages=messages,
        functions=[
            {
                "name": "get_different_responses",
                "description": "답변을 반말로 하기",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "response_down": {
                            "type": "string",
                            "description": "답변을 반말로 바꿈"
                        }
                    },
                    "required": ["response_down"]
                }
            }
        ],
        function_call={"name": "get_different_responses"}
    )
    # 결과 반환(문자열로 넘어옴)
    response_str = response.choices[0].message["function_call"]["arguments"]
    # dict로
    response_dict = json.loads(response_str)

    # 결과 반환
    return response_dict["response_down"]

- *비슷한 내용은 생략하겠음

- langchain을 사용하지 않기 때문에 직접 시스템메세지, assistant메세지, user메세지 입력

messages.append(system_content)

messages.append({"role": "assistant", "content": log.content})
messages.append({"role": "user", "content": log.content})

- API에 호출을 보낼때 functions라는 요소를 넣어서 보내야 한다.(function_call은 안 넣어도 작동되지만, 넣는 예제를 많이 봐서 넣었다)

- 중요한 부분은 두부분인데, properties 안의 내용이다.

- "type": "string" : 답변으로 줄 리턴의 구조. 나는 채팅 응답이니 문자열로 선언

- "description": "답변을 반말로 바꿈": ai가 해석을 해서 적용을 해준다.("답변을 2문장으로 바꾸기"로 하면 2문장으로 응답)

- function_call이 실행되면, response.choices[0].message를 보면,

{

  "role": "assistant",

  "content": null,

  "function_call": {

     "name": "get_different_responses",

     "arguments" : "{ \"response_down\": \"대답문장 솰라솰라\" }"

  }

}

이렇게 되어 있음

- response.choices[0].message["function_call"]["arguments"]["response_down"]으로 하면 호출이 안됨. 위에 보는 것처럼 문자열로 응답을 함. arguments값을 받아서, json을 활용하여 dict로 변환해야 함

- response_dict["response_down"]: dict로 변환된 gpt응답 값을 ai_response로 전달하여 응답

 

반말을 한다

 

두문장으로 대답하기...잘 하넵 ㅋ

 

3. 정리

- function_call을 활용해서, 인터넷 검색, 날씨 검색도 가능하다

- function_call 역시 알고리즘이 많이 들어가는게 아닌, 나온 결과(응답)를 똑똑한 ai가 한번더 작업(반말로)을 해주는 느낌.

- 약간 기존에 채팅을 만들때, gpt답변을 바로 응답으로 보내는게 아니라, 여러개의 gpt api를 활용해서 보정해서 응답하는 느낌과 비슷한 구조인듯

- 토큰값은 적게 나올지 모르겠다. 많이 나오겠지....ㅜ