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
2 changes: 1 addition & 1 deletion Backend_Config
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,28 @@
@Getter
@Builder
public class CreateChatbotResponseDto {
private String chatContent; // ์ฑ—๋ด‡ ์‘๋‹ต
private String graphId; // ์ง€์‹๊ทธ๋ž˜ํ”„ ID
private String chatContent;
private String graphId;
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm")
private LocalDateTime createdAt; // ์‘๋‹ต ์ƒ์„ฑ ์‹œ๊ฐ
private List<String> retrievedChunks; // RAG: ๊ฒ€์ƒ‰๋œ ๋ฌธ์žฅ๋“ค
private List<String> sourceNodes; // RAG: ์ฐธ์กฐ๋œ ์ง€์‹๊ทธ๋ž˜ํ”„ ๋…ธ๋“œ ID
private Map<String, String> ragMeta; // RAG: ์ ์ˆ˜, ๊ฒ€์ƒ‰ method ๋“ฑ
private LocalDateTime createdAt;
private List<String> retrievedChunks;
private List<String> sourceNodes;
private Map<String, String> ragMeta;

public static CreateChatbotResponseDto of(
String chatContent,
String graphId,
LocalDateTime createdAt,
List<String> retrievedChunks,
List<String> sourceNodes
) {
return CreateChatbotResponseDto.builder()
.chatContent(chatContent)
.graphId(graphId)
.createdAt(createdAt)
.retrievedChunks(retrievedChunks)
.sourceNodes(sourceNodes)
.ragMeta(Map.of("chunkCount", String.valueOf(retrievedChunks.size())))
.build();
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/going/server/domain/chatbot/entity/Chatting.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,22 @@ public class Chatting {
private Sender sender;

private LocalDateTime createdAt;

public static Chatting ofUser(Graph graph, String content) {
return Chatting.builder()
.graph(graph)
.content(content)
.sender(Sender.USER)
.createdAt(LocalDateTime.now())
.build();
}

public static Chatting ofGPT(Graph graph, String content) {
return Chatting.builder()
.graph(graph)
.content(content)
.sender(Sender.GPT)
.createdAt(LocalDateTime.now())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@
import com.going.server.domain.chatbot.entity.Sender;
import com.going.server.domain.chatbot.repository.ChattingRepository;
import com.going.server.domain.graph.entity.Graph;
import com.going.server.domain.graph.entity.GraphNode;
import com.going.server.domain.graph.exception.GraphContentNotFoundException;
import com.going.server.domain.graph.repository.GraphRepository;
import com.going.server.domain.graph.repository.GraphNodeRepository;
import com.going.server.domain.openai.dto.ImageCreateRequestDto;
import com.going.server.domain.openai.service.ImageCreateService;
import com.going.server.domain.openai.service.RAGAnswerCreateService;
import com.going.server.domain.openai.service.SimpleAnswerCreateService;
import com.going.server.domain.openai.service.TextSummaryCreateService;
import com.going.server.domain.rag.service.GraphRAGService;
import com.going.server.domain.rag.service.SimilarityFilterService;
import com.going.server.domain.rag.util.PromptBuilder;
import lombok.RequiredArgsConstructor;
Expand All @@ -24,8 +23,6 @@
import java.time.LocalDateTime;
import java.util.*;

import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Transactional
Expand All @@ -38,13 +35,15 @@ public class ChatbotServiceImpl implements ChatbotService {
// openai ๊ด€๋ จ service
private final TextSummaryCreateService textSummaryCreateService;
private final SimpleAnswerCreateService simpleAnswerCreateService;
private final RAGAnswerCreateService ragAnswerCreateService;
private final ImageCreateService imageCreateService;
// graphRAG
private final GraphRAGService graphRAGService;

// ์›๋ฌธ ๋ฐ˜ํ™˜
@Override
public CreateChatbotResponseDto getOriginalText(String graphId) {
Graph graph = graphRepository.getByGraph(Long.valueOf(graphId));
Long dbId = graphRepository.findDbIdByGraphId(Long.valueOf(graphId));
Graph graph = graphRepository.getByGraph(dbId);

return CreateChatbotResponseDto.builder()
.chatContent(graph.getContent()) // ์›๋ฌธ ํ…์ŠคํŠธ
Expand All @@ -56,7 +55,8 @@ public CreateChatbotResponseDto getOriginalText(String graphId) {
// ์š”์•ฝ๋ณธ ์ƒ์„ฑ
@Override
public CreateChatbotResponseDto getSummaryText(String graphId) {
Graph graph = graphRepository.getByGraph(Long.valueOf(graphId));
Long dbId = graphRepository.findDbIdByGraphId(Long.valueOf(graphId));
Graph graph = graphRepository.getByGraph(dbId);

String context = Optional.ofNullable(graph.getContent())
.filter(s -> !s.trim().isEmpty())
Expand All @@ -71,120 +71,53 @@ public CreateChatbotResponseDto getSummaryText(String graphId) {
.build();
}


// RAG ์ฑ—๋ด‡ ์‘๋‹ต ์ƒ์„ฑ
// GraphRAG ์ฑ—๋ด‡ ์‘๋‹ต ์ƒ์„ฑ
@Override
public CreateChatbotResponseDto createAnswerWithRAG(String graphStrId, CreateChatbotRequestDto createChatbotRequestDto) {
Long graphId = Long.valueOf(graphStrId);

// 404 : ์ง€์‹๊ทธ๋ž˜ํ”„ ์ฐพ์„ ์ˆ˜ ์—†์Œ
Graph graph = graphRepository.getByGraph(graphId);

// RAG: ์‚ฌ์šฉ์ž ์งˆ๋ฌธ
String userQuestion = createChatbotRequestDto.getChatContent();

// RAG : ํ‚ค์›Œ๋“œ ์ถ”์ถœ
List<String> keywords = extractKeywords(userQuestion);
System.out.println("[RAG] ์ถ”์ถœ๋œ ํ‚ค์›Œ๋“œ: " + keywords);

// RAG: ์œ ์‚ฌ ๋…ธ๋“œ ๊ฒ€์ƒ‰ ๋ฐ ๋ฌธ์žฅ ์ถ”์ถœ
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)
.distinct()
.collect(Collectors.toList());

// RAG: ์œ ์‚ฌ ๋ฌธ์žฅ ํ•„ํ„ฐ๋ง
List<String> filteredChunks = similarityFilterService.filterRelevantSentences(userQuestion, candidateSentences);

// RAG: ์ตœ์ข… ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ
String finalPrompt = promptBuilder.buildPrompt(filteredChunks, userQuestion);
System.out.println("finalPrompt: " + finalPrompt);

// RAG: ๋ฉ”ํƒ€์ •๋ณด ์ˆ˜์ง‘
List<String> retrievedChunks = new ArrayList<>(filteredChunks);
List<String> sourceNodes = new ArrayList<>(
matchedNodes.stream().map(GraphNode::getLabel).distinct().toList()
);
Map<String, String> ragMeta = Map.of(
"chunkCount", String.valueOf(filteredChunks.size())
);
public CreateChatbotResponseDto createAnswerWithRAG(String graphStrId, CreateChatbotRequestDto requestDto) {
Long dbId = graphRepository.findDbIdByGraphId(Long.valueOf(graphStrId));
Graph graph = graphRepository.getByGraph(dbId);

// ์ƒˆ๋กœ์šด ๋Œ€ํ™”์ธ ๊ฒฝ์šฐ ๊ธฐ์กด ์ฑ„ํŒ… ์‚ญ์ œ
if (createChatbotRequestDto.isNewChat()) {
deletePreviousChat(graphId);
if (requestDto.isNewChat()) {
deletePreviousChat(dbId);
}

// ๊ธฐ์กด ์ฑ„ํŒ… ๋‚ด์—ญ ์กฐํšŒ
List<Chatting> chatHistory = chattingRepository.findAllByGraphId(graphId);

// ์ƒˆ๋กœ์šด ์ฑ„ํŒ…
String newChat = createChatbotRequestDto.getChatContent();

// ์ƒˆ๋กœ์šด ์ฑ„ํŒ… repository์— ์ €์žฅ
Chatting chatting = Chatting.builder()
Chatting userChat = Chatting.builder()
.graph(graph)
.content(newChat)
.content(requestDto.getChatContent())
.sender(Sender.USER)
.createdAt(LocalDateTime.now())
.build();
chattingRepository.save(chatting);

// ์‘๋‹ต ์ƒ์„ฑ
String chatContent;

// RAG: ์œ ์‚ฌ ๋ฌธ์žฅ์ด ์žˆ์„ ๊ฒฝ์šฐ ์ปจํ…์ŠคํŠธ ํ™œ์šฉ
if (retrievedChunks.isEmpty()) {
System.out.println("[INFO] RAG ๋ฏธ์ ์šฉ - ์ผ๋ฐ˜ ์ฑ„ํŒ… ๊ธฐ๋ฐ˜ ์‘๋‹ต");
System.out.println("[INFO] RAG ๋ฏธ์ ์šฉ - ์œ ์‚ฌ ๋ฌธ์žฅ ์—†์Œ");
System.out.println("[DEBUG] matchedNodes.size(): " + matchedNodes.size());
System.out.println("[DEBUG] candidateSentences.size(): " + candidateSentences.size());
System.out.println("[DEBUG] filteredChunks.size(): " + filteredChunks.size());
chatContent = ragAnswerCreateService.chat(chatHistory, newChat);
} else {
System.out.println("[INFO] RAG ์ ์šฉ๋จ - ์œ ์‚ฌ ๋ฌธ์žฅ " + retrievedChunks.size() + "๊ฐœ ํฌํ•จ");
chatContent = ragAnswerCreateService.chatWithContext(chatHistory, finalPrompt);
}
chattingRepository.save(userChat);

// ์‘๋‹ต repository์— ์ €์žฅ
Chatting answer = Chatting.builder()
.graph(graph)
.content(chatContent)
.sender(Sender.GPT)
.createdAt(LocalDateTime.now())
.build();
chattingRepository.save(answer);
List<Chatting> chatHistory = chattingRepository.findAllByGraphId(dbId);

// ๋ฐ˜ํ™˜
return CreateChatbotResponseDto.builder()
.chatContent(chatContent)
.graphId(graphStrId)
.createdAt(answer.getCreatedAt())
.retrievedChunks(retrievedChunks)
.sourceNodes(sourceNodes)
.ragMeta(ragMeta)
.build();
// RAG ์‘๋‹ต ์ƒ์„ฑ (์‘๋‹ต + ๋ฉ”ํƒ€ ํฌํ•จ)
CreateChatbotResponseDto responseDto = graphRAGService.createAnswerWithGraphRAG(
dbId,
requestDto.getChatContent(),
chatHistory
);

// ์‘๋‹ต ์ฑ„ํŒ… ์ €์žฅ
Chatting gptChat = Chatting.ofGPT(graph, responseDto.getChatContent());
chattingRepository.save(gptChat);

return responseDto;
}

// RAG ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์‘๋‹ต ์ƒ์„ฑ
// ๊ธฐ๋ณธ ์‘๋‹ต ์ƒ์„ฑ
@Override
public CreateChatbotResponseDto createSimpleAnswer(String graphStrId, CreateChatbotRequestDto createChatbotRequestDto) {
Long graphId = Long.valueOf(graphStrId);

// 404 : ์ง€์‹๊ทธ๋ž˜ํ”„ ์ฐพ์„ ์ˆ˜ ์—†์Œ
Graph graph = graphRepository.getByGraph(graphId);
Long dbId = graphRepository.findDbIdByGraphId(Long.valueOf(graphStrId));
Graph graph = graphRepository.getByGraph(dbId);

// ์ƒˆ๋กœ์šด ๋Œ€ํ™”์ธ ๊ฒฝ์šฐ ๊ธฐ์กด ์ฑ„ํŒ… ์‚ญ์ œ
if (createChatbotRequestDto.isNewChat()) {
deletePreviousChat(graphId);
deletePreviousChat(dbId);
}

// ๊ธฐ์กด ์ฑ„ํŒ… ๋‚ด์—ญ ์กฐํšŒ
List<Chatting> chatHistory = chattingRepository.findAllByGraphId(graphId);
List<Chatting> chatHistory = chattingRepository.findAllByGraphId(dbId);

// ์‚ฌ์šฉ์ž ์ž…๋ ฅ ์ฑ„ํŒ…
String newChat = createChatbotRequestDto.getChatContent();
Expand Down Expand Up @@ -293,17 +226,4 @@ 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());
}
}
13 changes: 7 additions & 6 deletions src/main/java/com/going/server/domain/graph/entity/Graph.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.Setter;
import org.springframework.data.neo4j.core.schema.*;

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

@Node("Graph")
Expand All @@ -15,7 +16,10 @@
public class Graph extends BaseEntity {
@Id
@GeneratedValue
private Long id; //๊ทธ๋ž˜ํ”„ id -> ํ”„๋ก ํŠธ์™€ ํ†ต์‹ ์—์„œ๋Š” String ๊ฐ’์œผ๋กœ ์‚ฌ์šฉ
private Long dbId; // ๋‚ด๋ถ€ ๊ด€๋ฆฌ์šฉ elementId์™€ ์—ฐ๊ฒฐ๋จ

@Property("id")
private Long id; // ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๋ช…์‹œ์  ID

private String title;

Expand All @@ -26,11 +30,8 @@ public class Graph extends BaseEntity {
private boolean connectPerfect; //connect ํ€ด์ฆˆ ๋งŒ์ ‘ ์—ฌ๋ถ€
private boolean picturePerfect; //picture ํ€ด์ฆˆ ๋งŒ์ ‘ ์—ฌ๋ถ€

@Builder.Default
@Relationship(type = "HAS_NODE", direction = Relationship.Direction.OUTGOING)
private List<GraphNode> nodes;
private List<GraphNode> nodes = new ArrayList<>();

// Long โ†’ String ๋ณ€ํ™˜ (ํ”„๋ก ํŠธ ์ „์†ก ์‹œ)
public String getIdAsString() {
return id != null ? String.valueOf(id) : null;
}
}
48 changes: 25 additions & 23 deletions src/main/java/com/going/server/domain/graph/entity/GraphEdge.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.going.server.domain.graph.entity;

import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.neo4j.core.schema.*;
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.RelationshipProperties;
import org.springframework.data.neo4j.core.schema.TargetNode;

import java.util.ArrayList;
import java.util.Objects;

@RelationshipProperties
@Getter
Expand All @@ -15,32 +19,30 @@ public class GraphEdge {

@Id
@GeneratedValue
private Long id; // Neo4j ๋‚ด๋ถ€ ID
private Long id;

@EqualsAndHashCode.Include
private String source;

private String label; // ๊ด€๊ณ„ ๋ผ๋ฒจ
@EqualsAndHashCode.Include
private String label;

@EqualsAndHashCode.Include
@TargetNode
private GraphNode target; // ์—ฐ๊ฒฐ ๋Œ€์ƒ ๋…ธ๋“œ

// private GraphEdge createEdge(Long edgeId, String label, GraphNode source, GraphNode target) {
// GraphEdge edge = new GraphEdge();
// edge.setEdgeId(edgeId);
// edge.setLabel(label);
// edge.setTarget(target);
//
// // outbound edge๋ฅผ source์— ์—ฐ๊ฒฐ
// if (source.getEdges() == null) {
// source.setEdges(new ArrayList<>());
// }
// source.getEdges().add(edge);
// return edge;
// }

// Long โ†’ String ๋ณ€ํ™˜ (ํ”„๋ก ํŠธ ์ „์†ก ์‹œ)
public String getIdAsString() {
return id != null ? String.valueOf(id) : null;
private GraphNode target; // ์—ฌ๊ธฐ์— ๋ฐฉํ–ฅ ๋ถ™์ด์ง€ ๋งˆ์„ธ์š”

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof GraphEdge edge)) return false;
return Objects.equals(source, edge.source) &&
Objects.equals(label, edge.label) &&
target != null && edge.target != null &&
Objects.equals(target.getNodeId(), edge.target.getNodeId());
}

@Override
public int hashCode() {
return Objects.hash(source, label, target != null ? target.getNodeId() : null);
}
}
Loading