openAI Assistant가 무엇인지

간단하게 OpenAI에게 역할과 성격을 부여해서 "특정 일을 하는 직원처럼" 일하게 만드는 기능 : 예를 들어, "너는 이제부터 이력서 전문가야"라고 역할을 고정시키는 것임
즉, 기존 ChatGPT 모델을 하나의 "역할을 가진 AI"로 정의하고, 지속적인 대화(thread)를 저장하며, 파일 업로드, 코드 실행 등까지 가능한 GPT 확장 기능 : 단순히 prompt만 보내던 방식에서 벗어나 GPT를 "챗봇 인격 하나"처럼 만들 수 있는 고급 API
thread_id
Assistant와 사용자 간의 "하나의 대화방 ID"라고 할 수 있음 : 즉, thread_id는 특정 대화 세션을 식별하는 고유 ID : Assistant와의 대화는 thread를 기준으로 저장 : 하나의 사용자와 Assistant 간 대화 흐름은 하나의 thread에 기록 : thread_id를 생성하면 이후의 메시지는 해당 thread에 추가
즉, thread_id 없이는 Assistant에게 "지속적인 대화"를 시킬 수 없음
assistant에 인스트럭션(설명, 챗봇의 역할), 본인 샘플 이력서 업로드

OpenAI Assistant의 행동 지침을 결정짓는 설정인 System instructions 입력 : Name 아래가 thread_id : 업로드한 File을 Assistant가 읽고, 그 안에서 적절한 정보를 찾아 답변에 활용할 수 있도록 File Search 활성화 후, 샘플 이력서 첨부
API 엔드포인트에 sendMessage에 요청을 했을때, 메세지 내용이 Assistant에 요청 및 응답을 받아오도록
백엔드(app.py) 변경
@app.route('/sendMessage', methods=['POST'])
def send_message():
try:
# 메시지와 스레드 ID 받기 (대화 맥락 유지)
user_message = request.json.get('message')
thread_id = request.json.get('thread_id')
# 스레드 생성 또는 기존 스레드 사용
if not thread_id:
# 새 스레드 생성 API 호출
thread_response = requests.post(...)
thread_id = thread_data.get("id")
# 메시지 추가 → 실행 → 완료 대기 → 응답 가져오기 단계로 분리
# 메시지 추가
message_response = requests.post(...)
# 어시스턴트 실행
run_response = requests.post(...)
# 완료 대기 (폴링)
while attempts < max_attempts:
# 상태 확인...
# 완료되면 응답 가져오기
if status == "completed":
messages_response = requests.get(...)
# 메타데이터 제거 및 응답 반환
cleaned_content = clean_assistant_response(assistant_content)
return jsonify({"response": cleaned_content, "thread_id": thread_id})
# 각 단계별 오류 처리 추가
except Exception:
# 예외 처리
- API 변경: Chat Completions → Assistants API (v2)
- 대화 맥락 관리: 스레드 ID를 통한 대화 이력 유지 추가
- 단계 세분화: 4단계 처리 과정으로 분리 (메시지 추가 → 실행 → 대기 → 응답 가져오기)
- 오류 처리 강화: 각 단계별 오류 확인 및 예외 처리
- 대기 방식: 폴링 방식으로 완료 대기, 최대 시간 제한
프론트엔드(Chat.jsx) 변경
const [inputMessage, setInputMessage] = useState('');
const [threadId, setThreadId] = useState(null);
const [isTyping, setIsTyping] = useState(false);
const messagesEndRef = useRef(null);
const chatContainerRef = useRef(null);
const typingSpeed = 10; // 타이핑 속도 (ms)를 10ms로 변경하여 더 빠르게 설정
// 새 메시지가 추가될 때 채팅창 내부만 스크롤
useEffect(() => {
if (messagesEndRef.current && chatContainerRef.current) {
// 채팅 컨테이너 내부에서만 스크롤 수행
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
}
}, [messages]);
// 타이핑 효과 구현
useEffect(() => {
const typingMessage = messages.find(msg => msg.isTyping);
if (typingMessage) {
if (typingMessage.displayText.length < typingMessage.text.length) {
const timer = setTimeout(() => {
setMessages(prevMessages =>
prevMessages.map(msg =>
msg.id === typingMessage.id
? {
...msg,
displayText: msg.text.substring(0, msg.displayText.length + 1)
}
: msg
)
);
}, typingSpeed);
return () => clearTimeout(timer);
} else {
// 타이핑 완료
setMessages(prevMessages =>
prevMessages.map(msg =>
msg.id === typingMessage.id
? { ...msg, isTyping: false }
: msg
)
);
setIsTyping(false);
}
}
}, [messages]);
// 채팅 메시지 전송 함수
const handleSendMessage = async (e) => {
e.preventDefault();
if (inputMessage.trim() === '' || isTyping) return;
// 사용자 메시지 추가
const newUserMessage = {
id: messages.length + 1,
text: inputMessage,
displayText: inputMessage,
isUser: true
};
setMessages([...messages, newUserMessage]);
setInputMessage('');
try {
// 로딩 메시지 추가
setMessages(prev => [
...prev,
{
id: prev.length + 1,
text: '',
displayText: '',
isUser: false,
isLoading: true
}
]);
// Flask 서버로 POST 요청 (JSON 방식)
const response = await axios.post('http://localhost:5001/sendMessage', {
message: inputMessage,
thread_id: threadId
});
// 로딩 메시지 제거
setMessages(prev => prev.filter(msg => !msg.isLoading));
// 응답에서 스레드 ID 업데이트
if (response.data.thread_id) {
setThreadId(response.data.thread_id);
}
// AI 응답 추출 및 추가
if (response.data.response) {
setIsTyping(true);
setMessages(prev => [
...prev,
{
id: prev.length + 1,
text: response.data.response,
displayText: '', // 처음에는 빈 문자열로 시작
isUser: false,
isTyping: true
}
]);
} else if (response.data.error) {
// 오류 메시지 처리
setMessages(prev => [
...prev,
{
id: prev.length + 1,
text: `오류: ${response.data.error}`,
displayText: `오류: ${response.data.error}`,
isUser: false
}
]);
}
} catch (error) {
// 로딩 메시지 제거
setMessages(prev => prev.filter(msg => !msg.isLoading));
// 오류 메시지 추가
setMessages(prev => [
...prev,
{
id: prev.length + 1,
text: 'AI 서버와 통신 중 오류가 발생했습니다.',
displayText: 'AI 서버와 통신 중 오류가 발생했습니다.',
isUser: false
}
]);
console.error('에러 상세 내용:', error);
}
};
function renderMessageText(message) {
const { text, displayText, isLoading } = message;
// 로딩 메시지인 경우
if (isLoading) {
return (
<div className="flex items-center justify-center">
<div className="dots-container">
<div className="dot"></div>
<div className="dot"></div>
<div className="dot"></div>
</div>
</div>
);
}
// 코드 블록 감지
const codeBlockMatch = displayText.match(/```(.*?)\\n([\\s\\S]*?)```/);
if (codeBlockMatch) {
const language = codeBlockMatch[1];
const code = codeBlockMatch[2].replace(/\\n/g, '\n'); // 줄바꿈 처리
return (
<pre style={{background: '#222', color: '#fff', padding: 10, borderRadius: 8, overflowX: 'auto'}}>
<code>
{code}
</code>
</pre>
);
}
// 일반 텍스트
return <span>{displayText}</span>;
}
- 상태 관리 확장: threadId 상태 추가로 대화 맥락 유지
- UX 개선: 로딩 상태 표시로 사용자 피드백 강화
- 응답 구조 변경: 서버 응답 구조에 맞게 처리 방식 수정
- 기존: response.data.choices[0].message.content
- 변경: response.data.response
- 오류 처리 개선: 로딩 메시지 제거 및 다양한 오류 응답 처리
- 스타일 개선: 로딩 애니메이션 추가 및 메시지 렌더링 기능 강화
- 타이핑 효과 구현 : isTyping 상태 추가로 응답이 한 글자씩 나타나는 효과 구현
실제 사용


'IT_알토르 > 기술 및 코드과제' 카테고리의 다른 글
13. 웹 백엔드 인프라 구축 (EC2 서버 설정, HTTPS 적용, 도메인 연결, 포트포워딩) (0) | 2025.05.08 |
---|---|
12. AWS EC2 인스턴스 생성(Ubuntu 서버), Nginx 설치, EC2 IP 접속 (0) | 2025.05.04 |
10. GitHub 배포, Vercel 사용, OpenAI API (0) | 2025.04.27 |
9. Git, CursorAI, Python flask (0) | 2025.04.07 |
8. Next.js (0) | 2025.03.29 |