Modelowanie złożonych typów danych w usłudze Azure AI Search
Zewnętrzne zestawy danych używane do wypełniania indeksu usługi Azure AI Search mogą znajdować się w wielu kształtach. Czasami obejmują hierarchiczną lub zagnieżdżonych podstruktury. Przykłady mogą obejmować wiele adresów dla jednego klienta, wiele kolorów i rozmiarów pojedynczego produktu, wielu autorów pojedynczej książki itd. W kategoriach modelowania można zobaczyć te struktury nazywane złożonymi, złożonymi, złożonymi lub agregowanymi typami danych. Termin Azure AI Search używa dla tej koncepcji jest typem złożonym. W usłudze Azure AI Search złożone typy są modelowane przy użyciu złożonych pól. Pole złożone to pole zawierające pola podrzędne (podpola), które mogą być dowolnym typem danych, w tym innymi innymi typami złożonymi. Działa to podobnie jak typy danych strukturalnych w języku programowania.
Pola złożone reprezentują pojedynczy obiekt w dokumencie lub tablicę obiektów w zależności od typu danych. Pola typu Edm.ComplexType
reprezentują pojedyncze obiekty, a pola typu Collection(Edm.ComplexType)
reprezentują tablice obiektów.
Usługa Azure AI Search natywnie obsługuje złożone typy i kolekcje. Te typy umożliwiają modelowanie niemal dowolnej struktury JSON w indeksie usługi Azure AI Search. W poprzednich wersjach interfejsów API usługi Azure AI Search można zaimportować tylko spłaszczone zestawy wierszy. W najnowszej wersji indeks może teraz ściślej odpowiadać danym źródłowym. Innymi słowy, jeśli dane źródłowe mają złożone typy, indeks może mieć również złożone typy.
Aby rozpocząć, zalecamy zestaw danych Hotele, który można załadować w kreatorze importu danych w witrynie Azure Portal. Kreator wykrywa złożone typy w źródle i sugeruje schemat indeksu na podstawie wykrytych struktur.
Uwaga
Obsługa typów złożonych stała się ogólnie dostępna, począwszy od .api-version=2019-05-06
Jeśli rozwiązanie wyszukiwania jest oparte na wcześniejszych obejściach spłaszczonego zestawu danych w kolekcji, należy zmienić indeks tak, aby uwzględniał złożone typy tak, jak są obsługiwane w najnowszej wersji interfejsu API. Aby uzyskać więcej informacji na temat uaktualniania wersji interfejsu API, zobacz Uaktualnianie do najnowszej wersji interfejsu API REST lub Uaktualnianie do najnowszej wersji zestawu .NET SDK.
Przykład złożonej struktury
Poniższy dokument JSON składa się z prostych pól i pól złożonych. Pola złożone, takie jak Address
i Rooms
, mają pola podrzędne. Address
ma jeden zestaw wartości dla tych pól podrzędnych, ponieważ jest to pojedynczy obiekt w dokumencie. Rooms
Natomiast ma wiele zestawów wartości dla swoich pól podrzędnych, po jednym dla każdego obiektu w kolekcji.
{
"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,
}
. . .
]
}
Tworzenie pól złożonych
Podobnie jak w przypadku dowolnej definicji indeksu, możesz użyć witryny Azure Portal, interfejsu API REST lub zestawu SDK platformy .NET, aby utworzyć schemat zawierający złożone typy.
Inne zestawy SDK platformy Azure udostępniają przykłady w językach Python, Java i JavaScript.
Zaloguj się w witrynie Azure Portal.
Na stronie Przegląd usługi wyszukiwania wybierz kartę Indeksy.
Otwórz istniejący indeks lub utwórz nowy indeks.
Wybierz kartę Pola , a następnie wybierz pozycję Dodaj pole. Zostanie dodane puste pole. Jeśli pracujesz z istniejącą kolekcją pól, przewiń w dół, aby skonfigurować pole.
Nadaj polu nazwę i ustaw typ na
Edm.ComplexType
wartość lubCollection(Edm.ComplexType)
.Wybierz wielokropek po prawej stronie, a następnie wybierz pozycję Dodaj pole lub Dodaj pole podrzędne, a następnie przypisz atrybuty.
Złożone limity kolekcji
Podczas indeksowania można mieć maksymalnie 3000 elementów we wszystkich złożonych kolekcjach w ramach jednego dokumentu. Element kolekcji złożonej jest elementem członkowskim tej kolekcji. W przypadku pokoi (jedyna złożona kolekcja w przykładzie hotelowym) każdy pokój jest elementem. W powyższym przykładzie, jeśli "Stay-Kay City Hotel" miał 500 pokoi, dokument hotelowy będzie miał 500 elementów pokoju. W przypadku zagnieżdżonych kolekcji każdy zagnieżdżony element jest również zliczany, oprócz elementu zewnętrznego (nadrzędnego).
Ten limit dotyczy tylko złożonych kolekcji, a nie złożonych typów (takich jak Adres) lub kolekcji ciągów (takich jak Tagi).
Aktualizowanie pól złożonych
Wszystkie reguły ponownego indeksowania, które mają zastosowanie do pól w ogóle, nadal mają zastosowanie do pól złożonych. Dodanie nowego pola do typu złożonego nie wymaga ponownego kompilowania indeksu, ale większość innych modyfikacji wymaga ponownego kompilowania.
Aktualizacje strukturalne definicji
Nowe pola podrzędne można dodawać do złożonego pola w dowolnym momencie bez konieczności ponownego kompilowania indeksu. Na przykład dodanie wartości "ZipCode" do Address
lub "Udogodnienia" Rooms
jest dozwolone, podobnie jak dodanie pola najwyższego poziomu do indeksu. Istniejące dokumenty mają wartość null dla nowych pól do momentu jawnego wypełnienia tych pól przez zaktualizowanie danych.
Zwróć uwagę, że w obrębie typu złożonego każde pole podrzędne ma typ i może mieć atrybuty, podobnie jak pola najwyższego poziomu
Aktualizacje danych
Aktualizowanie istniejących dokumentów w indeksie za pomocą upload
akcji działa tak samo w przypadku złożonych i prostych pól: wszystkie pola są zastępowane. merge
Jednak (lub mergeOrUpload
w przypadku zastosowania do istniejącego dokumentu) nie działa tak samo we wszystkich polach. W szczególności merge
nie obsługuje scalania elementów w kolekcji. To ograniczenie istnieje w przypadku kolekcji typów pierwotnych i kolekcji złożonych. Aby zaktualizować kolekcję, musisz pobrać pełną wartość kolekcji, wprowadzić zmiany, a następnie uwzględnić nową kolekcję w żądaniu interfejsu API indeksowania.
Wyszukiwanie złożonych pól w zapytaniach tekstowych
Wyrażenia wyszukiwania w dowolnej formie działają zgodnie z oczekiwaniami w przypadku typów złożonych. Jeśli dowolne pole z możliwością wyszukiwania lub pole podrzędne w dowolnym miejscu w dokumencie jest zgodne, sam dokument jest zgodny.
Zapytania są bardziej zniuansowane, gdy masz wiele terminów i operatorów, a niektóre terminy mają określone nazwy pól, jak to możliwe w składni Lucene. Na przykład to zapytanie próbuje dopasować dwa terminy: "Portland" i "OR" względem dwóch pól podrzędnych pola adresu:
search=Address/City:Portland AND Address/State:OR
Zapytania takie jak te są niekorzystne w przypadku wyszukiwania pełnotekstowego, w przeciwieństwie do filtrów. W filtrach zapytania dotyczące podpole złożonej kolekcji są skorelowane przy użyciu zmiennych zakresu w elem any
lub all
. Powyższe zapytanie Lucene zwraca dokumenty zawierające zarówno "Portland, Maine" i "Portland, Oregon" wraz z innymi miastami w Oregonie. Dzieje się tak, ponieważ każda klauzula ma zastosowanie do wszystkich wartości pola w całym dokumencie, więc nie ma pojęcia "bieżącego dokumentu podrzędnego". Aby uzyskać więcej informacji na ten temat, zobacz Understanding OData collection filters in Azure AI Search (Omówienie filtrów kolekcji OData w usłudze Azure AI Search).
Wyszukiwanie złożonych pól w zapytaniach RAG
Wzorzec RAG przekazuje wyniki wyszukiwania do modelu czatu na potrzeby generowania sztucznej inteligencji i wyszukiwania konwersacyjnego. Domyślnie wyniki wyszukiwania przekazywane do usługi LLM to spłaszczone zestawy wierszy. Jeśli jednak indeks zawiera złożone typy, zapytanie może podać te pola, jeśli najpierw przekonwertujesz wyniki wyszukiwania na format JSON, a następnie przekażesz kod JSON do modułu LLM.
Przykład częściowy ilustruje technikę:
- Wskaż żądane pola w wierszu polecenia lub w zapytaniu
- Upewnij się, że pola można przeszukiwać i pobierać w indeksie
- Wybieranie pól dla wyników wyszukiwania
- Formatowanie wyników w formacie JSON
- Wyślij żądanie ukończenia czatu do dostawcy 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)
Aby zapoznać się z przykładem kompleksowego, zobacz Szybki start: wyszukiwanie generowania (RAG) z danymi uziemienia z usługi Azure AI Search.
Wybieranie pól złożonych
Parametr służy do wybierania $select
pól zwracanych w wynikach wyszukiwania. Aby użyć tego parametru, aby wybrać określone pola podrzędne pola złożonego, uwzględnij pole nadrzędne i pole podrzędne oddzielone ukośnikiem (/
).
$select=HotelName, Address/City, Rooms/BaseRate
Pola muszą być oznaczone jako Możliwe do pobrania w indeksie, jeśli mają być wyświetlane w wynikach wyszukiwania. W instrukcji $select
można używać tylko pól oznaczonych jako Możliwe do pobrania.
Filtrowanie, tworzenie aspektów i sortowanie złożonych pól
Ta sama składnia ścieżki OData używana do filtrowania i wyszukiwania pól może być również używana do tworzenia aspektów, sortowania i wybierania pól w żądaniu wyszukiwania. W przypadku typów złożonych reguły określają, które pola podrzędne mogą być oznaczone jako sortowalne lub aspektowe. Aby uzyskać więcej informacji na temat tych reguł, zobacz Dokumentację interfejsu API tworzenia indeksu.
Pola podrzędne tworzenia aspektów
Każde pole podrzędne można oznaczyć jako element aspektowy, chyba że ma Edm.GeographyPoint
typ lub Collection(Edm.GeographyPoint)
.
Liczby dokumentów zwracanych w wynikach aspektu są obliczane dla dokumentu nadrzędnego (hotel), a nie dokumentów podrzędnych w złożonej kolekcji (pokoje). Załóżmy na przykład, że hotel ma 20 pokoi typu "suite". Biorąc pod uwagę ten parametr facet=Rooms/Type
aspektu , liczba aspektów jest jedną dla hotelu, a nie 20 dla pokoi.
Sortowanie złożonych pól
Operacje sortowania dotyczą dokumentów (hoteli), a nie dokumentów podrzędnych (Pokoje). Jeśli masz złożoną kolekcję typów, taką jak Pokoje, ważne jest, aby pamiętać, że w ogóle nie można sortować w pokojach. W rzeczywistości nie można sortować w żadnej kolekcji.
Operacje sortowania działają, gdy pola mają pojedynczą wartość na dokument, niezależnie od tego, czy pole jest prostym polem, czy polem podrzędnym w typie złożonym. Na przykład może być sortowane, Address/City
ponieważ istnieje tylko jeden adres na hotel, więc $orderby=Address/City
sortuje hotele według miasta.
Filtrowanie w polach złożonych
W wyrażeniu filtru można odwoływać się do pól podrzędnych pola złożonego pola. Wystarczy użyć tej samej składni ścieżki OData, która jest używana do tworzenia aspektów, sortowania i wybierania pól. Na przykład następujący filtr zwraca wszystkie hotele w Kanadzie:
$filter=Address/Country eq 'Canada'
Aby filtrować według złożonego pola kolekcji, można użyć wyrażenia lambda z operatoramiany
i .all
W takim przypadku zmienna zakresu wyrażenia lambda jest obiektem z polami podrzędnymi. Można odwoływać się do tych pól podrzędnych przy użyciu standardowej składni ścieżki OData. Na przykład następujący filtr zwraca wszystkie hotele z co najmniej jednym pokojem typu deluxe i wszystkimi pokojami niesmokingowymi:
$filter=Rooms/any(room: room/Type eq 'Deluxe Room') and Rooms/all(room: not room/SmokingAllowed)
Podobnie jak w przypadku prostych pól najwyższego poziomu, proste pola podrzędne złożonych pól mogą być uwzględniane tylko w filtrach, jeśli mają atrybut filtrowalny ustawiony na true
wartość w definicji indeksu. Aby uzyskać więcej informacji, zobacz Dokumentację interfejsu API tworzenia indeksu.
Obejście złożonego limitu kolekcji
Pamiętaj, że usługa Azure AI Search ogranicza złożone obiekty w kolekcji do 3000 obiektów na dokument. Przekroczenie tego limitu powoduje wyświetlenie następującego komunikatu:
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."
Jeśli potrzebujesz więcej niż 3000 elementów, możesz przesłać potok (|
) lub użyć dowolnej formy ogranicznika, aby rozdzielić wartości, połączyć je i przechowywać je jako rozdzielany ciąg. Nie ma ograniczenia liczby ciągów przechowywanych w tablicy. Przechowywanie złożonych wartości jako ciągów pomija złożone ograniczenie kolekcji.
Aby zilustrować, załóżmy, że masz tablicę "searchScope
z ponad 3000 elementami:
"searchScope": [
{
"countryCode": "FRA",
"productCode": 1234,
"categoryCode": "C100"
},
{
"countryCode": "USA",
"productCode": 1235,
"categoryCode": "C200"
}
. . .
]
Obejście dotyczące przechowywania wartości jako ciągu rozdzielanego może wyglądać następująco:
"searchScope": [
"|FRA|1234|C100|",
"|FRA|*|*|",
"|*|1234|*|",
"|*|*|C100|",
"|FRA|*|C100|",
"|*|1234|C100|"
]
Przechowywanie wszystkich wariantów wyszukiwania w rozdzielanym ciągu jest przydatne w scenariuszach wyszukiwania, w których chcesz wyszukać elementy, które mają tylko wartość "FRA" lub "1234" lub inną kombinację w tablicy.
Oto fragment kodu formatowania filtru w języku C#, który konwertuje dane wejściowe na ciągi z możliwością wyszukiwania:
foreach (var filterItem in filterCombinations)
{
var formattedCondition = $"searchScope/any(s: s eq '{filterItem}')";
combFilter.Append(combFilter.Length > 0 ? " or (" + formattedCondition + ")" : "(" + formattedCondition + ")");
}
Poniższa lista zawiera dane wejściowe i ciągi wyszukiwania (dane wyjściowe) obok siebie:
W przypadku kodu powiatu "FRA" i kodu produktu "1234" sformatowane dane wyjściowe to
|FRA|1234|*|
.W przypadku kodu produktu "1234" sformatowane dane wyjściowe to
|*|1234|*|
.W przypadku kodu kategorii "C100" sformatowane dane wyjściowe to
|*|*|C100|
.
Podaj symbol wieloznaczny (*
), jeśli implementujesz obejście tablicy ciągów. W przeciwnym razie, jeśli używasz typu złożonego, filtr może wyglądać następująco:
var countryFilter = $"searchScope/any(ss: search.in(countryCode ,'FRA'))";
var catgFilter = $"searchScope/any(ss: search.in(categoryCode ,'C100'))";
var combinedCountryCategoryFilter = "(" + countryFilter + " and " + catgFilter + ")";
Jeśli zaimplementujesz obejście, pamiętaj, aby przetestować zakres.
Następne kroki
Wypróbuj zestaw danych Hotels w kreatorze Importuj dane. Aby uzyskać dostęp do danych, potrzebne są informacje o połączeniu usługi Azure Cosmos DB podane w pliku readme.
Dzięki tym informacjom pierwszym krokiem kreatora jest utworzenie nowego źródła danych usługi Azure Cosmos DB. Dalej w kreatorze, gdy dotrzesz do docelowej strony indeksu, zobaczysz indeks z typami złożonymi. Utwórz i załaduj ten indeks, a następnie wykonaj zapytania, aby zrozumieć nową strukturę.