From 6414288aaeef9a4a75f81abf38e4d06dd4b585a1 Mon Sep 17 00:00:00 2001 From: khyaejin Date: Thu, 29 May 2025 10:29:23 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20[Perf]=20GraphRAG=20-?= =?UTF-8?q?=20GraphQueryResult=20=EB=AA=A8=EC=96=91=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/going/server/domain/rag/dto/GraphQueryResult.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/going/server/domain/rag/dto/GraphQueryResult.java b/src/main/java/com/going/server/domain/rag/dto/GraphQueryResult.java index ef185d5..d3d0e0f 100644 --- a/src/main/java/com/going/server/domain/rag/dto/GraphQueryResult.java +++ b/src/main/java/com/going/server/domain/rag/dto/GraphQueryResult.java @@ -6,6 +6,13 @@ @Getter @AllArgsConstructor public class GraphQueryResult { + private String sourceLabel; + private String relationLabel; + private String targetLabel; private String sentence; private String nodeLabel; + + public String toTripleString() { + return String.format("(%s)-[:RELATED {label: '%s'}]->(%s)", sourceLabel, relationLabel, targetLabel); + } } \ No newline at end of file From 493f1d136953a3c3fd26e58fd9b4e95a8e40e050 Mon Sep 17 00:00:00 2001 From: khyaejin Date: Thu, 29 May 2025 10:50:23 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20[Perf]=20GraphRAG=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B0=98=EC=98=81=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EB=AC=B8=EB=A7=A5=20=EC=B6=94=EC=B6=9C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatbot/dto/CreateChatbotResponseDto.java | 4 +- .../domain/rag/dto/GraphQueryResult.java | 13 +++--- .../rag/service/GraphQueryExecutor.java | 29 ++++++++++-- .../domain/rag/service/GraphRAGService.java | 46 +++++++++++-------- .../server/domain/rag/util/PromptBuilder.java | 18 ++++++-- 5 files changed, 73 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/going/server/domain/chatbot/dto/CreateChatbotResponseDto.java b/src/main/java/com/going/server/domain/chatbot/dto/CreateChatbotResponseDto.java index 0ec162f..1a7244e 100644 --- a/src/main/java/com/going/server/domain/chatbot/dto/CreateChatbotResponseDto.java +++ b/src/main/java/com/going/server/domain/chatbot/dto/CreateChatbotResponseDto.java @@ -15,9 +15,9 @@ public class CreateChatbotResponseDto { private String graphId; @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm") private LocalDateTime createdAt; + private List contextChunks; //LLM에 넘긴 context 문장들 (이름은 `augmentedSentences` 등으로 변경 권장) private List retrievedTriples; //관계 중심의 3요소 표현 ("물 -상태변화→ 응고") private List sourceNodes; //질의에 사용된 핵심 노드들 ("물", "응고" 등) - private List 증강할때쓴자료; //LLM에 넘긴 context 문장들 (이름은 `augmentedSentences` 등으로 변경 권장) private Map ragMeta; //(ex: 사용한 쿼리문 등) @@ -25,6 +25,7 @@ public static CreateChatbotResponseDto of( String chatContent, String graphId, LocalDateTime createdAt, + List contextChunks, List retrievedChunks, List sourceNodes ) { @@ -32,6 +33,7 @@ public static CreateChatbotResponseDto of( .chatContent(chatContent) .graphId(graphId) .createdAt(createdAt) + .contextChunks(contextChunks) .retrievedTriples(retrievedChunks) .sourceNodes(sourceNodes) .ragMeta(Map.of("chunkCount", String.valueOf(retrievedChunks.size()))) diff --git a/src/main/java/com/going/server/domain/rag/dto/GraphQueryResult.java b/src/main/java/com/going/server/domain/rag/dto/GraphQueryResult.java index d3d0e0f..48cd1c0 100644 --- a/src/main/java/com/going/server/domain/rag/dto/GraphQueryResult.java +++ b/src/main/java/com/going/server/domain/rag/dto/GraphQueryResult.java @@ -6,13 +6,14 @@ @Getter @AllArgsConstructor public class GraphQueryResult { - private String sourceLabel; - private String relationLabel; - private String targetLabel; - private String sentence; - private String nodeLabel; + private String sentence; // 예: "물은 응고되어 얼음이 된다." + private String sourceLabel; // 예: "물" + private String relationLabel; // 예: "응고" + private String targetLabel; // 예: "얼음" + private String nodeLabel; // 예: "물" (질의어에 가까운 노드) public String toTripleString() { + if (sourceLabel == null || relationLabel == null || targetLabel == null) return null; return String.format("(%s)-[:RELATED {label: '%s'}]->(%s)", sourceLabel, relationLabel, targetLabel); } -} \ No newline at end of file +} diff --git a/src/main/java/com/going/server/domain/rag/service/GraphQueryExecutor.java b/src/main/java/com/going/server/domain/rag/service/GraphQueryExecutor.java index 8b506d1..1114a7a 100644 --- a/src/main/java/com/going/server/domain/rag/service/GraphQueryExecutor.java +++ b/src/main/java/com/going/server/domain/rag/service/GraphQueryExecutor.java @@ -14,26 +14,45 @@ @RequiredArgsConstructor public class GraphQueryExecutor { - private final Driver neo4jDriver; // Neo4j Java Driver + private final Driver neo4jDriver; public List runQuery(Long graphId, String cypherQuery) { List results = new ArrayList<>(); try (Session session = neo4jDriver.session()) { Result result = session.run(cypherQuery); + while (result.hasNext()) { Record record = result.next(); - // 필드 이름은 Cypher 쿼리 결과와 일치해야 함 - String sentence = record.get("sentence").asString(""); - String nodeLabel = record.get("nodeLabel").asString(""); + String sentence = getSafeString(record, "sentence"); + String nodeLabel = getSafeString(record, "nodeLabel"); + + String sourceLabel = getSafeString(record, "sourceLabel"); + String relationLabel = getSafeString(record, "relationLabel"); + String targetLabel = getSafeString(record, "targetLabel"); - results.add(new GraphQueryResult(sentence, nodeLabel)); + results.add(new GraphQueryResult( + sentence, + nodeLabel, + sourceLabel, + relationLabel, + targetLabel + )); } + } catch (Exception e) { + System.err.println("[GraphRAG] Cypher 쿼리 실행 중 오류 발생:"); e.printStackTrace(); } return results; } + + // 안전한 String 추출 (null-safe) + private String getSafeString(Record record, String key) { + return record.containsKey(key) && !record.get(key).isNull() + ? record.get(key).asString() + : null; + } } \ No newline at end of file diff --git a/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java b/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java index 4537065..65364ec 100644 --- a/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java +++ b/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java @@ -13,6 +13,8 @@ import org.springframework.stereotype.Service; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Service @RequiredArgsConstructor @@ -47,32 +49,44 @@ public CreateChatbotResponseDto createAnswerWithGraphRAG( log.info("[GraphRAG] dbId: {}, question: {}", dbId, userQuestion); // 1. 질문 → Cypher 쿼리 생성 - String cypherQuery = cypherQueryGenerator.generate(userQuestion).trim() - .replaceAll("(?s)```cypher.*?```", "") // 마크다운 제거 - .replaceAll("```", "") // 남은 ``` 제거 - .trim(); - log.info("[GraphRAG] Generated Cypher Query:\n{}", cypherQuery); + String rawQuery = cypherQueryGenerator.generate(userQuestion); + // ```cypher ~ ``` 블록 제거 + Matcher m = Pattern.compile("(?s)```cypher\\s*(.*?)\\s*```").matcher(rawQuery); + String cleaned = m.find() ? m.group(1) : rawQuery; + // 남은 ``` 제거 + cleaned = cleaned.replaceAll("```", "").trim(); + log.info("[GraphRAG] Cypher Query 생성됨:\n----\n{}\n----", cleaned); // 2. 쿼리 실행 → 문맥(context) 및 노드 라벨 추출 - List queryResults = graphQueryExecutor.runQuery(dbId, cypherQuery); + List queryResults = graphQueryExecutor.runQuery(dbId, cleaned); + // 문장 List contextChunks = queryResults.stream() .map(GraphQueryResult::getSentence) .toList(); - + // 관계 트리플 + List retrievedTriples = queryResults.stream() + .map(GraphQueryResult::toTripleString) + .distinct() + .toList(); + // 노드 List sourceNodes = queryResults.stream() .map(GraphQueryResult::getNodeLabel) + .filter(n -> n != null && !n.isBlank()) .distinct() .toList(); + log.info("[GraphRAG] Retrieved {} context chunks", contextChunks.size()); + log.info("[GraphRAG] Retrieved {} triples", retrievedTriples.size()); // 3. 프롬프트 구성 - String finalPrompt = promptBuilder.buildPrompt(contextChunks, userQuestion); + String finalPrompt = promptBuilder.buildPrompt(contextChunks, retrievedTriples, userQuestion); log.info("[GraphRAG] Final Prompt constructed"); // 4. RAG 응답 생성 - String response = contextChunks.isEmpty() - ? ragAnswerCreateService.chat(chatHistory, userQuestion) - : ragAnswerCreateService.chatWithContext(chatHistory, finalPrompt); + boolean hasContext = !contextChunks.isEmpty() || !retrievedTriples.isEmpty(); + String response = hasContext + ? ragAnswerCreateService.chatWithContext(chatHistory, finalPrompt) + : ragAnswerCreateService.chat(chatHistory, userQuestion); log.info("[GraphRAG] Response generated by LLM"); // 5. 응답 저장 @@ -80,19 +94,11 @@ public CreateChatbotResponseDto createAnswerWithGraphRAG( chattingRepository.save(answer); log.info("[GraphRAG] Response saved to DB"); - // 임시 retrievedTriples 설정 - List retrievedTriples = List.of( - "(물)-[:RELATED {label: '상태변화'}]->(기화)", - "(기화)-[:RELATED {label: '조건'}]->(높은 온도)", - "(수증기)-[:RELATED {label: '응결'}]->(물방울)", - "(물)-[:RELATED {label: '응고'}]->(얼음)", - "(응고)-[:RELATED {label: '예시'}]->(겨울철 얼어붙은 길)" - ); - return CreateChatbotResponseDto.of( response, dbId.toString(), answer.getCreatedAt(), + contextChunks, retrievedTriples, sourceNodes ); diff --git a/src/main/java/com/going/server/domain/rag/util/PromptBuilder.java b/src/main/java/com/going/server/domain/rag/util/PromptBuilder.java index 1584d18..c0bc211 100644 --- a/src/main/java/com/going/server/domain/rag/util/PromptBuilder.java +++ b/src/main/java/com/going/server/domain/rag/util/PromptBuilder.java @@ -7,17 +7,25 @@ @Component public class PromptBuilder { - public String buildPrompt(List chunks, String question) { + public String buildPrompt(List contextChunks, List triples, String userQuestion) { StringBuilder sb = new StringBuilder(); sb.append("다음 정보를 참고하여 질문에 답해주세요.\n\n"); - sb.append("[관련 정보]\n"); - for (String chunk : chunks) { - sb.append("- ").append(chunk.trim()).append("\n"); + if (!triples.isEmpty()) { + sb.append("[관계 정보]\n"); + triples.forEach(triple -> sb.append("- ").append(triple).append("\n")); + sb.append("\n"); + } + if (!contextChunks.isEmpty()) { + sb.append("[설명 문장]\n"); + for (int i = 0; i < contextChunks.size(); i++) { + sb.append(i + 1).append(". ").append(contextChunks.get(i)).append("\n"); + } + sb.append("\n"); } - sb.append("\n[질문]\n").append(question.trim()).append("\n\n"); + sb.append("질문: ").append(userQuestion); sb.append("[답변]\n"); return sb.toString(); From 8191094dcd45d406705984afc033c1756b578433 Mon Sep 17 00:00:00 2001 From: khyaejin Date: Thu, 29 May 2025 10:55:50 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20[Perf]=20GraphRAG=20?= =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EA=B0=9C=EC=84=A0=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=ED=94=84=EB=A1=AC=ED=94=84=ED=8A=B8=20=ED=8A=9C?= =?UTF-8?q?=EB=8B=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rag/service/CypherQueryGenerator.java | 28 +++++++++---------- .../domain/rag/service/GraphRAGService.java | 8 +----- .../rag/service/RagAnswerCreateService.java | 15 ++++++---- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/going/server/domain/rag/service/CypherQueryGenerator.java b/src/main/java/com/going/server/domain/rag/service/CypherQueryGenerator.java index e42a735..263843d 100644 --- a/src/main/java/com/going/server/domain/rag/service/CypherQueryGenerator.java +++ b/src/main/java/com/going/server/domain/rag/service/CypherQueryGenerator.java @@ -16,23 +16,23 @@ public class CypherQueryGenerator { public String generate(String userQuestion) { String prompt = """ - 당신은 Neo4j용 Cypher 쿼리를 생성하는 AI입니다. - 주어진 질문에 대해 Cypher 쿼리만 반환하세요. 코드블록, 설명 없이 오직 쿼리만 출력해야 합니다. - - 예: - 질문: "고래와 관련된 개념들을 알려줘" - → MATCH (n:GraphNode)-[r]->(m:GraphNode)\s - WHERE n.label = '고래'\s - RETURN m.label AS nodeLabel, m.includeSentence AS sentence\s - LIMIT 10 - - 질문: "${userQuestion}" - → - """.formatted(userQuestion); + 당신은 Neo4j용 Cypher 쿼리를 생성하는 AI입니다. + 주어진 질문에 대해 Cypher 쿼리만 반환하세요. 코드블록, 설명 없이 오직 쿼리만 출력해야 합니다. + + 예: + 질문: "고래와 관련된 개념들을 알려줘" + → MATCH (n:GraphNode)-[r]->(m:GraphNode) + WHERE toLower(n.label) CONTAINS toLower('고래') + RETURN m.label AS nodeLabel, m.includeSentence AS sentence + LIMIT 10 + + 질문: "%s" + → + """.formatted(userQuestion); return openAIService.getCompletionResponse( List.of(new ChatMessage("user", prompt)), - "gpt-4-0125-preview", 0.2, 1000 + "gpt-4o", 0.2, 500 ); } } \ No newline at end of file diff --git a/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java b/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java index 65364ec..2d161e4 100644 --- a/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java +++ b/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java @@ -32,13 +32,7 @@ public class GraphRAGService { /** * 사용자 질문에 대해 Cypher 쿼리 → 그래프 정보 검색 → 프롬프트 생성 → LLM 응답 생성 - * 본 메서드는 LangChain 없이 구현한 Spring 기반 GraphRAG의 핵심 흐름입니다. - * - * private LocalDateTime createdAt; - * private List retrievedTriples; //관계 중심의 3요소 표현 ("물 -상태변화→ 응고") - * private List sourceNodes; //질의에 사용된 핵심 노드들 ("물", "응고" 등) - * private List 증강할때쓴자료; //LLM에 넘긴 context 문장들 (이름은 `augmentedSentences` 등으로 변경 권장) - * -> 이렇게 결과 나오도록 정리 + * LangChain 없이 구현한 Spring 기반 GraphRAG의 핵심 흐름 */ public CreateChatbotResponseDto createAnswerWithGraphRAG( Long dbId, diff --git a/src/main/java/com/going/server/domain/rag/service/RagAnswerCreateService.java b/src/main/java/com/going/server/domain/rag/service/RagAnswerCreateService.java index 2da4c4b..575e079 100644 --- a/src/main/java/com/going/server/domain/rag/service/RagAnswerCreateService.java +++ b/src/main/java/com/going/server/domain/rag/service/RagAnswerCreateService.java @@ -18,15 +18,18 @@ public class RagAnswerCreateService { private final OpenAIService openAIService; private static final String SYSTEM_PROMPT = """ - 당신은 초등학생의 이해를 돕는 친절하고 정확한 지식 튜터입니다. - - 아래 제공된 데이터를 기반으로 질문에 대해 매우 길고 정확하게 설명해주세요. - - 만약 참고 데이터가 없다면, 관련정보 없다고 하세요. - - 반드시 한글로만 응답하고, 인사말이나 불필요한 문장은 생략한 대답만 반환하세요. - """; + 당신은 초등학생의 이해를 돕는 친절하고 정확한 지식 튜터입니다. + + - 아래에 제공된 '관계 정보'와 '설명 문장'은 질문과 관련된 지식그래프에서 추출된 정보입니다. + - 반드시 이 정보를 바탕으로 질문에 대해 정확하고 구체적으로 설명해주세요. + - 관계 간의 연결 흐름이나 개념 간 연관성을 쉽게 풀어 설명해 주세요. + - 필요 이상으로 친절하거나 장황하게 말하지 말고, 정확하고 알기 쉽게 대답만 하세요. + - 대답은 반드시 한글로만 작성하고, 인사말이나 부가 설명 없이 본문만 반환하세요. + """; private static final String MODEL_NAME = "gpt-4o"; private static final double TEMPERATURE = 0.3; - private static final int MAX_TOKENS = 1500; + private static final int MAX_TOKENS = 1200; public String chat(List chatHistory, String question) { List messages = new ArrayList<>(); From 1bf9ccc4486389f03b783305bf0c46eb5dcc6404 Mon Sep 17 00:00:00 2001 From: khyaejin Date: Thu, 29 May 2025 11:03:52 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=90=9B=20[Fix]=20retrievedTriples=20?= =?UTF-8?q?=EA=B0=80=20null=EB=A1=9C=20=EB=82=98=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chatbot/dto/CreateChatbotResponseDto.java | 7 ++-- .../domain/rag/dto/GraphQueryResult.java | 5 +++ .../rag/service/CypherQueryGenerator.java | 36 ++++++++++++------- .../domain/rag/service/GraphRAGService.java | 4 ++- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/going/server/domain/chatbot/dto/CreateChatbotResponseDto.java b/src/main/java/com/going/server/domain/chatbot/dto/CreateChatbotResponseDto.java index 1a7244e..13045cd 100644 --- a/src/main/java/com/going/server/domain/chatbot/dto/CreateChatbotResponseDto.java +++ b/src/main/java/com/going/server/domain/chatbot/dto/CreateChatbotResponseDto.java @@ -19,14 +19,12 @@ public class CreateChatbotResponseDto { private List retrievedTriples; //관계 중심의 3요소 표현 ("물 -상태변화→ 응고") private List sourceNodes; //질의에 사용된 핵심 노드들 ("물", "응고" 등) - private Map ragMeta; //(ex: 사용한 쿼리문 등) - public static CreateChatbotResponseDto of( String chatContent, String graphId, LocalDateTime createdAt, List contextChunks, - List retrievedChunks, + List retrievedTriples, List sourceNodes ) { return CreateChatbotResponseDto.builder() @@ -34,9 +32,8 @@ public static CreateChatbotResponseDto of( .graphId(graphId) .createdAt(createdAt) .contextChunks(contextChunks) - .retrievedTriples(retrievedChunks) + .retrievedTriples(retrievedTriples) .sourceNodes(sourceNodes) - .ragMeta(Map.of("chunkCount", String.valueOf(retrievedChunks.size()))) .build(); } } \ No newline at end of file diff --git a/src/main/java/com/going/server/domain/rag/dto/GraphQueryResult.java b/src/main/java/com/going/server/domain/rag/dto/GraphQueryResult.java index 48cd1c0..5aeece8 100644 --- a/src/main/java/com/going/server/domain/rag/dto/GraphQueryResult.java +++ b/src/main/java/com/going/server/domain/rag/dto/GraphQueryResult.java @@ -14,6 +14,11 @@ public class GraphQueryResult { public String toTripleString() { if (sourceLabel == null || relationLabel == null || targetLabel == null) return null; + + // 혹시 내부 문자열이 "null"로 들어오는 것도 막기 + if ("null".equals(sourceLabel) || "null".equals(relationLabel) || "null".equals(targetLabel)) return null; + return String.format("(%s)-[:RELATED {label: '%s'}]->(%s)", sourceLabel, relationLabel, targetLabel); } + } diff --git a/src/main/java/com/going/server/domain/rag/service/CypherQueryGenerator.java b/src/main/java/com/going/server/domain/rag/service/CypherQueryGenerator.java index 263843d..4cd77c9 100644 --- a/src/main/java/com/going/server/domain/rag/service/CypherQueryGenerator.java +++ b/src/main/java/com/going/server/domain/rag/service/CypherQueryGenerator.java @@ -1,7 +1,6 @@ package com.going.server.domain.rag.service; import com.going.server.domain.openai.service.OpenAIService; -import com.theokanning.openai.OpenAiService; import com.theokanning.openai.completion.chat.ChatMessage; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -16,23 +15,34 @@ public class CypherQueryGenerator { public String generate(String userQuestion) { String prompt = """ - 당신은 Neo4j용 Cypher 쿼리를 생성하는 AI입니다. - 주어진 질문에 대해 Cypher 쿼리만 반환하세요. 코드블록, 설명 없이 오직 쿼리만 출력해야 합니다. + 당신은 Neo4j 그래프 데이터베이스에서 정보를 추출하기 위한 Cypher 쿼리를 생성하는 AI입니다. + + - 주어진 질문에서 핵심 개념과 연관된 개념들을 찾아야 합니다. + - 질문에 포함된 키워드와 의미적으로 밀접한 노드 쌍 간의 관계(triple)를 추출해야 합니다. + - 반드시 관계 중심 구조 (시작 노드, 관계 라벨, 도착 노드)를 반환하는 Cypher 쿼리를 작성하세요. + - 관계에 연결된 설명 문장이 있다면 함께 반환하세요. (r.sentence 또는 관계에 걸린 문장) + - 코드는 반드시 Cypher 쿼리 한 줄만 출력하며, 설명이나 코드블록 없이 순수 쿼리만 출력하세요. - 예: - 질문: "고래와 관련된 개념들을 알려줘" - → MATCH (n:GraphNode)-[r]->(m:GraphNode) - WHERE toLower(n.label) CONTAINS toLower('고래') - RETURN m.label AS nodeLabel, m.includeSentence AS sentence - LIMIT 10 + 예시: + 질문: "고래와 관련된 개념들을 알려줘" + → + MATCH (a:GraphNode)-[r:RELATED]->(b:GraphNode) + WHERE toLower(a.label) CONTAINS toLower('고래') OR toLower(b.label) CONTAINS toLower('고래') + RETURN + a.label AS sourceLabel, + r.label AS relationLabel, + b.label AS targetLabel, + r.sentence AS sentence, + a.label AS nodeLabel + LIMIT 10 - 질문: "%s" - → - """.formatted(userQuestion); + 질문: "%s" + → + """.formatted(userQuestion); return openAIService.getCompletionResponse( List.of(new ChatMessage("user", prompt)), "gpt-4o", 0.2, 500 ); } -} \ No newline at end of file +} diff --git a/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java b/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java index 2d161e4..b1d80f7 100644 --- a/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java +++ b/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java @@ -70,6 +70,9 @@ public CreateChatbotResponseDto createAnswerWithGraphRAG( .toList(); log.info("[GraphRAG] Retrieved {} context chunks", contextChunks.size()); + retrievedTriples.forEach(triple -> + log.info("[GraphRAG] Triple: {}", triple) + ); log.info("[GraphRAG] Retrieved {} triples", retrievedTriples.size()); // 3. 프롬프트 구성 @@ -86,7 +89,6 @@ public CreateChatbotResponseDto createAnswerWithGraphRAG( // 5. 응답 저장 Chatting answer = Chatting.ofGPT(graph, response); chattingRepository.save(answer); - log.info("[GraphRAG] Response saved to DB"); return CreateChatbotResponseDto.of( response, From fc8635a2c89c29512d0b572875137a1c1f3bc8c6 Mon Sep 17 00:00:00 2001 From: khyaejin Date: Thu, 29 May 2025 11:05:55 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=90=9B=20[Fix]=20contextChunks?= =?UTF-8?q?=EA=B0=80=20null=EC=9D=B8=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/rag/service/CypherQueryGenerator.java | 13 +++++++------ .../server/domain/rag/service/GraphRAGService.java | 2 ++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/going/server/domain/rag/service/CypherQueryGenerator.java b/src/main/java/com/going/server/domain/rag/service/CypherQueryGenerator.java index 4cd77c9..7a9be04 100644 --- a/src/main/java/com/going/server/domain/rag/service/CypherQueryGenerator.java +++ b/src/main/java/com/going/server/domain/rag/service/CypherQueryGenerator.java @@ -7,7 +7,6 @@ import java.util.List; -// 1. 질문 → Cypher 쿼리 생성 (LLM) @Component @RequiredArgsConstructor public class CypherQueryGenerator { @@ -20,21 +19,23 @@ public String generate(String userQuestion) { - 주어진 질문에서 핵심 개념과 연관된 개념들을 찾아야 합니다. - 질문에 포함된 키워드와 의미적으로 밀접한 노드 쌍 간의 관계(triple)를 추출해야 합니다. - 반드시 관계 중심 구조 (시작 노드, 관계 라벨, 도착 노드)를 반환하는 Cypher 쿼리를 작성하세요. - - 관계에 연결된 설명 문장이 있다면 함께 반환하세요. (r.sentence 또는 관계에 걸린 문장) - - 코드는 반드시 Cypher 쿼리 한 줄만 출력하며, 설명이나 코드블록 없이 순수 쿼리만 출력하세요. + - 관계나 노드에 포함된 설명 문장 중 하나를 함께 반환하세요. (r.sentence → 없으면 a.includeSentence → 없으면 b.includeSentence 순으로) + - 반환 항목은 다음과 같아야 합니다: + sourceLabel, relationLabel, targetLabel, sentence, nodeLabel + - 코드는 반드시 Cypher 쿼리 한 줄만 출력하며, 코드블록이나 설명은 포함하지 마세요. 예시: 질문: "고래와 관련된 개념들을 알려줘" → - MATCH (a:GraphNode)-[r:RELATED]->(b:GraphNode) + MATCH (a:GraphNode)-[r:RELATED]-(b:GraphNode) WHERE toLower(a.label) CONTAINS toLower('고래') OR toLower(b.label) CONTAINS toLower('고래') RETURN a.label AS sourceLabel, r.label AS relationLabel, b.label AS targetLabel, - r.sentence AS sentence, + COALESCE(r.sentence, a.includeSentence, b.includeSentence, "") AS sentence, a.label AS nodeLabel - LIMIT 10 + LIMIT 15 질문: "%s" → diff --git a/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java b/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java index b1d80f7..c965a3e 100644 --- a/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java +++ b/src/main/java/com/going/server/domain/rag/service/GraphRAGService.java @@ -56,6 +56,8 @@ public CreateChatbotResponseDto createAnswerWithGraphRAG( // 문장 List contextChunks = queryResults.stream() .map(GraphQueryResult::getSentence) + .filter(s -> s != null && !s.isBlank()) + .distinct() .toList(); // 관계 트리플 List retrievedTriples = queryResults.stream()