Migrationshandbuch für den semantischen Kernel-Python-Vektorspeicher

Überblick

In diesem Leitfaden werden die wichtigsten Vektorspeicherupdates behandelt, die in Der Version 1.34 des semantischen Kernels eingeführt wurden. Dies stellt eine erhebliche Überholung der Vektorspeicherimplementierung dar, die sich an das .NET SDK richtet und eine einheitlichere, intuitivere API bereitstellt. Die Änderungen konsolidieren alles unter semantic_kernel.data.vector und verbessern die Konnektorarchitektur.

Zusammenfassung der wichtigsten Verbesserungen

  • Einheitliches Feldmodell: Einzelne VectorStoreField Klasse ersetzt mehrere Feldtypen.
  • Integrierte Einbettungen: Direkte Einbettungsgenerierung in Vektorfeldspezifikationen
  • Vereinfachte Suche: Einfache Erstellung von Suchfunktionen direkt in Sammlungen
  • Konsolidierte Struktur: Alles unter semantic_kernel.data.vector und semantic_kernel.connectors
  • Erweiterte Textsuche: Verbesserte Textsuchfunktionen mit optimierten Konnektoren
  • Veraltete Funktion: Alte „memory_stores“ sind zugunsten der neuen Vektorspeicherarchitektur veraltet

1. Integrierte Einbettungen und Vektorspeichermodelle/-felder Updates

Es gibt eine Reihe von Änderungen an der Art und Weise, wie Sie Ihr Vektorspeichermodell definieren, das größte ist, dass wir jetzt integrierte Einbettungen direkt in die Vektorspeicherfelddefinitionen unterstützen. Dies bedeutet, dass der Inhalt dieses Felds automatisch mithilfe des angegebenen Einbettungsgenerators eingebettet wird, z. B. das Texteinbettungsmodell von OpenAI, wenn Sie ein Feld als Vektor angeben. Dies vereinfacht das Erstellen und Verwalten von Vektorfeldern.

Wenn Sie dieses Feld definieren, müssen Sie drei Dinge sicherstellen, insbesondere bei Verwendung eines pydantischen Modells:

  1. Tippen: Das Feld könnte drei Typen haben, list[float], str oder etwas anderes für die Eingabe in den Einbettungsgenerator, und None für den Fall, dass das Feld nicht gesetzt ist.
  2. Standardwert: Das Feld muss einen Standardwert von None oder einen anderen Wert besitzen, damit beim Abrufen von Datensätzen von get oder search mit include_vectors=False, das jetzt der Standard ist, kein Fehler auftritt.

Es gibt hier zwei Bedenken: Das erste ist, dass beim Dekorieren einer Klasse mit vectorstoremodel die erste Typannotation des Felds verwendet wird, um den type-Parameter der VectorStoreField-Klasse auszufüllen. Daher müssen Sie sicherstellen, dass die erste Typannotation der richtige Typ für die zu erstellende Vektorspeichersammlung ist, oft list[float]. Standardmäßig schließen die Methoden get und search keine Vektoren in die Ergebnisse ein, sodass das Feld einen Standardwert benötigt und die Typisierung damit übereinstimmen muss. Daher ist None häufig zulässig und der Standardwert wird auf None gesetzt. Wenn das Feld erstellt wird, befinden sich die Werte, die eingebettet werden müssen, in diesem Feld, häufig auch Zeichenfolgen, sodass str auch enthalten sein muss. Der Grund für diese Änderung besteht darin, mehr Flexibilität bei dem, was eingebettet wird, und dem, was tatsächlich in Datenfeldern gespeichert ist, zu ermöglichen. Dies wäre eine übliche Konfiguration.

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}"

Beachten Sie die post_init-Methode. Dadurch wird ein Wert erstellt, der eingebettet wird und mehr als nur ein einzelnes Feld umfasst. Die drei Typen sind ebenfalls vorhanden.

Vorher: Separate Feldklassen

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")
]

Danach: Vereinheitlichtes VectorStoreField mit integrierten Einbettungen

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
    )
]

Wichtige Änderungen in der Felddefinition

  1. Single Field Class: VectorStoreField ersetzt alle vorherigen Feldtypen
  2. Feldtypspezifikation: Parameter verwenden field_type: Literal["key", "data", "vector"] , dies kann ein Positionsparameter sein. Dies ist also VectorStoreField("key") gültig.
  3. Erweiterte Eigenschaften:
    • storage_name wurde hinzugefügt, und wenn dieser festgelegt ist, wird er als Feldname im Vektorspeicher verwendet; andernfalls wird der name-Parameter verwendet.
    • dimensions ist jetzt ein erforderlicher Parameter für Vektorfelder.
    • distance_function und index_kind sind beide optional und werden auf DistanceFunction.DEFAULT bzw. IndexKind.DEFAULT festgelegt, wenn nicht angegeben. Nur für Vektorfelder verfügt jede Vektorspeicherimplementierung über Logik, die einen Standardwert für diesen Speicher auswählt.
  4. Eigenschaftsumbenennungen:
    • property_type type_ als Attribut und type in Konstruktoren
    • is_filterableis_indexed
    • is_full_text_searchableis_full_text_indexed
  5. Integrierte Einbettungen: Fügen Sie embedding_generator direkt zu Vektorfeldern hinzu. Alternativ können Sie die embedding_generator direkt in der Sammlung im Vektorspeicher festlegen, die für alle Vektorfelder in diesem Speicher verwendet wird. Dieser Wert hat Vorrang vor dem Einbettungsgenerator auf Sammlungsebene.

2. Neue Methoden für Stores und Sammlungen

Erweiterte Store-Schnittstelle

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.

Erweiterte Sammlungsschnittstelle

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)

Die neue Implementierung des Vektorspeichers wechselt von Zeichenfolgenbasierten FilterClause-Objekten zu leistungsstärkeren und typsicheren Lambda-Ausdrücken oder aufrufbaren Filtern.

Vor: FilterClause-Objekte

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)
)

Nach: Lambda-Ausdrucksfilter

# 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
)

Migrationstipps für Filter

  1. Einfache Gleichheit: filter.equal_to("field", "value") wird lambda r: r.field == "value"
  2. Mehrere Bedingungen: Verketten mit and/or Operatoren anstelle mehrerer Filteraufrufe
  3. Tag/Array-Eindämmung: filter.any_tag_equal_to("tags", "value") wird lambda r: "value" in r.tags
  4. Erweiterte Funktionen: Unterstützung für Bereichsabfragen, komplexe boolesche Logik und benutzerdefinierte Prädikate

Erleichterte Erstellung von Suchfunktionen

Vorher: Erstellen einer Suchfunktion mit 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',
    ...
)

Nach: Erstellung einer direkten Suchfunktion

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. Connector benennt Änderungen um und importiert Änderungen

Importpfadkonsolidierung

# 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
)

Umbenennungen der Connector-Klasse

Alter Name Neuer Name
AzureCosmosDBforMongoDB* CosmosMongo*
AzureCosmosDBForNoSQL* CosmosNoSql*

6. Verbesserungen bei der Textsuche und Entfernung des Bing Connectors

Bing Connector entfernt und erweiterte Textsuchschnittstelle

Der Bing-Textsuche-Connector wurde entfernt. Migrieren zu alternativen Suchanbietern:

# 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)

Verbesserte Suchmethoden

Vorher: Drei separate Suchmethoden mit unterschiedlichen Rückgabetypen

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,
)

Nachher: Einheitliche Suchmethode mit Ausgabe-Typ-Parameter

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. Veraltete Speicherorte für alten Speicher

Alle alten Speicher, die auf MemoryStoreBase basieren, wurden in semantic_kernel.connectors.memory_stores verschoben und sind jetzt als veraltet gekennzeichnet. Die meisten von ihnen haben eine gleichwertige neue Implementierung basierend auf VectorStore und VectorStoreCollection, die in „semantic_kernel.connectors.memory“ zu finden ist.

Diese Steckverbinder werden vollständig entfernt.

  • AstraDB
  • Milvus
  • Usearch

Wenn Sie diese noch benötigen, stellen Sie sicher, dass Sie den Code aus dem veralteten Modul und dem semantic_kernel.memory Ordner übernehmen oder ihre eigene Vektorspeichersammlung basierend auf der neuen VectorStoreCollection Klasse implementieren.

Wenn es eine große Nachfrage auf der Grundlage von GitHub-Feedback gibt, werden wir erwägen, sie zurückzubringen, aber vorerst werden sie nicht gepflegt und in Zukunft entfernt werden.

Migration von 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"
)

Speicher-Plugin-Migration

Wenn Sie über ein Plug-In verfügen möchten, das auch Informationen speichern kann, können Sie das ganz einfach wie folgt erstellen:

# 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")

Schritt 1: Aktualisieren von Importen

  • [ ] Speicherimporte durch Vektorspeicheräquivalente ersetzen
  • Aktualisieren der Feldimporte zur Verwendung VectorStoreField
  • [ ] Entfernen von Bing-Connector-Importen

Schritt 2: Aktualisieren von Felddefinitionen

  • [ ] In einheitliche VectorStoreField Klasse konvertieren
  • [ ] Eigenschaftennamen aktualisieren (is_filterableis_indexed)
  • [ ] Integrierte Einbettungsgeneratoren zu Vektorfeldern hinzufügen

Schritt 3: Aktualisieren der Sammlungsverwendung

  • [ ] Ersetzen von Speichervorgängen durch Vektorspeichermethoden
  • [ ] Verwenden neuer Batchvorgänge, sofern zutreffend
  • Implementierung der Erstellung neuer Suchfunktionen

Schritt 4: Aktualisieren der Suchimplementierung

  • [ ] Ersetzen manueller Suchfunktionen durch create_search_function
  • [ ] Aktualisieren der Textsuche für die Verwendung neuer Anbieter
  • [ ] Implementieren der Hybridsuche, wo vorteilhaft
  • [ ] Migrieren von FilterClause zu lambda Ausdrücken zum Filtern

Schritt 5: Entfernen veralteter Code

  • [ ] Entfernen der Verwendung von SemanticTextMemory
  • [ ] Abhängigkeiten TextMemoryPlugin entfernen

Leistungs- und Featurevorteile

Leistungsverbesserungen

  • Batchvorgänge: Neue Batch-Upsert-/Löschmethoden verbessern den Durchsatz
  • Integrierte Einbettungen: Beseitigt separate Schritte zur Erstellung von Einbettungen
  • Optimierte Suche: Integrierte Suchfunktionen sind für jeden Speichertyp optimiert.

Funktionserweiterungen

  • Hybridsuche: Kombiniert Vektor- und Textsuche für bessere Ergebnisse
  • Erweiterte Filterung: Erweiterte Filterausdrücke und Indizierung

Entwicklererlebnis

  • Vereinfachte API: Weniger Klassen und Methoden zum Erlernen
  • Einheitliche Schnittstelle: Einheitlicher Ansatz in allen Vektorspeichern
  • Bessere Dokumentation: Klare Beispiele und Migrationspfade
  • Zukunftssicher: Abgestimmt auf .NET SDK für eine konsistente plattformübergreifende Entwicklung

Schlussfolgerung

Die oben beschriebenen Vektorspeicherupdates stellen eine erhebliche Verbesserung im Semantischer Kernel Python SDK dar. Die neue einheitliche Architektur bietet eine bessere Leistung, verbesserte Features und eine intuitivere Entwicklerumgebung. Die Migration erfordert zwar das Aktualisieren von Importen und umgestalten von vorhandenem Code, aber die Vorteile bei der Wartung und Funktionalität empfehlen dieses Upgrade dringend.

Weitere Hilfe zur Migration finden Sie in den aktualisierten Beispielen im samples/concepts/memory/ Verzeichnis und in der umfassenden API-Dokumentation.