이번 시간에는 현재 구현된 AI Chatbot 시스템에서 기존에 작성된 OpenAI Instruction 에 추가로 특정 문구를 추가하여 추천 질문을 사용자에게 전달할 수 있는 시스템을 구현한 내용을 기반으로 블로그 포스팅을 진행하도록 하겠습니다.
추천 질문 시스템
해당 게시글에서 다루는 추천 질문 시스템은 사용자가 AI Chatbot 과 상호작용 과정에서 다음에 어떤 질문을 할 수 있을지에 대한 제안을 사용자에게 출력해주는 시스템입니다.
이를 통해 사용자는 더욱 향상된 경험을 얻을 수 있고, 효과적으로 정보를 전달할 수 있을 것으로 생각되며, 특히 사용자가 Chatbot 에게 무엇을 물어봐야 할 지 모르는 상황이 있을 수 있는데 이때 효과적인 대안이 될 것으로 생각됩니다.
OpenAI Assistant Instruction ?
OpenAI Assistant 의 Instuction 은 특정한 답변 형식을 제어할 수 있는 지침으로 Chatbot이 어떻게 응답할 지 지정할 수 있는 지침이라 볼 수 있습니다.
해당 작업 과정에서는 기존 작성된 Instruction에서 추가로 두 가지 지침을 추가하였습니다.
- JSON 형식 응답 : 모든 응답을 다음과 같이 JSON 형태로 출력할 수 있도록 합니다. {"response": "답변", "Suggested question": ["추천 질문1", "추천 질문2", "추천 질문3"]} 과 같은 형식으로 추천 질문의 경우 배열의 형태로 응답해주세요.
- 추천 질문 지침 : 추천 질문의 경우 면접관이 "사용자"에 대해 궁금할 사항을 추천 질문으로 응답해주세요
백엔드 코드 구현
OpenAI 응답 (JSON 응답) 처리 및 MongoDB 저장 코드
아래와 같은 코드를 작성하여 사용자의 질문을 Assistant에서 처리 후 JSON 응답이 발생할 경우 response_data, suggested_questions 변수에 각각 응답과 추천 질문을 저장하여 사용자에게 전달할 수 있도록 했습니다.
하지만, AI 응답의 경우 명확하게 지정이 불가능하기에 가끔 JSON 응답이 출력되지 않는 경우가 발생할 수 있는데 해당 경우를 위해 JSON 응답이 발생하지 않을 경우 파싱 에러가 프린트 될 수 있도록하고, response_content 변수에 응답을 저장하고, suggested_questions 변수의 경우 빈 배열로 응답할 수 있도록 지정했습니다.
만약 빈배열이 아닌 기본 질문을 삽입하여 응답으로 전달할 경우 최초 추천 질문이 사용자에게 응답으로 전달되도록 지정할 수도 있습니다.
if last_message:
try:
response_data = last_message.content[0].text.value
response_json = json.loads(response_data) # OpenAI Assistant의 응답이 JSON 형식임을 가정
response_content = response_json.get("response", "")
suggested_questions = response_json.get("Suggested question", [])
except (json.JSONDecodeError, KeyError, TypeError) as e:
print(f"JSON parsing error: {e}")
response_content = last_message.content[0].text.value
suggested_questions = []
data = {
"time": datetime.datetime.now(datetime.UTC),
"client_ip": client_ip,
"question": user_message,
"response": response_content,
"suggested_questions": suggested_questions
}
try:
mongo.db.responses.insert_one({
"_time": data["time"],
"client_ip": data['client_ip'],
"user_message": data['question'],
"assistant_message": data['response'],
"suggested_questions": data['suggested_questions']
})
except Exception as e:
print(f"Mongodb에 응답을 저장하는데 실패했습니다. {e}")
return jsonify({"response": "데이터베이스에 응답을 저장하는데 실패했습니다. project5587@gmail.com 로 문의바랍니다."}), 500
return jsonify({"response": response_content, "suggested_questions": suggested_questions, "thread_id": thread.id})
else:
return jsonify({"response": "Assistant로부터 응답이 없습니다. 잠시후 다시 시도해주세요."}), 500
프론트엔드 코드 구현 (Chatbot Component)
아래와 같이 Chatbot Component 구현하였습니다.
import React, { useState, useRef, useEffect } from "react";
import styles from "../styles/Chatbot.module.css";
import axios from "axios";
function Chatbot() {
const [isChatbotVisible, setIsChatbotVisible] = useState(false);
const [input, setInput] = useState("");
const [messages, setMessages] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [suggestedQuestions, setSuggestedQuestions] = useState([
"이종욱에 대해 말해주세요.",
"이종욱의 이력을 알려주세요.",
"이종욱의 성격의 장단점을 알려주세요."
]);
const messagesEndRef = useRef(null);
const chatbotContainerRef = useRef(null);
const handleChatbot = () => {
setIsChatbotVisible(!isChatbotVisible);
};
const handleClickOutside = (event) => {
if (chatbotContainerRef.current && !chatbotContainerRef.current.contains(event.target)) {
setIsChatbotVisible(false);
}
};
useEffect(() => {
if (isChatbotVisible) {
document.addEventListener("mousedown", handleClickOutside);
} else {
document.removeEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [isChatbotVisible]);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const sendMessage = (question) => {
const getChatbotResponse = async () => {
const threadId = localStorage.getItem("thread_id");
const requestData = {
question: question || input
};
if (threadId) {
requestData.thread_id = threadId;
}
try {
const response = await axios.post(
"https://api.jongwook.xyz/chat",
requestData,
{
headers: {
"Content-Type": "application/json",
},
}
);
const botResponse = response.data.response;
const responseThreadId = response.data.thread_id;
const newSuggestedQuestions = response.data.suggested_questions;
// Save thread_id to localStorage
if (responseThreadId) {
localStorage.setItem("thread_id", responseThreadId);
}
setMessages((prevMessages) => [
...prevMessages,
{ text: botResponse, user: "bot" },
]);
if (newSuggestedQuestions) {
setSuggestedQuestions(newSuggestedQuestions);
}
else {
setSuggestedQuestions(["이종욱에 대해 말해주세요.", "이종욱의 이력을 알려주세요.", "이종욱의 성격의 장단점을 알려주세요."]);
}
} catch (error) {
console.error(error);
setMessages((prevMessages) => [
...prevMessages,
{ text: error.response.data.response, user: "bot" },
]);
} finally {
setIsLoading(false);
}
};
if ((question || input).trim()) {
setMessages((prevMessages) => [
...prevMessages,
{ text: question || input, user: "me" },
]);
setInput("");
setIsLoading(true);
setSuggestedQuestions([]);
getChatbotResponse();
}
};
return (
<>
<div className={styles.chatbot}>
<button className={styles.chatbotBtn} onClick={handleChatbot}>
<img
src="https://freesvg.org/img/1538298822.png"
alt="chatbot"
className={styles.chatbotImg}
/>
</button>
</div>
{isChatbotVisible && (
<div className={styles.overlay} onClick={handleChatbot}>
<div className={styles.chatbotContainer} ref={chatbotContainerRef} onClick={(e) => e.stopPropagation()}>
<div className={styles.chatHeader}>AI Chatbot</div>
<div className={styles.chatMessages}>
{messages.map((message, index) => (
<div
key={index}
className={`${styles.message} ${message.user === "me" ? styles.me : styles.bot}`}
>
<p>{message.text}</p>
</div>
))}
{isLoading && (
<div className={styles.loading}>
<p>Loading...</p>
</div>
)}
<div ref={messagesEndRef} />
</div>
{suggestedQuestions.length > 0 && (
<div className={styles.suggestedQuestions}>
{suggestedQuestions.map((question, index) => (
<button
key={index}
className={styles.suggestedQuestionBtn}
onClick={() => sendMessage(question)}
disabled={isLoading}
>
{question}
</button>
))}
</div>
)}
<div className={styles.chatInputContainer}>
<input
type="text"
className={styles.chatInput}
value={input}
placeholder="Type a message..."
onChange={(e) => setInput(e.target.value)}
onKeyUp={(e) => e.key === "Enter" && !isLoading && sendMessage()}
disabled={isLoading}
/>
<button
className={styles.sendButton}
onClick={() => sendMessage()}
disabled={isLoading}
>
Send
</button>
</div>
</div>
</div>
)}
</>
);
}
export default Chatbot;
실제 구현
홈페이지 접근 후 우측 하단 Chatbot 버튼 클릭 시 다음과 같이 추천 질문 시스템이 포함된 인터페이스 출력
이후 추천 질문에 출력된 질문 클릭 시 아래와 같이 백엔드 서버로 질문이 전달되며, 해당 질문에 대한 추가적인 추천 질문이 인터페이스 내에 출력되는 것을 볼 수 있음
'Project > resume' 카테고리의 다른 글
AI Chatbot 응답 개선 : 프론트엔드 코드 최적화 (0) | 2024.08.24 |
---|---|
MongoDB, Flask, Next.js 를 활용한 동적 블로그 컴포넌트 구현 (0) | 2024.08.17 |
[Github Actions] CI/CD 파이프라인 구축 / Github Actions 사용법 / Snyk Application CI 파이프라인 통합 / EC2 배포 자동화 (0) | 2024.08.13 |
MongoDB를 이용하여 Python에서 IP기반 요청 제한 구현 (0) | 2024.08.12 |
OpenAI API를 이용한 이력서 기반 AI ChatBot 백엔드 코드 구현 (0) | 2024.07.19 |