프로젝트/Reviewing(리뷰잉)

강의 추천/검색 기능 구현 2탄 - OpenAI Vector Embeddings API 사용법 (Java 실습)

노루스름한맛 2025. 2. 19. 18:54

Reviewing(리뷰잉) 프로젝트의 강의 추천 기능을 구현하며 공부했던 내용과 기능 구현 시리즈를 만들고 있다.

이전글 강의 추천/검색 기능 구현 1탄 - OpenSearch와 ElasticSearch 에서는 OpenSearch를 사용하기 위해

공부한 내용을 정리했었다.

 

이번에는 강의 추천 기능을 구현하기위해 벡터 유사도 계산을 구현했는데 이때 텍스트를 벡터로 변환할 때 사용했던

OpenAI Vector Embeddings에 대한 내용을 Embeddings부터 공부하고 직접 벡터 변환 구현까지 정리할 예정이다.

Vector(벡터)란?

벡터란 크기와 방향을 가지고 있는 양을 나타내는 개념이다.

벡터의 차원은 벡터 성분의 개수를 의미하는데 2차원 벡터는 2개의 성분을 갖는다.

Ex) [1,2] - 2차원 벡터, [1,2,3] - 3차원 벡터 등

Embedding(임베딩)이란?

임베딩이란 텍스트, 이미지 및 오디오와 같은 데이터를 실수 벡터 리스트로 변환한 결과물 또는 변환 전 과정을 의미한다. 

검색, 추천, 클러스터링, 군집화 등 서로 다른 데이터(단어, 문서 등)간의 의미적 관계를 파악할 때 주로 사용된다.

https://www.syncly.kr/blog/what-is-embedding-and-how-to-use

임베딩을 이용해 텍스트를 벡터화하는 이유

추천 기능을 구현할 때 임베딩을 사용한 이유는 유사도 검색을 위해서이다.

텍스트에대해 임베딩을 사용해서 변환한 벡터는 텍스트의 문맥상 의미를 분석해서 의미적 유사성을 반영한 벡터 리스트를 생성하는데

이렇게 생성된 벡터들의 유사도 계산(거리가 가까우면 높은 유사도, 거리가 멀면 낮은 유사도)을 통해 검색, 추천 기능 등을 구현할 수 있다.

 

나는 "Spring 관련 백엔드 강의 추천해줘"라는 질문에 대해 DB에 저장되어있는 강의들 중 Spring 백엔드와 유사한 내용의 강의들을 추천해주는 기능을 구현하기위해 임베딩을 사용했다. 

 

Embedding에 관한 내용은 아래 링크에 자세하고 이해하기 쉽게 되어있어서 상당히 좋은 자료인 것 같다.

https://www.syncly.kr/blog/what-is-embedding-and-how-to-use

 

Embedding이란 무엇이고, 어떻게 사용하는가? - 싱클리(Syncly)

본 글에서는, AI에서 중요하게 취급되는 개념 중 하나인 embedding에 대해서 알아보고자 합니다. 현재 Syncly에서도 Feedback Auto-Categorization, Sentiment Classification 등의 기능에 embedding이 활용되고 있습니

www.syncly.kr

더 깊이 공부할수록 인공지능 분야로 들어가는 것 같아서 이 글에서는 간단한 설명만 포함했다. 

(하지만 매우 흥미로운 내용이어서  더 자세히 공부해봐야겠다!)

OpenAI Vector Embeddings 소개

이번 프로젝트에서 Open AI 에서 제공하는 Embeddings API를 사용했다.

GPT 모델 기반 LLM을 사용하고 적은 비용과 간단한 API 호출만으로도 고성능 임베딩 벡터를 생성해주기때문에 선택했다. 

공식문서 에 사용법, 예제부터 자세한 설명이 잘 나와있다. 

 

Embeddings가 쓰이는 곳

Open AI 공식문서에 나온 Embeddings가 쓰이는 경우이다.

문맥간의 관계 유사도 기반 검색, 추천, 군집화 등에 쓰인다.

제공 모델

2025.02.19 기준 3가지 모델을 제공한다.

text-embedding-3-small, 

공식 문서의 달러당 페이지 수 (한 페이지에 약 800토큰), 모델 성능, 최대 입력

 

OpenAI 유료 서비스 사용시 Credit 충전 (돈 충전 안 하면 사용 못함)

임베딩 API는 토큰의 크기를 기준으로 요금이 지불되는 유료 서비스이기때문에 OpenAI에서 Credit balance가 0이면 사용할 수 없다. 

https://platform.openai.com/settings/organization/billing/overview

위의 사이트에서 로그인 후 Billing에서 결제 카드 등록 후 충전해야한다 (최소 충전 단위는 5USD이다.)

텍스트 → 벡터 변환 실습 

OpenAI Key 발급

https://platform.openai.com/settings/organization/api-keys

API 호출을 위해 위의 페이지에서 Key를 발급해준다.

Create New Secret Key를 누른 후 이름과 권한 등을 설정한 후 생성한다.

이때 생성된 Key는 바로 복사해둔다. (생성될 때 한번 보여주고 다시 볼 수 없다!)

Key는 절대 유출되면 안된다!!

API 요청 전체 코드

@Controller
public class Embedding {

    @Value("${gpt.key}")
    private String OPENAI_API_KEY;
    private static final String OPENAI_API_URL = "https://api.openai.com/v1/embeddings";

    @PostMapping("/vector")
    public ResponseEntity<Map> createEmbedding(@RequestParam(value = "input") String input) {

        RestTemplate restTemplate = new RestTemplate();

        // HTTP 헤더 설정
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + OPENAI_API_KEY);
        headers.set("Content-Type", "application/json");

        // 요청 본문
        String requestBody = """
            {
                "model": "text-embedding-3-small",
                "input": "%s"
            }
        """.formatted(input);

        HttpEntity<String> request = new HttpEntity<>(requestBody, headers);

        // API 호출
        return restTemplate.postForEntity(OPENAI_API_URL, request, Map.class);
    }
}

 

Embedding 요청 API Url이다.

OPENAI_API_URL = "https://api.openai.com/v1/embeddings";

 

requestBody에 모델과 요청 텍스트를 넣어준다.

model, input말고도 encoding_format, dimensions, user를 정의할수도있다. 

String requestBody = """
    {
        "model": "text-embedding-3-small",
        "input": "%s"
    }
""".formatted(input);

model이 text-embedding-3-small 일때

입력

결과

 

Postman으로 위와 같이 텍스트("강의 제목: Java 강의, 강사: 사람, 평점: 5")에 대한 문맥이 반영된

1536차원의 벡터가 생성된 것을 확인할 수 있다. 

text-embedding-3-small 모델은 기본이 1536차원 벡터로 생성된다. 

 

model이 text-embedding-3-large 일때

입력

결과

같은 입력에 대해 text-embedding-3-large 모델은 기본으으로 3072 차원의 벡터가 생성된다.

 

임베팅 벡터 차원 줄이기

코드

String requestBody = """
            {
                "model": "text-embedding-3-large",
                "input": "%s",
                "dimensions" : 256
            }
        """.formatted(input);

ㅇ입력

결과

임베딩 벡터의 차원이 1536, 3072 등으로 커질수록 벡터를 저장할 때 더 많은 메모리 및 스토리지 비용을 사용한다.

이를 해결하기 위해 벡터를 생성할 때 dementions 조건에 차원을 지정할 수 있다.

위의 예시로 기본 값이 3076차원인 text-embedding-3-large의 차원을 256으로 지정하면

256차원의 벡터로 조정된 결과를 확인할 수 있다.

 

물론 고차원 벡터 결과보다는 정확도가 떨어질 순 있지만 

Both of our new embedding models were trained with a technique that allows developers to trade-off performance and cost of using embeddings. Specifically, developers can shorten embeddings (i.e. remove some numbers from the end of the sequence) without the embedding losing its concept-representing properties by passing in the dimensions API parameter. For example, on the MTEB benchmark, a text-embedding-3-large embedding can be shortened to a size of 256 while still outperforming an unshortened text-embedding-ada-002 embedding with a size of 1536. You can read more about how changing the dimensions impacts performance in our embeddings v3 launch blog post.

(공식 문서: https://platform.openai.com/docs/guides/embeddings#faq)

공식 문서에 이렇게 나와있으므로 사용하는 저장소 스토리지에 맞춰서 적당히 줄이는 건 괜찮을 것 같다.

 

참고 자료

https://platform.openai.com/docs/guides/embeddings

https://www.elastic.co/kr/what-is/word-embedding

https://www.syncly.kr/blog/what-is-embedding-and-how-to-use

https://ayoung0073.tistory.com/entry/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%83%89%EC%9D%B8Index-%EC%97%AD%EC%83%89%EC%9D%B8Inverted-Index?category=1100790

https://hudi.blog/elasticsearch-inverted-index/

https://techblog.woowahan.com/7425/

https://medium.com/spoontech/aws-opensearch%EC%97%90-%EC%9D%98%EB%AF%B8-%EB%A5%BC-%EB%8B%B4%EC%95%84%EB%B3%B4%EC%9E%90-knn-ac1aa3d35dc1

https://velog.io/@dl-00-e8/Elastic-Search%EC%99%80-Open-Search-%EA%B7%B8%EB%A6%AC%EA%B3%A0-VectorDB

https://wikidocs.net/214400