OpenSearch와 OpenAI를 연동한 벡터 검색 구현
OpenSearch와 OpenAI를 연동한 벡터 검색 구현 방법을 설명합니다.
- 예상 소요 시간: 30분
- 권장 운영 체제: macOS, Ubuntu
- 사전 준비 사항
- Advanced Managed Search 클러스터 생성
- OpenSearch API 접근 정보 (마스터 사용자)
- OpenAI API Key 발급
- OpenAI API 사용을 위한 결제 등록 및 사용량 한도 확인
시나리오 소개
이 튜토리얼에서는 Advanced Managed Search와 OpenAI Embedding API를 연동하여 텍스트 데이터를 벡터로 변환하고, 의미 기반 검색(Vector Search)을 구현합니다.
기존의 키워드 기반 검색은 단어가 정확히 일치하지 않으면 원하는 결과를 찾기 어렵지만, 벡터 검색은 문장의 의미를 기반으로 유사한 문서를 찾아낼 수 있습니다. 이를 통해 FAQ 검색, 문서 추천, 지식 검색, RAG 기반 질의응답과 같은 AI 검색 시나리오를 구현할 수 있습니다.
주요 내용은 다음과 같습니다.
- OpenAI Embedding API를 활용한 텍스트 벡터화
- OpenSearch ML Connector를 통한 외부 AI 모델 연동
- 벡터 인덱스(knn_vector) 구성 및 문서 색인
- 의미 기반 유사도 검색(Vector Search) 구현
시작하기 전에
이 튜토리얼을 진행하기 앞서, 먼저 클러스터 생성 및 관리를 참고하여 Advanced Managed Search 클러스터를 생성해 주세요.
이 튜토리얼에서 사용하는 모든 OpenSearch API 요청은 Advanced Managed Search 클러스터 엔드포인트를 기준으로 호출합니다. 클러스터 생성 시 카카오클라우드 VPC를 사용하는 경우, 동일 VPC에 생성한 인스턴스에서 명령어를 실행하세요.
클러스터 상세 정보 페이지에서 엔드포인트(Endpoint) 와 마스터 사용자 계정 정보를 확인한 뒤, 아래 예시의 <AMS_ENDPOINT>, <MASTER_USER>, <MASTER_PASSWORD> 값을 실제 값으로 변경하여 요청을 실행하세요.
예: https://<AMS_ENDPOINT>/_plugins/_ml/connectors/_create
시작하기
Step 1. OpenAI Model Group 생성
OpenSearch ML 플러그인에서 사용할 모델 그룹을 생성합니다.
curl -XPOST 'https://<AMS_ENDPOINT>/_plugins/_ml/model_groups/_register' \
-u '<MASTER_USER>:<MASTER_PASSWORD>' \
-H 'Content-Type: application/json' \
-d '{"name": "openai"}'
응답에 포함된 MODEL_GROUP_ID를 Step 3에서 사용합니다.
Step 2. OpenAI Embedding Connector 생성
OpenAI Embedding API를 호출하는 Connector를 생성합니다.
curl -XPOST 'https://<AMS_ENDPOINT>/_plugins/_ml/connectors/_create' \
-u '<MASTER_USER>:<MASTER_PASSWORD>' \
-H 'Content-Type: application/json' \
-d '{
"name": "OpenAI Embedding Connector",
"description": "Connector for OpenAI Embedding API",
"version": 1,
"protocol": "http",
"parameters": {
"endpoint": "api.openai.com",
"model": "text-embedding-ada-002"
},
"credential": {
"OPENAI_API_KEY": "${OPENAI_API_KEY}"
},
"actions": [
{
"action_type": "predict",
"method": "POST",
"url": "https://${parameters.endpoint}/v1/embeddings",
"headers": {
"Authorization": "Bearer ${credential.OPENAI_API_KEY}",
"Content-Type": "application/json"
},
"request_body": "{ \"model\": \"${parameters.model}\", \"input\": ${parameters.input}, \"encoding_format\": \"float\" }",
"pre_process_function": "connector.pre_process.openai.embedding",
"post_process_function": "connector.post_process.openai.embedding"
}
]
}'
| 환경변수 | 설명 |
|---|---|
| OPENAI_API_KEY🖌︎ | OpenAI에서 발급한 API Key |
응답에 포함된 CONNECTOR_ID를 다음 단계에서 사용합니다.
Step 3. 모델 등록 및 배포
curl -XPOST 'https://<AMS_ENDPOINT>/_plugins/_ml/models/_register?deploy=true' \
-u '<MASTER_USER>:<MASTER_PASSWORD>' \
-H 'Content-Type: application/json' \
-d '{
"name": "OpenAI Embedding Model",
"function_name": "remote",
"model_group_id": "${MODEL_GROUP_ID}",
"description": "OpenAI embedding model",
"connector_id": "${CONNECTOR_ID}"
}'
| 환경변수 | 설명 |
|---|---|
| MODEL_GROUP_ID🖌︎ | Step 1에서 반환된 Model Group ID |
| CONNECTOR_ID🖌︎ | Step 2에서 반환된 Connector ID |
응답에 포함된 MODEL_ID를 다음 단계에서 사용합니다.
Step 4. Embedding 테스트
curl -XPOST 'https://<AMS_ENDPOINT>/_plugins/_ml/models/${MODEL_ID}/_predict' \
-u '<MASTER_USER>:<MASTER_PASSWORD>' \
-H 'Content-Type: application/json' \
-d '{
"parameters": {
"input": ["Ian is not cool"]
}
}'
| 환경변수 | 설명 |
|---|---|
| MODEL_ID🖌︎ | Step 3에서 반환된 Model ID |
OpenAI API 결제 등록이 되어 있지 않거나 사용량 한도를 초과한 경우, Embedding 테스트 시 insufficient_quota 오류가 발생할 수 있습니다. 오류가 발생하면 OpenAI 계정의 결제 등록 상태와 사용량 한도를 확인하세요.
Step 5. Embedding Ingest Pipeline 생성
curl -XPUT 'https://<AMS_ENDPOINT>/_ingest/pipeline/embedding_pipeline' \
-u '<MASTER_USER>:<MASTER_PASSWORD>' \
-H 'Content-Type: application/json' \
-d '{
"description": "Convert text to embedding using OpenAI",
"processors": [
{
"text_embedding": {
"field_map": {
"text": "embedding"
},
"model_id": "${MODEL_ID}"
}
}
]
}'
| 환경변수 | 설명 |
|---|---|
| MODEL_ID🖌︎ | Step 3에서 반환된 Model ID |
Step 6. 벡터 인덱스 생성
curl -XPUT 'https://<AMS_ENDPOINT>/my-index' \
-u '<MASTER_USER>:<MASTER_PASSWORD>' \
-H 'Content-Type: application/json' \
-d '{
"settings": {
"default_pipeline": "embedding_pipeline",
"index.knn": true
},
"mappings": {
"properties": {
"text": { "type": "text" },
"embedding": {
"type": "knn_vector",
"dimension": 1536
}
}
}
}'
Step 7. 문서 색인
curl -XPOST 'https://<AMS_ENDPOINT>/my-index/_doc' \
-u '<MASTER_USER>:<MASTER_PASSWORD>' \
-H 'Content-Type: application/json' \
-d '{ "text": "Ian is cool!" }'
curl -XPOST 'https://<AMS_ENDPOINT>/my-index/_doc' \
-u '<MASTER_USER>:<MASTER_PASSWORD>' \
-H 'Content-Type: application/json' \
-d '{ "text": "Ian is super cool!" }'
curl -XPOST 'https://<AMS_ENDPOINT>/my-index/_doc' \
-u '<MASTER_USER>:<MASTER_PASSWORD>' \
-H 'Content-Type: application/json' \
-d '{ "text": "Ian is not cool!" }'
Step 8. 벡터 검색
curl -XGET 'https://<AMS_ENDPOINT>/my-index/_search' \
-u '<MASTER_USER>:<MASTER_PASSWORD>' \
-H 'Content-Type: application/json' \
-d '{
"_source": { "excludes": ["embedding"] },
"query": {
"neural": {
"embedding": {
"query_text": "Ian is cool?",
"model_id": "${MODEL_ID}",
"k": 2
}
}
}
}'
| 환경변수 | 설명 |
|---|---|
| MODEL_ID🖌︎ | Step 3에서 반환된 Model ID |
결과 예시
{
"hits": {
"hits": [
{ "_source": { "text": "Ian is cool!" } },
{ "_source": { "text": "Ian is super cool!" } }
]
}
}
1. 의미 기반 검색: 위 결과에서 질의어(Ian is cool?)와 가장 의미가 가까운 문서들이 상위 결과로 반환되었습니다. "Ian is not cool!"은 단어 구성은 비슷하지만 의미가 반대이기 때문에 유사도 점수가 낮게 측정되었습니다.
2. K 값의 역할: 쿼리에서 k 값을 2로 설정했기 때문에, 유사도가 가장 높은 상위 2개의 문서만 결과에 포함되었습니다. 만약 더 많은 결과를 보고 싶다면 k 값을 조절해 보세요.