Azure AI Service 벡터 스토어

Azure AI Service 벡터 스토어 (Azure AI Service)

이 섹션에서는 AzureVectorStore를 설정해서 Azure AI Search Service를 사용해 문서 임베딩을 저장하고 유사도 검색을 수행하는 방법을 안내해요. Azure AI Search는 Microsoft의 더 큰 AI 플랫폼의 일부인 다용도 클라우드 호스팅 정보 검색 시스템으로, 벡터 기반 저장·검색으로 정보를 질의할 수 있게 해 줘요.

출처: 문서

본문

Azure AI Service

이 섹션에서는 AzureVectorStore를 설정해 Azure AI Search Service를 사용해 문서 임베딩을 저장하고 유사도 검색을 수행하는 방법을 안내해요.

Azure AI Search는 Microsoft의 더 큰 AI 플랫폼의 일부인 다용도 클라우드 호스팅 클라우드 정보 검색 시스템이에요. 다른 기능들 중에서도 벡터 기반 저장·검색으로 정보를 질의할 수 있게 해 줘요.

사전 준비 (Prerequisites)

  1. Azure 구독: Azure 서비스를 사용하려면 Azure subscription이 필요해요.
  2. Azure AI Search Service: AI Search service를 만드세요. 서비스가 만들어지면 Settings 아래의 Keys 섹션에서 admin apiKey를 얻고, Overview 섹션의 Url 필드에서 엔드포인트를 가져오세요.
  3. (선택) Azure OpenAI Service: Azure OpenAI service를 만드세요. 참고: Azure Open AI 서비스에 접근하려면 별도의 양식을 작성해야 할 수 있어요. 서비스가 만들어지면 Resource Management 아래의 Keys and Endpoint 섹션에서 엔드포인트와 apiKey를 얻으세요.

구성 (Configuration)

시작 시 AzureVectorStore는 생성자에서 관련 initialize-schema boolean 프로퍼티를 true로 설정하거나, Spring Boot 사용 시 application.properties 파일에서 …initialize-schema=true로 설정해 선택하면 AI Search 서비스 인스턴스 내에 새 인덱스 생성을 시도할 수 있어요.

참고: 이것은 호환성을 깨는 변경이에요! 이전 버전의 Spring AI에서는 이 스키마 초기화가 기본적으로 일어났어요.

인덱스를 수동으로 만들 수도 있어요.

AzureVectorStore를 설정하려면 위 사전 준비에서 얻은 설정과 인덱스 이름이 필요해요:

  • Azure AI Search 엔드포인트
  • Azure AI Search 키
  • (선택) Azure OpenAI API 엔드포인트
  • (선택) Azure OpenAI API 키

이 값들을 OS 환경 변수로 제공할 수 있어요.

export AZURE_AI_SEARCH_API_KEY=<My AI Search API Key>
export AZURE_AI_SEARCH_ENDPOINT=<My AI Search Index>
export OPENAI_API_KEY=<My Azure AI API Key> (Optional)

참고: Azure Open AI 구현을 Embeddings 인터페이스를 지원하는 유효한 OpenAI 구현으로 대체할 수 있어요. 예를 들어 임베딩에 Azure 구현 대신 Spring AI의 Open AI나 TransformersEmbedding 구현을 사용할 수 있어요.

의존성 (Dependencies)

참고: Spring AI auto-configuration과 starter 모듈의 아티팩트 이름에 큰 변화가 있었어요. 자세한 내용은 upgrade notes를 참고해 주세요.

이 의존성들을 프로젝트에 추가하세요:

1. Embeddings 인터페이스 구현 선택. 다음 중에서 선택할 수 있어요:

  • OpenAI Embedding
  • Azure AI Embedding
  • Local Sentence Transformers Embedding
<dependency>
   <groupId>org.springframework.ai</groupId>
   <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.ai</groupId>
 <artifactId>spring-ai-starter-model-azure-openai</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.ai</groupId>
 <artifactId>spring-ai-starter-model-transformers</artifactId>
</dependency>

2. Azure (AI Search) Vector Store

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-azure-store</artifactId>
</dependency>

참고: 빌드 파일에 Spring AI BOM을 추가하려면 Dependency Management 섹션을 참고해 주세요.

구성 프로퍼티 (Configuration Properties)

Spring Boot 구성에서 다음 프로퍼티를 사용해 Azure 벡터 스토어를 커스터마이즈할 수 있어요.

Property Default value
spring.ai.vectorstore.azure.url
spring.ai.vectorstore.azure.api-key
spring.ai.vectorstore.azure.use-keyless-auth false
spring.ai.vectorstore.azure.initialize-schema false
spring.ai.vectorstore.azure.index-name spring_ai_azure_vector_store
spring.ai.vectorstore.azure.default-top-k 4
spring.ai.vectorstore.azure.default-similarity-threshold 0.0
spring.ai.vectorstore.azure.content-field-name content
spring.ai.vectorstore.azure.embedding-field-name embedding
spring.ai.vectorstore.azure.metadata-field-name metadata

샘플 코드 (Sample Code)

애플리케이션에서 Azure SearchIndexClient를 구성하려면 다음 코드를 사용할 수 있어요:

@Bean
public SearchIndexClient searchIndexClient() {
  return new SearchIndexClientBuilder().endpoint(System.getenv("AZURE_AI_SEARCH_ENDPOINT"))
    .credential(new AzureKeyCredential(System.getenv("AZURE_AI_SEARCH_API_KEY")))
    .buildClient();
}

벡터 스토어를 만들려면 위 샘플에서 생성한 SearchIndexClient 빈과 원하는 Embeddings 인터페이스를 구현하는 Spring AI 라이브러리가 제공하는 EmbeddingModel을 주입해 다음 코드를 사용할 수 있어요.

@Bean
public VectorStore vectorStore(SearchIndexClient searchIndexClient, EmbeddingModel embeddingModel) {

  return AzureVectorStore.builder(searchIndexClient, embeddingModel)
    .initializeSchema(true)
    // Define the metadata fields to be used
    // in the similarity search filters.
    .filterMetadataFields(List.of(MetadataField.text("country"), MetadataField.int64("year"),
            MetadataField.date("activationDate")))
    .defaultTopK(5)
    .defaultSimilarityThreshold(0.7)
    .indexName("spring-ai-document-index")
    .build();
}

참고: 필터 표현식에 사용되는 모든 메타데이터 키에 대해 모든 메타데이터 필드 이름과 타입을 명시적으로 나열해야 해요. 위 목록은 country(TEXT 타입), year(INT64 타입), active(BOOLEAN 타입)의 필터 가능한 메타데이터 필드를 등록해요.

필터 가능한 메타데이터 필드가 새 항목으로 확장되면 이 메타데이터로 문서를 (재)업로드/업데이트해야 해요.

메인 코드에서 몇 개의 문서를 만드세요:

List<Document> documents = List.of(
	new Document("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!", Map.of("country", "BG", "year", 2020)),
	new Document("The World is Big and Salvation Lurks Around the Corner"),
	new Document("You walk forward facing the past and you turn back toward the future.", Map.of("country", "NL", "year", 2023)));

벡터 스토어에 문서를 추가하세요:

vectorStore.add(documents);

마지막으로 쿼리와 유사한 문서를 검색하세요:

List<Document> results = vectorStore.similaritySearch(
    SearchRequest.builder()
      .query("Spring")
      .topK(5).build());

모두 잘 되면 "Spring AI rocks!!" 텍스트를 담은 문서를 검색하게 될 거예요.

메타데이터 필터링 (Metadata filtering)

AzureVectorStore에서도 일반적이고 휴대 가능한 메타데이터 필터를 활용할 수 있어요.

예를 들어 텍스트 표현 언어를 사용할 수 있어요:

vectorStore.similaritySearch(
   SearchRequest.builder()
      .query("The World")
      .topK(TOP_K)
      .similarityThreshold(SIMILARITY_THRESHOLD)
      .filterExpression("country in ['UK', 'NL'] && year >= 2020").build());

또는 표현식 DSL로 프로그래밍 방식으로:

FilterExpressionBuilder b = new FilterExpressionBuilder();

vectorStore.similaritySearch(
    SearchRequest.builder()
      .query("The World")
      .topK(TOP_K)
      .similarityThreshold(SIMILARITY_THRESHOLD)
      .filterExpression(b.and(
         b.in("country", "UK", "NL"),
         b.gte("year", 2020)).build()).build());

휴대 가능한 필터 표현식은 자동으로 고유한 Azure Search OData 필터로 변환돼요. 예를 들어 다음 휴대 가능한 필터 표현식:

country in ['UK', 'NL'] && year >= 2020

은 다음 Azure OData 필터 표현식으로 변환돼요:

$filter search.in(meta_country, 'UK,NL', ',') and meta_year ge 2020

커스텀 필드 이름 (Custom Field Names)

기본적으로 Azure Vector Store는 Azure AI Search 인덱스에서 다음 필드 이름을 사용해요:

  • content - 문서 텍스트용
  • embedding - 벡터 임베딩용
  • metadata - 문서 메타데이터용

하지만 다른 필드 이름을 사용하는 기존 Azure AI Search 인덱스로 작업할 때는 인덱스 스키마에 맞게 커스텀 필드 이름을 구성할 수 있어요. 이를 통해 기존 인덱스를 수정하지 않고도 Spring AI를 사전 구축된 인덱스와 통합할 수 있어요.

사용 사례 (Use Cases)

커스텀 필드 이름은 다음 경우에 특히 유용해요:

  • 기존 인덱스와의 통합: 조직에 이미 확립된 필드 명명 규칙(예: chunk_text, vector, meta_data)이 있는 Azure AI Search 인덱스가 있는 경우.
  • 명명 표준 따르기: 팀이 기본값과 다른 특정 명명 규칙을 따르는 경우.
  • 다른 시스템에서 마이그레이션: 다른 벡터 데이터베이스나 검색 시스템에서 마이그레이션하면서 일관된 필드 이름을 유지하려는 경우.

프로퍼티로 구성 (Configuration via Properties)

Spring Boot 애플리케이션 프로퍼티로 커스텀 필드 이름을 구성할 수 있어요:

spring.ai.vectorstore.azure.url=${AZURE_AI_SEARCH_ENDPOINT}
spring.ai.vectorstore.azure.api-key=${AZURE_AI_SEARCH_API_KEY}
spring.ai.vectorstore.azure.index-name=my-existing-index
spring.ai.vectorstore.azure.initialize-schema=false

# Custom field names to match existing index schema
spring.ai.vectorstore.azure.content-field-name=chunk_text
spring.ai.vectorstore.azure.embedding-field-name=vector
spring.ai.vectorstore.azure.metadata-field-name=meta_data

참고: 커스텀 필드 이름으로 기존 인덱스를 사용할 때는 initialize-schema=false로 설정해서 Spring AI가 기본 스키마로 새 인덱스를 만들려고 하지 않게 하세요.

빌더 API로 구성 (Configuration via Builder API)

또는 빌더 API로 커스텀 필드 이름을 프로그래밍 방식으로 구성할 수 있어요:

@Bean
public VectorStore vectorStore(SearchIndexClient searchIndexClient, EmbeddingModel embeddingModel) {

	return AzureVectorStore.builder(searchIndexClient, embeddingModel)
		.indexName("my-existing-index")
		.initializeSchema(false) // Don't create schema - use existing index
		// Configure custom field names to match existing index
		.contentFieldName("chunk_text")
		.embeddingFieldName("vector")
		.metadataFieldName("meta_data")
		.filterMetadataFields(List.of(
			MetadataField.text("category"),
			MetadataField.text("source")))
		.build();
}

전체 예제: 기존 인덱스와 함께 작업 (Complete Example: Working with Existing Index)

커스텀 필드 이름이 있는 기존 Azure AI Search 인덱스와 Spring AI를 사용하는 방법을 보여 주는 완전한 예제는 다음과 같아요:

@Configuration
public class VectorStoreConfig {

	@Bean
	public SearchIndexClient searchIndexClient() {
		return new SearchIndexClientBuilder()
			.endpoint(System.getenv("AZURE_AI_SEARCH_ENDPOINT"))
			.credential(new AzureKeyCredential(System.getenv("AZURE_AI_SEARCH_API_KEY")))
			.buildClient();
	}

	@Bean
	public VectorStore vectorStore(SearchIndexClient searchIndexClient,
			EmbeddingModel embeddingModel) {

		return AzureVectorStore.builder(searchIndexClient, embeddingModel)
			.indexName("production-documents-index")
			.initializeSchema(false) // Use existing index
			// Map to existing index field names
			.contentFieldName("document_text")
			.embeddingFieldName("text_vector")
			.metadataFieldName("document_metadata")
			// Define filterable metadata fields from existing schema
			.filterMetadataFields(List.of(
				MetadataField.text("department"),
				MetadataField.int64("year"),
				MetadataField.date("created_date")))
			.defaultTopK(10)
			.defaultSimilarityThreshold(0.75)
			.build();
	}
}

그런 다음 벡터 스토어를 평소처럼 사용할 수 있어요:

// Search using the existing index with custom field names
List<Document> results = vectorStore.similaritySearch(
	SearchRequest.builder()
		.query("artificial intelligence")
		.topK(5)
		.filterExpression("department == 'Engineering' && year >= 2023")
		.build());

// The results contain documents with text from the 'document_text' field
results.forEach(doc -> System.out.println(doc.getText()));

커스텀 필드 이름으로 새 인덱스 만들기 (Creating New Index with Custom Field Names)

initializeSchema=true로 설정해 커스텀 필드 이름으로 새 인덱스를 만들 수도 있어요:

@Bean
public VectorStore vectorStore(SearchIndexClient searchIndexClient,
		EmbeddingModel embeddingModel) {

	return AzureVectorStore.builder(searchIndexClient, embeddingModel)
		.indexName("new-custom-index")
		.initializeSchema(true) // Create new index with custom field names
		.contentFieldName("text_content")
		.embeddingFieldName("content_vector")
		.metadataFieldName("doc_metadata")
		.filterMetadataFields(List.of(
			MetadataField.text("category"),
			MetadataField.text("author")))
		.build();
}

이렇게 하면 커스텀 필드 이름으로 새 Azure AI Search 인덱스가 생성되어 처음부터 자신만의 명명 규칙을 확립할 수 있어요.

네이티브 클라이언트 접근 (Accessing the Native Client)

Azure Vector Store 구현은 getNativeClient() 메서드를 통해 내부 네이티브 Azure Search 클라이언트(SearchClient)에 접근을 제공해요:

AzureVectorStore vectorStore = context.getBean(AzureVectorStore.class);
Optional<SearchClient> nativeClient = vectorStore.getNativeClient();

if (nativeClient.isPresent()) {
    SearchClient client = nativeClient.get();
    // Use the native client for Azure Search-specific operations
}

네이티브 클라이언트는 VectorStore 인터페이스로는 노출되지 않는 Azure Search 특화 기능과 작업에 접근할 수 있게 해 줘요.

더 알아보기 (Learn more)