[ChatGPT] OpenAI의 function_call활용해서, 반말 챗봇 만들기
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를 활용해서 보정해서 응답하는 느낌과 비슷한 구조인듯
- 토큰값은 적게 나올지 모르겠다. 많이 나오겠지....ㅜ