이 문서는 Nebula AI 채팅 API를 사용하여 클라이언트를 개발하는 방법을 설명합니다.
POST /chat/sessions- 새 채팅 세션 생성POST /chat/stream- 채팅 스트리밍 (SSE)GET /chat/sessions- 사용자 세션 목록 조회GET /chat/sessions/{session_id}/messages- 세션 메시지 조회
// 1. 새 세션 생성
const sessionResponse = await fetch('/chat/sessions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user_id: 123,
title: "AI와의 대화"
})
});
const { session_id } = await sessionResponse.json();
// 2. 해당 세션으로 채팅
const response = await fetch('/chat/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user_id: 123,
message: "안녕하세요!",
session_id: session_id // 기존 세션 연결
})
});const response = await fetch('/chat/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user_id: 123,
message: "안녕하세요!"
// session_id 없음 -> 새 세션 자동 생성
})
});const eventSource = new EventSource('/chat/stream', {
method: 'POST',
body: JSON.stringify(requestData)
});
let currentSessionId = null;
let currentMessage = "";
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
switch(data.type) {
case 'session_start':
// 🆕 세션 정보 즉시 수신
currentSessionId = data.data.session_id;
console.log('세션 시작:', currentSessionId);
break;
case 'chunk':
// AI 응답 실시간 스트리밍
currentMessage += data.data;
updateUI(currentMessage);
break;
case 'session_end':
// 응답 완료
console.log('세션 완료:', data.session_id);
console.log('AI 메시지 ID:', data.ai_message_id);
saveSessionId(currentSessionId); // 다음 요청을 위해 저장
break;
case 'error':
console.error('오류:', data.data);
break;
}
};// 저장된 세션 ID로 연속 대화
const continueChat = async (sessionId, newMessage) => {
const response = await fetch('/chat/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user_id: 123,
message: newMessage,
session_id: sessionId // 기존 세션 연결
})
});
// SSE 처리...
};- 요청과 동시에 즉시 저장됨
role: "user"session_start이벤트에서user_message_id제공
- 스트리밍 완료 후 저장됨
role: "assistant"session_end이벤트에서ai_message_id제공
// LocalStorage 활용
const saveSessionId = (sessionId) => {
localStorage.setItem('current_chat_session', sessionId);
};
const getCurrentSessionId = () => {
return localStorage.getItem('current_chat_session');
};const getUserSessions = async (userId) => {
const response = await fetch(`/chat/sessions?user_id=${userId}&limit=20`);
return await response.json();
};const getSessionMessages = async (sessionId, userId) => {
const response = await fetch(`/chat/sessions/${sessionId}/messages?user_id=${userId}`);
return await response.json();
};const chatWithIdempotency = async (message, sessionId = null) => {
const idempotencyKey = generateUUID(); // 고유 키 생성
const response = await fetch('/chat/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'idempotency-key': idempotencyKey // 중복 방지 키
},
body: JSON.stringify({
user_id: 123,
message: message,
session_id: sessionId
})
});
};import { useState, useEffect } from 'react';
const ChatComponent = ({ userId }) => {
const [sessionId, setSessionId] = useState(null);
const [messages, setMessages] = useState([]);
const [currentMessage, setCurrentMessage] = useState('');
const [isStreaming, setIsStreaming] = useState(false);
const sendMessage = async (text) => {
if (isStreaming) return;
setIsStreaming(true);
setCurrentMessage('');
// 사용자 메시지 즉시 표시
setMessages(prev => [...prev, { role: 'user', content: text }]);
const response = await fetch('/chat/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user_id: userId,
message: text,
session_id: sessionId
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.substring(6));
switch (data.type) {
case 'session_start':
setSessionId(data.data.session_id);
break;
case 'chunk':
setCurrentMessage(prev => prev + data.data);
break;
case 'session_end':
setMessages(prev => [...prev, {
role: 'assistant',
content: currentMessage
}]);
setCurrentMessage('');
setIsStreaming(false);
break;
}
}
}
}
};
return (
<div className="chat-container">
<div className="messages">
{messages.map((msg, idx) => (
<div key={idx} className={`message ${msg.role}`}>
{msg.content}
</div>
))}
{isStreaming && currentMessage && (
<div className="message assistant streaming">
{currentMessage}
</div>
)}
</div>
<ChatInput onSend={sendMessage} disabled={isStreaming} />
</div>
);
};-
세션 ID 관리: 클라이언트에서 세션 ID를 적절히 저장하고 관리해야 연속 대화가 가능합니다.
-
스트림 처리: SSE 연결이 끊어질 수 있으므로 재연결 로직을 구현하세요.
-
오류 처리: 네트워크 오류, API 오류 등에 대한 적절한 처리가 필요합니다.
-
성능: 긴 대화에서는 메시지 히스토리를 적절히 관리하여 메모리 사용량을 최적화하세요.
# 1. 새 세션 생성
curl -X POST "http://localhost:8000/chat/sessions" \
-H "Content-Type: application/json" \
-d '{"user_id": 123, "title": "테스트 대화"}'
# 2. 채팅 (SSE 스트림)
curl -X POST "http://localhost:8000/chat/stream" \
-H "Content-Type: application/json" \
-d '{"user_id": 123, "message": "안녕하세요!", "session_id": "세션ID"}' \
--no-buffer
# 3. 세션 목록 조회
curl "http://localhost:8000/chat/sessions?user_id=123&limit=10"
# 4. 메시지 히스토리 조회
curl "http://localhost:8000/chat/sessions/세션ID/messages?user_id=123"이제 클라이언트에서 연속 대화가 가능하며, 세션 관리가 훨씬 명확해졌습니다! 🎉