Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
4cc2282
feat: 기존 커플 연동 관련 API Deprecated
RoundTable02 Mar 16, 2026
6ba0f94
feat: 상대 및 자신의 유형을 등록하는 API 추가
RoundTable02 Mar 16, 2026
705f54b
feat: 애착유형 mbti 결과 응답 API 개발
RoundTable02 Mar 16, 2026
f7dacde
feat: mbti 애착유형 정보를 프롬프트에 주입
RoundTable02 Mar 16, 2026
851e2f5
[MM-188] 신규 성향 입력 (#122)
RoundTable02 Mar 16, 2026
e96aff2
fix: mbti 명칭을 personalityType으로 통일
RoundTable02 Mar 16, 2026
c71c60a
fix: 애착유형 정보 응답
RoundTable02 Mar 19, 2026
7e86648
feat: 상대 프로필 변경 API 단순화
RoundTable02 Mar 21, 2026
21b0172
fix: 상대방 성향정보 업데이트 방식을 선택적으로 변경
RoundTable02 Mar 21, 2026
0341de1
feat: gemini 모델 추가 및 상황별 다른 effort 적용
RoundTable02 Mar 21, 2026
0a034f6
Gemini 모델 추가 및 상황별 다른 reasoning effort 적용 (#123)
RoundTable02 Mar 21, 2026
d1443a5
fix: 애착유형 기본 값을 null로 변경
RoundTable02 Mar 23, 2026
b0c0c80
test: 타입 불일치로 인한 테스트 실패 수정
RoundTable02 Mar 23, 2026
628165a
test: 애착유형 미결정 상황 null
RoundTable02 Mar 23, 2026
cb8b5b7
feat: 2단계 분석 직후 상대 애착유형 추론 저장
RoundTable02 Mar 24, 2026
e3e45ef
docs: 2단계 분석 후 상대 애착유형 추론 저장 관련 문서 업데이트
RoundTable02 Mar 24, 2026
d57ad21
[MM-188] 2단계 분석 직후 상대 애착유형 추론 저장 (#124)
RoundTable02 Mar 24, 2026
4555207
docs: 마이그레이션용 통합 SQL 추가
RoundTable02 Apr 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deploy-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ jobs:
- APPLE_CLIENT_ID=${{ secrets.APPLE_CLIENT_ID }}
- KAKAO_ADMIN_KEY=${{ secrets.KAKAO_ADMIN_KEY }}
- OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}
- GEMINI_API_KEY=${{ secrets.GEMINI_API_KEY }}
- SWAGGER_SERVER_PRODUCTION_URL=${{ secrets.TEST_SWAGGER_SERVER_PRODUCTION_URL }}
- SECURITY_CLIENT_URL_PRODUCTION=${{ secrets.SECURITY_CLIENT_URL_PRODUCTION }}
- SECURITY_CLIENT_URL_DEVELOPMENT=${{ secrets.SECURITY_CLIENT_URL_DEVELOPMENT }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ jobs:
- APPLE_CLIENT_ID=${{ secrets.APPLE_CLIENT_ID }}
- KAKAO_ADMIN_KEY=${{ secrets.KAKAO_ADMIN_KEY }}
- OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}
- GEMINI_API_KEY=${{ secrets.GEMINI_API_KEY }}
- SWAGGER_SERVER_PRODUCTION_URL=${{ secrets.SWAGGER_SERVER_PRODUCTION_URL }}
- SECURITY_CLIENT_URL_PRODUCTION=${{ secrets.SECURITY_CLIENT_URL_PRODUCTION }}
- SECURITY_CLIENT_URL_DEVELOPMENT=${{ secrets.SECURITY_CLIENT_URL_DEVELOPMENT }}
Expand Down
46 changes: 45 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ MZ세대는 연인과의 갈등 원인으로 '의사소통 방식'과 '성향
### 1. 애착 유형 진단

- ECR 검사 문항 기반 애착 유형 진단
- 커플 연동으로 서로의 결과 공유 및 AI 상담에 활용
- 사용자가 직접 입력한 커플/상대 정보 기반으로 결과를 해석하고 AI 상담에 활용

### 2. AI 갈등 상담

Expand Down Expand Up @@ -57,6 +57,50 @@ MZ세대는 연인과의 갈등 원인으로 '의사소통 방식'과 '성향



---

## LLM / CI-CD 설정

- 현재 기본 LLM 구현체는 OpenAI client이며, 기본 모델은 `gpt-5.4-mini`입니다.
- Gemini 구현체도 코드에 유지되며, 필요 시 `@Primary`를 이동해 전환할 수 있습니다.
- QA/Prod 배포 workflow는 API Key, Base URL, Model 같은 민감/배포 환경별 값만 환경변수로 주입합니다.
- `GEMINI_API_KEY`
- `GEMINI_MODEL`
- `GEMINI_BASE_URL`
- `OPENAI_API_KEY`
- `OPENAI_MODEL`
- `OPENAI_BASE_URL`
- `OPENAI_STATUS_URL`
- `reasoning-effort`는 환경변수가 아니라 profile YML에 직접 선언합니다.
- 권장 YML 예시
```yml
openai:
api:
reasoning-effort:
default: medium
scenarios:
structured-chat: low
free-conversation: low
validation: none
summary: none
auxiliary-extraction: none

gemini:
api:
reasoning-effort:
default: high
scenarios:
structured-chat: medium
free-conversation: low
validation: low
summary: low
auxiliary-extraction: low
```
- 롤백 절차
- OpenAI 구현체에 `@Primary`를 옮깁니다.
- 필요 시 `OPENAI_MODEL`만 조정합니다.
- 재배포합니다. reasoning effort 조정은 `application-*.yml` 수정으로 처리합니다.

---

## 🚎 Architecture
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ services:
- KAKAO_REST_API_KEY=${KAKAO_REST_API_KEY}
- APPLE_REST_API_KEY=${APPLE_REST_API_KEY}
- OPENAI_API_KEY=${OPENAI_API_KEY}
- GEMINI_API_KEY=${GEMINI_API_KEY}
- SWAGGER_SERVER_PRODUCTION_URL=${SWAGGER_SERVER_PRODUCTION_URL}
- SECURITY_CLIENT_URL_PRODUCTION=${SECURITY_CLIENT_URL_PRODUCTION}
- SECURITY_CLIENT_URL_DEVELOPMENT=${SECURITY_CLIENT_URL_DEVELOPMENT}
Expand Down
141 changes: 141 additions & 0 deletions docs/API-CHANGES-CHAT-PROMPT-PERSONALITY-TYPE-LOVETYPE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# 채팅 프롬프트 변경 사항 - personalityType + 애착유형 조합 프롬프트 주입

## 개요

채팅 시스템 메시지의 `[사용자 메타데이터]` 블록에 사용자와 상대방의 `personalityType + 애착유형` 조합별 프롬프트를 함께 주입합니다.

이 변경의 목적은 다음과 같습니다.

1. 상담 모델이 사용자 성향과 상대방 성향을 더 구체적으로 이해하도록 돕기
2. `UNKNOWN` 상태를 명시적으로 전달해 대화 문맥으로 추론하게 만들기
3. 조합 데이터가 일부 비어 있어도 채팅을 실패시키지 않고 안전하게 계속 진행하기

---

## 데이터 소스

### 신규 테이블

`love_type_personality_type_prompt`

| column | type | description |
| --- | --- | --- |
| `personality_type` | `VARCHAR(4)` | MBTI 4자리 문자열 |
| `lovetype` | `VARCHAR(255)` | `LoveTypeCategory` enum 문자열 |
| `prompts` | `TEXT` | 채팅 메타데이터에 삽입할 프롬프트 전문 |

### 키 규칙

- `(personality_type, lovetype)` 복합 PK
- 애플리케이션은 MBTI를 대문자로 정규화해 조회합니다.
- `UNKNOWN` row는 저장하지 않습니다.

### DDL

```sql
CREATE TABLE love_type_personality_type_prompt (
personality_type VARCHAR(4) NOT NULL,
lovetype VARCHAR(255) NOT NULL,
prompts TEXT,
PRIMARY KEY (personality_type, lovetype)
);
```

실제 배포용 SQL은 `sqls/MM-181.sql`에 있습니다.

---

## 채팅 메타데이터 주입 규칙

프롬프트는 `ChatPromptBuilder`에서 `[사용자 메타데이터]` 문자열을 만들 때 삽입됩니다.

### 추가되는 항목

```text
- 사용자 성향 프롬프트:
{사용자 조합 프롬프트 또는 폴백 문구}

- 상대방 성향 프롬프트:
{상대방 조합 프롬프트 또는 폴백 문구}
```

### 사용자 본인

- `member.personalityType`와 `member.loveTypeCategory`가 모두 있으면 `love_type_personality_type_prompt`를 조회합니다.
- 조합 row가 존재하면 `prompts` 컬럼 값을 그대로 삽입합니다.
- 둘 중 하나라도 없거나 조합 row가 없으면 아래 폴백 문구를 삽입합니다.

```text
UNKNOWN, 사용자와의 대화로부터 유추할 것
```

### 상대방

- `otherPersonalityType`가 있는 경우에만 `- 상대방 성향 프롬프트:` 항목을 추가합니다.
- `partnerLoveTypeCategory`가 `UNKNOWN` 또는 `null`이면 DB 조회 없이 바로 폴백 문구를 삽입합니다.
- 다만 1단계 완료 후 생성되는 **2단계 첫 분석 메시지 직후** 내부 추론이 성공하면, 이후부터는 저장된 확정값으로 조회됩니다.
- `otherPersonalityType`와 확정된 `partnerLoveTypeCategory`가 모두 있으면 `(otherPersonalityType, partnerLoveTypeCategory)` 조합으로 조회합니다.
- 조합 row가 없으면 채팅은 실패시키지 않고 동일한 폴백 문구를 삽입합니다.

---

## 런타임 동작

### 성공 케이스

- 사용자 조합 row가 있으면 사용자 성향 프롬프트에 해당 전문이 들어갑니다.
- 상대방 조합 row가 있으면 상대방 성향 프롬프트에 해당 전문이 들어갑니다.

### 폴백 케이스

아래 경우에는 모두 동일한 폴백 문구를 사용합니다.

- 사용자 personalityType 없음
- 사용자 애착유형 없음
- 상대방 personalityType 없음
- 상대방 애착유형이 `UNKNOWN`
- 상대방 애착유형이 `null`
- 조합 row 없음
- `prompts` 값이 비어 있음

### 로깅

- MBTI/애착유형 값은 존재하지만 조합 row가 없는 경우 `warn` 로그를 남깁니다.
- 이 경우에도 채팅 요청은 실패하지 않습니다.

---

## 외부 API 영향

외부 HTTP API 스펙 변경은 없습니다.

다만 아래 프로필 데이터가 채팅 프롬프트 생성에 직접 활용됩니다.

- `GET /members`의 `personalityType`
- `GET /members`의 `loveTypeCategory`
- `GET /members`의 `otherPersonalityType`
- `GET /members`의 `partnerLoveTypeCategory`

추가로, 사용자가 상대방 애착유형을 입력하지 않았더라도 1단계 완료 후 2단계 첫 분석 메시지 직후 내부 추론 결과가 `partnerLoveTypeCategory`에 저장될 수 있습니다.
관련 상세 내용은 `docs/API-CHANGES-PARTNER-LOVETYPE-INFERENCE-PERSISTENCE.md`를 참고합니다.

---

## 테스트

검증한 항목:

- MBTI 대소문자 무관 조회
- 사용자 조합 프롬프트 삽입
- 사용자 정보 없음 시 폴백 문구 삽입
- 상대방 조합 프롬프트 삽입
- 상대방 애착유형 `UNKNOWN` 시 폴백 문구 삽입
- 상대방 프로필 미등록 시 상대방 성향 프롬프트 항목 생략
- 조합 row 누락 시 예외 없이 폴백 처리

관련 테스트:

- `src/test/java/makeus/cmc/malmo/application/service/chat/ChatPromptBuilderTest.java`
- `src/test/java/makeus/cmc/malmo/integration_test/LoveTypePersonalityTypePromptPersistenceAdapterTest.java`

실제 시스템 메시지 예시는 `docs/CHAT-PROMPT-PERSONALITY-TYPE-LOVETYPE-EXAMPLE.md`를 참고합니다.
87 changes: 87 additions & 0 deletions docs/API-CHANGES-LOVETYPE-DATA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
## 신규 API 스펙
### `POST /members/partners` — 상대방 프로필 최초 등록
**Request**
```ts
{
personalityType: string,
loveTypeCategory: 'STABLE_TYPE' | 'ANXIETY_TYPE' | 'AVOIDANCE_TYPE' | 'CONFUSION_TYPE' | null
// null = "모르겠어요" 선택
}
```
**Response**
```ts
{
personalityType: string,
loveTypeCategory: 'STABLE_TYPE' | 'ANXIETY_TYPE' | 'AVOIDANCE_TYPE' | 'CONFUSION_TYPE' | 'UNKNOWN',
description: string
}
```
---
### `PATCH /members/partners` — 상대방 프로필 수정
두 필드를 항상 함께 전송해야 하며, 전달된 값으로 기존 값을 덮어씁니다.

**Request**
```ts
{
personalityType: string, // 영문 4자리 (예: "INTJ")
loveTypeCategory: 'STABLE_TYPE' | 'ANXIETY_TYPE' | 'AVOIDANCE_TYPE' | 'CONFUSION_TYPE' | null
// null = "모르겠어요" 선택
}
```
**Response**
```ts
{
personalityType: string,
loveTypeCategory: 'STABLE_TYPE' | 'ANXIETY_TYPE' | 'AVOIDANCE_TYPE' | 'CONFUSION_TYPE' | 'UNKNOWN',
description: string
}
```
---
### `PATCH /members` — 기존 수정
기존 필드 유지, 아래 필드 추가
```ts
{
loveTypeCategory?: 'STABLE_TYPE' | 'ANXIETY_TYPE' | 'AVOIDANCE_TYPE' | 'CONFUSION_TYPE'
}
```
---
### `GET /members` 응답 필드 추가
기존 응답 유지, 아래 필드 추가
```ts
{
personalityType: string, // 내 MBTI
loveTypeCategory: enum, // 내 애착유형
otherPersonalityType: string, // 상대 MBTI
partnerLoveTypeCategory: 'STABLE_TYPE' | 'ANXIETY_TYPE' | 'AVOIDANCE_TYPE' | 'CONFUSION_TYPE' | 'UNKNOWN'
// undefined = 미입력 / UNKNOWN = "모르겠어요" 선택됨
// 또는 1단계 완료 후 2단계 첫 분석 메시지 직후 내부 추론값이 저장될 수 있음
}
```

---

## 채팅 프롬프트 활용

사용자와 상대방 프로필의 `personalityType`, `loveTypeCategory`, `otherPersonalityType`, `partnerLoveTypeCategory`는 채팅 시스템 메시지의 메타데이터 구성에도 사용됩니다.

### 활용 규칙

- 사용자 본인
- `personalityType`와 `loveTypeCategory`가 모두 있으면 `(personalityType, lovetype)` 조합으로 상세 프롬프트를 조회합니다.
- 둘 중 하나라도 없거나 매칭 row가 없으면 `UNKNOWN, 사용자와의 대화로부터 유추할 것`을 사용합니다.
- 상대방
- `otherPersonalityType`가 있을 때만 상대방 성향 프롬프트 항목이 추가됩니다.
- `partnerLoveTypeCategory`가 `UNKNOWN` 또는 `null`이면 DB 조회 없이 `UNKNOWN, 사용자와의 대화로부터 유추할 것`을 사용합니다.
- 이후 1단계 완료 후 생성되는 2단계 첫 분석 메시지 직후 내부 추론이 성공하면, 저장된 확정값으로 프롬프트를 조회합니다.
- `otherPersonalityType`와 확정된 `partnerLoveTypeCategory`가 모두 있으면 `(otherPersonalityType, partnerLoveTypeCategory)` 조합으로 상세 프롬프트를 조회합니다.
- 매칭 row가 없으면 채팅은 실패하지 않고 동일한 폴백 문구를 사용합니다.

### 조회 대상 테이블

- `love_type_personality_type_prompt`
- `personality_type`: MBTI 4자리 문자열
- `lovetype`: `STABLE_TYPE | ANXIETY_TYPE | AVOIDANCE_TYPE | CONFUSION_TYPE`
- `prompts`: 실제 채팅 메타데이터에 삽입할 프롬프트 전문

상세 동작 예시는 `docs/API-CHANGES-CHAT-PROMPT-PERSONALITY-TYPE-LOVETYPE.md`, 실제 전달 예시는 `docs/CHAT-PROMPT-PERSONALITY-TYPE-LOVETYPE-EXAMPLE.md`,
영속화 동작은 `docs/API-CHANGES-PARTNER-LOVETYPE-INFERENCE-PERSISTENCE.md`를 참고합니다.
51 changes: 51 additions & 0 deletions docs/API-CHANGES-LOVETYPE-PERSONALITY-TYPE-RESULT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
## 신규 API 스펙

### `GET /love-types/result` - personalityType + 애착유형 상세 결과 조회

`personalityType`와 `lovetype` 조합으로 상세 결과를 조회합니다.

기존 API와의 차이:
- `POST /love-types/result`: 검사 답변을 제출하고 임시 결과를 생성
- `GET /love-types/result/{loveTypeId}`: 생성된 임시 검사 결과를 조회
- `GET /love-types/result`: `(personalityType, lovetype)` 조합에 대응하는 고정 상세 콘텐츠를 조회

## Request

| name | type | required | description |
| --- | --- | --- | --- |
| `personalityType` | String | Y | 영문 4자리 MBTI. 대소문자 무관, 내부적으로 대문자로 정규화 |
| `lovetype` | String | Y | `STABLE_TYPE`, `ANXIETY_TYPE`, `AVOIDANCE_TYPE`, `CONFUSION_TYPE` 중 하나 |

```http
GET /love-types/result?personalityType=enfp&lovetype=stable_type
```

## Response

```ts
{
personalityType: string,
loveTypeCategory: 'STABLE_TYPE' | 'ANXIETY_TYPE' | 'AVOIDANCE_TYPE' | 'CONFUSION_TYPE',
summary: string,
keywords: string[],
strengths: Array<{ title: string | null, description: string | null }>,
weaknesses: Array<{ title: string | null, description: string | null }>,
patterns: Array<{ title: string | null, description: string | null }>,
loveTypeFeatures: Array<{ title: string | null, description: string | null }>,
datingGuides: Array<{ title: string, description: string | null }>,
bestMatches: Array<{ personalityType: string | null, description: string | null }>,
worstMatches: Array<{ personalityType: string | null, description: string | null }>
}
```

## DB 매핑 기준

- 조회 대상 테이블은 `love_type_personality_type_feature`입니다.
- 복합키는 `(personality_type, lovetype)`입니다.
- 궁합 컬럼은 `best_personality_type1/2`, `worst_personality_type1/2`를 사용합니다.

## 에러 응답

- `personalityType`가 영문 4자리가 아닌 경우
- `lovetype`이 허용 enum 값이 아닌 경우
- `personalityType + lovetype` 조합에 해당하는 row가 없는 경우
Loading
Loading