Skip to content

FEAT: call -> ai 배선 구현#57

Merged
oofrog merged 18 commits into
developfrom
feat/#49
Jul 23, 2026
Merged

FEAT: call -> ai 배선 구현#57
oofrog merged 18 commits into
developfrom
feat/#49

Conversation

@oofrog

@oofrog oofrog commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

통화(call) 도메인을 실제 AI 대화로 배선했습니다. 에코를 AI chat 호출로 교체하고, POST /calls부터 WebSocket 티켓 인증·세션 바인딩, STT final → AI /chat → TTS 다운스트림까지 전 구간을 연결했습니다. AI 서버 계약 정합을 실서버 왕복(200)과 통화 e2e 실측으로 검증했습니다.

Related Issue

Describe your code

작업 내용 (What I Did)

  • 통화 시작 API POST /calls(dial): characterId 수신 → 존재·소유·main 검증(실패 시 CallErrorCode) → Call 생성 → 단명 wsTicket 발급. 응답 {callId, callStatus(DIALING), wsTicket}.
  • wsTicket 발급/검증: 인메모리 30초·1회용(WsTicketStore) + 핸드셰이크 인터셉터 검증(WsTicketHandshakeInterceptor) + /ws/** permitAll. (브라우저 WS가 JWT 헤더를 못 실어 URL 쿼리 단명 티켓으로 인증)
  • 세션 바인딩: 검증된 신원(callId/relationshipId/characterId)을 ActiveCall에 바인딩.
  • 에코 → AI chat 대체: CallConversationService가 스냅샷 조립 → AiConversationService.chat() → reply를 TTS로. 세션 history 버퍼, stale-version 가드, 턴 실패 시 그 턴만 폐기·통화 유지.
  • AI 서버 계약 정합: role(@JsonProperty), relationshipStage 캐논 매핑(SOME→CRUSH 등), 관계 점수(temperature←affinityScore, trust/repair/breakup 시드), requestId 필수 대응.
  • Relationship.affinityScore: @column(nullable=false) + @min(0)@max(100), 초기값 50(관계 온도 baseline 겸용).

스크린샷/결과

  • AI /chat 실서버 200 (계약 정합 확인).
  • POST /calls → 티켓 → WS 인증 무효/유효/재사용 = 401 / 101 / 401.
  • 오디오 업스트림 → CLOVA STT final → AI chat → CLOVA TTS(mono 24kHz) 다운스트림 전 구간 실측.
  • 실제 마이크로 말하고 AI 음성 응답 재생 확인.

논의사항/질문 (To Reviewers)

  • 관계 점수 소스: temperature는 affinityScore로, trust/repair/breakup은 대응 컬럼이 없어 매퍼 시드(50/0/0). delta 영속화(컬럼 설계)는 별도 이슈. 이 시드 접근 괜찮은지?
  • AiRelationshipSnapshot 하이브리드: 계약 점수 + 원본 필드(emotion/speechStyle/spiceLevel/version) 병존. 원본은 서버가 무시하나 기존 설계라 보존(version은 stale 가드용).
  • AiChatResponse 평면 필드(trust 등)는 실제 응답에선 null(서버가 relationshipDelta/nextRelationship에 중첩) — 채팅 배선 시 중첩 매핑 필요(주석 명시).
  • 동시성: 통화당 단일 스레드 워커 유지(공유 풀 교체는 후순위).
  • 지연: 실측상 LLM 추론 3~5초가 지배 → 스트리밍(TTFA ~2초 목표)은 후순위로 기록.

▎ ⚠ 공유: ① AI 계약 문서상 requestId가 "선택"이나 실제 필수(없으면 400) — AI 담당자 정정 요망. ② affinityScore 초기값 0→50 변경 — 도메인 담당자 공유.

Checklist

  • 리뷰어 등록

@oofrog oofrog self-assigned this Jul 21, 2026
@oofrog oofrog linked an issue Jul 21, 2026 that may be closed by this pull request
4 tasks
@coderabbitai

This comment was marked as low quality.

@oofrog oofrog added the FEAT 기능 구현 label Jul 21, 2026
@github-actions

Copy link
Copy Markdown

🛡️ Issues

  • WsTicketStore에는 만료되었지만 시작/소비되지 않은 티켓이 ConcurrentHashMap에서 제거되지 않아 메모리 누수가 발생합니다.

💡 Improvements

  • WsTicketStore에서 만료된 티켓을 자동으로 제거하기 위해 Caffeine과 같은 자체 만료 캐시 또는 Spring @Scheduled 작업을 사용하세요.
  • CallAudioWebSocketHandler 내의 하드코딩된 "user""assistant" 문자열 리터럴을 타입-세이프 Role enum 또는 공유 상수로 대체하세요.
  • AiRelationshipSnapshotMapper의 단계 매핑 switch 블록을 RelationshipStage enum 내에 매핑된 문자열 값을 직접 정의하여 결합도를 낮추세요.

@oofrog oofrog changed the title Feat: FEAT: call -> ai 배선 구현 Jul 21, 2026

@seokMini-2 seokMini-2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

고생하셨습니다!! 리뷰 확인해주세요!

Comment thread src/main/java/com/example/umcCall/domain/call/entity/Call.java
* @param relationshipId chat 컨텍스트 + stale-version 가드의 기준 관계
* @param characterId 통화 상대 캐릭터(로그·편의용, relationship에서도 도달 가능)
*/
public record WsTicket(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

캐릭터는 계속 변경이 가능하니 티켓이 멤버에 묶여있어야 하지 않을까요??

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

통화 상대가 발신 시점에 확정됩니다. 매 턴 티켓에 들어있는 정보를 파악하는데, 멤버에 묶인다면 통화 도중 메인 연인이 바뀌는 경우 버그가 일어 날 수 있습니다. 멤버 신원이 필요하다면 relationship으로 접근하면 될 것같습니다.

@oofrog
oofrog requested review from hyeonky0w0 and seokMini-2 July 21, 2026 16:23
@github-actions

Copy link
Copy Markdown

💡 개선 사항

  • 반복되는 문자열 리터럴 ("ticket", "user", "assistant")을 전용 정적 상수 또는 열거형으로 추출합니다.
  • CallConversationService 내의 반복적인 엔티티 페칭 상용구 코드를 단일 다중 페치 메서드 또는 리포지토리 헬퍼를 사용하여 간소화합니다.
  • AiRelationshipSnapshotMapper에 있는 기본 상태 시드 값을 도메인 엔티티의 기본값 또는 전용 설정 속성으로 이동합니다.

@github-actions

Copy link
Copy Markdown

💡 개선 사항

  • CallAudioWebSocketHandler에 하드코딩된 스피커 식별자 "nara"를 애플리케이션 설정 속성으로 추출해야 합니다.
  • CallConversationService.respond()에 있는 4개의 개별 데이터베이스 조회 쿼리를 단일 조인 페치 쿼리로 결합해야 합니다.
  • Call 엔티티의 callTimeDuration.getSeconds() 반환 유형과 일치하도록 Long으로 변경하여 명시적인 정수 형변환을 피해야 합니다.

@seokMini-2 seokMini-2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

아래 한가지만 확인해주세용 수고하셨습니당

Comment thread src/main/java/com/example/umcCall/domain/call/entity/Call.java
int last = conversation.size() - 1;
String message = conversation.get(last).content();
// subList는 뷰지만 AiChatRequest 생성자가 List.copyOf로 복사하므로 안전하다.
List<AiChatHistoryItem> history = conversation.subList(0, last);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

현재는 통화 세션의 전체 대화를 전달하고 있는데, 추후 장시간 통화 테스트 시 응답 속도가 너무 느려지는 현상이 나타난다면, 최근 N개 메세지만 전달하거나 이전 내용을 요약해서 포함하는 방식도 고려해볼 수 있을 것 같습니다!

@github-actions

Copy link
Copy Markdown

💡 개선 사항

  • CallConversationService.respond()에 빈 리스트 검사를 추가하여 빈 대화 로그에서 IndexOutOfBoundsException이 발생하는 것을 방지하십시오.
  • WsTicketStore의 기회주의적 정리(opportunistic cleanup)를 예약된 백그라운드 작업으로 교체하여 만료된 티켓의 예측 가능한 제거를 보장하십시오.
  • CallAudioWebSocketHandler에 있는 AI_SPEAKER와 같은 하드코딩된 오디오 설정 상수를 애플리케이션 속성으로 외부화하십시오.

@hyeonky0w0 hyeonky0w0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

확인했습니다!
AI 서버 계약과 대조해 다시 확인했는데, requestId, history role, 응답 reply, 관계 stage 및 필수 점수 매핑 모두 현재 계약과 일치합니다. AI 연동 문서도 실제 구현에 맞게 수정했습니다.
통화에서 reply만 사용하는 현재 범위는 문제없어 Approve하겠습니다.
추후 관계값 반영 시에는 relationshipDelta/nextRelationship 중첩 응답 매핑이 필요할 것 같습니다..

@oofrog
oofrog merged commit 99bb2a7 into develop Jul 23, 2026
2 checks passed
@oofrog
oofrog deleted the feat/#49 branch July 23, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FEAT 기능 구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ [FEAT] call→ai 배선 (wsTicket 세션 바인딩 + STT final → LLM → TTS)

4 participants