Поделиться через


Руководство по миграции семантического ядра Python Vector Store

Обзор

В этом руководстве рассматриваются основные обновления векторного хранилища, представленные в семантической версии 1.34, которые представляют собой значительный ремонт реализации векторного хранилища для выравнивания с пакетом SDK для .NET и предоставления более унифицированного, интуитивно понятного API. Изменения объединяют все под semantic_kernel.data.vector и улучшают архитектуру соединителя.

Сводка по ключевым улучшениям

  • Модель единого поля: один VectorStoreField класс заменяет несколько типов полей
  • Встроенные представления: прямое создание внедрений в спецификации векторного поля
  • Упрощенный поиск: простое создание функций поиска непосредственно в коллекциях
  • Консолидированная структура: все под semantic_kernel.data.vector и semantic_kernel.connectors
  • Расширенный поиск текста: улучшенные возможности поиска текста с помощью оптимизированных соединителей
  • Нерекомендуемое: старое memory_stores не рекомендуется использовать в пользу новой архитектуры хранилища векторов

1. Интеграция эмбеддингов и обновление моделей/полей векторного хранилища данных

Существует ряд изменений в том, как вы определяете модель векторного хранилища, самое большое — это то, что теперь мы поддерживаем интегрированные внедрения непосредственно в определения полей векторного хранилища. Это означает, что при указании поля для вектора содержимое этого поля автоматически внедряется с помощью указанного генератора внедрения, например модели внедрения текста OpenAI. Это упрощает процесс создания полей векторов и управления ими.

При определении этого поля необходимо убедиться в трех вещах, особенно при использовании модели Pydantic:

  1. типизация: поле, скорее всего, будет иметь три типа, list[float], str или что-то другое для входных данных генератора встраивания, а None - когда поле не задано.
  2. значение по умолчанию: поле должно иметь значение по умолчанию None или что-то другое, чтобы не возникало ошибки при получении записей из get или search с использованием include_vectors=False, которое сейчас является значением по умолчанию.

Здесь есть две проблемы. Во-первых, при декорировании класса с помощью vectorstoremodel, первая аннотация типа поля используется для заполнения type параметра класса VectorStoreField, поэтому необходимо убедиться, что первая аннотация типа является правильным типом для коллекции векторного хранилища, создаваемой с list[float]. По умолчанию методы get и search не включают вектора в результаты, поэтому поле должно иметь значение по умолчанию, и типизация должна соответствовать этому, поэтому часто разрешено 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")
]

После: Унифицированное поле VectorStore с интегрированными вложениями

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_function и index_kind являются необязательными и, если они не указаны, будут назначены значениями DistanceFunction.DEFAULT и IndexKind.DEFAULT соответственно. Это применимо только к векторным полям, так как каждая реализация хранилища векторов имеет свою логику, которая выбирает значение по умолчанию для этого хранилища.
  4. Переименование свойств:
    • property_typetype_ в качестве атрибута и в 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 и улучшен интерфейс текстового поиска.

Соединитель текстового поиска 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 зависимостей

Преимущества производительности и функций

Улучшения производительности

  • Пакетные операции: новые методы upsert/delete повышают пропускную способность.
  • Интегрированные внедрения: устраняет отдельные этапы внедрения
  • Оптимизированный поиск: встроенные функции поиска оптимизированы для каждого типа хранилища.

Улучшения функций

  • Гибридный поиск: объединяет векторный и текстовый поиск для улучшения результатов
  • Расширенная фильтрация: расширенные выражения фильтров и индексирование

Опыт разработчика

  • Упрощенный API: меньше классов и методов для изучения
  • Согласованный интерфейс: единый подход во всех хранилищах векторов
  • Улучшенная документация: четкие примеры и пути миграции
  • Защита на будущее: согласовано с SDK .NET для согласованной кроссплатформенной разработки

Заключение

Обновления хранилища векторов, описанные выше, представляют собой значительное улучшение пакета SDK для Python для семантического ядра. Новая унифицированная архитектура обеспечивает более высокую производительность, расширенные возможности и более интуитивно понятный интерфейс разработчика. Хотя миграция требует обновления импорта и рефакторинга существующего кода, преимущества в обслуживании и функциональности делают это обновление настоятельно рекомендуемым.

Дополнительные сведения о миграции см. в обновленных примерах в каталоге samples/concepts/memory/ и комплексной документации по API.