Skip to content

[feat] 임베딩 기반 자연어 코스 추천 API#355

Merged
uykm merged 11 commits intodevelopfrom
feat/#354-course-embedding-recommend
Apr 8, 2026
Merged

[feat] 임베딩 기반 자연어 코스 추천 API#355
uykm merged 11 commits intodevelopfrom
feat/#354-course-embedding-recommend

Conversation

@uykm
Copy link
Copy Markdown
Collaborator

@uykm uykm commented Apr 8, 2026

🌳이슈 번호

resolves #354


☀️어떻게 이슈를 해결했나요?

  • CourseSearchDocument 엔티티를 신규 생성해 코스별 임베딩 벡터를 MySQL MEDIUMBLOB에 저장
  • 코스명 · 동네 · 태그명 · 태그 meaning · 소개 · 장소 수 · 장소 유형을 결합한 retrieval_text 생성 (CourseRetrievalTextBuilder)
  • 임베딩 파이프라인: 코스 생성 시 CourseCreatedEvent → 비공개 코스 제외 후 즉시 임베딩 / 수정 시 DIRTY → 매일 04:00 배치 재임베딩
  • POST /api/recommend/courses/embedding 호출 시 쿼리 임베딩 → 코사인 유사도 계산 → Top-3 선정 → LLM 추천 이유 생성
  • 광역 동네(parentTown = null)는 자식 동네 코스를 모두 포함해 검색
  • Flyway V12~V14: 테이블 생성, 기존 공유 코스 INIT 삽입, 코스 태그 meaning 데이터 추가
  • POST /api/admin/courses/search-documents/initialize — INIT·FAILED 문서 즉시 임베딩 (테스트용)

🗯️ PR 포인트

  • isShared = false인 비공개 코스는 이벤트 리스너에서 조기 반환으로 임베딩 생략
  • embedInChunks / splitIntoChunks 네이밍으로 청크 분할 목적을 명시
  • ReasonGenerationServiceCourseContext + generateCourseReasons 추가 — 장소 추천 이유 생성과 대칭 구조 유지
  • 초기화 API는 기존 AdminCourseController에 추가해 어드민 인증 체계 재사용

Summary by CodeRabbit

  • 신규 기능
    • 자연어(검색어) 기반 AI 코스 추천: 검색어와 지역을 입력하면 관련 코스를 AI가 유사도 기반으로 추천하고 추천 이유를 제공합니다.
    • 관리자용 코스 검색 문서 초기화 및 배치 처리: 코스 검색 문서를 초기화하고 백그라운드에서 임베딩 생성·재생성 작업을 수행하여 검색/추천 인덱스를 유지합니다.
    • 코스 생성·수정 시 검색 색인 상태 자동 관리: 코스 변경 시 색인 상태를 갱신해 추천 품질을 유지합니다.

@uykm uykm added the 💭 FEAT label Apr 8, 2026
@uykm uykm self-assigned this Apr 8, 2026
@uykm uykm requested review from 88guri and ImHyungsuk April 8, 2026 15:40
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 01e7230a-5854-4351-b12b-f05ec2ff24e3

📥 Commits

Reviewing files that changed from the base of the PR and between e270a61 and 1d2521b.

📒 Files selected for processing (2)
  • src/main/java/org/sopt/solply_server/domain/course/repository/CourseRepository.java
  • src/main/java/org/sopt/solply_server/domain/place/entity/Place.java

Walkthrough

코스 검색 문서 엔티티, 임베딩 생성/배치/오케스트레이션, 이벤트 기반 초기화, 임베딩 기반 추천 API 및 LLM 추천 이유 생성을 추가했습니다.

Changes

Cohort / File(s) Summary
엔티티 · 저장소 · 마이그레이션
src/main/java/.../course/entity/CourseSearchDocument.java, src/main/java/.../course/repository/CourseSearchDocumentRepository.java, src/main/resources/db/migration/V12__create_course_search_documents.sql, src/main/resources/db/migration/V13__insert_init_course_search_documents_for_existing_courses.sql
코스 임베딩 저장용 JPA 엔티티 및 리포지토리 추가. 임베딩/상태 기반 조회, 상태별 ID 조회 및 DIRTY 마킹 업데이트 쿼리 추가. DB 마이그레이션 파일로 테이블 생성 및 기존 공유 코스 INIT 역채우기.
임베딩 배치 · 파사드 · 빌더
src/main/java/.../course/service/CourseEmbeddingBatchProcessor.java, src/main/java/.../course/service/facade/CourseEmbeddingFacade.java, src/main/java/.../course/service/CourseRetrievalTextBuilder.java
단일/배치 임베딩 생성 로직, 청크 처리/실패 마킹, 초기화·스케줄 재임베딩 파사드 및 코스 기반 retrieval text 생성기 추가.
코스 서비스 · 이벤트 · 리스너
src/main/java/.../course/service/CourseService.java, src/main/java/.../course/service/event/CourseCreatedEvent.java, src/main/java/.../course/service/event/CourseCreatedEventListener.java
코스 생성 시 이벤트 발행 추가, 장소 변경 시 문서 DIRTY 마킹, 트랜잭션 후 비동기 리스너로 임베딩 생성 트리거 추가.
추천 서비스 · 컨트롤러 · DTOs
src/main/java/.../recommend/service/CourseEmbeddingRecommendService.java, src/main/java/.../recommend/controller/RecommendController.java, src/main/java/.../recommend/dto/EmbeddingCourseRecommendRequest.java, src/main/java/.../recommend/dto/RecommendedCourseDto.java, src/main/java/.../recommend/dto/response/EmbeddingCourseRecommendGetResponse.java
자연어 질의 임베딩과 코스 임베딩 간 코사인 유사도로 상위 N개(기본 3) 추천하는 서비스 및 API(POST /api/recommend/courses/embedding)와 관련 DTO 추가.
LLM 이유 생성 확장
src/main/java/.../global/ai/ReasonGenerationService.java
코스 컨텍스트용 CourseContext 레코드 및 generateCourseReasons(...) 추가, 기존 장소 응답 포맷 분리 및 실패 시 빈 문자열 리스트 반환 처리.
관리자 엔드포인트
src/main/java/.../admin/course/controller/AdminCourseController.java
관리자용 엔드포인트 POST /api/admin/courses/search-documents/initialize 추가하여 CourseEmbeddingFacade.initializePendingDocuments() 호출.
레포지토리 · 기타 수정
src/main/java/.../course/repository/CourseRepository.java, src/main/java/.../place/entity/Place.java
추천용 즉시로딩 쿼리 findByIdInWithAllForRecommendation 추가 및 Place.placeTags@BatchSize(50) 적용.
DB 태그 업데이트 마이그레이션
src/main/resources/db/migration/V14__add_meaning_to_course_tags.sql
태그 의미 필드 업데이트 SQL 추가.

Sequence Diagram

sequenceDiagram
    actor User
    participant RecommendController as "RecommendController"
    participant RecommendService as "CourseEmbeddingRecommendService"
    participant EmbeddingService as "EmbeddingService"
    participant DocRepo as "CourseSearchDocumentRepository"
    participant CourseRepo as "CourseRepository"
    participant ReasonSvc as "ReasonGenerationService"

    User->>RecommendController: POST /api/recommend/courses/embedding (query, townId)
    RecommendController->>RecommendService: recommendByQuery(query, townId, userId)
    RecommendService->>EmbeddingService: embed(query)
    RecommendService->>DocRepo: find active shared docs with embedding (town / parentTown)
    DocRepo-->>RecommendService: documents with embeddings
    Note over RecommendService, EmbeddingService: 코사인 유사도 계산 및 상위 K 선택
    RecommendService->>CourseRepo: load courses with related data for selected IDs
    RecommendService->>ReasonSvc: generateCourseReasons(query, userName, courseContexts)
    ReasonSvc-->>RecommendService: reasons[]
    RecommendService-->>RecommendController: EmbeddingCourseRecommendGetResponse
    RecommendController-->>User: 200 OK (추천 코스 목록)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 분

Possibly related PRs

Suggested labels

🛠️ FIX

Suggested reviewers

  • ImHyungsuk
  • 88guri

Poem

🐇🥕 임베딩 밭에 씨를 뿌렸네,
유사도 길 따라 코스 세 개 찾네,
토끼가 노래로 이유를 속삭여,
새로운 추천 길이 반짝이네,
축하하라, 임베딩 여정!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 임베딩 기반 자연어 코스 추천 API 기능을 명확하게 설명하며, 변경사항의 주요 목적과 일치합니다.
Linked Issues check ✅ Passed PR의 모든 주요 구현 사항이 이슈 #354의 작업 체크리스트와 일치합니다. CourseSearchDocument 엔티티, retrieval_text 빌더, 마이그레이션, 임베딩 파이프라인, 이벤트 처리, 추천 API, LLM 연동 등이 모두 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 이슈 #354에 정의된 임베딩 기반 코스 추천 기능과 관련이 있습니다. 추가된 엔티티, 서비스, API, 마이그레이션은 모두 해당 기능의 요구사항에 부합합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#354-course-embedding-recommend

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.

@uykm uykm merged commit a3736a2 into develop Apr 8, 2026
1 check passed
@uykm uykm deleted the feat/#354-course-embedding-recommend branch April 8, 2026 15:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 임베딩 기반 자연어 코스 추천 API

1 participant