Sdílet prostřednictvím


Průvodce migrací sémantického jádra Python Vector Store

Přehled

Tato příručka se zabývá hlavními aktualizacemi úložiště vektorů zavedenými v sémantickém jádru verze 1.34, která představuje významnou opravu implementace vektorového úložiště, která odpovídá sadě .NET SDK a poskytuje jednotnější a intuitivnější rozhraní API. Změny konsolidují vše pod semantic_kernel.data.vector a vylepšují architekturu konektoru.

Shrnutí klíčových vylepšení

  • Model sjednoceného pole: Jedna VectorStoreField třída nahrazuje více typů polí.
  • Integrované vkládání: Přímé generování vkládání ve specifikacích vektorových polí
  • Zjednodušené vyhledávání: Snadné vytváření vyhledávacích funkcí přímo v kolekcích
  • Konsolidovaná struktura: Vše pod semantic_kernel.data.vector a semantic_kernel.connectors
  • Rozšířené vyhledávání textu: Vylepšené možnosti vyhledávání textu se zjednodušenými konektory
  • Vyřazení: Staré memory_stores jsou odsouzené k vyřazení ve prospěch nové architektury vektorového úložiště.

1. Integrované vkládání a aktualizace modelů a polí úložiště vektorů

Způsob, jakým definujete model vektorového úložiště, existuje celá řada změn. Největší je, že teď podporujeme integrované vkládání přímo do definic polí úložiště vektorů. To znamená, že když zadáte pole, které má být vektorem, obsah tohoto pole se automaticky vloží pomocí zadaného generátoru vkládání, jako je model vkládání textu OpenAI. To zjednodušuje proces vytváření a správy vektorových polí.

Když toto pole definujete, musíte se ujistit o třech věcech, zejména při použití Pydantického modelu:

  1. typing: Pole bude pravděpodobně obsahovat tři typy, list[float], str, nebo něco jiného pro vstup do generátoru vkládání, a None pro případ, kdy je pole nenastavené.
  2. výchozí hodnota: Pole musí mít výchozí hodnotu nebo jinou hodnotu None , takže při získávání záznamů z get nebo search s include_vectors=False výchozím nastavením neexistuje žádná chyba.

Existují zde dvě problémy, první je, že při dekorování třídy pomocí vectorstoremodel, se první typ anotace pole používá k vyplnění parametru type třídy VectorStoreField, takže je třeba zajistit, aby první typ anotace byl správný typ pro kolekci vektorových úložišť, která má být vytvořena, často list[float]. Ve výchozím nastavení metody get a search nezahrnují None ve výsledcích, takže pole potřebuje výchozí hodnotu a psaní musí tomu odpovídat, proto je často povoleno None a výchozí hodnota je nastavena na None. Při vytváření pole jsou hodnoty, které je potřeba vložit do tohoto pole, často řetězce, takže str je potřeba zahrnout také. Důvodem této změny je umožnit větší flexibilitu v tom, co je vloženo a co je skutečně uloženo v datových polích, toto by bylo běžné nastavení:

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

Všimněte si metody post_init, která vytváří hodnotu vloženou do struktury, což přesahuje jeden jediný atribut. Existují také tři typy.

Před: Samostatné třídy polí

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

Po: Unified VectorStoreField s integrovanými vnořeními

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

Klíčové změny v definici pole

  1. Třída s jedním polem: VectorStoreField Nahrazuje všechny předchozí typy polí.
  2. Specifikace typu pole: Použijte field_type: Literal["key", "data", "vector"] parametr, může to být poziční parametr, takže VectorStoreField("key") je platný.
  3. Vylepšené vlastnosti:
    • storage_name byl přidán, při nastavení se použije jako název pole v úložišti vektorů, jinak se použije parametr name.
    • dimensions je teď povinný parametr pro vektorová pole.
    • distance_function a index_kind jsou volitelné a budou nastaveny na DistanceFunction.DEFAULT a IndexKind.DEFAULT, pokud nejsou zadány, a to pouze pro vektorová pole. Každá implementace úložiště vektorů má logiku, která zvolí výchozí nastavení pro toto úložiště.
  4. Přejmenování vlastností:
    • property_type type_→ jako atribut a type v konstruktorech
    • is_filterableis_indexed
    • is_full_text_searchableis_full_text_indexed
  5. Integrované vkládání: Přidejte embedding_generator přímo do vektorových polí, případně můžete nastavit embedding_generator samotnou kolekci vektorového úložiště, která se použije pro všechna vektorová pole v tomto úložišti, má tato hodnota přednost před generátorem vkládání na úrovni kolekce.

2. Nové metody pro obchody a kolekce

Rozšířené rozhraní úložiště

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.

Rozšířené rozhraní kolekce

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)

Nová implementace úložiště vektorů se přesune z objektů FilterClause založených na řetězci na výkonnější a typově bezpečné výrazy lambda nebo volatelné filtry.

Před: FilterClause – objekty

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

Po: Filtry výrazů lambda

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

Tipy pro migraci pro filtry

  1. Jednoduchá rovnost: filter.equal_to("field", "value")se stáválambda r: r.field == "value"
  2. Více podmínek: Řetězení s operátory and/or místo opakovaného volání filtru
  3. Zahrnutí značek nebo polí: filter.any_tag_equal_to("tags", "value") se stane lambda r: "value" in r.tags
  4. Vylepšené možnosti: Podpora dotazů na rozsah, komplexní logickou logiku a vlastní predikáty

4. Vylepšené usnadnění vytváření vyhledávacích funkcí

Před: Vytvoření funkce vyhledávání pomocí 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',
    ...
)

Po: Vytvoření přímé vyhledávací funkce

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. Přejmenování a import změn konektoru

Sloučení importních cest

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

Přejmenování třídy konektoru

Starý název Nový název
AzureCosmosDBforMongoDB* CosmosMongo*
AzureCosmosDBForNoSQL* CosmosNoSql*

6. Vylepšení vyhledávání textu a odebrání konektoru Bing

Odebrání konektoru Bingu a rozšířeného rozhraní pro vyhledávání textu

Konektor Bingu pro vyhledávání textu byl odebrán. Migrace na alternativní poskytovatele vyhledávání:

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

Vylepšené metody vyhledávání

Před: Tři samostatné vyhledávací metody s různými návratovými typy

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

After: Sjednocená vyhledávací metoda s parametrem výstupního typu

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. Vyřazení starých úložišť paměti

Všechna stará úložiště paměti založená na MemoryStoreBase byla přesunuta do semantic_kernel.connectors.memory_stores a nyní jsou označena jako zastaralá. Většina z nich má ekvivalentní novou implementaci založenou na VectorStore a VectorStoreCollection, které lze nalézt v semantic_kernel.connectors.memory.

Tyto konektory budou zcela odebrány:

  • AstraDB
  • Milvus
  • Usearch

Pokud některou z těchto možností přesto potřebujete, nezapomeňte převzít kód z zastaralého semantic_kernel.memory modulu a složky nebo implementovat vlastní kolekci vektorového úložiště založenou na nové VectorStoreCollection třídě.

Pokud na základě zpětné vazby na GitHubu existuje velká poptávka, zvažme jejich vrácení zpět, ale prozatím je neudržujeme a v budoucnu se odeberou.

Migrace z SémanticTextMemory

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

Migrace paměťového pluginu

Pokud chcete mít modul plug-in, který může také ukládat informace, můžete snadno vytvořit takto:

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

Krok 1: Aktualizace importů

  • [ ] Nahraďte importy paměťového úložiště ekvivalenty vektorových úložišť
  • [ ] Aktualizovat importy polí pro použití VectorStoreField
  • [ ] Odstranit importy konektoru Bing

Krok 2: Aktualizace definic polí

  • [ ] Převod na sjednocenou VectorStoreField třídu
  • [ ] Aktualizovat názvy vlastností (is_filterableis_indexed)
  • [ ] Přidání integrovaných generátorů vkládání do vektorových polí

Krok 3: Aktualizace využití kolekce

  • [ ] Nahrazení operací paměti metodami úložiště vektorů
  • [ ] Pokud je to možné, použijte nové dávkové operace.
  • [ ] Implementace vytvoření nové vyhledávací funkce

Krok 4: Aktualizace implementace vyhledávání

  • [ ] Nahrazení ručních vyhledávacích funkcí pomocí create_search_function
  • [ ] Aktualizujte vyhledávání textu tak, aby používalo nové zprostředkovatele.
  • [ ] Implementace hybridního vyhledávání, kde je výhodné
  • [ ] Přechod z FilterClause výrazů na lambda výrazy pro filtrování

Krok 5: Odebrání zastaralého kódu

  • [ ] Odebrat SemanticTextMemory použití
  • [ ] Odebrat TextMemoryPlugin závislosti

Výhody výkonu a funkcí

Vylepšení výkonu

  • Dávkové operace: Nové metody dávkového vložení nebo odstranění zlepšují výkonnost
  • Integrované vkládání: Eliminuje samostatné kroky generování vkládání.
  • Optimalizované vyhledávání: Předdefinované vyhledávací funkce jsou optimalizované pro každý typ úložiště.

Vylepšení funkcí

  • Hybridní vyhledávání: Kombinuje vektorové a textové vyhledávání pro lepší výsledky
  • Rozšířené filtrování: Rozšířené výrazy filtru a indexování

Prostředí pro vývojáře

  • Zjednodušené rozhraní API: Méně tříd a metod, které se mají naučit
  • Konzistentní rozhraní: Jednotný přístup napříč všemi vektorovými úložišti
  • Lepší dokumentace: Jasné příklady a migrační cesty
  • Připraveno pro budoucnost: V souladu se sadou .NET SDK pro konzistentní multiplatformní vývoj

Závěr

Aktualizace úložiště vektorů, které jsou popsány výše, představují významné vylepšení sady Python SDK sémantického jádra. Nová sjednocená architektura poskytuje lepší výkon, vylepšené funkce a intuitivnější vývojářské prostředí. I když migrace vyžaduje aktualizaci importu a refaktoringu stávajícího kódu, výhody udržovatelnosti a funkčnosti tento upgrade důrazně doporučují.

Další pomoc s migrací najdete v aktualizovaných ukázkách v samples/concepts/memory/ adresáři a v komplexní dokumentaci k rozhraní API.