Skip to content

Commit b7dc075

Browse files
authored
Merge pull request #193 from capgoing/langchain
🐛 [fix] 지식그래프 생성 순환참조 오류 해결
2 parents c62f8a4 + 130ffed commit b7dc075

23 files changed

+486
-315
lines changed

Backend_Config

src/main/java/com/going/server/domain/chatbot/dto/CreateChatbotResponseDto.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,28 @@
1111
@Getter
1212
@Builder
1313
public class CreateChatbotResponseDto {
14-
private String chatContent; // 챗봇 응답
15-
private String graphId; // 지식그래프 ID
14+
private String chatContent;
15+
private String graphId;
1616
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm")
17-
private LocalDateTime createdAt; // 응답 생성 시각
18-
private List<String> retrievedChunks; // RAG: 검색된 문장들
19-
private List<String> sourceNodes; // RAG: 참조된 지식그래프 노드 ID
20-
private Map<String, String> ragMeta; // RAG: 점수, 검색 method 등
17+
private LocalDateTime createdAt;
18+
private List<String> retrievedChunks;
19+
private List<String> sourceNodes;
20+
private Map<String, String> ragMeta;
21+
22+
public static CreateChatbotResponseDto of(
23+
String chatContent,
24+
String graphId,
25+
LocalDateTime createdAt,
26+
List<String> retrievedChunks,
27+
List<String> sourceNodes
28+
) {
29+
return CreateChatbotResponseDto.builder()
30+
.chatContent(chatContent)
31+
.graphId(graphId)
32+
.createdAt(createdAt)
33+
.retrievedChunks(retrievedChunks)
34+
.sourceNodes(sourceNodes)
35+
.ragMeta(Map.of("chunkCount", String.valueOf(retrievedChunks.size())))
36+
.build();
37+
}
2138
}

src/main/java/com/going/server/domain/chatbot/entity/Chatting.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,22 @@ public class Chatting {
2626
private Sender sender;
2727

2828
private LocalDateTime createdAt;
29+
30+
public static Chatting ofUser(Graph graph, String content) {
31+
return Chatting.builder()
32+
.graph(graph)
33+
.content(content)
34+
.sender(Sender.USER)
35+
.createdAt(LocalDateTime.now())
36+
.build();
37+
}
38+
39+
public static Chatting ofGPT(Graph graph, String content) {
40+
return Chatting.builder()
41+
.graph(graph)
42+
.content(content)
43+
.sender(Sender.GPT)
44+
.createdAt(LocalDateTime.now())
45+
.build();
46+
}
2947
}

src/main/java/com/going/server/domain/chatbot/service/ChatbotServiceImpl.java

Lines changed: 34 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@
66
import com.going.server.domain.chatbot.entity.Sender;
77
import com.going.server.domain.chatbot.repository.ChattingRepository;
88
import com.going.server.domain.graph.entity.Graph;
9-
import com.going.server.domain.graph.entity.GraphNode;
109
import com.going.server.domain.graph.exception.GraphContentNotFoundException;
1110
import com.going.server.domain.graph.repository.GraphRepository;
1211
import com.going.server.domain.graph.repository.GraphNodeRepository;
1312
import com.going.server.domain.openai.dto.ImageCreateRequestDto;
1413
import com.going.server.domain.openai.service.ImageCreateService;
15-
import com.going.server.domain.openai.service.RAGAnswerCreateService;
1614
import com.going.server.domain.openai.service.SimpleAnswerCreateService;
1715
import com.going.server.domain.openai.service.TextSummaryCreateService;
16+
import com.going.server.domain.rag.service.GraphRAGService;
1817
import com.going.server.domain.rag.service.SimilarityFilterService;
1918
import com.going.server.domain.rag.util.PromptBuilder;
2019
import lombok.RequiredArgsConstructor;
@@ -24,8 +23,6 @@
2423
import java.time.LocalDateTime;
2524
import java.util.*;
2625

27-
import java.util.stream.Collectors;
28-
2926
@Service
3027
@RequiredArgsConstructor
3128
@Transactional
@@ -38,13 +35,15 @@ public class ChatbotServiceImpl implements ChatbotService {
3835
// openai 관련 service
3936
private final TextSummaryCreateService textSummaryCreateService;
4037
private final SimpleAnswerCreateService simpleAnswerCreateService;
41-
private final RAGAnswerCreateService ragAnswerCreateService;
4238
private final ImageCreateService imageCreateService;
39+
// graphRAG
40+
private final GraphRAGService graphRAGService;
4341

4442
// 원문 반환
4543
@Override
4644
public CreateChatbotResponseDto getOriginalText(String graphId) {
47-
Graph graph = graphRepository.getByGraph(Long.valueOf(graphId));
45+
Long dbId = graphRepository.findDbIdByGraphId(Long.valueOf(graphId));
46+
Graph graph = graphRepository.getByGraph(dbId);
4847

4948
return CreateChatbotResponseDto.builder()
5049
.chatContent(graph.getContent()) // 원문 텍스트
@@ -56,7 +55,8 @@ public CreateChatbotResponseDto getOriginalText(String graphId) {
5655
// 요약본 생성
5756
@Override
5857
public CreateChatbotResponseDto getSummaryText(String graphId) {
59-
Graph graph = graphRepository.getByGraph(Long.valueOf(graphId));
58+
Long dbId = graphRepository.findDbIdByGraphId(Long.valueOf(graphId));
59+
Graph graph = graphRepository.getByGraph(dbId);
6060

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

74-
75-
// RAG 챗봇 응답 생성
74+
// GraphRAG 챗봇 응답 생성
7675
@Override
77-
public CreateChatbotResponseDto createAnswerWithRAG(String graphStrId, CreateChatbotRequestDto createChatbotRequestDto) {
78-
Long graphId = Long.valueOf(graphStrId);
79-
80-
// 404 : 지식그래프 찾을 수 없음
81-
Graph graph = graphRepository.getByGraph(graphId);
82-
83-
// RAG: 사용자 질문
84-
String userQuestion = createChatbotRequestDto.getChatContent();
85-
86-
// RAG : 키워드 추출
87-
List<String> keywords = extractKeywords(userQuestion);
88-
System.out.println("[RAG] 추출된 키워드: " + keywords);
89-
90-
// RAG: 유사 노드 검색 및 문장 추출
91-
List<GraphNode> matchedNodes = graphNodeRepository.findByGraphIdAndKeywords(graphId, keywords);
92-
// List<GraphNode> matchedNodes = graphNodeRepository.findByGraphIdAndKeywordsWithEdges(graphId, keywords);
93-
System.out.println("[RAG] matchedNodes: " + matchedNodes);
94-
95-
List<String> candidateSentences = matchedNodes.stream()
96-
.map(GraphNode::getIncludeSentence)
97-
.filter(Objects::nonNull)
98-
.distinct()
99-
.collect(Collectors.toList());
100-
101-
// RAG: 유사 문장 필터링
102-
List<String> filteredChunks = similarityFilterService.filterRelevantSentences(userQuestion, candidateSentences);
103-
104-
// RAG: 최종 프롬프트 구성
105-
String finalPrompt = promptBuilder.buildPrompt(filteredChunks, userQuestion);
106-
System.out.println("finalPrompt: " + finalPrompt);
107-
108-
// RAG: 메타정보 수집
109-
List<String> retrievedChunks = new ArrayList<>(filteredChunks);
110-
List<String> sourceNodes = new ArrayList<>(
111-
matchedNodes.stream().map(GraphNode::getLabel).distinct().toList()
112-
);
113-
Map<String, String> ragMeta = Map.of(
114-
"chunkCount", String.valueOf(filteredChunks.size())
115-
);
76+
public CreateChatbotResponseDto createAnswerWithRAG(String graphStrId, CreateChatbotRequestDto requestDto) {
77+
Long dbId = graphRepository.findDbIdByGraphId(Long.valueOf(graphStrId));
78+
Graph graph = graphRepository.getByGraph(dbId);
11679

117-
// 새로운 대화인 경우 기존 채팅 삭제
118-
if (createChatbotRequestDto.isNewChat()) {
119-
deletePreviousChat(graphId);
80+
if (requestDto.isNewChat()) {
81+
deletePreviousChat(dbId);
12082
}
12183

122-
// 기존 채팅 내역 조회
123-
List<Chatting> chatHistory = chattingRepository.findAllByGraphId(graphId);
124-
125-
// 새로운 채팅
126-
String newChat = createChatbotRequestDto.getChatContent();
127-
128-
// 새로운 채팅 repository에 저장
129-
Chatting chatting = Chatting.builder()
84+
Chatting userChat = Chatting.builder()
13085
.graph(graph)
131-
.content(newChat)
86+
.content(requestDto.getChatContent())
13287
.sender(Sender.USER)
13388
.createdAt(LocalDateTime.now())
13489
.build();
135-
chattingRepository.save(chatting);
136-
137-
// 응답 생성
138-
String chatContent;
139-
140-
// RAG: 유사 문장이 있을 경우 컨텍스트 활용
141-
if (retrievedChunks.isEmpty()) {
142-
System.out.println("[INFO] RAG 미적용 - 일반 채팅 기반 응답");
143-
System.out.println("[INFO] RAG 미적용 - 유사 문장 없음");
144-
System.out.println("[DEBUG] matchedNodes.size(): " + matchedNodes.size());
145-
System.out.println("[DEBUG] candidateSentences.size(): " + candidateSentences.size());
146-
System.out.println("[DEBUG] filteredChunks.size(): " + filteredChunks.size());
147-
chatContent = ragAnswerCreateService.chat(chatHistory, newChat);
148-
} else {
149-
System.out.println("[INFO] RAG 적용됨 - 유사 문장 " + retrievedChunks.size() + "개 포함");
150-
chatContent = ragAnswerCreateService.chatWithContext(chatHistory, finalPrompt);
151-
}
90+
chattingRepository.save(userChat);
15291

153-
// 응답 repository에 저장
154-
Chatting answer = Chatting.builder()
155-
.graph(graph)
156-
.content(chatContent)
157-
.sender(Sender.GPT)
158-
.createdAt(LocalDateTime.now())
159-
.build();
160-
chattingRepository.save(answer);
92+
List<Chatting> chatHistory = chattingRepository.findAllByGraphId(dbId);
16193

162-
// 반환
163-
return CreateChatbotResponseDto.builder()
164-
.chatContent(chatContent)
165-
.graphId(graphStrId)
166-
.createdAt(answer.getCreatedAt())
167-
.retrievedChunks(retrievedChunks)
168-
.sourceNodes(sourceNodes)
169-
.ragMeta(ragMeta)
170-
.build();
94+
// RAG 응답 생성 (응답 + 메타 포함)
95+
CreateChatbotResponseDto responseDto = graphRAGService.createAnswerWithGraphRAG(
96+
dbId,
97+
requestDto.getChatContent(),
98+
chatHistory
99+
);
100+
101+
// 응답 채팅 저장
102+
Chatting gptChat = Chatting.ofGPT(graph, responseDto.getChatContent());
103+
chattingRepository.save(gptChat);
104+
105+
return responseDto;
171106
}
172107

173-
// RAG 사용하지 않는 응답 생성
108+
// 기본 응답 생성
174109
@Override
175110
public CreateChatbotResponseDto createSimpleAnswer(String graphStrId, CreateChatbotRequestDto createChatbotRequestDto) {
176-
Long graphId = Long.valueOf(graphStrId);
177-
178-
// 404 : 지식그래프 찾을 수 없음
179-
Graph graph = graphRepository.getByGraph(graphId);
111+
Long dbId = graphRepository.findDbIdByGraphId(Long.valueOf(graphStrId));
112+
Graph graph = graphRepository.getByGraph(dbId);
180113

181114
// 새로운 대화인 경우 기존 채팅 삭제
182115
if (createChatbotRequestDto.isNewChat()) {
183-
deletePreviousChat(graphId);
116+
deletePreviousChat(dbId);
184117
}
185118

186119
// 기존 채팅 내역 조회
187-
List<Chatting> chatHistory = chattingRepository.findAllByGraphId(graphId);
120+
List<Chatting> chatHistory = chattingRepository.findAllByGraphId(dbId);
188121

189122
// 사용자 입력 채팅
190123
String newChat = createChatbotRequestDto.getChatContent();
@@ -293,17 +226,4 @@ private void deletePreviousChat(Long graphId) {
293226
chattingRepository.deleteByGraphId(graphId);
294227
}
295228

296-
297-
// RAG : 키워드 추출
298-
private List<String> extractKeywords(String text) {
299-
List<String> stopwords = List.of("은", "는", "이", "가", "을", "를", "에", "의", "와", "과", "에서", "하다");
300-
301-
return Arrays.stream(text.split("[\\s,.!?]+"))
302-
.map(word -> word.replaceAll("(은|는|이|가|을|를|에|의|와|과|에서)$", "")) // ✅ 조사 제거
303-
.map(String::toLowerCase)
304-
.filter(word -> word.length() > 1 && !stopwords.contains(word))
305-
.distinct()
306-
.limit(5)
307-
.collect(Collectors.toList());
308-
}
309229
}

src/main/java/com/going/server/domain/graph/entity/Graph.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import lombok.Setter;
77
import org.springframework.data.neo4j.core.schema.*;
88

9+
import java.util.ArrayList;
910
import java.util.List;
1011

1112
@Node("Graph")
@@ -15,7 +16,10 @@
1516
public class Graph extends BaseEntity {
1617
@Id
1718
@GeneratedValue
18-
private Long id; //그래프 id -> 프론트와 통신에서는 String 값으로 사용
19+
private Long dbId; // 내부 관리용 elementId와 연결됨
20+
21+
@Property("id")
22+
private Long id; // 우리가 직접 사용하는 명시적 ID
1923

2024
private String title;
2125

@@ -26,11 +30,8 @@ public class Graph extends BaseEntity {
2630
private boolean connectPerfect; //connect 퀴즈 만접 여부
2731
private boolean picturePerfect; //picture 퀴즈 만접 여부
2832

33+
@Builder.Default
2934
@Relationship(type = "HAS_NODE", direction = Relationship.Direction.OUTGOING)
30-
private List<GraphNode> nodes;
35+
private List<GraphNode> nodes = new ArrayList<>();
3136

32-
// Long → String 변환 (프론트 전송 시)
33-
public String getIdAsString() {
34-
return id != null ? String.valueOf(id) : null;
35-
}
3637
}
Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package com.going.server.domain.graph.entity;
22

33
import lombok.Builder;
4+
import lombok.EqualsAndHashCode;
45
import lombok.Getter;
56
import lombok.Setter;
6-
import org.springframework.data.neo4j.core.schema.*;
7+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
8+
import org.springframework.data.neo4j.core.schema.Id;
9+
import org.springframework.data.neo4j.core.schema.RelationshipProperties;
10+
import org.springframework.data.neo4j.core.schema.TargetNode;
711

8-
import java.util.ArrayList;
12+
import java.util.Objects;
913

1014
@RelationshipProperties
1115
@Getter
@@ -15,32 +19,30 @@ public class GraphEdge {
1519

1620
@Id
1721
@GeneratedValue
18-
private Long id; // Neo4j 내부 ID
22+
private Long id;
1923

24+
@EqualsAndHashCode.Include
2025
private String source;
2126

22-
private String label; // 관계 라벨
27+
@EqualsAndHashCode.Include
28+
private String label;
2329

30+
@EqualsAndHashCode.Include
2431
@TargetNode
25-
private GraphNode target; // 연결 대상 노드
26-
27-
// private GraphEdge createEdge(Long edgeId, String label, GraphNode source, GraphNode target) {
28-
// GraphEdge edge = new GraphEdge();
29-
// edge.setEdgeId(edgeId);
30-
// edge.setLabel(label);
31-
// edge.setTarget(target);
32-
//
33-
// // outbound edge를 source에 연결
34-
// if (source.getEdges() == null) {
35-
// source.setEdges(new ArrayList<>());
36-
// }
37-
// source.getEdges().add(edge);
38-
// return edge;
39-
// }
40-
41-
// Long → String 변환 (프론트 전송 시)
42-
public String getIdAsString() {
43-
return id != null ? String.valueOf(id) : null;
32+
private GraphNode target; // 여기에 방향 붙이지 마세요
33+
34+
@Override
35+
public boolean equals(Object o) {
36+
if (this == o) return true;
37+
if (!(o instanceof GraphEdge edge)) return false;
38+
return Objects.equals(source, edge.source) &&
39+
Objects.equals(label, edge.label) &&
40+
target != null && edge.target != null &&
41+
Objects.equals(target.getNodeId(), edge.target.getNodeId());
4442
}
4543

44+
@Override
45+
public int hashCode() {
46+
return Objects.hash(source, label, target != null ? target.getNodeId() : null);
47+
}
4648
}

0 commit comments

Comments
 (0)