From f26f6f0a00cba30fd44f2f225ccd67db02e0c2a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=98=84=EA=B2=BD?= Date: Wed, 12 Aug 2026 12:28:23 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refacotr:=20=EC=98=81=EC=96=B4=20=EA=B5=90?= =?UTF-8?q?=EC=A0=95=20=ED=95=9C=EA=B5=AD=EC=96=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/diary/prompt/AnswerCorrectionPromptFactory.java | 4 +++- .../diary/prompt/AnswerCorrectionPromptFactoryTest.java | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/todayEng/domain/diary/prompt/AnswerCorrectionPromptFactory.java b/src/main/java/com/example/todayEng/domain/diary/prompt/AnswerCorrectionPromptFactory.java index 412da2f..be83439 100644 --- a/src/main/java/com/example/todayEng/domain/diary/prompt/AnswerCorrectionPromptFactory.java +++ b/src/main/java/com/example/todayEng/domain/diary/prompt/AnswerCorrectionPromptFactory.java @@ -13,7 +13,9 @@ public String create(AnswerCorrectionCommand c) { return """ You are an English reflection-writing coach. Correct the answer at the user's English level while preserving its original meaning. - Never invent or add facts. Give a concise correction reason and useful alternative expressions. + Never invent or add facts. + Write correctionReason only in Korean. + Keep correctedText and alternativeExpressions in English. %s English level: %s Question: %s diff --git a/src/test/java/com/example/todayEng/domain/diary/prompt/AnswerCorrectionPromptFactoryTest.java b/src/test/java/com/example/todayEng/domain/diary/prompt/AnswerCorrectionPromptFactoryTest.java index e33ce3f..35e17ba 100644 --- a/src/test/java/com/example/todayEng/domain/diary/prompt/AnswerCorrectionPromptFactoryTest.java +++ b/src/test/java/com/example/todayEng/domain/diary/prompt/AnswerCorrectionPromptFactoryTest.java @@ -17,6 +17,8 @@ void mainPromptRequiresGroundedFollowUpAndMeaningPreservation() throws Exception "It good", EnglishLevel.BEGINNER, new ObjectMapper().readTree("{\"event\":\"walk\"}"))); assertThat(prompt).contains("preserving its original meaning", "Never invent or add facts", + "Write correctionReason only in Korean", + "Keep correctedText and alternativeExpressions in English", "exactly one natural follow-up", "BEGINNER", "walk"); } From 9a16afe10ab422c2a636bb4a1fa61bbf9652177f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=98=84=EA=B2=BD?= Date: Wed, 12 Aug 2026 12:36:25 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AnswerCorrectionLanguageValidator.java | 26 ++++++++++++++++++ .../GeminiAnswerCorrectionLlmClient.java | 10 +++++-- ...AnswerCorrectionLanguageValidatorTest.java | 27 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/example/todayEng/domain/diary/client/AnswerCorrectionLanguageValidator.java create mode 100644 src/test/java/com/example/todayEng/domain/diary/client/AnswerCorrectionLanguageValidatorTest.java diff --git a/src/main/java/com/example/todayEng/domain/diary/client/AnswerCorrectionLanguageValidator.java b/src/main/java/com/example/todayEng/domain/diary/client/AnswerCorrectionLanguageValidator.java new file mode 100644 index 0000000..bc963a7 --- /dev/null +++ b/src/main/java/com/example/todayEng/domain/diary/client/AnswerCorrectionLanguageValidator.java @@ -0,0 +1,26 @@ +package com.example.todayEng.domain.diary.client; + +import java.util.List; +import java.util.regex.Pattern; +import org.springframework.stereotype.Component; + +@Component +public class AnswerCorrectionLanguageValidator { + + private static final Pattern HANGUL = Pattern.compile("[가-힣ㄱ-ㅎㅏ-ㅣ]"); + private static final Pattern LATIN = Pattern.compile("[A-Za-z]"); + + public boolean isKoreanExplanation(String value) { + return value != null && HANGUL.matcher(value).find(); + } + + public boolean isEnglishText(String value) { + return value != null + && LATIN.matcher(value).find() + && !HANGUL.matcher(value).find(); + } + + public boolean areEnglishExpressions(List values) { + return values != null && values.stream().allMatch(this::isEnglishText); + } +} diff --git a/src/main/java/com/example/todayEng/domain/diary/client/GeminiAnswerCorrectionLlmClient.java b/src/main/java/com/example/todayEng/domain/diary/client/GeminiAnswerCorrectionLlmClient.java index cc6f0a8..744bda8 100644 --- a/src/main/java/com/example/todayEng/domain/diary/client/GeminiAnswerCorrectionLlmClient.java +++ b/src/main/java/com/example/todayEng/domain/diary/client/GeminiAnswerCorrectionLlmClient.java @@ -30,13 +30,16 @@ public class GeminiAnswerCorrectionLlmClient implements AnswerCorrectionLlmClien private final GeminiProperties properties; private final AnswerCorrectionPromptFactory promptFactory; private final ObjectMapper objectMapper; + private final AnswerCorrectionLanguageValidator languageValidator; public GeminiAnswerCorrectionLlmClient(@Qualifier("geminiRestClient") RestClient restClient, - GeminiProperties properties, AnswerCorrectionPromptFactory promptFactory, ObjectMapper objectMapper) { + GeminiProperties properties, AnswerCorrectionPromptFactory promptFactory, ObjectMapper objectMapper, + AnswerCorrectionLanguageValidator languageValidator) { this.restClient = restClient; this.properties = properties; this.promptFactory = promptFactory; this.objectMapper = objectMapper; + this.languageValidator = languageValidator; } @Override @@ -111,7 +114,10 @@ private String extract(GeminiResponse response) { } private void validate(QuestionType type, AnswerCorrectionLlmResponse r) { - if (r == null || blank(r.correctedText()) || blank(r.correctionReason()) || r.alternativeExpressions() == null + if (r == null + || !languageValidator.isEnglishText(r.correctedText()) + || !languageValidator.isKoreanExplanation(r.correctionReason()) + || !languageValidator.areEnglishExpressions(r.alternativeExpressions()) || (type == QuestionType.MAIN && (r.followUpQuestion() == null || blank(r.followUpQuestion().questionText()) || blank(r.followUpQuestion().koreanTranslation()))) || (type == QuestionType.FOLLOW_UP && r.followUpQuestion() != null)) { diff --git a/src/test/java/com/example/todayEng/domain/diary/client/AnswerCorrectionLanguageValidatorTest.java b/src/test/java/com/example/todayEng/domain/diary/client/AnswerCorrectionLanguageValidatorTest.java new file mode 100644 index 0000000..2849a8c --- /dev/null +++ b/src/test/java/com/example/todayEng/domain/diary/client/AnswerCorrectionLanguageValidatorTest.java @@ -0,0 +1,27 @@ +package com.example.todayEng.domain.diary.client; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class AnswerCorrectionLanguageValidatorTest { + + private final AnswerCorrectionLanguageValidator validator = new AnswerCorrectionLanguageValidator(); + + @Test + void acceptsExpectedLanguages() { + assertThat(validator.isEnglishText("It was a good day.")).isTrue(); + assertThat(validator.isKoreanExplanation("과거형에 맞게 표현을 수정했어요.")).isTrue(); + assertThat(validator.areEnglishExpressions(List.of("I had a good day.", "It was enjoyable."))).isTrue(); + assertThat(validator.areEnglishExpressions(List.of())).isTrue(); + } + + @Test + void rejectsLanguageContractViolations() { + assertThat(validator.isEnglishText("좋은 하루였어요.")).isFalse(); + assertThat(validator.isEnglishText("It was 좋은 day.")).isFalse(); + assertThat(validator.isKoreanExplanation("Changed the sentence to past tense.")).isFalse(); + assertThat(validator.areEnglishExpressions(List.of("I had fun.", "즐거웠어요."))).isFalse(); + } +}