Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,15 @@ public CreateChatbotResponseDto createAnswerWithRAG(String graphStrId, CreateCha
// RAG: 사용자 질문
String userQuestion = createChatbotRequestDto.getChatContent();

// RAG : 키워드 추출
List<String> keywords = extractKeywords(userQuestion);
System.out.println("[RAG] 추출된 키워드: " + keywords);

// RAG: 유사 노드 검색 및 문장 추출
List<GraphNode> matchedNodes = graphNodeRepository.findByKeyword(userQuestion);
List<GraphNode> matchedNodes = graphNodeRepository.findByGraphIdAndKeywords(graphId, keywords);
// List<GraphNode> matchedNodes = graphNodeRepository.findByGraphIdAndKeywordsWithEdges(graphId, keywords);
System.out.println("[RAG] matchedNodes: " + matchedNodes);

List<String> candidateSentences = matchedNodes.stream()
.map(GraphNode::getIncludeSentence)
.filter(Objects::nonNull)
Expand All @@ -96,6 +103,7 @@ public CreateChatbotResponseDto createAnswerWithRAG(String graphStrId, CreateCha

// RAG: 최종 프롬프트 구성
String finalPrompt = promptBuilder.buildPrompt(filteredChunks, userQuestion);
System.out.println("finalPrompt: " + finalPrompt);

// RAG: 메타정보 수집
List<String> retrievedChunks = new ArrayList<>(filteredChunks);
Expand Down Expand Up @@ -284,4 +292,18 @@ public CreateChatbotResponseDto recommendVideo(String graphId, CreateChatbotRequ
private void deletePreviousChat(Long graphId) {
chattingRepository.deleteByGraphId(graphId);
}


// RAG : 키워드 추출
private List<String> extractKeywords(String text) {
List<String> stopwords = List.of("은", "는", "이", "가", "을", "를", "에", "의", "와", "과", "에서", "하다");

return Arrays.stream(text.split("[\\s,.!?]+"))
.map(word -> word.replaceAll("(은|는|이|가|을|를|에|의|와|과|에서)$", "")) // ✅ 조사 제거
.map(String::toLowerCase)
.filter(word -> word.length() > 1 && !stopwords.contains(word))
.distinct()
.limit(5)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,26 @@ default GraphNode getByNode(Long nodeId) {
@Query("MATCH (n:GraphNode) RETURN COALESCE(MAX(n.node_id), 0)")
Long findMaxNodeId();

// RAG: 키워드 기반 노드 검색
// RAG: 키워드 기반 노드 검색 (엣지 포함 x)
@Query("""
MATCH (n:GraphNode)
WHERE toLower(n.includeSentence) CONTAINS toLower($keyword)
OR toLower(n.label) CONTAINS toLower($keyword)
MATCH (g:Graph)-[:HAS_NODE]->(n:GraphNode)
WHERE id(g) = $graphId AND
ANY(kw IN $keywords WHERE
toLower(n.label) CONTAINS toLower(kw) OR
toLower(n.includeSentence) CONTAINS toLower(kw))
RETURN n
""")
List<GraphNode> findByKeyword(String keyword);
List<GraphNode> findByGraphIdAndKeywords(Long graphId, List<String> keywords);

// RAG: 키워드 기반 노드 검색 (엣지 포함)
@Query("""
MATCH (g:Graph)-[:HAS_NODE]->(n:GraphNode)
OPTIONAL MATCH (n)-[r:RELATED]->(m:GraphNode)
WHERE id(g) = $graphId AND
ANY(kw IN $keywords WHERE
toLower(n.label) CONTAINS toLower(kw) OR
toLower(n.includeSentence) CONTAINS toLower(kw))
RETURN DISTINCT n, collect(r) AS edges
""")
List<GraphNode> findByGraphIdAndKeywordsWithEdges(Long graphId, List<String> keywords);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,33 @@
import com.going.server.domain.chatbot.entity.Chatting;
import com.going.server.domain.chatbot.entity.Sender;
import com.going.server.domain.openai.dto.ChatCompletionRequestDto;
import com.theokanning.openai.completion.chat.ChatMessage;
import com.theokanning.openai.OpenAiService;
import com.theokanning.openai.completion.chat.ChatMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

// RAG 대화
@Service
@RequiredArgsConstructor
public class RAGAnswerCreateService {
private final OpenAiService openAiService;

// 시스템 역할 설정
private static final String SYSTEM_PROMPT = """
당신은 초등학생의 이해를 돕는 친절하고 정확한 지식 튜터입니다.
- 아래 제공된 데이터를 기반으로 질문에 대해 매우 길고 정확하게 설명해주세요.
- 만약 참고 데이터가 없다면, 교육 도메인의 일반적인 지식을 바탕으로 충실하게 답변해주세요.
- 반드시 한글로만 응답하고, 인사말이나 불필요한 문장은 생략한 대답만 반환하세요.
""";
당신은 초등학생의 이해를 돕는 친절하고 정확한 지식 튜터입니다.
- 아래 제공된 데이터를 기반으로 질문에 대해 매우 길고 정확하게 설명해주세요.
- 만약 참고 데이터가 없다면, 교육 도메인의 일반적인 지식을 바탕으로 충실하게 답변해주세요.
- 반드시 한글로만 응답하고, 인사말이나 불필요한 문장은 생략한 대답만 반환하세요.
""";

// 모델 스펙 정의
private static final String MODEL_NAME = "gpt-4-0125-preview";
// private static final String MODEL_NAME = "gpt-4o";

private static final double TEMPERATURE = 0.3 ;
private static final int MAX_TOKENS = 3000;

// 기존 채팅 이력을 기반으로 GPT 응답 생성
public String chat(List<Chatting> chatHistory, String question) {
Expand All @@ -35,33 +41,10 @@ public String chat(List<Chatting> chatHistory, String question) {
messages.add(new ChatMessage("user", question)); // 새로운 질문

// DTO 기반 요청 생성
ChatCompletionRequestDto request = ChatCompletionRequestDto.builder()
.model("gpt-4-0125-preview")
.temperature(0.3)
.maxTokens(3500)
.messages(messages)
.build();
ChatCompletionRequestDto request = buildRequest(messages);

// OpenAI 모델에게 질문 및 응답 생성
return openAiService.createChatCompletion(request.toRequest())
.getChoices()
.get(0)
.getMessage()
.getContent();
}

private List<ChatMessage> convertHistoryToMessages(List<Chatting> chatHistory) {
return chatHistory.stream()
.map(chat -> new ChatMessage(
convertSenderToRole(chat.getSender()),
chat.getContent()
))
.toList();
}

// Chatting 엔티티의 Sender(Enum type) -> OpenAI 역할 문자열 변환
private String convertSenderToRole(Sender sender) {
return sender == Sender.USER ? "user" : "assistant";
return getResponseText(request);
}

// RAG 컨텍스트 기반 + 기존 채팅 이력을 함께 사용하는 GPT 응답 생성
Expand All @@ -76,17 +59,41 @@ public String chatWithContext(List<Chatting> chatHistory, String finalPrompt) {
// 마지막 질문을 RAG 컨텍스트 기반으로 전달
messages.add(new ChatMessage("user", finalPrompt));

ChatCompletionRequestDto request = ChatCompletionRequestDto.builder()
.model("gpt-4-0125-preview")
.temperature(0.3)
.maxTokens(3500)
ChatCompletionRequestDto request = buildRequest(messages);
return getResponseText(request);
}

// 요청 생성 메서드
private ChatCompletionRequestDto buildRequest(List<ChatMessage> messages) {
return ChatCompletionRequestDto.builder()
.model(MODEL_NAME)
.temperature(TEMPERATURE)
.maxTokens(MAX_TOKENS)
.messages(messages)
.build();
}

// 응답 추출 메서드
private String getResponseText(ChatCompletionRequestDto request) {
return openAiService.createChatCompletion(request.toRequest())
.getChoices()
.get(0)
.getMessage()
.getContent();
}
}

// Chatting 엔티티를 OpenAI ChatMessage로 변환
private List<ChatMessage> convertHistoryToMessages(List<Chatting> chatHistory) {
return chatHistory.stream()
.map(chat -> new ChatMessage(
convertSenderToRole(chat.getSender()),
chat.getContent()
))
.toList();
}

// Chatting 엔티티의 Sender(Enum type) -> OpenAI 역할 문자열 변환
private String convertSenderToRole(Sender sender) {
return sender == Sender.USER ? "user" : "assistant";
}
}