Delen via


Complexe gegevenstypen modelleren in Azure AI Search

Externe gegevenssets die worden gebruikt om een Azure AI Search-index te vullen, kunnen in veel vormen worden geleverd. Soms bevatten ze hiërarchische of geneste substructuren. Voorbeelden kunnen meerdere adressen bevatten voor één klant, meerdere kleuren en maten voor één product, meerdere auteurs van één boek, enzovoort. In modelleringstermen ziet u mogelijk dat deze structuren complexe, samengestelde, samengestelde of geaggregeerde gegevenstypen worden genoemd. De term die Azure AI Search voor dit concept gebruikt, is complex. In Azure AI Search worden complexe typen gemodelleerd met behulp van complexe velden. Een complex veld is een veld dat onderliggende elementen (subvelden) bevat die van elk gegevenstype kunnen zijn, inclusief andere complexe typen. Dit werkt op een vergelijkbare manier als gestructureerde gegevenstypen in een programmeertaal.

Complexe velden vertegenwoordigen één object in het document of een matrix met objecten, afhankelijk van het gegevenstype. Velden van het type Edm.ComplexType vertegenwoordigen afzonderlijke objecten, terwijl velden van het type Collection(Edm.ComplexType) matrices van objecten vertegenwoordigen.

Azure AI Search biedt systeemeigen ondersteuning voor complexe typen en verzamelingen. Met deze typen kunt u vrijwel elke JSON-structuur modelleren in een Azure AI Search-index. In eerdere versies van Azure AI Search-API's kunnen alleen platgemaakte rijensets worden geïmporteerd. In de nieuwste versie kan uw index nu nauwer overeenkomen met brongegevens. Met andere woorden, als uw brongegevens complexe typen hebben, kan uw index ook complexe typen hebben.

We raden u aan om aan de slag te gaan met de gegevensset Hotels, die u kunt laden in de wizard Gegevens importeren in Azure Portal. De wizard detecteert complexe typen in de bron en stelt een indexschema voor op basis van de gedetecteerde structuren.

Notitie

Ondersteuning voor complexe typen werd algemeen beschikbaar vanaf api-version=2019-05-06.

Als uw zoekoplossing is gebouwd op eerdere tijdelijke oplossingen voor platgemaakte gegevenssets in een verzameling, moet u uw index wijzigen in complexe typen zoals ondersteund in de nieuwste API-versie. Zie Upgrade uitvoeren naar de nieuwste REST API-versie of upgraden naar de nieuwste .NET SDK-versie voor meer informatie over het upgraden van API-versies.

Voorbeeld van een complexe structuur

Het volgende JSON-document bestaat uit eenvoudige velden en complexe velden. Complexe velden, zoals Address en Rooms, hebben subvelden. Address heeft één set waarden voor deze subvelden, omdat het één object in het document is. Er zijn daarentegen Rooms meerdere sets waarden voor de subvelden, één voor elk object in de verzameling.

{
  "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,
    }
    . . .
  ]
}

Complexe velden maken

Net als bij elke indexdefinitie kunt u de Azure-portal, REST API of .NET SDK gebruiken om een schema te maken dat complexe typen bevat.

Andere Azure SDK's bieden voorbeelden in Python, Java en JavaScript.

  1. Meld u aan bij het Azure-portaal.

  2. Selecteer op de pagina Overzicht van de zoekservice het tabblad Indexen.

  3. Open een bestaande index of maak een nieuwe index.

  4. Selecteer het tabblad Velden en selecteer vervolgens Veld toevoegen. Er wordt een leeg veld toegevoegd. Als u met een bestaande verzameling velden werkt, schuift u omlaag om het veld in te stellen.

  5. Geef het veld een naam en stel het type in op of Edm.ComplexType Collection(Edm.ComplexType).

  6. Selecteer het beletselteken uiterst rechts en selecteer vervolgens Veld toevoegen of Subveld toevoegen en wijs vervolgens kenmerken toe.

Limieten voor complexe verzamelingen

Tijdens het indexeren kunt u maximaal 3000 elementen in alle complexe verzamelingen binnen één document hebben. Een element van een complexe verzameling is lid van die verzameling. Voor kamers (de enige complexe verzameling in het voorbeeld van het hotel) is elke kamer een element. In het bovenstaande voorbeeld, als het "Stay-Kay City Hotel" 500 kamers had, zou het hoteldocument 500 kamerelementen hebben. Voor geneste complexe verzamelingen wordt elk genest element ook geteld, naast het buitenste (bovenliggende) element.

Deze limiet geldt alleen voor complexe verzamelingen en niet voor complexe typen (zoals Adres) of tekenreeksverzamelingen (zoals Tags).

Complexe velden bijwerken

Alle herindexeringsregels die van toepassing zijn op velden in het algemeen, zijn nog steeds van toepassing op complexe velden. Voor het toevoegen van een nieuw veld aan een complex type is geen herbouw van een index vereist, maar voor de meeste andere wijzigingen is een herbouw vereist.

Structurele updates voor de definitie

U kunt op elk gewenst moment nieuwe subvelden toevoegen aan een complex veld zonder dat u een index opnieuw hoeft op te bouwen. Het toevoegen van 'ZipCode' aan Address of 'Voorzieningen' Rooms is bijvoorbeeld toegestaan, net zoals het toevoegen van een veld op het hoogste niveau aan een index. Bestaande documenten hebben een null-waarde voor nieuwe velden totdat u deze velden expliciet vult door uw gegevens bij te werken.

U ziet dat elk subveld binnen een complex type een type heeft en kenmerken kan hebben, net zoals velden op het hoogste niveau doen

Gegevensupdates

Het bijwerken van bestaande documenten in een index met de upload actie werkt op dezelfde manier voor complexe en eenvoudige velden: alle velden worden vervangen. merge (of mergeOrUpload wanneer dit wordt toegepast op een bestaand document) werkt echter niet hetzelfde voor alle velden. merge Met name biedt geen ondersteuning voor het samenvoegen van elementen binnen een verzameling. Deze beperking bestaat voor verzamelingen primitieve typen en complexe verzamelingen. Als u een verzameling wilt bijwerken, moet u de volledige verzamelingswaarde ophalen, wijzigingen aanbrengen en vervolgens de nieuwe verzameling opnemen in de index-API-aanvraag.

Complexe velden zoeken in tekstquery's

Vrije zoekexpressies werken zoals verwacht met complexe typen. Als een doorzoekbaar veld of subveld ergens in een document overeenkomt, is het document zelf een overeenkomst.

Query's worden genuanceerder wanneer u meerdere termen en operators hebt en sommige termen veldnamen hebben opgegeven, zoals mogelijk met de Lucene-syntaxis. Met deze query worden bijvoorbeeld twee termen, Portland en OR, vergeleken met twee subvelden van het veld Adres:

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

Query's zoals deze zijn niet gerelateerd aan zoekopdrachten in volledige tekst, in tegenstelling tot filters. In filters worden query's over subvelden van een complexe verzameling gecorreleerd met bereikvariabelen in any of all. De Lucene-query hierboven retourneert documenten met zowel Portland, Maine als Portland, Oregon, samen met andere steden in Oregon. Dit gebeurt omdat elke component van toepassing is op alle waarden van het veld in het hele document, dus er is geen concept van een 'huidig subdocument'. Zie Inzicht in OData-verzamelingsfilters in Azure AI Search voor meer informatie hierover.

Complexe velden zoeken in RAG-query's

Een RAG-patroon geeft zoekresultaten door aan een chatmodel voor generatieve AI en conversationele zoekopdrachten. Zoekresultaten die aan een LLM worden doorgegeven, zijn standaard een afgevlakte rijenset. Als uw index echter complexe typen heeft, kan uw query deze velden opgeven als u de zoekresultaten eerst converteert naar JSON en de JSON vervolgens doorgeeft aan de LLM.

Een gedeeltelijk voorbeeld illustreert de techniek:

  • Geef de gewenste velden op in de prompt of in de query
  • Zorg ervoor dat de velden doorzoekbaar zijn en kunnen worden opgehaald in de index
  • Selecteer de velden voor de zoekresultaten
  • De resultaten opmaken als JSON
  • De aanvraag voor chatvoltooiing verzenden naar de modelprovider
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)

Zie quickstart: Generatieve zoekopdrachten (RAG) met grondgegevens uit Azure AI Search voor het end-to-end-voorbeeld.

Complexe velden selecteren

De $select parameter wordt gebruikt om te kiezen welke velden worden geretourneerd in zoekresultaten. Als u deze parameter wilt gebruiken om specifieke subvelden van een complex veld te selecteren, neemt u het bovenliggende veld en subveld op, gescheiden door een slash (/).

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

Velden moeten worden gemarkeerd als Ophaalbaar in de index als u ze in zoekresultaten wilt weergeven. Alleen velden die als ophaalbaar zijn gemarkeerd, kunnen in een $select instructie worden gebruikt.

Complexe velden filteren, facet en sorteren

Dezelfde OData-padsyntaxis die wordt gebruikt voor filteren en zoeken in velden, kan ook worden gebruikt voor facetten, sorteren en selecteren van velden in een zoekaanvraag. Voor complexe typen gelden regels die bepalen welke subvelden kunnen worden gemarkeerd als sorteerbaar of facetabel. Zie de naslaginformatie over de index-API maken voor meer informatie over deze regels.

Subvelden facet

Elk subveld kan worden gemarkeerd als facet, tenzij het van het type Edm.GeographyPoint is of Collection(Edm.GeographyPoint).

De aantallen documenten die in de facetresultaten worden geretourneerd, worden berekend voor het bovenliggende document (een hotel), niet de subdocumenten in een complexe verzameling (ruimten). Stel dat een hotel 20 kamers van het type 'suite' heeft. Gezien deze facetparameter facet=Rooms/Typeis het aantal facetten één voor het hotel, niet 20 voor de kamers.

Complexe velden sorteren

Sorteerbewerkingen zijn van toepassing op documenten (Hotels) en niet op subdocumenten (Ruimten). Wanneer u een complexe verzameling typen hebt, zoals Ruimten, is het belangrijk om te beseffen dat u helemaal niet op ruimten kunt sorteren. In feite kunt u niet sorteren op een verzameling.

Sorteerbewerkingen werken wanneer velden één waarde per document hebben, ongeacht of het veld een eenvoudig veld is of een subveld in een complex type. Het is bijvoorbeeld Address/City toegestaan om te sorteren omdat er slechts één adres per hotel is, dus $orderby=Address/City sorteert hotels op stad.

Filteren op complexe velden

U kunt verwijzen naar subvelden van een complex veld in een filterexpressie. Gebruik gewoon dezelfde OData-padsyntaxis die wordt gebruikt voor het faceten, sorteren en selecteren van velden. Met het volgende filter worden bijvoorbeeld alle hotels in Canada geretourneerd:

$filter=Address/Country eq 'Canada'

Als u wilt filteren op een complex verzamelingsveld, kunt u een lambda-expressie met de any en all operators gebruiken. In dat geval is de bereikvariabele van de lambda-expressie een object met subvelden. U kunt naar deze subvelden verwijzen met de standaard syntaxis van het OData-pad. Het volgende filter retourneert bijvoorbeeld alle hotels met ten minste één deluxe kamer en alle niet-mokkende kamers:

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

Net als bij eenvoudige velden op het hoogste niveau kunnen eenvoudige subvelden van complexe velden alleen worden opgenomen in filters als ze het filterbare kenmerk hebben ingesteld true in de indexdefinitie. Zie de naslaginformatie voor de Index-API maken voor meer informatie.

Tijdelijke oplossing voor de limiet voor complexe verzamelingen

Zoals u weet, beperkt Azure AI Search complexe objecten in een verzameling tot 3000 objecten per document. Als u deze limiet overschrijdt, wordt het volgende bericht weergegeven:

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

Als u meer dan 3000 items nodig hebt, kunt u () doorsturen (|) of een willekeurige vorm van scheidingsteken gebruiken om de waarden te scheiden, deze samen te voegen en op te slaan als een gescheiden tekenreeks. Er is geen beperking voor het aantal tekenreeksen dat is opgeslagen in een matrix. Als u complexe waarden opslaat als tekenreeksen, wordt de beperking van de complexe verzameling overgeslagen.

Ter illustratie gaat u ervan uit dat u een "searchScope"-matrix hebt met meer dan 3000 elementen:


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

De tijdelijke oplossing voor het opslaan van de waarden als een gescheiden tekenreeks kan er als volgt uitzien:

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

Het opslaan van alle zoekvarianten in de gescheiden tekenreeks is handig in zoekscenario's waarin u wilt zoeken naar items met alleen 'FRA' of '1234' of een andere combinatie in de matrix.

Hier volgt een filteropmaakfragment in C# waarmee invoer wordt geconverteerd naar doorzoekbare tekenreeksen:

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

De volgende lijst bevat invoer- en zoekreeksen (uitvoer) naast elkaar:

  • Voor "FRA" county code en de "1234" productcode, is de opgemaakte uitvoer |FRA|1234|*|.

  • Voor '1234'-productcode is |*|1234|*|de opgemaakte uitvoer .

  • Voor de categoriecode C100 is |*|*|C100|de opgemaakte uitvoer .

Geef alleen het jokerteken (*) op als u de tijdelijke oplossing voor de tekenreeksmatrix implementeert. Als u een complex type gebruikt, kan het filter er anders uitzien als in dit voorbeeld:

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

Als u de tijdelijke oplossing implementeert, moet u de test uitgebreid testen.

Volgende stappen

Probeer de gegevensset Hotels in de wizard Gegevens importeren. U hebt de Azure Cosmos DB-verbindingsgegevens in het leesmij-bestand nodig om toegang te krijgen tot de gegevens.

De eerste stap in de wizard bestaat uit het maken van een nieuwe Azure Cosmos DB-gegevensbron. Verderop in de wizard ziet u, wanneer u bij de doelindexpagina bent, een index met complexe typen. Maak en laad deze index en voer vervolgens query's uit om inzicht te hebben in de nieuwe structuur.