다음을 통해 공유


시맨틱 커널 "Python" 벡터 저장소 마이그레이션 가이드

개요

이 가이드에서는 의미 체계 커널 버전 1.34에 도입된 주요 벡터 저장소 업데이트에 대해 설명합니다. 이 업데이트는 .NET SDK에 맞게 조정하고 보다 통합되고 직관적인 API를 제공하기 위해 벡터 저장소 구현의 중요한 정비를 나타냅니다. 변경 내용은 모든 항목을 semantic_kernel.data.vector 통합하고 커넥터 아키텍처를 개선합니다.

주요 개선 사항 요약

  • 통합 필드 모델: 단일 VectorStoreField 클래스가 여러 필드 형식을 대체합니다.
  • 통합 임베딩: 벡터 필드 사양에서 직접 임베딩 생성
  • 간소화된 검색: 컬렉션에서 직접 검색 함수를 쉽게 만들 수 있습니다.
  • 통합 구조: 모든 항목 아래 semantic_kernel.data.vectorsemantic_kernel.connectors
  • 향상된 텍스트 검색: 간소화된 커넥터를 사용하여 향상된 텍스트 검색 기능
  • 사용 중단: 이전 memory_stores 은 새 벡터 저장소 아키텍처를 위해 더 이상 사용되지 않습니다.

1. 통합 포함 및 벡터 저장소 모델/필드 업데이트

벡터 저장소 모델을 정의하는 방식에는 여러 가지 변경 사항이 있으며, 가장 큰 변화는 이제 벡터 저장소 필드 정의에서 직접 통합된 포함을 지원한다는 것입니다. 즉, 필드를 벡터로 지정하면 OpenAI의 텍스트 포함 모델과 같은 지정된 포함 생성기를 사용하여 해당 필드의 내용이 자동으로 포함됩니다. 이렇게 하면 벡터 필드를 만들고 관리하는 프로세스가 간소화됩니다.

해당 필드를 정의할 때는 특히 Pydantic 모델을 사용하는 경우 세 가지를 확인해야 합니다.

  1. typing: 필드는 임베딩 생성기에 대한 입력을 위해 세 가지 유형 list[float], str 또는 다른 유형을 가질 수 있으며, 필드가 설정되지 않았을 때를 위해 None 사용할 수 있습니다.
  2. 기본값: 필드는 현재 기본값인 None 값이나 다른 값이 있어야 하며, 그렇게 함으로써 get 또는 search에서 include_vectors=False를 사용하여 레코드를 가져올 때 오류가 발생하지 않습니다.

여기서는 두 가지 문제가 있습니다. 첫 번째는 클래스 vectorstoremodel를 데코레이팅할 때 필드의 첫 번째 형식 주석을 사용하여 type 매개변수를 채우기 때문에, 첫 번째 형식 주석이 종종 list[float]와 같은 벡터 저장소 컬렉션을 생성하는 데 적합한 형식인지 확인해야 합니다. 기본적으로 getsearch 메서드는 결과에 "include_vectors"를 포함하지 않으므로 필드에 기본값이 필요하며, 타이핑이 일치하도록 해야 하므로 종종 None이 허용되고 기본값은 None으로 설정됩니다. 필드를 만들 때 포함해야 하는 값은 종종 문자열인 이 필드에 있으므로 str 포함해야 합니다. 이러한 변경의 이유는 포함된 내용과 실제로 데이터 필드에 저장되는 항목을 더 유연하게 사용할 수 있기 때문입니다. 이는 일반적인 설정입니다.

from semantic_kernel.data.vector import VectorStoreField, vectorstoremodel
from typing import Annotated
from dataclasses import dataclass

@vectorstoremodel
@dataclass
class MyRecord:
    content: Annotated[str, VectorStoreField('data', is_indexed=True, is_full_text_indexed=True)]
    title: Annotated[str, VectorStoreField('data', is_indexed=True, is_full_text_indexed=True)]
    id: Annotated[str, VectorStoreField('key')]
    vector: Annotated[list[float] | str | None, VectorStoreField(
        'vector', 
        dimensions=1536, 
        distance_function="cosine",
        embedding_generator=OpenAITextEmbedding(ai_model_id="text-embedding-3-small"),
    )] = None

    def __post_init__(self):
        if self.vector is None:
            self.vector = f"Title: {self.title}, Content: {self.content}"

post_init 메서드를 참고하십시오. 이 메서드는 포함된 값을 만들며, 이 값은 단일 필드 이상의 것입니다. 세 가지 형식도 있습니다.

이전: 별도의 필드 클래스

from semantic_kernel.data import (
    VectorStoreRecordKeyField,
    VectorStoreRecordDataField, 
    VectorStoreRecordVectorField
)

# Old approach with separate field classes
fields = [
    VectorStoreRecordKeyField(name="id"),
    VectorStoreRecordDataField(name="text", is_filterable=True, is_full_text_searchable=True),
    VectorStoreRecordVectorField(name="vector", dimensions=1536, distance_function="cosine")
]

이후: 포함 기능이 통합된 VectorStoreField 통합

from semantic_kernel.data.vector import VectorStoreField
from semantic_kernel.connectors.ai.open_ai import OpenAITextEmbedding

# New unified approach with integrated embeddings
embedding_service = OpenAITextEmbedding(
    ai_model_id="text-embedding-3-small"
)

fields = [
    VectorStoreField(
        "key",
        name="id",
    ),
    VectorStoreField(
        "data",
        name="text",
        is_indexed=True,  # Previously is_filterable
        is_full_text_indexed=True  # Previously is_full_text_searchable
    ),
    VectorStoreField(
        "vector",
        name="vector",
        dimensions=1536,
        distance_function="cosine",
        embedding_generator=embedding_service  # Integrated embedding generation
    )
]

필드 정의의 주요 변경 내용

  1. 단일 필드 클래스: VectorStoreField 모든 이전 필드 형식을 대체합니다.
  2. 필드 형식 사양: 매개 변수를 사용합니다 field_type: Literal["key", "data", "vector"] . 이 매개 변수는 위치 매개 변수일 수 있으므로 VectorStoreField("key") 유효합니다.
  3. 향상된 속성:
    • storage_name가 추가되었을 때, 설정하면 벡터 저장소에서 필드 이름으로 사용됩니다. 그렇지 않으면 name 매개 변수가 사용됩니다.
    • dimensions 는 이제 벡터 필드에 필요한 매개 변수입니다.
    • distance_functionindex_kind는 모두 선택 사항이며, 지정되지 않은 경우에는 각각 DistanceFunction.DEFAULTIndexKind.DEFAULT로 설정되고, 이는 벡터 필드에 대해서만 적용됩니다. 각 벡터 저장소 구현에는 해당 저장소에 대한 기본값을 선택하는 논리가 포함되어 있습니다.
  4. 속성 이름 바꾸기:
    • property_type type_ 특성 및 type 생성자에서 →
    • is_filterableis_indexed
    • is_full_text_searchableis_full_text_indexed
  5. 통합 포함: 벡터 필드에 직접 추가 embedding_generator 합니다. 또는 해당 저장소의 모든 벡터 필드에 사용되는 벡터 저장소 컬렉션 자체를 설정할 embedding_generator 수 있습니다. 이 값은 컬렉션 수준 포함 생성기보다 우선합니다.

2. 스토어 및 컬렉션의 새로운 메서드

향상된 스토어 인터페이스

from semantic_kernel.connectors.in_memory import InMemoryStore

# Before: Limited collection methods
collection = InMemoryStore.get_collection("my_collection", record_type=MyRecord)

# After: Slimmer collection interface with new methods
collection = InMemoryStore.get_collection(MyRecord)
# if the record type has the `vectorstoremodel` decorator it can contain both the collection_name and the definition for the collection.

# New methods for collection management
await store.collection_exists("my_collection")
await store.ensure_collection_deleted("my_collection")
# both of these methods, create a simple model to streamline doing collection management tasks.
# they both call the underlying `VectorStoreCollection` methods, see below.

향상된 컬렉션 인터페이스

from semantic_kernel.connectors.in_memory import InMemoryCollection

collection = InMemoryCollection(
    record_type=MyRecord,
    embedding_generator=OpenAITextEmbedding(ai_model_id="text-embedding-3-small")  # Optional, if there is no embedding generator set on the record type
)
# If both the collection and the record type have an embedding generator set, the record type's embedding generator will be used for the collection. If neither is set, it is assumed the vector store itself can create embeddings, or that vectors are included in the records already, if that is not the case, it will likely raise.

# Enhanced collection operations
await collection.collection_exists()
await collection.ensure_collection_exists()
await collection.ensure_collection_deleted()

# CRUD methods
# Removed batch operations, all CRUD operations can now take both a single record or a list of records
records = [
    MyRecord(id="1", text="First record"),
    MyRecord(id="2", text="Second record")
]
ids = ["1", "2"]
# this method adds vectors automatically
await collection.upsert(records)

# You can do get with one or more ids, and it will return a list of records
await collection.get(ids)  # Returns a list of records
# you can also do a get without ids, with top, skip and order_by parameters
await collection.get(top=10, skip=0, order_by='id')
# the order_by parameter can be a string or a dict, with the key being the field name and the value being True for ascending or False for descending order.
# At this time, not all vector stores support this method.

# Delete also allows for single or multiple ids
await collection.delete(ids)

query = "search term"
# New search methods, these use the built-in embedding generator to take the value and create a vector
results = await collection.search(query, top=10)
results = await collection.hybrid_search(query, top=10)

# You can also supply a vector directly
query_vector = [0.1, 0.2, 0.3]  # Example vector
results = await collection.search(vector=query_vector, top=10)
results = await collection.hybrid_search(query, vector=query_vector, top=10)

새 벡터 저장소 구현은 문자열 기반 FilterClause 개체에서 보다 강력하고 형식이 안전한 람다 식 또는 호출 가능한 필터로 이동합니다.

이전: FilterClause 개체

from semantic_kernel.data.text_search import SearchFilter, EqualTo, AnyTagsEqualTo
from semantic_kernel.data.vector_search import VectorSearchFilter

# Creating filters using FilterClause objects
text_filter = SearchFilter()
text_filter.equal_to("category", "AI")
text_filter.equal_to("status", "active")

# Vector search filters
vector_filter = VectorSearchFilter()
vector_filter.equal_to("category", "AI")
vector_filter.any_tag_equal_to("tags", "important")

# Using in search
results = await collection.search(
    "query text",
    options=VectorSearchOptions(filter=vector_filter)
)

이후: 람다 식 필터

# When defining the collection with the generic type hints, most IDE's will be able to infer the type of the record, so you can use the record type directly in the lambda expressions.
collection = InMemoryCollection[str, MyRecord](MyRecord)

# Using lambda expressions for more powerful and type-safe filtering
# The code snippets below work on a data model with more fields then defined earlier.

# Direct lambda expressions
results = await collection.search(
    "query text", 
    filter=lambda record: record.category == "AI" and record.status == "active"
)

# Complex filtering with multiple conditions
results = await collection.search(
    "query text",
    filter=lambda record: (
        record.category == "AI" and 
        record.score > 0.8 and
        "important" in record.tags
    )
)

# Combining conditions with boolean operators
results = await collection.search(
    "query text",
    filter=lambda record: (
        record.category == "AI" or record.category == "ML"
    ) and record.published_date >= datetime(2024, 1, 1)
)

# Range filtering (now possible with lambda expressions)
results = await collection.search(
    "query text",
    filter=lambda record: 0.5 <= record.confidence_score <= 0.9
)

필터에 대한 마이그레이션 팁

  1. 단순 같음: filter.equal_to("field", "value")lambda r: r.field == "value"로 바뀝니다.
  2. 여러 조건: 여러 필터 호출 대신 연산자를 사용하여 and/or 연결
  3. 태그/배열 포함: filter.any_tag_equal_to("tags", "value")lambda r: "value" in r.tags가 됩니다
  4. 향상된 기능: 범위 쿼리, 복잡한 부울 논리 및 사용자 지정 조건자 지원

4. 검색 함수 만들기의 용이성 향상

이전: VectorStoreTextSearch로 검색 기능 생성

from semantic_kernel.connectors.in_memory import InMemoryCollection
from semantic_kernel.data import VectorStoreTextSearch

collection = InMemoryCollection(collection_name='collection', record_type=MyRecord)
search = VectorStoreTextSearch.from_vectorized_search(vectorized_search=collection, embedding_generator=OpenAITextEmbedding(ai_model_id="text-embedding-3-small"))

search_function = search.create_search(
    function_name='search',
    ...
)

이후: 직접 검색 함수 만들기

collection = InMemoryCollection(MyRecord)
# Create search function directly on collection
search_function = collection.create_search_function(
    function_name="search",
    search_type="vector",  # or "keyword_hybrid"
    top=10,
    vector_property_name="vector",  # Name of the vector field
)

# Add to kernel directly
kernel.add_function(plugin_name="memory", function=search_function)

5. 커넥터 이름 바꾸기 및 변경 내용 가져오기

가져오기 경로 통합

# Before: Scattered imports
from semantic_kernel.connectors.memory.azure_cognitive_search import AzureCognitiveSearchMemoryStore
from semantic_kernel.connectors.memory.chroma import ChromaMemoryStore
from semantic_kernel.connectors.memory.pinecone import PineconeMemoryStore
from semantic_kernel.connectors.memory.qdrant import QdrantMemoryStore

# After: Consolidated under connectors
from semantic_kernel.connectors.azure_ai_search import AzureAISearchStore
from semantic_kernel.connectors.chroma import ChromaVectorStore
from semantic_kernel.connectors.pinecone import PineconeVectorStore
from semantic_kernel.connectors.qdrant import QdrantVectorStore

# Alternative after: Consolidated with lazy loading:
from semantic_kernel.connectors.memory import (
    AzureAISearchStore,
    ChromaVectorStore,
    PineconeVectorStore,
    QdrantVectorStore,
    WeaviateVectorStore,
    RedisVectorStore
)

커넥터 클래스 이름 바꾸기

이전 이름 새 이름
AzureCosmosDBforMongoDB* CosmosMongo*
AzureCosmosDBForNoSQL* CosmosNoSql*

6. 텍스트 검색 개선 및 Bing 커넥터 제거

Bing Connector 제거 및 향상된 텍스트 검색 인터페이스

Bing 텍스트 검색 커넥터가 제거되었습니다. 대체 검색 공급자로 마이그레이션:

# Before: Bing Connector (REMOVED)
from semantic_kernel.connectors.search.bing import BingConnector

bing_search = BingConnector(api_key="your-bing-key")

# After: Use Brave Search or other providers
from semantic_kernel.connectors.brave import BraveSearch
# or
from semantic_kernel.connectors.search import BraveSearch

brave_search = BraveSearch()

# Create text search function
text_search_function = brave_search.create_search_function(
    function_name="web_search",
    query_parameter_name="query",
    description="Search the web for information"
)

kernel.add_function(plugin_name="search", function=text_search_function)

향상된 검색 방법

이전: 반환 형식이 다른 세 가지 별도 검색 방법

from semantic_kernel.connectors.brave import BraveSearch
brave_search = BraveSearch()
# Before: Separate search methods
search_results: KernelSearchResult[str] = await brave_search.search(
    query="semantic kernel python",
    top=5,
)

search_results: KernelSearchResult[TextSearchResult] = await brave_search.get_text_search_results(
    query="semantic kernel python",
    top=5,
)

search_results: KernelSearchResult[BraveWebPage] = await brave_search.get_search_results(
    query="semantic kernel python",
    top=5,
)

이후: 출력 형식 매개 변수를 사용하는 통합 검색 방법

from semantic_kernel.data.text_search import SearchOptions
# Enhanced search results with metadata
search_results: KernelSearchResult[str] = await brave_search.search(
    query="semantic kernel python",
    output_type=str, # can also be TextSearchResult or anything else for search engine specific results, default is `str`
    top=5,
    filter=lambda result: result.country == "NL",  # Example filter
)

async for result in search_results.results:
    assert isinstance(result, str)  # or TextSearchResult if using that type
    print(f"Result: {result}")
    print(f"Metadata: {search_results.metadata}")

7. 이전 메모리 저장소 사용 중단

MemoryStoreBase를 기반으로 한 모든 오래된 메모리 저장소는 semantic_kernel.connectors.memory_stores로 이동되었으며 이제 사용되지 않는 것으로 표시됩니다. 대부분의 구현에는 VectorStore 및 VectorStoreCollection을 기반으로 하는 동일한 새 구현이 있으며, 이 구현은 다음에서 semantic_kernel.connectors.memory찾을 수 있습니다.

이러한 커넥터는 완전히 제거됩니다.

  • AstraDB
  • Milvus
  • Usearch

이러한 항목 중 하나라도 필요한 경우 사용되지 않는 모듈 및 semantic_kernel.memory 폴더에서 코드를 인수하거나 새 VectorStoreCollection 클래스를 기반으로 고유한 벡터 저장소 컬렉션을 구현해야 합니다.

github 피드백에 따라 수요가 많은 경우 다시 가져오는 것을 고려할 것이지만 지금은 유지 관리되지 않으며 향후 제거될 예정입니다.

SemanticTextMemory에서 마이그레이션

# Before: SemanticTextMemory (DEPRECATED)
from semantic_kernel.memory import SemanticTextMemory
from semantic_kernel.connectors.ai.open_ai import OpenAITextEmbeddingGenerationService

embedding_service = OpenAITextEmbeddingGenerationService(ai_model_id="text-embedding-3-small")
memory = SemanticTextMemory(storage=vector_store, embeddings_generator=embedding_service)

# Store memory
await memory.save_information(collection="docs", text="Important information", id="doc1")

# Search memory  
results = await memory.search(collection="docs", query="important", limit=5)
# After: Direct Vector Store Usage
from semantic_kernel.data.vector import VectorStoreField, vectorstoremodel
from semantic_kernel.connectors.in_memory import InMemoryCollection

# Define data model
@vectorstoremodel
@dataclass
class MemoryRecord:
    id: Annotated[str, VectorStoreField('key')]
    text: Annotated[str, VectorStoreField('data', is_full_text_indexed=True)]
    embedding: Annotated[list[float] | str | None, VectorStoreField('vector', dimensions=1536, distance_function="cosine", embedding_generator=OpenAITextEmbedding(ai_model_id="text-embedding-3-small"))] = None

# Create vector store with integrated embeddings
collection = InMemoryCollection(
    record_type=MemoryRecord,
    embedding_generator=OpenAITextEmbedding(ai_model_id="text-embedding-3-small")  # Optional, if not set on the record type
)

# Store with automatic embedding generation
record = MemoryRecord(id="doc1", text="Important information", embedding='Important information')
await collection.upsert(record)

# Search with built-in function
search_function = collection.create_search_function(
    function_name="search_docs",
    search_type="vector"
)

메모리 플러그 인 마이그레이션

정보를 저장할 수 있는 플러그 인을 사용하려는 경우 다음과 같이 쉽게 만들 수 있습니다.

# Before: TextMemoryPlugin (DEPRECATED)
from semantic_kernel.core_plugins import TextMemoryPlugin

memory_plugin = TextMemoryPlugin(memory)
kernel.add_plugin(memory_plugin, "memory")
# After: Custom plugin using vector store search functions
from semantic_kernel.functions import kernel_function

class VectorMemoryPlugin:
    def __init__(self, collection: VectorStoreCollection):
        self.collection = collection
    
    @kernel_function(name="save")
    async def save_memory(self, text: str, key: str) -> str:
        record = MemoryRecord(id=key, text=text, embedding=text)
        await self.collection.upsert(record)
        return f"Saved to {self.collection.collection_name}"
    
    @kernel_function(name="search") 
    async def search_memory(self, query: str, limit: int = 5) -> str:
        results = await self.collection.search(
            query, top=limit, vector_property_name="embedding"
        )        
        return "\n".join([r.record.text async for r in results.results])

# Register the new plugin
memory_plugin = VectorMemoryPlugin(collection)
kernel.add_plugin(memory_plugin, "memory")

1단계: 가져오기 업데이트

  • [ ] 메모리 저장소 가져오기를 벡터 저장소 등가 항목으로 바꾸기
  • [ ] 필드 가져오기를 VectorStoreField로 업데이트하기
  • [ ] Bing 커넥터 가져오기 제거

2단계: 필드 정의 업데이트

  • [ ] 통합 VectorStoreField 클래스로 변환
  • [ ] 속성 이름 업데이트(is_filterableis_indexed)
  • [ ] 벡터 필드에 통합 포함 생성기 추가

3단계: 컬렉션 사용량 업데이트

  • [ ] 메모리 작업을 벡터 저장소 메서드로 바꾸기
  • [ ] 해당하는 경우 새 일괄 처리 작업 사용
  • [ ] 새 검색 함수 만들기 구현

4단계: 검색 구현 업데이트

  • [ ] 수동 검색 함수를 으로 바꾸기 create_search_function
  • [ ] 새 공급자를 사용하도록 텍스트 검색 업데이트
  • [ ] 유익한 하이브리드 검색 구현
  • [ ] 필터링을 위해 FilterClause 식에서 lambda 식으로 마이그레이션

5단계: 사용되지 않는 코드 제거

  • [ ] 사용량 제거 SemanticTextMemory
  • [ ] 종속성 제거 TextMemoryPlugin

성능 및 기능 이점

성능 개선

  • 일괄 처리 작업: 새 batch upsert/delete 메서드는 처리량을 향상시킵니다.
  • 통합 포함: 별도의 포함 생성 단계를 제거합니다.
  • 최적화된 검색: 기본 제공 검색 함수는 각 저장소 유형에 맞게 최적화됩니다.

기능 향상

  • 하이브리드 검색: 더 나은 결과를 위해 벡터와 텍스트 검색을 결합합니다.
  • 고급 필터링: 향상된 필터 식 및 인덱싱

개발자 환경

  • 간소화된 API: 학습할 클래스 및 메서드 수가 줄어듭니다.
  • 일관된 인터페이스: 모든 벡터 저장소에서 통합된 접근 방식
  • 더 나은 설명서: 명확한 예제 및 마이그레이션 경로
  • 미래 대비: .NET SDK와 일치하여 일관된 플랫폼 간 개발

결론

위에서 설명한 벡터 저장소 업데이트는 의미 체계 커널 Python SDK가 크게 향상되었음을 나타냅니다. 새로운 통합 아키텍처는 더 나은 성능, 향상된 기능 및 보다 직관적인 개발자 환경을 제공합니다. 마이그레이션을 위해서는 가져오기를 업데이트하고 기존 코드를 리팩터링해야 하지만 유지 관리 기능 및 기능의 이점을 통해 이 업그레이드를 매우 권장합니다.

마이그레이션에 대한 추가 도움말은 디렉터리의 업데이트된 samples/concepts/memory/ 샘플 및 포괄적인 API 설명서를 참조하세요.