Sdílet prostřednictvím


Modelování složitých datových typů ve službě Azure AI Search

Externí datové sady používané k naplnění indexu Azure AI Search můžou mít mnoho obrazců. Někdy zahrnují hierarchické nebo vnořené podstruktury. Příkladem může být několik adres pro jednoho zákazníka, více barev a velikostí jednoho produktu, více autorů jedné knihy atd. V modelingu se tyto struktury můžou zobrazovat jako složité, složené, složené nebo agregované datové typy. Termín Azure AI Search používá pro tento koncept komplexní typ. Ve službě Azure AI Search se komplexní typy modelují pomocí složitých polí. Komplexní pole je pole, které obsahuje podřízené pole (podpole), které mohou být libovolného datového typu, včetně jiných složitých typů. Funguje to podobným způsobem jako strukturované datové typy v programovacím jazyce.

Složitá pole představují jeden objekt v dokumentu nebo pole objektů v závislosti na datovém typu. Pole typu Edm.ComplexType představují jednotlivé objekty, zatímco pole typu Collection(Edm.ComplexType) představují pole objektů.

Azure AI Search nativně podporuje komplexní typy a kolekce. Tyto typy umožňují modelovat téměř jakoukoli strukturu JSON v indexu Azure AI Search. V předchozích verzích rozhraní API služby Azure AI Search bylo možné importovat pouze sady plochých řádků. V nejnovější verzi teď index může přesněji odpovídat zdrojovým datům. Jinými slovy, pokud zdrojová data mají složité typy, může mít index také složité typy.

Pro začátek doporučujeme sadu dat Hotels, kterou můžete načíst v průvodci importem dat na webu Azure Portal. Průvodce zjistí složité typy ve zdroji a navrhne schéma indexu na základě zjištěných struktur.

Poznámka:

Podpora komplexních typů byla obecně dostupná od začátku api-version=2019-05-06.

Pokud je vaše řešení vyhledávání postavené na dřívějších alternativních řešeních zploštěných datových sad v kolekci, měli byste změnit index tak, aby zahrnoval složité typy podporované v nejnovější verzi rozhraní API. Další informace o upgradu verzí rozhraní API najdete v tématu Upgrade na nejnovější verzi rozhraní REST API nebo upgrade na nejnovější verzi sady .NET SDK.

Příklad komplexní struktury

Následující dokument JSON se skládá z jednoduchých polí a složitých polí. Složitá pole, například Address a Rooms, mají dílčí pole. Address obsahuje jednu sadu hodnot pro tato dílčí pole, protože se jedná o jeden objekt v dokumentu. Naproti tomu Rooms má několik sad hodnot pro jeho dílčí pole, jeden pro každý objekt v kolekci.

{
  "HotelId": "1",
  "HotelName": "Stay-Kay City Hotel",
  "Description": "Ideally located on the main commercial artery of the city in the heart of New York.",
  "Tags": ["Free wifi", "on-site parking", "indoor pool", "continental breakfast"],
  "Address": {
    "StreetAddress": "677 5th Ave",
    "City": "New York",
    "StateProvince": "NY"
  },
  "Rooms": [
    {
      "Description": "Budget Room, 1 Queen Bed (Cityside)",
      "RoomNumber": 1105,
      "BaseRate": 96.99,
    },
    {
      "Description": "Deluxe Room, 2 Double Beds (City View)",
      "Type": "Deluxe Room",
      "BaseRate": 150.99,
    }
    . . .
  ]
}

Vytváření složitých polí

Stejně jako u jakékoli definice indexu můžete pomocí webu Azure Portal, rozhraní REST API nebo sady .NET SDK vytvořit schéma, které zahrnuje komplexní typy.

Další sady Azure SDK poskytují ukázky v Pythonu, Javě a JavaScriptu.

  1. Přihlaste se k portálu Azure.

  2. Na stránce Přehled vyhledávací služby vyberte kartu Indexy.

  3. Otevřete existující index nebo vytvořte nový index.

  4. Vyberte kartu Pole a pak vyberte Přidat pole. Přidá se prázdné pole. Pokud pracujete s existující kolekcí polí, posuňte se dolů a nastavte pole.

  5. Zadejte název pole a nastavte typ buď nebo Edm.ComplexType Collection(Edm.ComplexType).

  6. Vyberte tři tečky úplně vpravo a pak vyberte přidat pole nebo přidat dílčí pole a pak přiřaďte atributy.

Omezení komplexních kolekcí

Během indexování můžete mít maximálně 3 000 prvků ve všech složitých kolekcích v rámci jednoho dokumentu. Prvek komplexní kolekce je členem této kolekce. Pro pokoje (jediná složitá kolekce v příkladu hotelu) je každý pokoj prvkem. V předchozím příkladu by pokud hotel Stay-Kay City Hotel měl 500 pokojů, měl by hotelový dokument 500 prvků pokoje. U vnořených komplexních kolekcí se počítá také každý vnořený prvek kromě vnějšího (nadřazeného) elementu.

Tento limit platí jenom pro složité kolekce a ne pro komplexní typy (například Adresy) nebo kolekce řetězců (například Značky).

Aktualizace složitých polí

Všechna pravidla přeindexování, která platí pro pole obecně platí pro složitá pole. Přidání nového pole do komplexního typu nevyžaduje opětovné sestavení indexu, ale většina dalších úprav vyžaduje opětovné sestavení.

Strukturální aktualizace definice

Do komplexního pole můžete kdykoli přidat nová dílčí pole bez nutnosti opětovného sestavení indexu. Například přidání psčovacího kódu do nebo "Vybavení" je Address Rooms povolené, stejně jako přidání pole nejvyšší úrovně do indexu. Existující dokumenty mají hodnotu null pro nová pole, dokud tato pole explicitně nenaplníte aktualizací dat.

Všimněte si, že v rámci komplexního typu má každé dílčí pole typ a může mít atributy, stejně jako pole nejvyšší úrovně.

Aktualizace dat

Aktualizace existujících dokumentů v indexu akcí upload funguje stejně pro složitá a jednoduchá pole: všechna pole se nahradí. ( merge nebo mergeOrUpload při použití u existujícího dokumentu) ale ve všech polích nefunguje stejně. Konkrétně merge nepodporuje slučování prvků v kolekci. Toto omezení platí pro kolekce primitivních typů a složitých kolekcí. Pokud chcete aktualizovat kolekci, musíte načíst úplnou hodnotu kolekce, provést změny a pak zahrnout novou kolekci do požadavku rozhraní API indexu.

Hledání složitých polí v textových dotazech

Výrazy pro vyhledávání ve volném formátu fungují podle očekávání se složitými typy. Pokud se libovolné prohledávatelné pole nebo dílčí pole kdekoli v dokumentu shoduje, pak se samotný dokument shoduje.

Dotazy mají větší nuance, pokud máte více termínů a operátorů a některé termíny mají zadané názvy polí, co je to možné pomocí syntaxe Lucene. Tento dotaz se například pokusí shodovat se dvěma termíny: "Portland" a "OR" proti dvěma dílčím polím pole Adresa:

search=Address/City:Portland AND Address/State:OR

Podobné dotazy nesouvisejí s fulltextovým vyhledáváním, na rozdíl od filtrů. Ve filtrech jsou dotazy nad dílčími poli komplexní kolekce korelovány pomocí proměnných rozsahu v any nebo all. Výše uvedený dotaz Lucene vrátí dokumenty obsahující "Portland, Maine" a "Portland, Oregon", spolu s dalšími městy v Oregonu. K tomu dochází, protože každá klauzule se vztahuje na všechny hodnoty jeho pole v celém dokumentu, takže neexistuje žádný koncept "aktuálního vnořeného dokumentu". Další informace najdete v tématu Principy filtrů kolekce OData ve službě Azure AI Search.

Vyhledávání složitých polí v dotazech RAG

Model RAG předává výsledky hledání do modelu chatu pro generování umělé inteligence a konverzační vyhledávání. Ve výchozím nastavení jsou výsledky hledání předávané do LLM plochou sadou řádků. Pokud má ale index složité typy, může dotaz tato pole poskytnout, pokud nejprve převedete výsledky hledání na JSON a pak předáte JSON do LLM.

Částečný příklad znázorňuje techniku:

  • Uveďte požadovaná pole na příkazovém řádku nebo v dotazu.
  • Ujistěte se, že jsou pole prohledávatelná a zobrazitelná v indexu.
  • Výběr polí pro výsledky hledání
  • Formátování výsledků ve formátu JSON
  • Odeslání žádosti o dokončení chatu poskytovateli modelu
import json

# Query is the question being asked. It's sent to the search engine and the LLM.
query="Can you recommend a few hotels that offer complimentary breakfast? Tell me their description, address, tags, and the rate for one room they have which sleep 4 people."

# Set up the search results and the chat thread.
# Retrieve the selected fields from the search index related to the question.
selected_fields = ["HotelName","Description","Address","Rooms","Tags"]
search_results = search_client.search(
    search_text=query,
    top=5,
    select=selected_fields,
    query_type="semantic"
)
sources_filtered = [{field: result[field] for field in selected_fields} for result in search_results]
sources_formatted = "\n".join([json.dumps(source) for source in sources_filtered])

response = openai_client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": GROUNDED_PROMPT.format(query=query, sources=sources_formatted)
        }
    ],
    model=AZURE_DEPLOYMENT_MODEL
)

print(response.choices[0].message.content)

Kompletní příklad najdete v tématu Rychlý start: Generování vyhledávání (RAG) se zemněním dat ze služby Azure AI Search.

Výběr složitých polí

Parametr $select slouží k výběru polí vrácených ve výsledcích hledání. Chcete-li tento parametr použít k výběru konkrétních dílčích polí komplexního pole, zahrňte nadřazené pole a dílčí pole oddělené lomítkem (/).

$select=HotelName, Address/City, Rooms/BaseRate

Pole musí být v indexu označena jako Načístelná, pokud je chcete mít ve výsledcích hledání. V příkazu lze použít $select pouze pole označená jako Retrievable.

Filtrování, omezující vlastnost a řazení složitých polí

Stejnou syntaxi cesty OData, která se používá pro filtrování a vyhledávání v polích, lze také použít pro fasety, řazení a výběr polí v požadavku hledání. U složitých typů platí pravidla, která určují, která dílčí pole je možné označit jako řaditelná nebo omezující. Další informace o těchto pravidlech najdete v referenčních informacích k rozhraní API pro vytvoření indexu.

Fasetové dílčí pole

Jakékoli dílčí pole lze označit jako omezující, pokud není typu Edm.GeographyPoint nebo Collection(Edm.GeographyPoint).

Počty dokumentů vrácené ve výsledcích omezující vlastnosti se počítají pro nadřazený dokument (hotel), ne pro vnořené dokumenty v komplexní kolekci (místnosti). Předpokládejme například, že hotel má 20 pokojů typu "suite". Vzhledem k tomuto parametru facet=Rooms/Typeomezující vlastnosti je počet omezujících vlastností jeden pro hotel, nikoli 20 pro pokoje.

Řazení složitých polí

Operace řazení se vztahují na dokumenty (Hotely) a ne na vnořené dokumenty (Místnosti). Pokud máte složitou kolekci typů, například Místnosti, je důležité si uvědomit, že se vůbec nedá řadit podle místností. Ve skutečnosti nemůžete řadit podle žádné kolekce.

Operace řazení fungují, když pole mají jednu hodnotu na dokument, ať už jde o jednoduché pole, nebo dílčí pole v komplexním typu. Je například možné řadit, Address/City protože na hotel je jenom jedna adresa, takže $orderby=Address/City seřadí hotely podle města.

Filtrování komplexních polí

Ve výrazu filtru můžete odkazovat na dílčí pole komplexního pole. Stačí použít stejnou syntaxi cesty OData, která se používá k fasetování, řazení a výběru polí. Například následující filtr vrátí všechny hotely v Kanadě:

$filter=Address/Country eq 'Canada'

Pokud chcete filtrovat komplexní pole kolekce, můžete použít výraz lambda s operátory a all operátoryany. V takovém případě je proměnná rozsahu výrazu lambda objektem s dílčími poli. Na tato podpole můžete odkazovat pomocí standardní syntaxe cesty OData. Například následující filtr vrátí všechny hotely s alespoň jedním pokojem deluxe a všemi nesmokovacími místnostmi:

$filter=Rooms/any(room: room/Type eq 'Deluxe Room') and Rooms/all(room: not room/SmokingAllowed)

Stejně jako u jednoduchých polí nejvyšší úrovně je možné do filtrů zahrnout pouze jednoduchá dílčí pole komplexních polí, pokud mají filtrovatelný atribut nastavený na true definici indexu. Další informace najdete v referenčních informacích k rozhraní API pro vytvoření indexu.

Alternativní řešení pro limit komplexní kolekce

Vzpomeňte si, že Azure AI Search omezuje složité objekty v kolekci na 3 000 objektů na dokument. Překročení tohoto limitu způsobí následující zprávu:

A collection in your document exceeds the maximum elements across all complex collections limit. 
The document with key '1052' has '4303' objects in collections (JSON arrays). 
At most '3000' objects are allowed to be in collections across the entire document. 
Remove objects from collections and try indexing the document again."

Pokud potřebujete více než 3 000 položek, můžete k oddělovači hodnot použít svislé roury (|) nebo použít libovolnou formu oddělovače, zřetězení a jejich uložení jako řetězec s oddělovači. Počet řetězců uložených v poli není nijak omezený. Ukládání složitých hodnot jako řetězců obchází omezení komplexní kolekce.

Pro ilustraci předpokládejme, že máte "searchScopepole s více než 3 000 prvky:


"searchScope": [
  {
     "countryCode": "FRA",
     "productCode": 1234,
     "categoryCode": "C100" 
  },
  {
     "countryCode": "USA",
     "productCode": 1235,
     "categoryCode": "C200" 
  }
  . . .
]

Alternativní řešení pro ukládání hodnot jako řetězce s oddělovači může vypadat takto:

"searchScope": [
        "|FRA|1234|C100|",
        "|FRA|*|*|",
        "|*|1234|*|",
        "|*|*|C100|",
        "|FRA|*|C100|",
        "|*|1234|C100|"
]

Ukládání všech variant hledání v řetězci s oddělovači je užitečné ve scénářích hledání, ve kterých chcete hledat položky, které mají pouze "FRA" nebo "1234" nebo jinou kombinaci v rámci pole.

Tady je fragment kódu formátování filtru v jazyce C#, který převádí vstupy na prohledávatelné řetězce:

foreach (var filterItem in filterCombinations)
        {
            var formattedCondition = $"searchScope/any(s: s eq '{filterItem}')";
            combFilter.Append(combFilter.Length > 0 ? " or (" + formattedCondition + ")" : "(" + formattedCondition + ")");
        }

Následující seznam obsahuje vstupy a vyhledávací řetězce (výstupy) vedle sebe:

  • Pro kód okresu "FRA" a kód produktu "1234" je |FRA|1234|*|formátovaný výstup .

  • Pro kód produktu "1234" je |*|1234|*|formátovaný výstup .

  • Pro kód kategorie "C100" je |*|*|C100|formátovaný výstup .

Zástupný znak (*) zadejte pouze v případě, že implementujete alternativní řešení pole řetězců. Jinak pokud používáte komplexní typ, může filtr vypadat jako v tomto příkladu:

var countryFilter = $"searchScope/any(ss: search.in(countryCode ,'FRA'))";
var catgFilter = $"searchScope/any(ss: search.in(categoryCode ,'C100'))";
var combinedCountryCategoryFilter = "(" + countryFilter + " and " + catgFilter + ")";

Pokud implementujete alternativní řešení, nezapomeňte provést testování rozsahu.

Další kroky

Vyzkoušejte sadu dat Hotels v průvodci importem dat. Pro přístup k datům potřebujete informace o připojení ke službě Azure Cosmos DB uvedené v souboru readme.

Po ruce s informacemi je prvním krokem v průvodci vytvoření nového zdroje dat Azure Cosmos DB. Dále v průvodci, když se dostanete na cílovou indexovou stránku, uvidíte index se složitými typy. Vytvořte a načtěte tento index a potom spusťte dotazy, abyste porozuměli nové struktuře.