감정 기반 AI 심리상담 서비스의 Python 백엔드. 사용자-AI 상담사 대화, 세션 종료 시 감정 분석 리포트·감정 카드 생성. Java Spring Boot BE와 내부 API 연동, AI 추론·임베딩·RAG 검색 전담 서버.
일상적인 감정 기록 + AI 심리상담 서비스. 이 레포지토리는 AI 추론 전담 서버로 다음 역할을 담당한다.
| 역할 | 설명 |
|---|---|
| 실시간 AI 상담 | LangGraph 기반 멀티 노드 그래프로 LLM 스트리밍 응답 제공 |
| 과거 기억 검색 | pgvector + Proposition-based RAG로 유사 과거 상담 컨텍스트 주입 |
| 감정 분석 | 상담 종료 시 Plutchik Wheel 24개 감정 분류 → 세션 대표 감정 TOP 3 집계 |
| 감정 카드 생성 | 감정 데이터 기반 라인아트 이미지 생성 (DALL-E 3 / Amazon Titan Image) |
| AI 리포트 | 주간/월간 심리 경향 분석 리포트 병렬 생성 |
| 데이터 보안 | Bedrock Guardrails PII 탐지·익명화, AES-256-GCM 민감 데이터 암호화 |
flowchart TD
User["사용자 (Mobile App)"]
Java["Java Spring Boot BE"]
AI["MindLog AI (FastAPI)"]
Redis["Redis\n대화 이력 공유"]
PG["PostgreSQL + pgvector\n벡터 저장·검색"]
User -- SSE --> Java
Java -- HTTP --> AI
Java <--> Redis
AI <--> Redis
AI <--> PG
Java <--> PG
subgraph Bedrock["AWS Bedrock"]
B1["Claude 3.x (LLM)"]
B2["Titan Embed v2"]
B3["Titan Image v2"]
B4["Guardrails"]
end
AI --> Bedrock
설계 원칙:
- AI 서버는 추론 전담 — DB 쓰기는 Java BE가 담당 (Redis 대화 이력 포함)
- 두 서버 간 통신은 내부 API (
/internal/v1/*) — 외부 노출 없음 - 대화 이력은 Redis, 벡터·지식 데이터는 PostgreSQL로 역할 분리
파일: app/graph/builder.py, app/services/chat_service.py
persona_node / history_node 병렬 실행 → chat_node 합류.
flowchart LR
START(["START"]) --> persona["persona_node\nDB 조회 + RAG 검색\n→ system_prompt 구성"]
START --> history["history_node\nRedis 대화 이력 로드"]
persona --> chat["chat_node\nLLM 호출 streaming=True"]
history --> chat
chat --> END(["END"])
persona_node 구현 포인트:
- 최근 5개 세션 컨텍스트 조회 (DB)
- 현재 발화로 유사 과거 상담 벡터 검색 (RAG) —
proposition_enabled플래그로 검색 방식 분기 - 사용자 닉네임 조회 → system_prompt에 참고용으로 주입 (매 발화 사용 강제 없음)
- 대화 흐름은 LLM이 대화 내용 기반으로 자율 판단 (턴 수 기반 단계 강제 없음)
파일: app/services/embedding_service.py, app/services/knowledge_service.py
세션 컨텍스트를 원자적 명제(Proposition) 단위로 분해해 검색 정밀도 향상.
저장 파이프라인:
flowchart TD
A["context_summary (1~3문장)"] --> B["LLM: 5~10개 원자적 명제 추출\nPROPOSITION_EXTRACTION_PROMPT"]
B --> C["AES-256-GCM 암호화\n→ session_propositions 저장"]
C --> D["Contextual Retrieval 적용\n상담 요약 + 명제 텍스트 결합"]
D --> E["Amazon Titan Embeddings v2\n1024차원 벡터 생성"]
E --> F["embeddings 테이블 저장\nsource_type='proposition'"]
검색 파이프라인:
flowchart TD
A["user_input"] --> B{"proposition_enabled?"}
B -- True --> C["search_by_propositions\ntop_k × 5 후보 조회"]
B -- False --> D["search_similar_contexts\ntop_k × 3 후보 조회\nlegacy session-level"]
C --> E["pgvector cosine 검색\nsession_id별 MAX score 집계"]
D --> E
E --> F["CRAG 3단계 필터링"]
F --> G["context_summary 복호화\n→ LLM 컨텍스트 주입"]
F --> H["Redis 캐시\nTTL 180초"]
CRAG (Corrective RAG) 3단계 필터링:
| 구간 | 처리 |
|---|---|
| score ≥ crag_high | Correct — 즉시 채택 |
| crag_low ≤ score < crag_high | Ambiguous — top_k 미충족 시 보완 채택 |
| score < crag_low | Incorrect — 폐기 |
Dense-only 선택 이유: 상담 내용(session_context_summaries.content)은 AES-256-GCM으로 암호화 저장되므로 역인덱스 기반 Full-Text Search 적용 불가 → pgvector cosine 검색만 사용.
파일: app/utils/emotion_analyzer.py, app/services/emotion_service.py
Robert Plutchik 감정의 수레바퀴 모델 기반 — 8개 카테고리 × 3개 강도 = 24개 감정, LLM으로 추출.
flowchart TD
A["상담 전체 사용자 발화"] --> B["extract_message_emotions\n→ LLM: MESSAGE_EMOTIONS_PROMPT"]
B --> C["per_message\n각 메시지별 감정 목록\n{emotion, intensity, source_keyword,\ntrigger_context, emotion_trajectory}"]
C --> D["validate_per_message\n24개 코드 외 제거\nintensity 1~10 클램프\ntrajectory 검증\n메시지당 최대 5개 감정"]
D --> E["aggregate_per_message_emotions\nΣ(intensity × trajectory_weight)\nTOP 3 집계"]
E --> F1["aggregated_emotions\n{emotion_type 한글, category,\nlevel, intensity, is_primary}\n→ 이미지 생성 사용"]
E --> F2["flat_emotions\n영문 코드\n→ Java BE 전달 → DB 저장"]
trajectory 가중치: peak: 1.2 / rising: 1.1 / stable: 1.0 / falling: 0.9 반환 intensity는 원본 평균 사용 (가중치 미적용 — DB 왜곡 방지)
감정 점수 산출 (compute_session_emotion_score):
감정 점수 = Σ(valence × intensity) / (count × 10) × 100 → -100 ~ +100
카테고리별 valence: joy(+1.0), trust(+0.8), anticipation(+0.5), surprise(0), fear(-0.8), disgust(-0.9), anger(-0.9), sadness(-1.0)
파일: app/services/image_service.py, app/services/summary_service.py
라인아트 스타일 감정 카드 이미지 생성. 색상은 프론트엔드 담당, 세션 종료 시 refine_session_title()과 병렬 실행.
flowchart TD
A["context_summary 한국어\n+ aggregated TOP3 감정"] --> B["generate_image_scene\n→ LLM: IMAGE_SCENE_PROMPT\n추상적·상징적 자연/사물/공간\n인물·얼굴 제외"]
B --> C["image_scene\n영어 추상 장면 묘사 1~2문장"]
C --> D["build_card_image_prompt\ncoloring book style, thin line art\nwhite background, no fill..."]
D --> E{"use_bedrock?"}
E -- True --> F["BedrockImageProvider\nAmazon Titan Image v2\nbase64 → S3 업로드"]
E -- False --> G["DallEProvider\nOpenAI DALL-E 3\nsize=1024×1792"]
F --> H["image_url 반환"]
G --> H
파일: app/services/guardrail_service.py
Bedrock Guardrails apply_guardrail API에 사전 검사 위임. 개입 결과는 3가지로 분류 → SSE guardrail_intervention 이벤트에 반영:
flowchart TD
A["사용자 입력"] --> B["apply_guardrail INPUT"]
B --> C{"결과 분류"}
C -- "주제 차단 / 콘텐츠 필터" --> D["input_blocked\n→ LLM 호출 없이 종료\nguardrail_intervention 이벤트"]
C -- "PII 감지" --> E["input_pii_masked\n→ 익명화된 텍스트로 LLM 호출\nguardrail_intervention 이벤트"]
C -- "이상 없음" --> F["원본 텍스트로 LLM 호출"]
F --> G["ChatBedrock guardrails 자동 검사"]
G -- "출력 차단" --> H["output_blocked\nguardrail_intervention 이벤트"]
G -- "통과" --> I["chunk 스트리밍"]
AWS 오류 발생 시 Fail-open 방식 적용 — 과도한 차단보다 서비스 가용성 우선.
파일: app/services/crisis_service.py
자해·자살·극단적 선택 의도 감지, 4단계 레벨 시스템. 대화 처리와 비동기 병렬 실행.
| 레벨 | 기준 | 처리 |
|---|---|---|
| 0 | 위험 없음 | — |
| 1 | 극심한 소진·무기력 | 모니터링 |
| 2 | 소멸·종결 암시 | crisis_check 이벤트 → Java BE |
| 3 | 자해·자살 직접 언급 | 즉각 개입 응답 |
flowchart LR
A["사용자 발화"] --> B["crisis_service.detect\n비동기 병렬 실행"]
B --> C{"레벨 판정"}
C -- "Level 0" --> D["통과"]
C -- "Level 1" --> E["모니터링\n이벤트 없음"]
C -- "Level 2+" --> F["crisis_check 이벤트\n→ SSE → Java BE"]
C -- "Level 3" --> G["즉각 개입 응답\n+ crisis_check 이벤트"]
파일: app/services/knowledge_service.py, app/models/knowledge.py
세션 종료 시 컨텍스트 요약에서 익명화된 심리적 패턴·관계 추출 → 사용자 프로파일 축적. PII 미저장, LLM 추상화 개념만 저장.
flowchart TD
A["context_summary"] --> B["LLM: ENTITY_EXTRACTION_PROMPT"]
B --> C["entities\n{entity_type, name, properties}\n예) 감정패턴: 분노 억압 후 자책"]
B --> D["relations\n{source, target, relation_type}\n예) CAUSED_BY, AMPLIFIED_BY, COOCCURS_WITH"]
C --> E["knowledge_entities upsert\nmention_count 증가"]
D --> F["knowledge_relations 저장"]
파일: app/utils/crypto.py
session_context_summaries, session_propositions DB 저장 시 AES-256-GCM 암호화 적용.
- 암호화 포맷:
base64(IV[12bytes] + ciphertext + tag[16bytes]) - RAG 검색은 평문 벡터로 수행 — 암호화된 content는 검색 불가
- 검색 결과 복호화 후 LLM 주입
CONTENT_ENCRYPTION_KEY미설정 시 암호화 비활성 (Fail-open)
파일: app/api/sessions.py, app/services/chat_service.py
Java BE ↔ AI BE 간 LLM 응답 실시간 스트리밍.
stream_message 이벤트 타입:
| 이벤트 | 용도 |
|---|---|
status |
처리 단계 진행 상황 |
ai_chunk |
LLM 응답 토큰 스트리밍 |
ai_complete |
전체 응답 완료 |
session_title |
첫 메시지 시 세션 제목 (비동기 생성) |
crisis_check |
L2+ 위기 감지 시 별도 이벤트 |
guardrail_intervention |
PII 익명화 또는 콘텐츠 차단 (input_blocked / input_pii_masked / output_blocked) |
finalize_session_stream 이벤트 타입:
| 이벤트 | 용도 |
|---|---|
status |
분석/요약/카드 생성 진행 상황 |
complete |
{summary, context_summary, emotions, card, title} 전체 결과 |
파일: app/services/report_service.py, app/prompts/analysis_prompts.py
독립 섹션 병렬 처리, Phase 간 의존성은 순차 실행. Phase 1의 변곡점 감지(_detect_inflection) — 연속 2세션 간 점수 차이 ±25 이상인 지점을 emotion_graphs에 표시.
flowchart TD
subgraph Phase1["Phase 1 (병렬)"]
P1A["graph_evaluation\n감정 점수 흐름 해석\n변곡점 감지 ±25 기준"]
P1B["topics 추출\nfacts ≥ 2 → LLM\nfacts < 2 → keyword 빈도 fallback"]
end
subgraph Phase2["Phase 2 (병렬)"]
P2A["topics_evaluation\n주제 패턴 분석"]
P2B["current_status\n현재 상태 요약"]
P2C["tendency\n전반적 심리 경향"]
end
subgraph Phase3["Phase 3 (순차)"]
P3A["suggestions\n행동 제언\nPhase 2 결과 기반"]
end
Phase1 --> Phase2
Phase2 --> Phase3
파일: app/metrics.py
/metrics 엔드포인트로 Prometheus 메트릭 노출.
| 메트릭 | 타입 | 측정 대상 |
|---|---|---|
bedrock_api_calls_total |
Counter | LLM 호출 횟수 |
bedrock_api_failures_total |
Counter | LLM 호출 실패 횟수 |
bedrock_api_retries_total |
Counter | LLM 재시도 횟수 |
bedrock_api_response_seconds |
Histogram | LLM 응답 레이턴시 |
embedding_api_calls_total |
Counter | 임베딩 API 호출 횟수 |
embedding_api_failures_total |
Counter | 임베딩 API 실패 횟수 |
embedding_api_response_seconds |
Histogram | 임베딩 생성 레이턴시 |
rag_search_seconds |
Histogram | RAG 검색 전체 레이턴시 (mode: session/proposition) |
rag_candidate_score |
Histogram | CRAG 필터링 전 후보 코사인 유사도 분포 |
rag_crag_category_total |
Counter | CRAG correct/ambiguous/drop 분류 횟수 |
rag_miss_total |
Counter | RAG 검색 결과 없음 횟수 |
guardrail_calls_total |
Counter | Guardrail 결과별 호출 횟수 |
proposition_encrypt_seconds |
Histogram | AES-256-GCM 암호화 레이턴시 |
| 분류 | 기술 |
|---|---|
| Web Framework | FastAPI, Uvicorn, Pydantic v2 |
| AI Orchestration | LangGraph, LangChain |
| LLM | Amazon Bedrock (Claude 3.x), OpenAI (DALL-E 3, fallback) |
| Embedding | Amazon Titan Embeddings v2 (1024차원) |
| Image Generation | Amazon Titan Image Generator v2, OpenAI DALL-E 3 |
| Vector DB | PostgreSQL + pgvector (cosine similarity) |
| Cache | Redis (대화 이력, RAG 캐시) |
| ORM / Migration | SQLAlchemy, Alembic |
| 분류 | 기술 |
|---|---|
| Cloud | AWS (Bedrock, S3, IAM) |
| Content Safety | AWS Bedrock Guardrails |
| Encryption | AES-256-GCM (콘텐츠 암호화) |
| Monitoring | Prometheus, Structlog |
| Rate Limiting | SlowAPI |
| Containerization | Docker, Docker Compose |
app/
├── api/
│ ├── sessions.py # SSE 스트리밍 엔드포인트 (messages, finalize)
│ └── reports.py # AI 리포트 생성 엔드포인트
├── graph/
│ ├── builder.py # LangGraph StateGraph 정의 (persona/history/chat 노드)
│ ├── prompts.py # 프롬프트 re-export
│ └── state.py # SessionState TypedDict
├── models/ # SQLAlchemy ORM 모델
│ ├── emotion_card.py # 감정 카드 (front/back image_url)
│ ├── knowledge.py # KnowledgeEntity, KnowledgeRelation, SessionProposition, Embedding
│ ├── session.py # CounselingSession
│ ├── summary.py # SessionSummary, SessionContextSummary
│ ├── user.py # User, AiPersona, JwtToken, Attendance
│ ├── report.py # AiReport, ReportEmotionGraph, ReportAnalysis, ReportActionSuggestion, ReportTopic
│ └── safety.py # CrisisLog, Notification
├── prompts/
│ ├── analysis_prompts.py # 모든 LLM 프롬프트 상수 (감정/요약/이미지/리포트/위기)
│ └── system_prompt_builder.py # 시스템 프롬프트 구성 (페르소나·닉네임·RAG 컨텍스트 조합)
├── schemas/
│ ├── session.py # MessageCreateRequest
│ └── report.py # ReportGenerateRequest
├── services/
│ ├── chat_service.py # SSE Facade (stream_message, finalize_session_stream)
│ ├── session_service.py # 세션 삭제 시 AI 소유 데이터 정리
│ ├── emotion_service.py # 감정 추출·검증
│ ├── embedding_service.py # 임베딩 저장·검색·CRAG 필터링
│ ├── image_service.py # 감정 카드 이미지 생성 (DALL-E / Bedrock)
│ ├── knowledge_service.py # 엔티티 추출·저장, 명제 생성
│ ├── guardrail_service.py # Bedrock Guardrails PII·콘텐츠 검사
│ ├── crisis_service.py # 위기 감지 (4단계)
│ ├── summary_service.py # 세션 요약·제목·이미지 장면 생성
│ ├── report_service.py # AI 심화 리포트 병렬 생성
│ ├── redis_service.py # 대화 이력 읽기
│ └── background_tasks.py # 임베딩·엔티티·명제 백그라운드 저장
├── utils/
│ ├── emotion_analyzer.py # Plutchik 24개 감정 맵, 집계 로직, 감정 점수 산출
│ ├── crypto.py # AES-256-GCM 암호화/복호화
│ ├── llm.py # LLM 팩토리 (Bedrock/OpenAI 분기)
│ ├── sse.py # SSE 포맷팅 공통 함수
│ └── safety.py # 위기 대응 메시지 및 가드레일 응답
├── metrics.py # Prometheus 메트릭 싱글톤
├── config.py # 환경 변수 설정 (Pydantic Settings)
├── database.py # SQLAlchemy 세션 설정
├── exceptions.py # 커스텀 예외 (MindLogError)
├── limiter.py # SlowAPI 레이트 리미터
└── main.py # FastAPI 앱 진입점
migrations/ # Alembic 마이그레이션
모든 엔드포인트는 내부 API (/internal/v1/*) — Java BE에서만 호출.
| Method | Path | 설명 |
|---|---|---|
POST |
/internal/v1/sessions/{session_id}/messages |
메시지 처리 + LLM 스트리밍 (SSE) |
POST |
/internal/v1/sessions/{session_id}/finalize |
상담 종료 처리 — 감정 분석, 요약, 카드 생성 (SSE) |
DELETE |
/internal/v1/sessions/{session_id} |
세션 삭제 시 AI 소유 데이터 정리 (embeddings) |
POST |
/internal/v1/reports/generate |
AI 심화 리포트 생성 (user_id는 body로 전달) |
GET |
/health |
헬스 체크 |
GET |
/metrics |
Prometheus 메트릭 |
- Python 3.11+
- Docker & Docker Compose
- AWS 계정 (Bedrock 활성화, us-east-1)
# 가상환경 생성
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# 의존성 설치
pip install -r requirements.txt
pip install -r requirements-dev.txtcp .env.example .env
# .env 파일에서 필수 값 설정:
# - AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
# - OPENAI_API_KEY (로컬 개발 시 DALL-E / GPT 사용)
# - DATABASE_URL, REDIS_URL
# - CONTENT_ENCRYPTION_KEY (선택, 미설정 시 암호화 비활성)# Docker Compose로 DB + Redis 실행
docker-compose -f docker-compose.local.yml up -d
# DB 마이그레이션
alembic upgrade head
# 개발 서버 실행
make dev