Skip to content

Commit 119aadf

Browse files
[FIX] 시나리오 생성 디버깅 (#140)
1 parent 97f9d5f commit 119aadf

File tree

13 files changed

+152
-49
lines changed

13 files changed

+152
-49
lines changed

back/src/main/java/com/back/domain/node/repository/DecisionLineRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,8 @@ public interface DecisionLineRepository extends JpaRepository<DecisionLine, Long
2020
@EntityGraph(attributePaths = {"user"})
2121
Optional<DecisionLine> findWithUserById(Long id);
2222

23+
@EntityGraph(attributePaths = {"user", "baseLine", "baseLine.baseNodes"})
24+
Optional<DecisionLine> findWithUserAndBaseLineById(Long id);
25+
2326
void deleteByBaseLine_Id(Long baseLineId);
2427
}

back/src/main/java/com/back/domain/scenario/service/ScenarioService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ private ScenarioValidationResult validateScenarioCreation(
102102
ScenarioCreateRequest request,
103103
@Nullable DecisionNodeNextRequest lastDecision) {
104104

105-
// DecisionLine 존재 여부 확인 (User EAGER 로딩)
106-
DecisionLine decisionLine = decisionLineRepository.findWithUserById(request.decisionLineId())
105+
// DecisionLine 존재 여부 확인 (User, BaseLine, BaseLine.baseNodes EAGER 로딩)
106+
DecisionLine decisionLine = decisionLineRepository.findWithUserAndBaseLineById(request.decisionLineId())
107107
.orElseThrow(() -> new ApiException(ErrorCode.DECISION_LINE_NOT_FOUND));
108108

109109
// 권한 검증

back/src/main/java/com/back/global/ai/client/text/GeminiJsonTextClient.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public CompletableFuture<String> generateText(String prompt) {
5757

5858
@Override
5959
public CompletableFuture<String> generateText(AiRequest aiRequest) {
60+
log.info("[CLIENT] GeminiJsonTextClient (2.0) is being used.");
6061
if (aiRequest == null || aiRequest.prompt() == null) {
6162
return CompletableFuture.failedFuture(new AiParsingException("Prompt is null"));
6263
}

back/src/main/java/com/back/global/ai/client/text/GeminiTextClient.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,12 @@ public CompletableFuture<String> generateText(String prompt) {
4545

4646
@Override
4747
public CompletableFuture<String> generateText(AiRequest aiRequest) {
48+
log.info("[CLIENT] GeminiTextClient (2.5) is being used.");
4849
return webClient
4950
.post()
5051
.uri("/v1beta/models/{model}:generateContent", textAiConfig.getModel())
5152
.contentType(MediaType.APPLICATION_JSON)
52-
.bodyValue(createGeminiRequest(aiRequest.prompt(), aiRequest.maxTokens()))
53+
.bodyValue(createGeminiRequest(aiRequest))
5354
.retrieve()
5455
.onStatus(HttpStatusCode::isError, this::handleErrorResponse)
5556
.bodyToMono(GeminiResponse.class)
@@ -64,17 +65,17 @@ public CompletableFuture<String> generateText(AiRequest aiRequest) {
6465
.toFuture();
6566
}
6667

67-
private Map<String, Object> createGeminiRequest(String prompt, int maxTokens) {
68+
private Map<String, Object> createGeminiRequest(AiRequest aiRequest) {
69+
// AiRequest로부터 generationConfig를 가져와 사용
70+
java.util.Map<String, Object> generationConfig = new java.util.HashMap<>(aiRequest.parameters());
71+
// maxTokens는 AiRequest의 전용 필드에서 가져와 확실히 설정
72+
generationConfig.put("maxOutputTokens", aiRequest.maxTokens());
73+
6874
return Map.of(
6975
"contents", List.of(
70-
Map.of("parts", List.of(Map.of("text", prompt)))
76+
Map.of("parts", List.of(Map.of("text", aiRequest.prompt())))
7177
),
72-
"generationConfig", Map.of(
73-
"temperature", 0.8, // 시나리오 생성용 창의성 향상 (0.7 → 0.8)
74-
"topK", 3, // 성능 최적화 (40 → 3, 10-15% 속도 향상)
75-
"topP", 0.95,
76-
"maxOutputTokens", maxTokens // AiRequest의 maxTokens 사용
77-
)
78+
"generationConfig", generationConfig
7879
);
7980
}
8081

back/src/main/java/com/back/global/ai/config/BaseScenarioAiProperties.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,21 @@ public class BaseScenarioAiProperties {
1111
private int maxOutputTokens = 1000;
1212
private int timeoutSeconds = 60;
1313

14+
// Generation Config (AI 응답 품질 제어)
15+
private double temperature = 0.7;
16+
private double topP = 0.9;
17+
private int topK = 40;
18+
1419
// getters/setters
1520
public int getMaxOutputTokens() { return maxOutputTokens; }
1621
public void setMaxOutputTokens(int maxOutputTokens) { this.maxOutputTokens = maxOutputTokens; }
1722
public int getTimeoutSeconds() { return timeoutSeconds; }
1823
public void setTimeoutSeconds(int timeoutSeconds) { this.timeoutSeconds = timeoutSeconds; }
24+
25+
public double getTemperature() { return temperature; }
26+
public void setTemperature(double temperature) { this.temperature = temperature; }
27+
public double getTopP() { return topP; }
28+
public void setTopP(double topP) { this.topP = topP; }
29+
public int getTopK() { return topK; }
30+
public void setTopK(int topK) { this.topK = topK; }
1931
}

back/src/main/java/com/back/global/ai/config/DecisionScenarioAiProperties.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,21 @@ public class DecisionScenarioAiProperties {
1111
private int maxOutputTokens = 1200;
1212
private int timeoutSeconds = 60;
1313

14+
// Generation Config (AI 응답 품질 제어)
15+
private double temperature = 0.7;
16+
private double topP = 0.9;
17+
private int topK = 40;
18+
1419
// getters/setters
1520
public int getMaxOutputTokens() { return maxOutputTokens; }
1621
public void setMaxOutputTokens(int maxOutputTokens) { this.maxOutputTokens = maxOutputTokens; }
1722
public int getTimeoutSeconds() { return timeoutSeconds; }
1823
public void setTimeoutSeconds(int timeoutSeconds) { this.timeoutSeconds = timeoutSeconds; }
24+
25+
public double getTemperature() { return temperature; }
26+
public void setTemperature(double temperature) { this.temperature = temperature; }
27+
public double getTopP() { return topP; }
28+
public void setTopP(double topP) { this.topP = topP; }
29+
public int getTopK() { return topK; }
30+
public void setTopK(int topK) { this.topK = topK; }
1931
}

back/src/main/java/com/back/global/ai/dto/result/BaseScenarioResult.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public record Indicator(
3131
* indicators 배열을 Map<Type, Integer>로 변환
3232
*/
3333
public Map<Type, Integer> indicatorScores() {
34+
if (indicators == null) {
35+
return java.util.Collections.emptyMap();
36+
}
3437
return indicators.stream()
3538
.collect(java.util.stream.Collectors.toMap(
3639
ind -> Type.valueOf(ind.type),
@@ -42,6 +45,9 @@ public Map<Type, Integer> indicatorScores() {
4245
* indicators 배열을 Map<Type, String>로 변환
4346
*/
4447
public Map<Type, String> indicatorAnalysis() {
48+
if (indicators == null) {
49+
return java.util.Collections.emptyMap();
50+
}
4551
return indicators.stream()
4652
.collect(java.util.stream.Collectors.toMap(
4753
ind -> Type.valueOf(ind.type),

back/src/main/java/com/back/global/ai/dto/result/DecisionScenarioResult.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ public record Comparison(
4242
* indicators 배열을 Map<Type, Integer>로 변환
4343
*/
4444
public Map<Type, Integer> indicatorScores() {
45+
if (indicators == null) {
46+
return java.util.Collections.emptyMap();
47+
}
4548
return indicators.stream()
4649
.collect(java.util.stream.Collectors.toMap(
4750
ind -> Type.valueOf(ind.type),
@@ -53,6 +56,9 @@ public Map<Type, Integer> indicatorScores() {
5356
* indicators 배열을 Map<Type, String>로 변환
5457
*/
5558
public Map<Type, String> indicatorAnalysis() {
59+
if (indicators == null) {
60+
return java.util.Collections.emptyMap();
61+
}
5662
return indicators.stream()
5763
.collect(java.util.stream.Collectors.toMap(
5864
ind -> Type.valueOf(ind.type),
@@ -64,6 +70,9 @@ public Map<Type, String> indicatorAnalysis() {
6470
* comparisons 배열을 Map<String, String>로 변환
6571
*/
6672
public Map<String, String> comparisonResults() {
73+
if (comparisons == null) {
74+
return java.util.Collections.emptyMap();
75+
}
6776
return comparisons.stream()
6877
.collect(java.util.stream.Collectors.toMap(
6978
Comparison::type,

back/src/main/java/com/back/global/ai/prompt/BaseScenarioPrompt.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public class BaseScenarioPrompt {
3434
## 현재 삶 정보
3535
베이스라인: {baselineDescription}
3636
37-
## 현재 분기점들
37+
## 과거 주요 인생 기록
3838
{baseNodes}
3939
4040
## 요구사항 (JSON 형식)
@@ -99,13 +99,13 @@ public static String generatePrompt(BaseLine baseLine) {
9999
BaseNode node = baseNodes.get(i);
100100
int actualYear = birthYear + node.getAgeYear() - 1; // 실제 연도 계산
101101

102-
baseNodesInfo.append(String.format("%d. 카테고리: %s | 나이: %d세 (%d년) | 상황: %s | 결정: %s\n",
102+
baseNodesInfo.append(String.format("%d. 카테고리: %s | 나이: %d세 (%d년) | 사건: %s | 결과: %s\n",
103103
i + 1,
104104
node.getCategory() != null ? node.getCategory().name() : "없음",
105105
node.getAgeYear(),
106106
actualYear,
107-
node.getSituation() != null ? node.getSituation() : "상황 없음",
108-
node.getDecision() != null ? node.getDecision() : "결정 없음"));
107+
node.getSituation() != null ? node.getSituation() : "사건 없음",
108+
node.getDecision() != null ? node.getDecision() : "결과 없음"));
109109

110110
// 가장 최근 노드의 연도를 시나리오 기준 연도로 사용
111111
if (i == baseNodes.size() - 1) {
@@ -117,13 +117,18 @@ public static String generatePrompt(BaseLine baseLine) {
117117
int currentYear = java.time.LocalDate.now().getYear();
118118
int userCurrentAge = currentYear - birthYear + 1;
119119

120-
// BaseNode들의 실제 연도들을 타임라인 연도로 사용
120+
// 맨 처음과 맨 끝 노드를 제외한 중간 노드들의 연도만 타임라인에 사용
121121
StringBuilder timelineYears = new StringBuilder();
122-
for (int i = 0; i < baseNodes.size(); i++) {
123-
BaseNode node = baseNodes.get(i);
124-
int actualYear = birthYear + node.getAgeYear() - 1;
125-
if (i > 0) timelineYears.append(", ");
126-
timelineYears.append('"').append(actualYear).append('"').append(": \"제목 (5단어 이내)\"");
122+
if (baseNodes.size() > 2) {
123+
java.util.List<BaseNode> intermediateNodes = baseNodes.subList(1, baseNodes.size() - 1);
124+
for (int i = 0; i < intermediateNodes.size(); i++) {
125+
BaseNode node = intermediateNodes.get(i);
126+
int actualYear = birthYear + node.getAgeYear() - 1;
127+
if (i > 0) {
128+
timelineYears.append(", ");
129+
}
130+
timelineYears.append('"').append(actualYear).append('"').append(": \"해당 연도 요약 (5단어 이내)\"");
131+
}
127132
}
128133

129134
// 사용자 정보 추출 (null-safe)

back/src/main/java/com/back/global/ai/prompt/DecisionScenarioPrompt.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class DecisionScenarioPrompt {
3939
설명: {baseDescription}
4040
지표: {baseIndicators}
4141
42-
## 대안 선택 경로
42+
## 새로운 선택 기록
4343
{decisionNodes}
4444
4545
## 요구사항 (JSON 형식)
@@ -116,12 +116,12 @@ public static String generatePrompt(DecisionLine decisionLine, Scenario baseScen
116116
int actualYear = birthYear + node.getAgeYear() - 1; // 실제 연도 계산
117117

118118
decisionNodesInfo.append(String.format(
119-
"%d단계 선택 (%d세, %d년):\n상황: %s\n결정: %s\n\n",
119+
"%d단계 선택 (%d세, %d년):\n사건: %s\n선택: %s\n\n",
120120
i + 1,
121121
node.getAgeYear(),
122122
actualYear,
123-
node.getSituation() != null ? node.getSituation() : "상황 정보 없음",
124-
node.getDecision() != null ? node.getDecision() : "결정 정보 없음"
123+
node.getSituation() != null ? node.getSituation() : "사건 정보 없음",
124+
node.getDecision() != null ? node.getDecision() : "선택 정보 없음"
125125
));
126126
}
127127

@@ -146,13 +146,18 @@ public static String generatePrompt(DecisionLine decisionLine, Scenario baseScen
146146
int careerScore = getScoreByType(baseSceneTypes, "직업");
147147
int healthScore = getScoreByType(baseSceneTypes, "건강");
148148

149-
// DecisionNode들의 실제 연도들을 타임라인 연도로 사용
149+
// 맨 처음과 맨 끝 노드를 제외한 중간 노드들의 연도만 타임라인에 사용
150150
StringBuilder timelineYears = new StringBuilder();
151-
for (int i = 0; i < decisionNodes.size(); i++) {
152-
DecisionNode node = decisionNodes.get(i);
153-
int actualYear = birthYear + node.getAgeYear() - 1;
154-
if (i > 0) timelineYears.append(", ");
155-
timelineYears.append('"').append(actualYear).append('"').append(": \"제목 (5단어 이내)\"");
151+
if (decisionNodes.size() > 2) {
152+
java.util.List<DecisionNode> intermediateNodes = decisionNodes.subList(1, decisionNodes.size() - 1);
153+
for (int i = 0; i < intermediateNodes.size(); i++) {
154+
DecisionNode node = intermediateNodes.get(i);
155+
int actualYear = birthYear + node.getAgeYear() - 1;
156+
if (i > 0) {
157+
timelineYears.append(", ");
158+
}
159+
timelineYears.append('"').append(actualYear).append('"').append(": \"해당 연도 요약 (5단어 이내)\"");
160+
}
156161
}
157162

158163
// 사용자 정보 추출 (null-safe)

0 commit comments

Comments
 (0)