앞으로 출력(GPT 응답)을 JSON 형식으로 받도록 수정할 예정 : GPT에게 프롬프트를 바꾸어 JSON 구조로 응답하라고 지시해야 함 : 그전에 백엔드, 프론트엔드 코드 구조를 다시 살펴보자
프론트엔드(Chat.jsx)
사용자의 입력값은 inputMessage 상태로 저장됨
const [inputMessage, setInputMessage] = useState('');
백엔드로 POST 요청 (JSON 형식으로 message + thread_id 전달)
const response = await axios.post(`${API_URL}/sendMessage`, {
message: inputMessage,
thread_id: threadId
});
여기까지 사용자의 응답값을 JSON으로 보내는 것은 이미 잘 구현됨
백엔드(app.py)
사용자가 보낸 메시지를 Flask에서 받으면,
@app.route('/sendMessage', methods=['POST'])
def send_message():
프론트엔드에서 axios.post(...)로 보낸 JSON 요청의 'message'와 'thread_id_'를 꺼냄
user_message = request.json.get('message')
thread_id = request.json.get('thread_id')
여기까지 입력을 JSON으로 받고 있는 것까지 잘 구현됨 : 이때, 아래처럼 user_message가 JSON 형태로 GPT에게 전달이 됨 : HTTP 요청 본문을 JSON 형식으로 보내겠다는 의미가 됨 : 하지만 여기서 전달된 메시지에는 JSON 형태로 응답이 '오도록' 하는 지시나 프롬프트가 없음 : 즉, GPT는 그냥 자연어 텍스트로 응답을 생성하게 됨
requests.post(
f"https://api.openai.com/v1/threads/{thread_id}/messages",
...
json={ "role": "user", "content": user_message }
)
예를 들어, 현재는 위의 코드에서 봤다시피 아래와 같이 요청을 보내고 있다고 가정
{
"role": "user",
"content": "안녕하세요"
}
그러면 이걸 받은 GPT는 아래와 같이 '자연어 텍스트'로 대답함
"안녕하세요! 무엇을 도와드릴까요?"
하지만 앞으로 원하는 건 다음과 같은 JSON 형식의 응답임
{
"answer": "안녕하세요! 무엇이 궁금하신가요?",
"recommendations": [
"당신은 누구인가요?",
"무엇을 할 수 있나요?",
"어떻게 도와줄 수 있나요?"
]
}
이러한 형식은 json.load()로 파싱이 가능 : 중괄호, 큰따옴표, 콤마 구분, 배열 구조 등 문법적으로 JSON 규칙을 모두 지켰을 때, 파이썬 import json 모듈을 통해 문자열로 되어 있는 JSON 데이터를 파이썬 객체로 변환할 수 있음을 의미 : 위의 응답이 json_string이라고 했을 때, parsed = json.loads(json_string) : print(parsed["answer"])과 같은 형식으로 가능한 것 : answer으로 메시지 출력 : recommendatons로 추천 질문 버튼으로 출력이 가능하게 될 것임
OpenAI API 응답을 JSON 형식으로 받도록 수정 - 프롬프트 조작 + 응답 파싱
백엔드(app.py) 수정
# 1. GPT 프롬프트 수정
message_response = requests.post(
f"https://api.openai.com/v1/threads/{thread_id}/messages",
headers={
"Authorization": f"Bearer {OPENAI_API_KEY}",
"Content-Type": "application/json",
"OpenAI-Beta": "assistants=v2"
},
json={
"role": "user",
"content": f"""다음 형식의 JSON으로만 응답해 주세요.
절대로 설명, 문장, 코드블록 없이 순수 JSON만 출력하세요:
{{
"answer": "GPT가 생성한 대답을 여기에 넣어주세요.",
"recommendations": [
"관련된 추천 질문 1",
"관련된 추천 질문 2",
"관련된 추천 질문 3"
]
}}
이제 다음 질문에 응답해 주세요:
{user_message}"""
}
)
기존에는 사용자의 입력만 그대로 GPT에 전달했음 : "안녕하세요" 등 : 이제는 그 입력 앞에 GPT가 JSON 형태로 응답할 수 있도록 위와 같은 지시 프롬프트를 추가한 것 : 앞으로 반드시 JSON 형식으로 응답하도록 유도하는 것임
# 2. JSON 파싱 및 응답 구조 변경
try:
# JSON 파싱 시도
json_response = json.loads(cleaned_content)
return jsonify({
"response": json_response.get("answer", ""),
"recommendations": json_response.get("recommendations", []),
"thread_id": thread_id
})
except json.JSONDecodeError:
# JSON 파싱 실패 시 원본 응답 반환
return jsonify({
"response": cleaned_content,
"recommendations": [],
"thread_id": thread_id
})
이제 GPT가 생성한 응답 즉, 문자열을 json.loads()로 파싱 : 파싱에 성공하면 : answer, recommendations를 꺼내서 각각 프론트에 전달 : Flask에서 @app.route로 요청이 들어오면 그 함수가 마지막에 return jsonify(...)를 호출하는 것 : 이 jsonify()가 HTTP 응답으로 변환되어 프론트엔드로 전송됨 : 파싱에 실패하면 원래 텍스트만 넘기는 fallback 처리
프론트엔드(Chat.jsx) 수정
// 1. 상태 관리 확장
const [messages, setMessages] = useState([
{
id: 1,
text: "안녕하세요! 저에 대해 궁금한 점이 있으시면 무엇이든 물어보세요 🙂 ",
isUser: false,
isTyping: false,
displayText: "안녕하세요! 저에 대해 궁금한 점이 있으시면 무엇이든 물어보세요 🙂 ",
recommendations: [] // 여기에 추천 질문 배열 추가
}
]);
recommendations을 추가하여 각 메시지에 추천 질문 목록도 포함 : 즉, 메시지가 하나의 텍스트뿐만 아니라 추천 질문(버튼) 리스트도 함께 가질 수 있게 됨
// 2. 추천 질문 클릭 처리 로직 : 사용자가 추천 질문 버튼을 클릭했을 때 호출됨 : 현재 GPT가 응답 중(isTyping)이면 실행하지 않음
const handleRecommendationClick = async (recommendation) => {
if (isTyping) return;
// 사용자 메시지 추가, 추천 질문을 마치 사용자의 입력처럼 메시지 목록에 추가
const newUserMessage = {
id: messages.length + 1,
text: recommendation,
displayText: recommendation,
isUser: true
};
setMessages([...messages, newUserMessage]);
...
// Flask 서버로 POST 요청 : 추천 질문을 서버에 전송 : 현재 사용 중인 thread_id를 같이 보내 같은 대화 흐름 유지
const response = await axios.post(`${API_URL}/sendMessage`, {
message: recommendation,
thread_id: threadId
});
...
// GPT 응답 메시지 추가 + 추천 질문 포함 : recommendations가 포함되어 있으면 같이 넣어서 아래에 추천 버튼이 나타남
if (response.data.response) {
setIsTyping(true);
setMessages(prev => [
...prev,
{
id: prev.length + 1,
text: response.data.response,
displayText: '',
isUser: false,
isTyping: true,
recommendations: response.data.recommendations || []
}
]);
}
사용자가 추천 질문 버튼을 클릭했을 때 실행되는 로직 : 내부 로직은 기존 handleSendMessage()와 거의 동일 : 하지만, 입력값이 사용자의 직접 입력이 아닌 추천 질문 문자열이라는 점만 다름
// 3. 메시지 UI 컴포넌트 및 스타일링
function renderMessageText(message) {
const { text, displayText, isLoading, recommendations } = message;
...
//recommendations가 존재하는지 체크 : 배열에 실제로 항목이 있는지 체크 : 즉, 추천 질문이 하나라도 있을 경우에만 아래 jsx를 보여줌 : 또한, displayText가 text와 같을 때(GPT 응답 타이핑이 완료되었을 때) 추천 리스트가 표시
if (recommendations && recommendations.length > 0 && displayText === text) {
return (
<div className="space-y-3">
<div>{displayText}</div>
<div className="mt-4">
<ul className="space-y-2">
{recommendations.map((rec, index) => (
<li key={index}>
<button
onClick={() => handleRecommendationClick(rec)}
className="w-full text-left px-4 py-2 text-sm bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 transition-colors"
>
{rec}
</button>
</li>
))}
</ul>
</div>
</div>
);
}
위와 같이 메시지를 화면에 보여주는 함수에 recommendations 즉, 추천 질문이 있는 경우도 추가 : 버튼을 누르면 handleRecommendationClick() 호출됨
실제 화면
위와 같이 추천 질문 리스트가 버튼 형식으로 답변 아래에 나타나며, 버튼 클릭 시 바로 질문이 서버에 넘어가 답변을 가져오고, 다시 추천 질문과 답변이 함께 나타나는 형태가 반복
'IT_알토르 > 기술 및 코드과제' 카테고리의 다른 글
14. 도메인 연동, API 엔드포인트 분리, .env 활용, gitignore 보안, 챗봇 기능 검증 (0) | 2025.05.14 |
---|---|
13. 웹 백엔드 인프라 구축 (EC2 서버 설정, HTTPS 적용, 도메인 연결, 포트포워딩) (0) | 2025.05.08 |
12. AWS EC2 인스턴스 생성(Ubuntu 서버), Nginx 설치, EC2 IP 접속 (0) | 2025.05.04 |
11. OpenAI Assistant, API 엔드포인트 sendMessage 요청 (0) | 2025.05.01 |
10. GitHub 배포, Vercel 사용, OpenAI API (0) | 2025.04.27 |