Skip to content
Merged

Test #720

Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,13 @@ public class VertexAiEmbeddingService {
* @param embeddingType CONCERT, AGENT, SEARCH
*/
@Transactional
@Caching(
cacheable = @Cacheable(
value = "embeddings",
key = "T(com.ticketmate.backend.common.core.util.CommonUtil)"
+ ".normalizeAndRemoveSpecialCharacters(#text)"
+ "+':' + #embeddingType",
condition = "#targetId == null",
unless = "#result == null"
)
)
public Embedding fetchOrGenerateEmbedding(UUID targetId, String text, EmbeddingType embeddingType) {
// 텍스트 정규화
String normalizeText = CommonUtil.normalizeAndRemoveSpecialCharacters(text);

// 검색 모드: 캐시나 DB 조회 후 없으면 생성
return embeddingRepository.findByTextAndEmbeddingType(normalizeText, embeddingType)
.orElseGet(() -> createAndSaveEmbedding(targetId, normalizeText, embeddingType));
.orElseGet(() -> createAndSaveEmbedding(targetId, normalizeText, embeddingType));
}

/**
Expand All @@ -67,11 +57,11 @@ private Embedding createAndSaveEmbedding(UUID targetId, String normalizedText, E
float[] vector = extractVector(generateEmbedding(normalizedText));

return embeddingRepository.save(Embedding.builder()
.targetId(targetId)
.text(normalizedText)
.embeddingVector(vector)
.embeddingType(type)
.build());
.targetId(targetId)
.text(normalizedText)
.embeddingVector(vector)
.embeddingType(type)
.build());
}

/**
Expand All @@ -83,9 +73,9 @@ private Embedding createAndSaveEmbedding(UUID targetId, String normalizedText, E
private EmbedContentResponse generateEmbedding(String text) {
try {
return genAiClient.models.embedContent(
googleGenAIProperties.model(),
text,
EmbedContentConfig.builder().build()
googleGenAIProperties.model(),
text,
EmbedContentConfig.builder().build()
);
} catch (ClientException e) {
log.error("Vertex AI 임베딩 API 호출 실패: {}", e.getMessage());
Expand All @@ -101,9 +91,9 @@ private EmbedContentResponse generateEmbedding(String text) {
*/
private float[] extractVector(EmbedContentResponse response) {
List<Float> embeddingValues = response.embeddings()
.flatMap(list -> list.stream().findFirst())
.flatMap(ContentEmbedding::values)
.orElseThrow(() -> new CustomException(ErrorCode.EMBEDDING_DATA_NOT_FOUND));
.flatMap(list -> list.stream().findFirst())
.flatMap(ContentEmbedding::values)
.orElseThrow(() -> new CustomException(ErrorCode.EMBEDDING_DATA_NOT_FOUND));
int size = embeddingValues.size();
float[] vector = new float[size];
for (int i = 0; i < size; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public interface EmbeddingRepository extends JpaRepository<Embedding, UUID> {
*/
@Query(value = "SELECT target_id FROM embedding e "
+ "WHERE e.embedding_type = 'CONCERT' "
+ "AND (e.embedding_vector <-> CAST(:vector AS vector)) <= 0.90 "
+ "AND EXISTS "
+ "(SELECT 1 FROM ticket_open_date t "
+ "JOIN concert c ON t.concert_concert_id = c.concert_id "
Expand All @@ -43,6 +44,7 @@ public interface EmbeddingRepository extends JpaRepository<Embedding, UUID> {
*/
@Query(value = "SELECT target_id FROM embedding "
+ "WHERE embedding_type = 'AGENT' "
+ "AND (embedding_vector <-> CAST(:vector AS vector)) <= 0.90 "
+ "ORDER BY embedding_vector <-> CAST(:vector AS vector) LIMIT :limit", nativeQuery = true)
List<UUID> findNearestAgentEmbeddings(@Param("vector") float[] vector, @Param("limit") int limit);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,44 +50,44 @@ public List<ConcertSearchInfo> findConcertDetailsByIds(List<UUID> concertIds) {
return Collections.emptyList();
}
return queryFactory
.select(Projections.constructor(ConcertSearchInfo.class,
CONCERT.concertId,
CONCERT.concertName,
CONCERT_HALL.concertHallName,
// 선예매 오픈일
Expressions.dateTimeTemplate(
Instant.class,
"min({0})",
new CaseBuilder()
.when(TICKET_OPEN_DATE.ticketOpenType.eq(TicketOpenType.PRE_OPEN))
.then(TICKET_OPEN_DATE.openDate)
.otherwise((Instant) null)
).as("ticketPreOpenDate"),
// 일반 예매 오픈일
Expressions.dateTimeTemplate(
Instant.class,
"min({0})",
new CaseBuilder()
.when(TICKET_OPEN_DATE.ticketOpenType.eq(TicketOpenType.GENERAL_OPEN))
.then(TICKET_OPEN_DATE.openDate)
.otherwise((Instant) null)
).as("ticketGeneralOpenDate"),
CONCERT_DATE.performanceDate.min().as("startDate"),
CONCERT_DATE.performanceDate.max().as("endDate"),
CONCERT.concertThumbnailStoredPath,
Expressions.constant(0.0)
))
.from(CONCERT)
.leftJoin(CONCERT.concertHall, CONCERT_HALL)
.join(CONCERT_DATE).on(CONCERT.eq(CONCERT_DATE.concert))
.join(TICKET_OPEN_DATE).on(CONCERT.eq(TICKET_OPEN_DATE.concert))
.where(CONCERT.concertId.in(concertIds))
.groupBy(CONCERT.concertId,
CONCERT.concertName,
CONCERT_HALL.concertHallName,
CONCERT.concertThumbnailStoredPath
)
.fetch();
.select(Projections.constructor(ConcertSearchInfo.class,
CONCERT.concertId,
CONCERT.concertName,
CONCERT_HALL.concertHallName,
// 선예매 오픈일
Expressions.dateTimeTemplate(
Instant.class,
"min({0})",
new CaseBuilder()
.when(TICKET_OPEN_DATE.ticketOpenType.eq(TicketOpenType.PRE_OPEN))
.then(TICKET_OPEN_DATE.openDate)
.otherwise((Instant) null)
).as("ticketPreOpenDate"),
// 일반 예매 오픈일
Expressions.dateTimeTemplate(
Instant.class,
"min({0})",
new CaseBuilder()
.when(TICKET_OPEN_DATE.ticketOpenType.eq(TicketOpenType.GENERAL_OPEN))
.then(TICKET_OPEN_DATE.openDate)
.otherwise((Instant) null)
).as("ticketGeneralOpenDate"),
CONCERT_DATE.performanceDate.min().as("startDate"),
CONCERT_DATE.performanceDate.max().as("endDate"),
CONCERT.concertThumbnailStoredPath,
Expressions.constant(0.0)
))
.from(CONCERT)
.leftJoin(CONCERT.concertHall, CONCERT_HALL)
.join(CONCERT_DATE).on(CONCERT.eq(CONCERT_DATE.concert))
.join(TICKET_OPEN_DATE).on(CONCERT.eq(TICKET_OPEN_DATE.concert))
.where(CONCERT.concertId.in(concertIds))
.groupBy(CONCERT.concertId,
CONCERT.concertName,
CONCERT_HALL.concertHallName,
CONCERT.concertThumbnailStoredPath
)
.fetch();
}

/**
Expand All @@ -102,20 +102,20 @@ public List<AgentSearchInfo> findAgentDetailsByIds(List<UUID> agentIds) {
return Collections.emptyList();
}
return queryFactory
.select(Projections.constructor(AgentSearchInfo.class,
MEMBER.memberId,
MEMBER.nickname,
MEMBER.profileImgStoredPath,
PORTFOLIO.portfolioDescription,
AGENT_PERFORMANCE_SUMMARY.averageRating,
AGENT_PERFORMANCE_SUMMARY.reviewCount,
Expressions.constant(0.0)
))
.from(MEMBER)
.leftJoin(PORTFOLIO).on(PORTFOLIO.member.eq(MEMBER))
.innerJoin(AGENT_PERFORMANCE_SUMMARY).on(MEMBER.eq(AGENT_PERFORMANCE_SUMMARY.agent))
.where(MEMBER.memberId.in(agentIds))
.fetch();
.select(Projections.constructor(AgentSearchInfo.class,
MEMBER.memberId,
MEMBER.nickname,
MEMBER.profileImgStoredPath,
PORTFOLIO.portfolioDescription,
AGENT_PERFORMANCE_SUMMARY.averageRating,
AGENT_PERFORMANCE_SUMMARY.reviewCount,
Expressions.constant(0.0)
))
.from(MEMBER)
.leftJoin(PORTFOLIO).on(PORTFOLIO.member.eq(MEMBER))
.innerJoin(AGENT_PERFORMANCE_SUMMARY).on(MEMBER.eq(AGENT_PERFORMANCE_SUMMARY.agent))
.where(MEMBER.memberId.in(agentIds))
.fetch();
}

/**
Expand All @@ -129,21 +129,21 @@ public List<AgentSearchInfo> findAgentDetailsByIds(List<UUID> agentIds) {
public List<UUID> findAgentIdsByKeyword(String keyword, int limit) {
// 동적 WHERE 절 조합
BooleanExpression whereClause = QueryDslUtil.allOf(
MEMBER.memberType.eq(MemberType.AGENT),
QueryDslUtil.anyOf(
QueryDslUtil.likeIgnoreCase(MEMBER.nickname, keyword),
QueryDslUtil.likeIgnoreCase(PORTFOLIO.portfolioDescription, keyword)
)
MEMBER.memberType.eq(MemberType.AGENT),
QueryDslUtil.anyOf(
QueryDslUtil.likeIgnoreCase(MEMBER.nickname, keyword),
QueryDslUtil.likeIgnoreCase(PORTFOLIO.portfolioDescription, keyword)
)
);

return queryFactory
.select(MEMBER.memberId)
.from(MEMBER)
.innerJoin(PORTFOLIO)
.on((PORTFOLIO.member).eq(MEMBER))
.where(whereClause)
.limit(limit)
.fetch();
.select(MEMBER.memberId)
.from(MEMBER)
.innerJoin(PORTFOLIO)
.on((PORTFOLIO.member).eq(MEMBER))
.where(whereClause)
.limit(limit)
.fetch();
}

/**
Expand Down