diff --git a/ai-python/__pycache__/main.cpython-313.pyc b/ai-python/__pycache__/main.cpython-313.pyc index 4f0d34f..f86462a 100644 Binary files a/ai-python/__pycache__/main.cpython-313.pyc and b/ai-python/__pycache__/main.cpython-313.pyc differ diff --git a/ai-python/app/__pycache__/__init__.cpython-313.pyc b/ai-python/app/__pycache__/__init__.cpython-313.pyc index c84f132..0c780e2 100644 Binary files a/ai-python/app/__pycache__/__init__.cpython-313.pyc and b/ai-python/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/ai-python/app/__pycache__/config.cpython-313.pyc b/ai-python/app/__pycache__/config.cpython-313.pyc index 1f295f0..24fa9a1 100644 Binary files a/ai-python/app/__pycache__/config.cpython-313.pyc and b/ai-python/app/__pycache__/config.cpython-313.pyc differ diff --git a/ai-python/app/api/__pycache__/__init__.cpython-313.pyc b/ai-python/app/api/__pycache__/__init__.cpython-313.pyc index 27e7bd3..aaf83d5 100644 Binary files a/ai-python/app/api/__pycache__/__init__.cpython-313.pyc and b/ai-python/app/api/__pycache__/__init__.cpython-313.pyc differ diff --git a/ai-python/app/api/v1/__init__.py b/ai-python/app/api/v1/__init__.py index cf5f171..b5b6d39 100644 --- a/ai-python/app/api/v1/__init__.py +++ b/ai-python/app/api/v1/__init__.py @@ -1,8 +1,10 @@ """API v1 Router""" from fastapi import APIRouter from .transcripts import router as transcripts_router +from .suggestions import router as suggestions_router router = APIRouter() # 라우터 등록 router.include_router(transcripts_router, prefix="/transcripts", tags=["Transcripts"]) +router.include_router(suggestions_router, prefix="/ai/suggestions", tags=["AI Suggestions"]) diff --git a/ai-python/app/api/v1/__pycache__/__init__.cpython-313.pyc b/ai-python/app/api/v1/__pycache__/__init__.cpython-313.pyc index 09b89a5..82f3c77 100644 Binary files a/ai-python/app/api/v1/__pycache__/__init__.cpython-313.pyc and b/ai-python/app/api/v1/__pycache__/__init__.cpython-313.pyc differ diff --git a/ai-python/app/api/v1/__pycache__/suggestions.cpython-313.pyc b/ai-python/app/api/v1/__pycache__/suggestions.cpython-313.pyc index c2f1348..d537860 100644 Binary files a/ai-python/app/api/v1/__pycache__/suggestions.cpython-313.pyc and b/ai-python/app/api/v1/__pycache__/suggestions.cpython-313.pyc differ diff --git a/ai-python/app/api/v1/__pycache__/transcripts.cpython-313.pyc b/ai-python/app/api/v1/__pycache__/transcripts.cpython-313.pyc index 3b410df..2c16c45 100644 Binary files a/ai-python/app/api/v1/__pycache__/transcripts.cpython-313.pyc and b/ai-python/app/api/v1/__pycache__/transcripts.cpython-313.pyc differ diff --git a/ai-python/app/models/__init__.py b/ai-python/app/models/__init__.py index a8b9a1b..0863f5d 100644 --- a/ai-python/app/models/__init__.py +++ b/ai-python/app/models/__init__.py @@ -6,6 +6,10 @@ from .transcript import ( ParticipantMinutes, ExtractedTodo ) +from .response import ( + SimpleSuggestion, + RealtimeSuggestionsResponse +) __all__ = [ "ConsolidateRequest", @@ -13,4 +17,6 @@ __all__ = [ "AgendaSummary", "ParticipantMinutes", "ExtractedTodo", + "SimpleSuggestion", + "RealtimeSuggestionsResponse", ] diff --git a/ai-python/app/models/__pycache__/__init__.cpython-313.pyc b/ai-python/app/models/__pycache__/__init__.cpython-313.pyc index ed1e908..5d30b7f 100644 Binary files a/ai-python/app/models/__pycache__/__init__.cpython-313.pyc and b/ai-python/app/models/__pycache__/__init__.cpython-313.pyc differ diff --git a/ai-python/app/models/__pycache__/response.cpython-313.pyc b/ai-python/app/models/__pycache__/response.cpython-313.pyc index e2cc2c1..87af357 100644 Binary files a/ai-python/app/models/__pycache__/response.cpython-313.pyc and b/ai-python/app/models/__pycache__/response.cpython-313.pyc differ diff --git a/ai-python/app/models/__pycache__/transcript.cpython-313.pyc b/ai-python/app/models/__pycache__/transcript.cpython-313.pyc index 63d5734..28e6f24 100644 Binary files a/ai-python/app/models/__pycache__/transcript.cpython-313.pyc and b/ai-python/app/models/__pycache__/transcript.cpython-313.pyc differ diff --git a/ai-python/app/prompts/__pycache__/consolidate_prompt.cpython-313.pyc b/ai-python/app/prompts/__pycache__/consolidate_prompt.cpython-313.pyc index bfa49a3..8065342 100644 Binary files a/ai-python/app/prompts/__pycache__/consolidate_prompt.cpython-313.pyc and b/ai-python/app/prompts/__pycache__/consolidate_prompt.cpython-313.pyc differ diff --git a/ai-python/app/prompts/suggestions_prompt.py b/ai-python/app/prompts/suggestions_prompt.py new file mode 100644 index 0000000..c885a87 --- /dev/null +++ b/ai-python/app/prompts/suggestions_prompt.py @@ -0,0 +1,72 @@ +"""AI 제안사항 추출 프롬프트""" + + +def get_suggestions_prompt(transcript_text: str) -> tuple[str, str]: + """ + 회의 텍스트에서 AI 제안사항을 추출하는 프롬프트 생성 + + Returns: + (system_prompt, user_prompt) 튜플 + """ + + system_prompt = """당신은 회의 내용 분석 전문가입니다. +회의 텍스트를 분석하여 실행 가능한 제안사항을 추출해주세요.""" + + user_prompt = f"""다음 회의 내용을 분석하여 **구체적이고 실행 가능한 제안사항**을 추출해주세요. + +# 회의 내용 +{transcript_text} + +--- + +# 제안사항 추출 기준 +1. **실행 가능성**: 바로 실행할 수 있는 구체적인 액션 아이템 +2. **명확성**: 누가, 무엇을, 언제까지 해야 하는지 명확한 내용 +3. **중요도**: 회의 목표 달성에 중요한 사항 +4. **완결성**: 하나의 제안사항이 독립적으로 완결된 내용 + +# 제안사항 유형 예시 +- **후속 작업**: "시장 조사 보고서를 다음 주까지 작성하여 공유" +- **의사결정 필요**: "예산안 3안 중 최종안을 이번 주 금요일까지 결정" +- **리스크 대응**: "법률 검토를 위해 법무팀과 사전 협의 필요" +- **일정 조율**: "다음 회의를 3월 15일로 확정하고 참석자에게 공지" +- **자료 준비**: "경쟁사 분석 자료를 회의 전까지 준비" +- **검토 요청**: "초안에 대한 팀원들의 피드백 수집 필요" +- **승인 필요**: "최종 기획안을 경영진에게 보고하여 승인 받기" + +# 제안사항 작성 가이드 +- **구체적으로**: "검토 필요" (X) → "법무팀과 계약서 조항 검토 미팅 잡기" (O) +- **명확하게**: "나중에 하기" (X) → "다음 주 화요일까지 완료" (O) +- **실행 가능하게**: "잘 되길 바람" (X) → "주간 진행상황 공유 미팅 설정" (O) + +--- + +# 출력 형식 +반드시 아래 JSON 형식으로만 응답하세요: + +```json +{{ + "suggestions": [ + {{ + "content": "제안사항 내용 (구체적이고 실행 가능하게, 50자 이상 작성)", + "confidence": 0.85 (이 제안사항의 중요도/확실성, 0.7-1.0 사이) + }}, + {{ + "content": "또 다른 제안사항", + "confidence": 0.92 + }} + ] +}} +``` + +# 중요 규칙 +1. **회의 내용에 명시된 사항만** 추출 (추측하지 않기) +2. **최소 3개, 최대 7개**의 제안사항 추출 +3. 중요도가 높은 순서로 정렬 +4. confidence는 **0.7 이상**만 포함 +5. 각 제안사항은 **50자 이상** 구체적으로 작성 +6. JSON만 출력 (```json이나 다른 텍스트 포함 금지) + +이제 위 회의 내용에서 제안사항을 JSON 형식으로 추출해주세요.""" + + return system_prompt, user_prompt diff --git a/ai-python/app/services/__pycache__/__init__.cpython-313.pyc b/ai-python/app/services/__pycache__/__init__.cpython-313.pyc index 3546a4c..37982cc 100644 Binary files a/ai-python/app/services/__pycache__/__init__.cpython-313.pyc and b/ai-python/app/services/__pycache__/__init__.cpython-313.pyc differ diff --git a/ai-python/app/services/__pycache__/claude_service.cpython-313.pyc b/ai-python/app/services/__pycache__/claude_service.cpython-313.pyc index 667eda0..3580aa6 100644 Binary files a/ai-python/app/services/__pycache__/claude_service.cpython-313.pyc and b/ai-python/app/services/__pycache__/claude_service.cpython-313.pyc differ diff --git a/ai-python/app/services/__pycache__/redis_service.cpython-313.pyc b/ai-python/app/services/__pycache__/redis_service.cpython-313.pyc index fe4e3d5..5acd9cb 100644 Binary files a/ai-python/app/services/__pycache__/redis_service.cpython-313.pyc and b/ai-python/app/services/__pycache__/redis_service.cpython-313.pyc differ diff --git a/ai-python/app/services/__pycache__/transcript_service.cpython-313.pyc b/ai-python/app/services/__pycache__/transcript_service.cpython-313.pyc index 9d98b20..0b593a5 100644 Binary files a/ai-python/app/services/__pycache__/transcript_service.cpython-313.pyc and b/ai-python/app/services/__pycache__/transcript_service.cpython-313.pyc differ diff --git a/ai-python/app/services/claude_service.py b/ai-python/app/services/claude_service.py index 5dfcd6e..896da50 100644 --- a/ai-python/app/services/claude_service.py +++ b/ai-python/app/services/claude_service.py @@ -85,6 +85,55 @@ class ClaudeService: logger.error(f"Claude API 호출 실패: {e}") raise + async def analyze_suggestions(self, transcript_text: str): + """ + 회의 텍스트에서 AI 제안사항 추출 + + Args: + transcript_text: 회의 텍스트 + + Returns: + RealtimeSuggestionsResponse 객체 + """ + from app.models import RealtimeSuggestionsResponse, SimpleSuggestion + from app.prompts.suggestions_prompt import get_suggestions_prompt + from datetime import datetime + import uuid + + try: + # 프롬프트 생성 + system_prompt, user_prompt = get_suggestions_prompt(transcript_text) + + # Claude API 호출 + result = await self.generate_completion( + prompt=user_prompt, + system_prompt=system_prompt + ) + + # 응답 파싱 + suggestions_data = result.get("suggestions", []) + + # SimpleSuggestion 객체로 변환 + suggestions = [ + SimpleSuggestion( + id=str(uuid.uuid4()), + content=s["content"], + timestamp=datetime.now().strftime("%H:%M:%S"), + confidence=s.get("confidence", 0.85) + ) + for s in suggestions_data + if s.get("confidence", 0) >= 0.7 # 신뢰도 0.7 이상만 + ] + + logger.info(f"AI 제안사항 {len(suggestions)}개 추출 완료") + + return RealtimeSuggestionsResponse(suggestions=suggestions) + + except Exception as e: + logger.error(f"제안사항 분석 실패: {e}", exc_info=True) + # 빈 응답 반환 + return RealtimeSuggestionsResponse(suggestions=[]) + # 싱글톤 인스턴스 claude_service = ClaudeService() diff --git a/test-audio/stt-test-ai.html b/test-audio/stt-test-ai.html index 7466d40..5092cc7 100644 --- a/test-audio/stt-test-ai.html +++ b/test-audio/stt-test-ai.html @@ -179,7 +179,7 @@

📋 테스트 정보

STT Service: ws://localhost:8084/ws/audio

-

AI Service: http://localhost:8086/api/v1/ai/suggestions

+

AI Service: http://localhost:8086/api/ai/suggestions

Meeting ID: test-meeting-001

@@ -345,7 +345,7 @@ // AI SSE 연결 function connectAIEventSource() { - const sseUrl = `http://localhost:8086/api/v1/ai/suggestions/meetings/${meetingId}/stream`; + const sseUrl = `http://localhost:8086/api/ai/suggestions/meetings/${meetingId}/stream`; addLog('AI SSE 연결 시도...', 'info'); aiEventSource = new EventSource(sseUrl); diff --git a/test-audio/stt-test-wav.html b/test-audio/stt-test-wav.html index 04b995a..23fb242 100644 --- a/test-audio/stt-test-wav.html +++ b/test-audio/stt-test-wav.html @@ -467,7 +467,7 @@ // AI 제안사항 SSE 연결 function connectAISuggestions() { - const sseUrl = `${aiServiceUrl}/api/v1/ai/suggestions/meetings/${meetingId}/stream`; + const sseUrl = `${aiServiceUrl}/api/ai/suggestions/meetings/${meetingId}/stream`; addLog('AI 제안사항 SSE 연결 시도: ' + sseUrl, 'info'); eventSource = new EventSource(sseUrl);