편집 꺼짐
Next.js 블로그 프로젝트
2025.05.17 07:51:57
16, 17일차에 구현했던 텍스트 임베딩과 코사인 유사도에 관한 내용을 정리한 글.
본래 인공지능 게시글 추천 기능에 대해 고민하였는데, 게시글 120개를 chatGPT가 모두 확인하고 처리하는 것이 불가능하였다.(회당 토큰 수 초과)
이에 따라 가장 간단하고 확실한 방법인 코사인 유사도를 통한 게시글 추천 기능을 구현하였다.
텍스트 임베딩이란?
텍스트 임베딩이란 문장이나 단어를 고차원 벡터 공간의 좌표로 변환하는 것.
예를 들어, "나는 밥을 먹었어"
→ [0.12, -0.98, 0.45, ..., 0.33]
(보통 384, 768, 1536차원 등)
핵심 요점
- 같은 의미를 가진 문장은 비슷한 벡터로 표현된다.
- 임베딩 벡터끼리의 거리나 각도를 이용해 유사도를 계산할 수 있다.
- 텍스트의 벡터화에는 OpenAI
text-embedding-3-small
를 활용하였다. 가격은 text-embedding-2보다 저렴하고, 차원은 더 넓게 사용하는 경향이 있다고 알려져 있다.
코사인 유사도
벡터끼리 얼마나 방향이 비슷한지를 계산하는 방식이다.
- 1에 가까울수록 → 완전히 같은 의미
- 0에 가까울수록 → 전혀 관련 없음
- (−1은 반대 의미, 일반적으로는 거의 사용되지 않음)
예시
문자를 벡터로 변환하였다면 아래와 같이 비교하게 된다.
문장 A | 문장 B | 유사도 |
---|---|---|
"나는 점심을 먹었다" | "밥을 먹었어" | 0.94 |
"나는 점심을 먹었다" | "운동을 했다" | 0.21 |
인공지능 요약 및 코사인 유사도 구현
인공지능 요약 버튼
main/components/post/right-panel/ai-panel.tsx
요약 생성 및 코사인 유사도 분석은 아래와 같은 과정으로 이루어진다.
- 인공지능 요약 생성 버튼을 누르면 api/summary API를 POST로 호출한다.
chatgpt-4o-latest
모델로 요약을 생성한다.- 생성된 요약을
text-embedding-3-small
모델로 벡터화한다.
- 요약과 벡터를 각각
sumaries
,summary_vectors
테이블에 저장한다. - 이어서 api/summary/recommended를 호출한다.
- DB에서 summary vector를 불러온다.
- target과 다른 게시글들 사이의 코사인 유사도를 분석한다.
- 코사인 유사도가 높은 10개의 게시글을
post_similarities
테이블에 저장한다.post_similarities
는 source가 target보다 사전 순으로 앞으로 오고, 두 쌍이 유니크 하도록 제약 조건을 걸어두었다.ALTER TABLE post_similarities ADD CONSTRAINT enforce_sorted_pair CHECK (source_post_id < target_post_id);
CREATE UNIQUE INDEX unique_similarity_pair ON post_similarities (source_post_id, target_post_id);
- 따라서 코사인 유사도 분석한 결과를 sort로 정렬하고, upsert를 통해 데이터에 저장한다.
// 결과 포맷 변환 const res = top10.map((item) => { const [a, b] = [sourceData.post_id, item.target_post_id].sort(); // 단방향 제약 추가 return { source_post_id: a, target_post_id: b, similarity: item.similarity, }; });
const { data } = await supabase .from("post_similarities") .upsert(sims, { onConflict: "source_post_id, target_post_id", }) .select();
- 요청이 완료되면 AiSummary 패널의 데이터를 revalidate하고 toast를 띄운다.