Skip to content

feat: AI 톤 교정 서비스 (#65 2/3) - #86

Merged
chjpooh2002 merged 1 commit into
developfrom
feat/65-tone-correction-service
Nov 13, 2026
Merged

chjpooh2002 merged 1 commit into
developfrom
feat/65-tone-correction-service

Conversation

@chjpooh2002

@chjpooh2002 chjpooh2002 commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

관련 이슈

작업 내용

① 준비 #85 위에 쌓았습니다 (다음은 ③ #87). ①이 병합되면 기준 브랜치를 develop으로 바꿉니다.

  • 커밋: 0564cca
  • AiToneCorrectionCommandService.correct(AiToneCorrectCommand) → AiToneCorrectResult
    • 클래스·메서드 모두 트랜잭션을 걸지 않음. 외부 호출(최대 20초) 동안 DB 커넥션을 쥐지 않게, 선점·반환이 각자 짧은 트랜잭션으로 끝남 (CLAUDE.md "명령 서비스는 쓰기 메서드마다 @Transactional" 규칙의 예외. 클래스 주석에 이유를 적음)
    • 순서
      1. UserQueryService.validateActive (없으면 USER404_1)
      2. AiToneUsageCommandService.reserve (남은 횟수가 없으면 AI429_1, 언어 모델을 부르지 않음)
      3. 언어 모델 호출: 문체 지시문, <text>로 감싼 원문, 출력 토큰 상한 6000
      4. 실패 처리. 셋 다 release한 뒤 에러를 던짐
        • 거절(refused)이거나 표식만 출력(앞뒤 공백 무시)하면 AI422_1
        • LanguageModelException이면 AI502_1 (출력 상한 max_tokens로 끝난 경우 포함, 잘린 결과를 내보내지 않음)
        • LanguageModelTimeoutException이면 AI504_1
      5. 결과 앞뒤 공백 제거(TextWhitespace), 이어서 KeyInfoChecker.compare
      6. usage는 다시 조회하지 않고 선점 결과로 만듦. remaining = 한도 − 선점 후 횟수, resetAt = 선점한 날짜의 다음 날 00:00
    • 반환은 위 세 경우에서만 함. 성공·핵심 정보 경고·AI429_1·예상 못 한 예외(500)에서는 반환하지 않음
    • 반환이 실패해도 원래 에러 코드를 그대로 던지고, 로그에는 회원 번호·날짜·예외 종류만 남김
  • 로그: 회원 번호·문체·결과 종류(success / blocked)·소요 시간·출력 토큰 수만. 실패는 언어 모델 예외 메시지(상태 코드·오류 종류만 담김)를 남김. 원문·결과는 남기지 않음
  • 명령·결과 객체의 toString은 원문·결과 대신 길이와 핵심 정보 개수만 보임

명세와 달라진 점

없음 (출력 토큰 상한 6000의 명세 수정은 ③ PR)

테스트

모키토 단위 테스트

  • 성공: 앞뒤 공백(NBSP 포함)을 지운 결과, 선점 결과로 만든 사용량(2회 사용 → 남은 3, 다음 날 00:00), 반환 안 함
  • 언어 모델 요청: 문체 지시문, <text>로 감싼 원문, 출력 상한 6000
  • 날짜·금액이 빠지거나 바뀌면 차이를 담고 반환 안 함
  • 없는 회원: USER404_1, 선점·언어 모델 호출 안 함
  • 남은 횟수 없음: AI429_1, 언어 모델 호출·반환 안 함
  • 거절·표식(공백·BOM·NBSP가 붙은 경우 포함) AI422_1, 외부 오류 AI502_1, 시간 초과 AI504_1, 셋 다 선점한 1회를 반환
  • 반환이 실패해도 원래 코드(AI504_1)
  • 예상 못 한 예외는 그대로 올라가고 반환 안 함
  • 명령·결과 toString에 원문·결과·핵심 정보 값이 없음

트랜잭션 밖에서 동작하는지는 ③ PR의 통합 테스트(실제 선점·반환)로 확인

확인 체크리스트

  • ./gradlew build 통과
  • 테스트 포함
  • 변경 400줄 안팎 (+372)
  • 병합된 마이그레이션 수정 안 함
  • 비밀 값·개인정보 없음
  • global 공통 코드를 바꿨다면 디스코드에 공유함 (변경 없음)

리뷰어에게

[중점] 반환이 세 경우에서만 일어나는지, 트랜잭션을 걸지 않은 판단, 로그에 남기는 값

oTAETAEo
oTAETAEo previously approved these changes Nov 12, 2026

@oTAETAEo oTAETAEo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다아 🙇‍♂️

Comment on lines +57 to +79
private LanguageModelResult complete(AiToneCorrectCommand command, AiToneReserveResult reservation) {
LanguageModelRequest request = new LanguageModelRequest(ToneCorrectionPrompt.systemPrompt(command.style()),
ToneCorrectionPrompt.userMessage(command.text()), MAX_OUTPUT_TOKENS);
long startedAt = System.nanoTime();
LanguageModelResult result;
try {
result = languageModelClient.complete(request);
} catch (LanguageModelTimeoutException e) {
logFailure(command, startedAt, e);
throw releaseAndFail(reservation, AiErrorCode.LANGUAGE_MODEL_TIMEOUT);
} catch (LanguageModelException e) {
logFailure(command, startedAt, e);
throw releaseAndFail(reservation, AiErrorCode.LANGUAGE_MODEL_FAILED);
}

boolean blocked = result.refused() || ToneCorrectionPrompt.isBlocked(result.text());
log.info("AI 톤 교정 userId={} style={} outcome={} elapsedMs={} outputTokens={}", command.userId(),
command.style(), blocked ? "blocked" : "success", elapsedMillis(startedAt), result.outputTokens());
if (blocked) {
throw releaseAndFail(reservation, AiErrorCode.INAPPROPRIATE_TEXT);
}
return result;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 부분이 리뷰어가 중점으로 보면 좋을 부분인 것으로 이해했습니다 !

[중점] 반환이 세 경우에서만 일어나는지, 트랜잭션을 걸지 않은 판단, 로그에 남기는 값

반환이 세 경우에서만 일어나는지
시간 초과, 외부 오류, 차단에서만 반환하는 것 확인했습니다.
그 밖의 예외로 500이 나면 1회가 차감되는데, 의도하신 거라면 명세 비고에 "500은 차감"을 적어 두면 좋을 것 같습니다

트랜잭션을 걸지 않은 판단
좋습니당 AI 호출할 때 트랜잭션이 걸려 있으면 DB 커넥션까지 잡혀 있게 되는 문제로 이해해서 적절한 것 같습니다 !

로그에 남기는 값
좋은 것 같아요 ! 원문·교정 결과는 빼고 필요한 부분만 잘 남기고 있는 것 같습니다 👍

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

짚어 주셔서 감사해요! 확인해 보니 외부 호출 쪽 예외는 거의 다 외부 오류로 감싸져서, 1회를 잡은 뒤 500이 나는 건 사실상 서버 버그 때문이더라구요. 저희 버그로 사용자 횟수가 줄어드는 건 맞지 않는 것 같아서, 명세에 적는 대신 예상 못 한 예외가 나도 1회를 되돌리도록 고쳤습니다!

반환하는 곳을 correct()의 catch 한 곳으로 모아서 이중 반환도 생기지 않게 했어요. 스택 구조라 수정은 #87에 올렸습니다 (8501137). 명세 22번 비고에도 "서버 내부 오류(500)도 미차감"으로 추가했어요!

Base automatically changed from feat/65-tone-correction-prep to develop November 13, 2026 04:54
@chjpooh2002
chjpooh2002 dismissed oTAETAEo’s stale review November 13, 2026 04:54

The base branch was changed.

@oTAETAEo oTAETAEo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

화긘했습니다 👍

@chjpooh2002
chjpooh2002 merged commit bd24b24 into develop Nov 13, 2026
@chjpooh2002
chjpooh2002 deleted the feat/65-tone-correction-service branch November 13, 2026 05:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai AI 톤 교정 feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants