Skip to content

fix : 시연 전후 수정사항들 반영#28

Merged
ahtop00 merged 3 commits intorefactorfrom
stt-analyze-voice
Dec 1, 2025
Merged

fix : 시연 전후 수정사항들 반영#28
ahtop00 merged 3 commits intorefactorfrom
stt-analyze-voice

Conversation

@jpark0506
Copy link
Collaborator

@jpark0506 jpark0506 commented Dec 1, 2025

🔎 Description

시연 영상 촬영 중 감정 분석 param에 대한 미세 조정과 timeout 조정이 포함되었습니다.

🔗 Related Issue

🏷️ What type of PR is this?

  • ✨ Feature
  • ♻️ Code Refactor
  • 🐛 Bug Fix
  • 🚑 Hot Fix
  • 📝 Documentation Update
  • 🎨 Style
  • ⚡️ Performance Improvements
  • ✅ Test
  • 👷 CI
  • 💚 CI Fix

📋 Changes Made

  • Change 1
  • Change 2
  • Change 3

🧪 Testing

  • Unit tests pass
  • Integration tests pass
  • Manual testing completed

📸 Screenshots (if applicable)

📝 Additional Notes

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 오디오 파일에 대한 S3 통합 및 사전 서명된 URL 지원 추가
    • 향상된 음성 분석 및 감정 감지 알고리즘 개선
  • 버그 수정

    • 두려움 및 불안감 감정 확률 계산 통합
    • 분노 감정 점수의 오버플로우 완화
    • 오디오 파일 형식 검증 및 정규화 강화
  • 개선사항

    • 음성 파일 처리 파이프라인 안정성 향상
    • 음성 인식 구성 최적화
    • 오디오 전처리 및 정리 프로세스 개선

✏️ Tip: You can customize this high-level summary in your review settings.

@jpark0506 jpark0506 self-assigned this Dec 1, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 1, 2025

둘러보기

이 변경사항은 음성 파일 처리 파이프라인을 확장하여 S3 업로드, 생성된 URL 지원, 향상된 STT 서비스 및 정서 분석 로직을 추가합니다. 두려움/불안감 확률 집계, 챗봇 통합 개선, 상세한 로깅 및 파일 검증 기능이 포함됩니다.

변경사항

코호트 / 파일(s) 변경 요약
음성 처리 및 STT 서비스
app/stt_service.py, app/voice_service.py
STT 서비스에 로깅, 클라이언트 초기화, 오디오 전처리 강화 추가. 음성 서비스에서 m4a/mp4 헤더 검증 및 파일 동기화 개선. 두려움 확률 집계 로직 업데이트. 새로운 공개 함수 transcribe_voicestt_service 인스턴스 추가.
채팅 분석 서비스 및 통합
app/services/analyze_chat_service.py, app/services/chatbot_integration.py
파일 검증, WAV 변환, S3 업로드, 생성된 URL 생성을 포함하는 포괄적인 분석 및 전송 흐름 추가. 음성 서비스 및 S3 서비스 통합. _send_to_chatbot 메서드에 s3_url 매개변수 추가. SQS 메시지 페이로드에 생성된 URL 포함. HTTP 타임아웃을 60초로 증가.
정서 분석 및 퓨전 로직
app/main.py, app/services/va_fusion.py
두려움 계산을 두려움 + 불안감 확률 합계로 변경. 오디오 분노 확률이 매우 낮을 때(≤0.2) 최종 분노 점수 감쇠 블록 추가.

시퀀스 다이어그램

sequenceDiagram
    participant Client
    participant AnalyzeChatService
    participant S3Service
    participant STTService
    participant VoiceService
    participant ChatbotIntegration
    participant S3 as AWS S3
    participant SQS as AWS SQS
    
    Client->>AnalyzeChatService: 음성 파일 업로드
    AnalyzeChatService->>AnalyzeChatService: 파일 확장자/컨텐츠 타입 검증
    AnalyzeChatService->>AnalyzeChatService: 파일 읽기 및 크기 검증
    AnalyzeChatService->>VoiceService: WAV 변환 (스레드)
    VoiceService->>VoiceService: m4a/mp4 헤더 검증
    VoiceService-->>AnalyzeChatService: WAV 컨텐츠 반환
    
    AnalyzeChatService->>S3Service: WAV 파일 S3 업로드
    S3Service->>S3: 파일 업로드
    S3-->>S3Service: 업로드 확인
    S3Service->>S3Service: 생성된 URL 생성 (7일 만료)
    S3Service-->>AnalyzeChatService: s3_url 반환
    
    AnalyzeChatService->>STTService: STT 처리 (WAV 컨텐츠)
    STTService->>STTService: 음성 전처리 (단일채널, 16kHz)
    STTService-->>AnalyzeChatService: 텍스트 및 신뢰도 반환
    
    AnalyzeChatService->>VoiceService: 정서 분석
    VoiceService->>VoiceService: 두려움+불안감 집계
    VoiceService-->>AnalyzeChatService: 정서 데이터 반환
    
    AnalyzeChatService->>ChatbotIntegration: STT, 정서, s3_url 전달
    ChatbotIntegration->>ChatbotIntegration: SQS 메시지 본문 생성 (s3_url 포함)
    ChatbotIntegration->>SQS: 메시지 전송
    SQS-->>ChatbotIntegration: 확인
    ChatbotIntegration-->>AnalyzeChatService: 성공 응답
    AnalyzeChatService-->>Client: 결과 반환
Loading

예상 코드 리뷰 노력

🎯 4 (복잡함) | ⏱️ ~60분

추가 주의가 필요한 영역:

  • app/services/analyze_chat_service.py: 음성 서비스 통합, S3 업로드, 파일 검증 흐름에서의 비동기 처리 및 에러 처리 로직 검증
  • app/stt_service.py: 클라이언트 초기화, 임시 파일 라이프사이클 관리, 오디오 전처리 파이프라인의 안정성 확인
  • app/services/chatbot_integration.py: SQS 메시지 페이로드 구조 변경 및 다운스트림 호환성 검증
  • app/voice_service.py & app/main.py: 두려움/불안감 집계 로직의 정서 분석 결과에 미치는 영향 평가
  • app/services/va_fusion.py: 분노 점수 감쇠 조건(≤0.2) 및 승수 계산의 음성/텍스트 가중치 검증

관련 PR

제안 레이블

enhancement

제안 리뷰어

  • H4nnhoi
  • ahtop00

🐰 음성 파일이 들어오니 변환하고, S3로 날려버리고,
생성된 주소 담아 챗봇에 보내고,
두려움과 불안감 손잡고 춤을 추네!
정서는 풍요롭고, 로그는 많고,
우리의 코드, 한 걸음 더 나아갔어요 🎉

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning 설명이 불완전합니다. PR 타입, 변경사항 목록, 테스트 항목이 모두 체크되지 않았으며, 실제 변경내용과 불일치합니다. PR 타입을 선택하고, 구체적인 변경사항 목록을 작성하며, 테스트 완료 여부를 표시해주시기 바랍니다. 감정 분석 모수 조정, 타임아웃 조정, 로깅 추가 등의 변경사항을 명시하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 73.68% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive 제목이 모호하고 구체적이지 않습니다. '시연 전후 수정사항들'은 어떤 변경사항인지 명확하게 설명하지 못하며, 실제 변경사항(감정 분석 모수 조정, 타임아웃 조정, 로깅 추가, S3 URL 지원 등)을 반영하지 못합니다. 제목을 더 구체적으로 수정하여 주요 변경사항을 명확히 드러내시기 바랍니다. 예: 'Add emotion analysis fine-tuning and S3 presigned URL support'
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch stt-analyze-voice

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jpark0506 jpark0506 changed the title fix : 디버깅 전 수정사항들 반영 fix : 시연 전후 수정사항들 반영 Dec 1, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (10)
app/services/va_fusion.py (1)

276-291: except: pass 블록은 디버깅을 어렵게 만듭니다.

현재 구현은 모든 예외를 무시합니다. 문제가 발생해도 로그가 남지 않아 추후 디버깅이 어려워질 수 있습니다.

최소한 경고 로그를 남기는 것을 권장합니다:

     except Exception:
-        # 로직 실패 시에는 안전하게 무시
-        pass
+        # 로직 실패 시 경고 로그 남기고 안전하게 무시
+        import logging
+        logging.getLogger(__name__).warning(
+            "Angry dampening logic failed, skipping adjustment",
+            exc_info=True
+        )

또는 모듈 상단에 logger를 정의하고 사용하는 것이 더 좋습니다.

app/stt_service.py (2)

88-114: robust_load 함수가 emotion_service.py와 중복됩니다.

이 함수는 app/emotion_service.py (lines 73-95)에 있는 robust_load와 거의 동일한 구현입니다. 코드 중복을 줄이기 위해 공통 유틸리티로 추출하는 것을 고려해 주세요.

예를 들어 app/utils/audio_utils.py에 공통 함수를 정의할 수 있습니다:

# app/utils/audio_utils.py
def robust_load(path: str, target_sr: int = 16000, logger=None):
    """soundfile 우선, 실패 시 librosa로 폴백. 모노, 정규화 반환."""
    # 공통 구현
    ...

196-202: 예외 메시지 포맷 개선 가능

str(e) 대신 !s 변환 플래그를 사용하면 더 명시적입니다.

-            return {
-                "error": f"STT 처리 중 오류 발생: {str(e)}",
+            return {
+                "error": f"STT 처리 중 오류 발생: {e!s}",
app/main.py (1)

923-927: to_bps 함수가 여러 곳에서 중복 정의되어 있습니다.

이 함수가 app/voice_service.py (lines 445-449)에도 동일하게 정의되어 있습니다. 공통 유틸리티로 추출하면 유지보수가 용이해집니다.

예를 들어 app/utils/conversion.py에 정의할 수 있습니다:

# app/utils/conversion.py
def to_bps(v: float) -> int:
    """float 값을 bps(basis points, 0-10000) 단위로 변환"""
    try:
        return max(0, min(10000, int(round(float(v) * 10000))))
    except Exception:
        return 0
app/voice_service.py (1)

46-58: 헤더 검증 및 로깅 추가가 적절합니다.

M4A/MP4 파일의 ftyp atom 검증과 경고 로깅은 손상된 파일을 조기에 감지하는 데 유용합니다. 다만 57번 라인의 f-string에 플레이스홀더가 없습니다.

-                print(f"[convert] WARNING: M4A 파일이지만 ftyp atom이 없음, 손상된 파일일 수 있음")
+                print("[convert] WARNING: M4A 파일이지만 ftyp atom이 없음, 손상된 파일일 수 있음")
app/services/chatbot_integration.py (1)

19-19: Depends(get_db)가 일반 함수에서 사용되고 있습니다.

send_analysis_to_chatbot 함수에서 db: Session = Depends(get_db)를 기본값으로 사용하고 있지만, 이 함수는 FastAPI 엔드포인트가 아닙니다. 현재 코드에서는 호출 시 db를 명시적으로 전달하고 있어 동작에는 문제가 없지만, 시그니처가 오해를 불러일으킬 수 있습니다.

-def send_analysis_to_chatbot(voice_id: int, db: Session = Depends(get_db)):
+def send_analysis_to_chatbot(voice_id: int, db: Session):
app/services/analyze_chat_service.py (4)

71-71: not in 연산자를 사용하세요.

정적 분석 도구에서 지적한 대로, not '.' in filename 대신 '.' not in filename을 사용해야 합니다.

-        if not '.' in filename or (not lower_name.endswith('.wav') and not lower_name.endswith('.m4a')):
+        if '.' not in filename or (not lower_name.endswith('.wav') and not lower_name.endswith('.m4a')):

113-118: _transcribe_audio에 전달된 content_type 파라미터가 사용되지 않습니다.

content_type을 전달하지만 _transcribe_audio 메서드 내부에서는 filename 기반으로 content_type을 결정합니다(line 180). 파라미터를 제거하거나 실제로 사용하도록 수정해주세요.

         content = await asyncio.to_thread(
             self._transcribe_audio,
             wav_content,
-            wav_filename,
-            content_type
+            wav_filename
         )

메서드 시그니처도 함께 수정:

     def _transcribe_audio(
         self,
         file_content: bytes,
-        filename: str,
-        content_type: str
+        filename: str
     ) -> str:

172-174: INFO 수준 로그에 WARNING 레벨 사용은 권장되지 않습니다.

주석에서 "INFO 레벨은 출력되지 않을 수 있으므로 WARNING으로"라고 설명하지만, 이는 로깅 설정 문제입니다. WARNING 로그를 오염시키는 대신 로거 설정을 수정하여 INFO 레벨이 출력되도록 하는 것이 올바른 접근입니다.

-        # INFO 레벨은 기본 로거 설정에 따라 출력되지 않을 수 있으므로 WARNING으로 남겨 확실히 보이게 한다.
-        logger.warning("AnalyzeChat STT 시작 - filename=%s, content_type=%s", filename, content_type)
+        logger.info("AnalyzeChat STT 시작 - filename=%s, content_type=%s", filename, content_type)

281-282: 명시적으로 Optional[str]을 사용하세요.

PEP 484에 따르면 암시적 Optional은 권장되지 않습니다. 코딩 가이드라인에서도 "Use Optional[Type] for nullable values"를 요구합니다.

파일 상단 imports에 Optional 추가:

from typing import Dict, Any, Optional

그리고 파라미터 수정:

-        s3_url: str = None  # [추가]
+        s3_url: Optional[str] = None  # [추가]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3339e0 and 9f5fff3.

📒 Files selected for processing (6)
  • app/main.py (1 hunks)
  • app/services/analyze_chat_service.py (8 hunks)
  • app/services/chatbot_integration.py (3 hunks)
  • app/services/va_fusion.py (2 hunks)
  • app/stt_service.py (3 hunks)
  • app/voice_service.py (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (.cursorrules)

**/*.py: Always use Depends(get_db) to inject database sessions into FastAPI endpoints; never use next(get_db()) as it causes connection leaks by preventing the finally block from executing
Never directly instantiate Service or Repository in endpoints; always use FastAPI's Depends() for dependency injection with dependency functions named get_{service_name}_dep or get_{repo_name}_dep
Never create database sessions directly using SessionLocal(); sessions must only be created through the get_db dependency function at the endpoint level
Use async def for all FastAPI endpoint functions
Define explicit response_model parameter in FastAPI endpoints using Pydantic models
Organize imports in the following order: (1) standard library, (2) third-party libraries, (3) local modules
Use snake_case for endpoint function names, PascalCase for class names, and UPPER_SNAKE_CASE for constants
Add docstrings to all functions and complex logic comments in the code
Store sensitive information such as database credentials in environment variables, never hardcode them
Use custom exceptions inheriting from AppException base class for consistent error handling and response formatting
Use global exception handlers to catch SQLAlchemyError and ensure consistent error response format for database errors
Use type hints on all function parameters and return values
Use Optional[Type] for nullable values and explicitly handle None cases in code
Use Pydantic models for all request and response data transfer objects (DTOs)
Set autocommit=False and explicitly call commit() for database transactions; call rollback() on exception to ensure data integrity
Use dependency function chaining: Session (top-level) → Service/Repository → endpoint injection
Use Annotated with Depends for complex dependency scenarios to improve code clarity and reduce repetition
Use streaming for large file processing to avoid excessive memory consumption
Monitor and log memory usage u...

Files:

  • app/main.py
  • app/services/va_fusion.py
  • app/services/chatbot_integration.py
  • app/services/analyze_chat_service.py
  • app/voice_service.py
  • app/stt_service.py
**/services/**/*.py

📄 CodeRabbit inference engine (.cursorrules)

**/services/**/*.py: Define dependency functions in service and repository files or in a dedicated dependencies.py file with explicit type hints for all Service and Repository dependencies
Business logic and Service functions must be located in the services/ directory and receive database sessions through constructor parameters or function parameters, not by direct creation

Files:

  • app/services/va_fusion.py
  • app/services/chatbot_integration.py
  • app/services/analyze_chat_service.py
🧬 Code graph analysis (5)
app/main.py (1)
app/voice_service.py (1)
  • to_bps (446-450)
app/services/chatbot_integration.py (2)
app/s3_service.py (1)
  • get_presigned_url (45-57)
app/exceptions.py (1)
  • InternalServerException (37-40)
app/services/analyze_chat_service.py (4)
app/s3_service.py (2)
  • upload_fileobj (26-32)
  • get_presigned_url (45-57)
app/auth_service.py (1)
  • get_auth_service (297-299)
app/voice_service.py (2)
  • get_voice_service (953-955)
  • _convert_to_wav (37-176)
app/main.py (1)
  • FileWrapper (915-919)
app/voice_service.py (1)
app/main.py (1)
  • to_bps (923-927)
app/stt_service.py (1)
app/emotion_service.py (1)
  • robust_load (74-96)
🪛 Ruff (0.14.6)
app/services/va_fusion.py

288-290: try-except-pass detected, consider logging the exception

(S110)


288-288: Do not catch blind exception: Exception

(BLE001)

app/services/chatbot_integration.py

70-70: Consider moving this statement to an else block

(TRY300)

app/services/analyze_chat_service.py

71-71: Test for membership should be not in

Convert to not in

(E713)


282-282: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)

app/voice_service.py

57-57: f-string without any placeholders

Remove extraneous f prefix

(F541)

app/stt_service.py

101-101: Do not catch blind exception: Exception

(BLE001)


199-199: Use explicit conversion flag

Replace with conversion flag

(RUF010)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🔇 Additional comments (8)
app/stt_service.py (1)

217-223: 모듈 레벨 인스턴스 생성 확인

stt_service = GoogleSTTService()가 모듈 임포트 시점에 실행됩니다. Google 인증 정보가 없으면 clientNone이 되어도 예외가 발생하지 않으므로 현재 구현은 안전합니다.

app/main.py (1)

932-937: fear/anxiety 합산 로직이 일관성 있게 적용되었습니다.

voice_service.py의 변경사항과 일관되게 fear와 anxiety를 합산하여 처리하고 있습니다.

app/voice_service.py (2)

77-83: fsync 호출과 크기 검증이 파일 쓰기 신뢰성을 높입니다.

임시 파일에 대한 강제 동기화와 쓰기 후 크기 검증은 데이터 무결성을 보장하는 좋은 방어적 프로그래밍입니다.


457-464: fear 관련 감정 통합 처리가 적절합니다.

fear, anxiety, fearful을 모두 합산하여 fear_bps에 저장하는 것은 감정 모델의 출력을 일관되게 처리하는 합리적인 접근입니다. app/main.py와 일관성이 유지됩니다.

app/services/chatbot_integration.py (2)

60-65: Presigned URL의 7일 만료 기간을 확인해 주세요.

expires_in=3600 * 24 * 7 (7일)은 상당히 긴 기간입니다. 보안 요구사항에 따라 이 값이 적절한지 확인이 필요합니다. 또한 get_presigned_url이 실패하면 빈 문자열을 반환하므로, 이 경우 s3_url이 빈 문자열이 될 수 있습니다.

# 현재 구현에서 get_presigned_url 실패 시 빈 문자열 반환
# s3_url이 "" (빈 문자열)인 경우와 None인 경우를 구분할 필요가 있는지 검토
if bucket and voice.voice_key:
    s3_url = get_presigned_url(bucket, voice.voice_key, expires_in=3600 * 24 * 7)
    if not s3_url:
        logger.warning(f"Failed to generate presigned URL for voice_id={voice_id}")

107-114: 메시지 페이로드 구조가 명확하게 개선되었습니다.

사용자 이름, 질문, 콘텐츠, 녹음 시간, S3 URL, 감정 데이터가 포함된 페이로드는 챗봇 측에서 풍부한 컨텍스트를 활용할 수 있게 합니다.

app/services/analyze_chat_service.py (2)

106-110: Presigned URL 만료 시간이 7일로 설정되어 있습니다.

expires_in=3600 * 24 * 7은 7일입니다. 보안상 민감한 음성 파일의 URL이 7일간 유효한 것이 의도된 것인지 확인해주세요. 일반적으로 1시간(3600초) 정도가 적절합니다.


328-330: LGTM!

의존성 함수가 코딩 가이드라인의 get_{service_name}_dep 패턴을 따르고 있으며, 다른 서비스(get_auth_service, get_voice_service)와 일관된 구조입니다.

Comment on lines 97 to 104
s3_key = await asyncio.to_thread(
self._upload_to_s3,
file_content,
filename,
wav_content,
wav_filename,
session_id,
user_id,
file.content_type
content_type
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

S3 업로드 시 Content-Type 불일치 버그.

wav_content(WAV 변환된 파일)를 업로드하면서 원본 파일의 content_type(예: audio/m4a)을 전달하고 있습니다. WAV 파일을 업로드하므로 content_type"audio/wav"여야 합니다.

         s3_key = await asyncio.to_thread(
             self._upload_to_s3,
             wav_content,
             wav_filename,
             session_id,
             user_id,
-            content_type
+            "audio/wav"
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
s3_key = await asyncio.to_thread(
self._upload_to_s3,
file_content,
filename,
wav_content,
wav_filename,
session_id,
user_id,
file.content_type
content_type
)
s3_key = await asyncio.to_thread(
self._upload_to_s3,
wav_content,
wav_filename,
session_id,
user_id,
"audio/wav"
)
🤖 Prompt for AI Agents
In app/services/analyze_chat_service.py around lines 97 to 104, the code uploads
wav_content but passes the original file's content_type (e.g., audio/m4a);
override or set the content_type to "audio/wav" when calling self._upload_to_s3
so the S3 object has the correct MIME type; update the caller to pass
content_type="audio/wav" (or assign a local variable content_type = "audio/wav"
before the await) and ensure downstream code that relies on content_type uses
the overridden value.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR includes fine-tuning adjustments for emotion analysis parameters and timeout configurations discovered during demo video recording. The changes focus on improving audio file handling, emotion analysis accuracy, and integration with external chatbot services.

Key Changes:

  • Enhanced emotion analysis by aggregating fear/anxiety emotions into a single category for more accurate representation
  • Improved audio file processing with better M4A header validation and file synchronization
  • Extended STT transcript aggregation to combine all recognition results and added comprehensive logging
  • Added S3 presigned URL generation for audio files sent to chatbot services
  • Increased external API timeout from 30s to 60s to accommodate processing latency

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
app/voice_service.py Added M4A file header validation, file sync operations, and fear/anxiety emotion aggregation; includes debug print statements
app/stt_service.py Enhanced with structured logging throughout STT pipeline, improved audio configuration (channel count, emoji handling), and full transcript aggregation from all results
app/services/va_fusion.py Implemented angry emotion dampening logic to prevent over-representation when audio angry levels are low
app/services/chatbot_integration.py Added S3 presigned URL generation for voice files and cleaned up documentation structure
app/services/analyze_chat_service.py Improved file validation, added WAV conversion before processing, S3 URL generation, extended chatbot API timeout, and enhanced logging
app/main.py Applied consistent fear/anxiety emotion aggregation in preview API to match changes in voice_service

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

else:
filename = (filename.rsplit('.', 1)[0] if '.' in filename else filename) + ".wav"

# 4. 파일 읽기 및 WAV 변환 (비동기로 처리하여 블로킹 방지)
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

This comment appears to be a leftover from a previous numbering scheme. The comment says "4. 파일 읽기 및 WAV 변환" (4. File reading and WAV conversion), but there are no preceding steps labeled 1, 2, and 3 in the visible code. This is misleading.

Either update the comment to reflect the actual step sequence or remove the numbering altogether. For example:

# 파일 읽기 및 WAV 변환 (비동기로 처리하여 블로킹 방지)
Suggested change
# 4. 파일 읽기 및 WAV 변환 (비동기로 처리하여 블로킹 방지)
# 파일 읽기 및 WAV 변환 (비동기로 처리하여 블로킹 방지)

Copilot uses AI. Check for mistakes.
Comment on lines +288 to +290
except Exception:
# 로직 실패 시에는 안전하게 무시
pass
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

Silent exception handling without any logging or error indication is problematic. If the angry emotion dampening logic fails, it will silently fall back to the original behavior without any indication of what went wrong.

At minimum, add logging to track when this logic fails:

except Exception as e:
    # 로직 실패 시에는 안전하게 무시
    logger.debug("Angry dampening logic failed: %s", str(e))
    pass

This helps with debugging and monitoring the stability of this emotion adjustment algorithm.

Copilot uses AI. Check for mistakes.
Comment on lines +459 to +464
fear_prob = (
float(probs.get("fear", 0) or 0)
+ float(probs.get("anxiety", 0) or 0)
+ float(probs.get("fearful", 0) or 0)
)
fear = to_bps(fear_prob)
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

The fear/anxiety aggregation logic could produce probabilities exceeding 1.0 before conversion to BPS. While to_bps() clips the result to a max of 10000, this aggregation means the total emotion probabilities could exceed 100% before normalization.

For example, if fear=0.3, anxiety=0.4, and fearful=0.4, the sum would be 1.1 (110%), which gets converted to 11000 BPS. The to_bps() function clips this to 10000, but this could skew the emotion distribution before the later normalization step.

Consider capping the aggregated probability before conversion:

fear_prob = min(1.0, 
    float(probs.get("fear", 0) or 0)
    + float(probs.get("anxiety", 0) or 0)
    + float(probs.get("fearful", 0) or 0)
)
fear = to_bps(fear_prob)

This ensures the aggregated probability stays within valid probability bounds before BPS conversion.

Copilot uses AI. Check for mistakes.
Comment on lines +933 to +937
fear_prob = (
float(probs.get("fear", 0) or 0)
+ float(probs.get("anxiety", 0) or 0)
)
fear = to_bps(fear_prob)
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

Similar to the issue in voice_service.py, this fear/anxiety aggregation could produce probabilities exceeding 1.0. If both fear and anxiety probabilities are present and sum to more than 1.0, this creates an invalid probability distribution.

Consider capping the aggregated value:

fear_prob = min(1.0,
    float(probs.get("fear", 0) or 0)
    + float(probs.get("anxiety", 0) or 0)
)
fear = to_bps(fear_prob)

This ensures the aggregated probability stays within valid bounds before BPS conversion.

Copilot uses AI. Check for mistakes.
for result in response.results
if result.alternatives[0].confidence > 0
]
# TODO : 추후 Confidence가 낮으면 인식에서 제외하는 로직 추가
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

[nitpick] The TODO comment indicates planned functionality for filtering low-confidence results, but this is a behavior change that should be carefully considered. Currently, all transcription results are included regardless of confidence level.

If this TODO is implemented, ensure:

  1. There's a clear threshold for what constitutes "low confidence"
  2. Users are informed when parts of their audio are excluded from the transcript
  3. The confidence threshold is configurable
  4. There's appropriate logging when segments are filtered out

Consider the user experience impact - it might be better to return all transcripts with confidence scores and let the caller decide whether to use them, rather than silently filtering.

Suggested change
# TODO : 추후 Confidence가 낮으면 인식에서 제외하는 로직 추가
# WARNING: Filtering out low-confidence results is a significant behavior change.
# If implementing such filtering, ensure:
# 1. The confidence threshold is clearly defined and configurable.
# 2. Users are informed when parts of their audio are excluded from the transcript.
# 3. There is appropriate logging when segments are filtered out.
# 4. Consider returning all transcripts with confidence scores and letting the caller decide.

Copilot uses AI. Check for mistakes.
Comment on lines 71 to 75
if not '.' in filename or (not lower_name.endswith('.wav') and not lower_name.endswith('.m4a')):
if "m4a" in content_type or "mp4" in content_type:
filename = (filename.rsplit('.', 1)[0] if '.' in filename else filename) + ".m4a"
else:
filename = (filename.rsplit('.', 1)[0] if '.' in filename else filename) + ".wav"
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

[nitpick] The file extension validation logic has redundant conditions. Line 71 checks if not '.' in filename, but then immediately uses the same condition if '.' in filename within the conditional block.

This can be simplified for clarity:

# 확장자 미포함/이상치인 경우 Content-Type 기반으로 보정
if not lower_name.endswith('.wav') and not lower_name.endswith('.m4a'):
    base_name = filename.rsplit('.', 1)[0] if '.' in filename else filename
    if "m4a" in content_type or "mp4" in content_type:
        filename = base_name + ".m4a"
    else:
        filename = base_name + ".wav"

This makes the intent clearer: if the file doesn't have a valid audio extension, correct it based on content type.

Suggested change
if not '.' in filename or (not lower_name.endswith('.wav') and not lower_name.endswith('.m4a')):
if "m4a" in content_type or "mp4" in content_type:
filename = (filename.rsplit('.', 1)[0] if '.' in filename else filename) + ".m4a"
else:
filename = (filename.rsplit('.', 1)[0] if '.' in filename else filename) + ".wav"
if not lower_name.endswith('.wav') and not lower_name.endswith('.m4a'):
base_name = filename.rsplit('.', 1)[0] if '.' in filename else filename
if "m4a" in content_type or "mp4" in content_type:
filename = base_name + ".m4a"
else:
filename = base_name + ".wav"

Copilot uses AI. Check for mistakes.
Comment on lines 173 to 174
# INFO 레벨은 기본 로거 설정에 따라 출력되지 않을 수 있으므로 WARNING으로 남겨 확실히 보이게 한다.
logger.warning("AnalyzeChat STT 시작 - filename=%s, content_type=%s", filename, content_type)
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

Inconsistent logging practice: This warning-level log is used in a place where info-level logging would be more appropriate. The comment suggests using WARNING level "so it's definitely visible", but this circumvents the logging framework's purpose.

If you need to ensure certain logs are visible, configure the logger's level appropriately rather than artificially elevating the severity. This log statement is just tracking the start of an STT operation, which is informational, not a warning condition:

logger.info("AnalyzeChat STT 시작 - filename=%s, content_type=%s", filename, content_type)

If visibility is truly a concern, consider using a dedicated logger configuration for this module rather than misusing log levels.

Suggested change
# INFO 레벨은 기본 로거 설정에 따라 출력되지 않을 수 있으므로 WARNING으로 남겨 확실히 보이게 한다.
logger.warning("AnalyzeChat STT 시작 - filename=%s, content_type=%s", filename, content_type)
# STT 변환 시작 - 정보성 로그
logger.info("AnalyzeChat STT 시작 - filename=%s, content_type=%s", filename, content_type)

Copilot uses AI. Check for mistakes.
@@ -253,7 +299,7 @@
raise InternalServerException("CHATBOT_API_URL not configured")

try:
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

The timeout has been increased from 30.0 to 60.0 seconds without explanation. While this might resolve timeout issues, it could also mask performance problems or hide issues with the external chatbot API.

Consider:

  1. Adding a comment explaining why this timeout increase was necessary
  2. Monitoring the actual response times to ensure they don't consistently approach the timeout
  3. Ensuring the external API has appropriate performance expectations documented

Example:

# Increased timeout to 60s due to chatbot API processing latency for emotion analysis
async with httpx.AsyncClient(timeout=60.0) as client:
Suggested change
try:
try:
# Increased timeout to 60s due to observed latency with the external chatbot API.
# Consider monitoring response times and reviewing this value if performance issues persist.

Copilot uses AI. Check for mistakes.
Comment on lines 145 to 148
"[GoogleSTT] STT 요청 구성 완료 - sample_rate=%d, model=%s, enhanced=%s",
sample_rate,
"latest_long",
"enabled",
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

The log message shows enhanced=%s with a hardcoded string "enabled", but there's no indication that any enhancement is actually enabled in the configuration. The RecognitionConfig doesn't have an explicit use_enhanced parameter set.

This is misleading logging. Either:

  1. Remove the enhanced=%s parameter from the log message if no enhancement is being used
  2. Add actual enhancement configuration if it's intended

Example fix:

logger.info(
    "[GoogleSTT] STT 요청 구성 완료 - sample_rate=%d, model=%s",
    sample_rate,
    "latest_long",
)
Suggested change
"[GoogleSTT] STT 요청 구성 완료 - sample_rate=%d, model=%s, enhanced=%s",
sample_rate,
"latest_long",
"enabled",
"[GoogleSTT] STT 요청 구성 완료 - sample_rate=%d, model=%s",
sample_rate,
"latest_long",

Copilot uses AI. Check for mistakes.
@ahtop00 ahtop00 changed the base branch from main to refactor December 1, 2025 16:14
@ahtop00
Copy link
Contributor

ahtop00 commented Dec 1, 2025

테스트 시연 수고하셨습니다! 👍

@ahtop00 ahtop00 merged commit a28e3e1 into refactor Dec 1, 2025
7 checks passed
@ahtop00 ahtop00 deleted the stt-analyze-voice branch December 1, 2025 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants