Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Note
Wyszukiwanie AI platformy Azure jest dostępna za pośrednictwem portalu Azure, interfejsów API REST i Azure SDKs. Jest także podstawą Foundry IQ — zarządzanej warstwy wiedzy, która przekształca treści przedsiębiorstwa w bazy wiedzy wielokrotnego użytku z uwzględnieniem uprawnień dla agentów w portalu Microsoft Foundry.
W poprzednim kroku wdrożono witrynę internetową z włączoną obsługą wyszukiwania w celu Azure Container Apps. W tym artykule przedstawiono podstawowe kroki, które umożliwiają nawiązanie integracji wyszukiwania. Pomyśl o tym jako ściągawce na potrzeby integracji wyszukiwania z aplikacją internetową.
Azure SDK Azure. Search.Documents
Interfejs API używa Azure SDK dla Wyszukiwanie AI platformy Azure:
- NuGet: Azure. Search.Documents
- Dokumentacja referencyjna: Biblioteka klienta
Interfejs API uwierzytelnia się za pośrednictwem zestawu SDK do opartego na chmurze interfejsu API Wyszukiwanie AI platformy Azure przy użyciu nazwy usługi wyszukiwania i nazwy indeksu. W Azure Container Apps środowisko kontenera udostępnia wartości konfiguracji. Tożsamość zarządzana to domyślna ścieżka poświadczeń.
Uwierzytelnianie tożsamości zarządzanej
Każda funkcja Azure w interfejsie API tworzy jej SearchClient za pośrednictwem klasy udostępnionejSearchClientFactory, więc każda funkcja uwierzytelnia się w ten sam sposób. Domyślnie fabryka kompiluje element DefaultAzureCredential i używa go do żądania tokenów dla Wyszukiwanie AI platformy Azure. W Azure Container Apps DefaultAzureCredential jest rozpoznawana tożsamość zarządzana przypisana do aplikacji kontenera.
Poniższa metoda tworzy SearchClientFactory.cs to poświadczenie. Gdy aplikacja kontenera ma tożsamość zarządzaną przypisaną przez użytkownika, identyfikator klienta ze AZURE_CLIENT_ID zmiennej środowiskowej jest przekazywany do DefaultAzureCredentialOptions , dlatego pozyskiwanie tokenu nie jest niejednoznaczne.
private static DefaultAzureCredential CreateManagedIdentityCredential()
{
var options = new DefaultAzureCredentialOptions();
if (!string.IsNullOrWhiteSpace(ManagedIdentityClientId))
{
options.ManagedIdentityClientId = ManagedIdentityClientId;
}
return new DefaultAzureCredential(options);
}
Infrastruktura Bicep przypisuje tożsamości zarządzanej dostęp do płaszczyzny danych Wyszukiwanie AI platformy Azure podczas .azd up To przypisanie roli umożliwia interfejsowi API wykonywanie zapytań względem indeksu good-books bez przechowywania klucza zapytania w środowisku kontenera.
Lokalne a wdrożone rozpoznawanie poświadczeń
Lokalnie, jeśli AZURE_CLIENT_ID jest niezastawiony, DefaultAzureCredential powraca do standardowego łańcucha poświadczeń i rozpoznaje poświadczenia zalogowanego dewelopera, takie jak Azure CLI lub konto Visual Studio Code użyte do zalogowania. Po wdrożeniu w Azure Container Apps infrastruktura Bicep ustawia AZURE_CLIENT_ID identyfikator klienta tożsamości zarządzanej przypisanej przez użytkownika, dlatego DefaultAzureCredential jest przeznaczona dla tej tożsamości zamiast rozpoznawać niejednoznacznie między wieloma tożsamościami, które może uwidocznić host.
Aby zamiast tego użyć kluczy interfejsu API, ustaw wartość USE_KEYLESS_AUTH na false przed wdrożeniem:
azd env set USE_KEYLESS_AUTH false
azd up
Użyj uwierzytelniania klucza tylko wtedy, gdy środowisko tego wymaga.
Ustawienia programowania lokalnego
W przypadku programowania lokalnego przykładowy sample.local.settings.json plik pokazuje wartości oczekiwane przez interfejs API. Użyj ustawień lokalnych tylko do programowania. W Azure Container Apps konfiguracja wdrożenia zapewnia równoważne wartości środowiska kontenera.
| Setting | Purpose | Wymagane, gdy |
|---|---|---|
SearchServiceName |
Nazwa usługi Wyszukiwanie AI platformy Azure. Łączy się z elementem .search.windows.net w celu utworzenia identyfikatora URI punktu końcowego usługi. |
Zawsze |
SearchIndexName |
Nazwa indeksu wyszukiwania do kwerendy. Wartość domyślna to good-books , jeśli nie jest ustawiona. |
Optional |
SEARCH_USE_KEY_AUTH |
Wartość domyślna to false, używa tożsamości zarządzanej. Ustaw wartość na wartość , aby true użyć klucza interfejsu API zamiast tożsamości zarządzanej. |
Opcjonalne uwierzytelnianie za pomocą klucza |
SearchApiKey |
Klucz administratora Wyszukiwanie AI platformy Azure. | Wymagane, gdy SEARCH_USE_KEY_AUTH jest true |
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"SearchServiceName": "",
"SearchIndexName": "good-books"
},
"Host": {
"CORS": "*"
}
}
Funkcja: wyszukiwanie w wykazie
Interfejs API wyszukiwania przyjmuje termin wyszukiwania i wyszukuje je w dokumentach w indeksie wyszukiwania, zwracając listę dopasowań. Za pośrednictwem interfejsu API Sugerowanie ciągi częściowe są wysyłane do wyszukiwarki jako typ użytkownika. Interfejs API sugeruje terminy wyszukiwania, takie jak tytuły książek i autorzy, na podstawie dokumentów w indeksie wyszukiwania i zwracają niewielką listę dopasowań.
Funkcja Azure pobiera informacje o konfiguracji wyszukiwania ze środowiska kontenera, tworzy klienta Wyszukiwanie AI platformy Azure i spełnia zapytanie.
Sugestor wyszukiwaniasg, jest zdefiniowany w pliku schematu używanym podczas importu zbiorczego.
using Azure;
using Azure.Core.Serialization;
using Azure.Identity;
using Azure.Search.Documents;
using Azure.Search.Documents.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;
using WebSearch.Models;
using SearchFilter = WebSearch.Models.SearchFilter;
namespace WebSearch.Function
{
public class Search
{
private readonly ILogger<Lookup> _logger;
public Search(ILogger<Lookup> logger)
{
_logger = logger;
}
[Function("search")]
public async Task<HttpResponseData> RunAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req,
FunctionContext executionContext)
{
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var data = JsonSerializer.Deserialize<RequestBodySearch>(requestBody);
// Azure AI Search (managed identity by default; API key only when SEARCH_USE_KEY_AUTH=true)
SearchClient searchClient = SearchClientFactory.CreateSearchClient();
SearchOptions options = new()
{
Size = data.Size,
Skip = data.Skip,
IncludeTotalCount = true,
Filter = CreateFilterExpression(data.Filters)
};
options.Facets.Add("authors");
options.Facets.Add("language_code");
SearchResults<SearchDocument> searchResults = searchClient.Search<SearchDocument>(data.SearchText, options);
var facetOutput = new Dictionary<string, IList<FacetValue>>();
foreach (var facetResult in searchResults.Facets)
{
facetOutput[facetResult.Key] = facetResult.Value
.Select(x => new FacetValue { value = x.Value.ToString(), count = x.Count })
.ToList();
}
// Data to return
var output = new SearchOutput
{
Count = searchResults.TotalCount,
Results = searchResults.GetResults().ToList(),
Facets = facetOutput
};
var response = req.CreateResponse(HttpStatusCode.Found);
// Serialize data
var serializer = new JsonObjectSerializer(
new JsonSerializerOptions(JsonSerializerDefaults.Web));
await response.WriteAsJsonAsync(output, serializer);
return response;
}
public static string CreateFilterExpression(List<SearchFilter> filters)
{
if (filters is null or { Count: <= 0 })
{
return null;
}
List<string> filterExpressions = new();
List<SearchFilter> authorFilters = filters.Where(f => f.field == "authors").ToList();
List<SearchFilter> languageFilters = filters.Where(f => f.field == "language_code").ToList();
List<string> authorFilterValues = authorFilters.Select(f => f.value).ToList();
if (authorFilterValues.Count > 0)
{
string filterStr = string.Join(",", authorFilterValues);
filterExpressions.Add($"{"authors"}/any(t: search.in(t, '{filterStr}', ','))");
}
List<string> languageFilterValues = languageFilters.Select(f => f.value).ToList();
foreach (var value in languageFilterValues)
{
filterExpressions.Add($"language_code eq '{value}'");
}
return string.Join(" and ", filterExpressions);
}
}
}
Aby niezależnie zweryfikować funkcję, wywołaj /api/search termin wyszukiwania w treści żądania i potwierdź, że odpowiedź zawiera pasujące dokumenty książki, łączną liczbę i wartości aspektów.
Klient: wyszukiwanie w wykazie
Strona Wyszukiwania klienta React wywołuje search funkcję Azure za każdym razem, gdy użytkownik wprowadza zapytanie, zmienia filtr aspektu lub przechodzi do nowej strony wyników. Klient wysyła tekst wyszukiwania, wartości bieżącej strony skip i top wszystkie wybrane filtry autora lub języka w treści POST do /api/search. Funkcja zwraca listę pasujących dokumentów książki, łączną liczbę i wartości aspektów, których strona używa do renderowania listy wyników, pager i filtrów aspektów. Poniższy kod w programie \client\src\pages\Search\Search.jsx tworzy żądanie i przechowuje odpowiedź w stanie składnika:
import React, { useEffect, useState, Suspense } from 'react';
import fetchInstance from '../../url-fetch';
import CircularProgress from '@mui/material/CircularProgress';
import { useLocation, useNavigate } from "react-router-dom";
import Results from '../../components/Results/Results';
import Pager from '../../components/Pager/Pager';
import Facets from '../../components/Facets/Facets';
import SearchBar from '../../components/SearchBar/SearchBar';
import "./Search.css";
export default function Search() {
let location = useLocation();
const navigate = useNavigate();
const [results, setResults] = useState([]);
const [resultCount, setResultCount] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
const [q, setQ] = useState(new URLSearchParams(location.search).get('q') ?? "*");
const [top] = useState(new URLSearchParams(location.search).get('top') ?? 8);
const [skip, setSkip] = useState(new URLSearchParams(location.search).get('skip') ?? 0);
const [filters, setFilters] = useState([]);
const [facets, setFacets] = useState({});
const [isLoading, setIsLoading] = useState(true);
let resultsPerPage = top;
// Handle page changes in a controlled manner
function handlePageChange(newPage) {
setCurrentPage(newPage);
}
// Calculate skip value and fetch results when relevant parameters change
useEffect(() => {
// Calculate skip based on current page
const calculatedSkip = (currentPage - 1) * top;
// Only update if skip has actually changed
if (calculatedSkip !== skip) {
setSkip(calculatedSkip);
return; // Skip the fetch since skip will change and trigger another useEffect
}
// Proceed with fetch
setIsLoading(true);
const body = {
q: q,
top: top,
skip: skip,
filters: filters
};
fetchInstance('/api/search', { body, method: 'POST' })
.then(response => {
setResults(response.results);
setFacets(response.facets);
setResultCount(response.count);
setIsLoading(false);
})
.catch(error => {
console.log(error);
setIsLoading(false);
});
}, [q, top, skip, filters, currentPage]);
// pushing the new search term to history when q is updated
// allows the back button to work as expected when coming back from the details page
useEffect(() => {
navigate('/search?q=' + q);
setCurrentPage(1);
setFilters([]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [q]);
let postSearchHandler = (searchTerm) => {
setQ(searchTerm);
}
// filters should be applied across entire result set,
// not just within the current page
const updateFilterHandler = (newFilters) => {
// Reset paging
setSkip(0);
setCurrentPage(1);
// Set filters
setFilters(newFilters);
};
return (
<main className="main main--search container-fluid">
<div className="row">
<div className="search-bar-column col-md-3">
<div className="search-bar-column-container">
<SearchBar postSearchHandler={postSearchHandler} query={q} width={false}></SearchBar>
</div>
<Facets facets={facets} filters={filters} setFilters={updateFilterHandler}></Facets>
</div>
<div className="search-bar-results">
{isLoading ? (
<div className="col-md-9">
<CircularProgress />
</div>
) : (
<div className="search-results-container">
<Results documents={results} top={top} skip={skip} count={resultCount} query={q}></Results>
<Pager className="pager-style" currentPage={currentPage} resultCount={resultCount} resultsPerPage={resultsPerPage} onPageChange={handlePageChange}></Pager>
</div>
)}
</div>
</div>
</main>
);
}
Aby zweryfikować tę integrację, wprowadź termin wyszukiwania na pasku wyszukiwania witryny internetowej i upewnij się, że lista wyników, liczba wyników i aspekty są aktualizowane.
Klient: sugestie z wykazu
Interfejs API funkcji Suggest jest wywoływany w aplikacji React w \client\src\components\SearchBar\SearchBar.jsx ramach składnika Autouzupełnianie interfejsu użytkownika materiału. Ten składnik używa tekstu wejściowego do wyszukiwania autorów i książek, które są zgodne. Następnie wyświetla te możliwe dopasowania jako elementy możliwe do wybrania na liście rozwijanej.
import React, { useState, useEffect } from 'react';
import { TextField, Autocomplete, Button, Box } from '@mui/material';
import fetchInstance from '../../url-fetch';
import './SearchBar.css';
export default function SearchBar({ postSearchHandler, query, width }) {
const [q, setQ] = useState(() => query || '');
const [suggestions, setSuggestions] = useState([]);
const search = (value) => {
postSearchHandler(value);
};
useEffect(() => {
if (q) {
const body = { q, top: 5, suggester: 'sg' };
fetchInstance('/api/suggest', { body, method: 'POST' })
.then(response => {
setSuggestions(response.suggestions.map(s => s.text));
})
.catch(error => {
console.log(error);
setSuggestions([]);
});
}
}, [q]);
const onInputChangeHandler = (event, value) => {
setQ(value);
};
const onChangeHandler = (event, value) => {
setQ(value);
search(value);
};
const onEnterButton = (event) => {
// if enter key is pressed
if (event.key === 'Enter') {
search(q);
}
};
return (
<div
className={width ? "search-bar search-bar-wide" : "search-bar search-bar-narrow"}
>
<Box className="search-bar-box">
<Autocomplete
className="autocomplete"
freeSolo
value={q}
options={suggestions}
onInputChange={onInputChangeHandler}
onChange={onChangeHandler}
disableClearable
renderInput={(params) => (
<TextField
{...params}
id="search-box"
className="form-control rounded-0"
placeholder="What are you looking for?"
onBlur={() => setSuggestions([])}
onClick={() => setSuggestions([])}
onKeyDown={onEnterButton}
/>
)}
/>
<div className="search-button" >
<Button variant="contained" color="primary" onClick={() => {
search(q)
}
}>
Search
</Button>
</div>
</Box>
</div>
);
}
Aby zweryfikować tę integrację, wprowadź tekst na pasku wyszukiwania witryny internetowej i upewnij się, że pasujące tytuły książek i autorzy są wyświetlane na liście rozwijanej autouzupełniania.
Funkcja: Uzyskiwanie określonego dokumentu
Interfejs API wyszukiwania dokumentów pobiera pełny dokument dla pojedynczej książki po wybraniu go przez użytkownika z wyników wyszukiwania. Funkcja odczytuje książkę id z ciągu zapytania żądania, używa SearchClientFactory metody do utworzenia uwierzytelnionego SearchClientelementu i wywołuje GetDocumentAsync funkcję w celu wyszukania tego klucza w indeksie good-books . Zwraca wynikowy dokument opakowany w LookupOutput obiekt.
using Azure;
using Azure.Core.Serialization;
using Azure.Identity;
using Azure.Search.Documents;
using Azure.Search.Documents.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Text.Json;
using WebSearch.Models;
namespace WebSearch.Function
{
public class Lookup
{
private readonly ILogger<Lookup> _logger;
public Lookup(ILogger<Lookup> logger)
{
_logger = logger;
}
[Function("lookup")]
public async Task<HttpResponseData> RunAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
FunctionContext executionContext)
{
// Get Document Id
var query = System.Web.HttpUtility.ParseQueryString(req.Url.Query);
string documentId = query["id"].ToString();
// Azure AI Search (managed identity by default; API key only when SEARCH_USE_KEY_AUTH=true)
SearchClient searchClient = SearchClientFactory.CreateSearchClient();
var getDocumentResponse = await searchClient.GetDocumentAsync<SearchDocument>(documentId);
// Data to return
var output = new LookupOutput
{
Document = getDocumentResponse.Value
};
var response = req.CreateResponse(HttpStatusCode.Found);
// Serialize data
var serializer = new JsonObjectSerializer(
new JsonSerializerOptions(JsonSerializerDefaults.Web));
await response.WriteAsJsonAsync(output, serializer);
return response;
}
}
}
Aby niezależnie sprawdzić funkcję Lookup, wywołaj /api/lookup prawidłową książkę id i potwierdź, że odpowiedź zwraca pełny dokument tej książki.
Klient: Uzyskiwanie określonego dokumentu
Gdy użytkownik wybierze książkę z wyników wyszukiwania, strona Szczegóły wymaga kompletnego dokumentu dla tej książki, w tym pól, które nie są wyświetlane na liście podsumowań. Strona Szczegóły odczytuje książkę id z parametrów trasy i wywołuje interfejs API wyszukiwania dokumentów za pomocą /api/lookup polecenia podczas instalowania składnika. Przechowuje zwracany dokument w stanie składnika i renderuje go na kartach Wynik i Nieprzetworzone dane . Poniższy kod w pliku \client\src\pages\Details\Details.jsx wykonuje to wyszukiwanie podczas inicjowania składnika:
import React, { useState, useEffect } from "react";
import { useParams } from 'react-router-dom';
import Rating from '@mui/material/Rating';
import CircularProgress from '@mui/material/CircularProgress';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';
import fetchInstance from '../../url-fetch';
import "./Details.css";
function CustomTabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
className="tab-panel"
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
// Ensure it takes full width
>
{value === index && <Box className="tab-panel-value">{children}</Box>}
</div>
);
}
export default function BasicTabs() {
const { id } = useParams();
const [document, setDocument] = useState({});
const [value, setValue] = React.useState(0);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
setIsLoading(true);
fetchInstance('/api/lookup', { query: { id } })
.then(response => {
console.log(JSON.stringify(response))
const doc = response.document;
setDocument(doc);
setIsLoading(false);
})
.catch(error => {
console.log(error);
setIsLoading(false);
});
}, [id]);
const handleChange = (event, newValue) => {
setValue(newValue);
};
if (isLoading || !id || Object.keys(document).length === 0) {
return (
<div className="loading-container">
<CircularProgress />
<p>Loading...</p>
</div>
);
}
return (
<Box className="details-box-parent">
<Box className="details-tab-box-header">
<Tabs value={value} onChange={handleChange} aria-label="book-details-tabs">
<Tab label="Result" />
<Tab label="Raw Data" />
</Tabs>
</Box>
<CustomTabPanel value={value} index={0} className="tab-panel box-content">
<div className="card-body">
<h5 className="card-title">{document.original_title}</h5>
<img className="image" src={document.image_url} alt="Book cover"></img>
<p className="card-text">{document.authors?.join('; ')} - {document.original_publication_year}</p>
<p className="card-text">ISBN {document.isbn}</p>
<Rating name="half-rating-read" value={parseInt(document.average_rating)} precision={0.1} readOnly></Rating>
<p className="card-text">{document.ratings_count} Ratings</p>
</div>
</CustomTabPanel>
<CustomTabPanel value={value} index={1} className="tab-panel">
<div className="card-body text-left card-text details-custom-tab-panel-json-div" >
<pre><code>
{JSON.stringify(document, null, 2)}
</code></pre>
</div>
</CustomTabPanel>
</Box>
);
}
Aby zweryfikować tę integrację, wybierz książkę z wyników wyszukiwania i upewnij się, że jej szczegóły, w tym obraz okładki, autorzy i ocena, są wyświetlane na stronie Szczegóły.
Modele języka C#, które obsługują interfejs API
Interfejs API Azure Functions i projekt importu zbiorczego współużytkuje zestaw klas modeli języka C#. Te klasy definiują treść żądania wysyłane przez klienta, takie jak tekst wyszukiwania, wartości stronicowania i filtry. Definiują one również kształty odpowiedzi oczekiwane przez klienta, takie jak wyniki wyszukiwania, wartości aspektów i pojedynczy wyszukiwany dokument. Zachowanie tych modeli w jednym pliku gwarantuje, że punkty końcowe wyszukiwania wyszukiwania, sugestii i dokumentów pozostają zgodne z oczekiwaniami klienta React. Następujące modele zdefiniowane w Models.csprogramie obsługują funkcje w tej aplikacji:
using Azure.Search.Documents.Models;
using System.Text.Json.Serialization;
namespace WebSearch.Models
{
public class RequestBodyLookUp
{
[JsonPropertyName("id")]
public string Id { get; set; }
}
public class RequestBodySuggest
{
[JsonPropertyName("q")]
public string SearchText { get; set; }
[JsonPropertyName("top")]
public int Size { get; set; }
[JsonPropertyName("suggester")]
public string SuggesterName { get; set; }
}
public class RequestBodySearch
{
[JsonPropertyName("q")]
public string SearchText { get; set; }
[JsonPropertyName("skip")]
public int Skip { get; set; }
[JsonPropertyName("top")]
public int Size { get; set; }
[JsonPropertyName("filters")]
public List<SearchFilter> Filters { get; set; }
}
public class SearchFilter
{
public string field { get; set; }
public string value { get; set; }
}
public class FacetValue
{
public string value { get; set; }
public long? count { get; set; }
}
class SearchOutput
{
[JsonPropertyName("count")]
public long? Count { get; set; }
[JsonPropertyName("results")]
public List<SearchResult<SearchDocument>> Results { get; set; }
[JsonPropertyName("facets")]
public Dictionary<String, IList<FacetValue>> Facets { get; set; }
}
class LookupOutput
{
[JsonPropertyName("document")]
public SearchDocument Document { get; set; }
}
public class BookModel
{
public string id { get; set; }
public decimal? goodreads_book_id { get; set; }
public decimal? best_book_id { get; set; }
public decimal? work_id { get; set; }
public decimal? books_count { get; set; }
public string isbn { get; set; }
public string isbn13 { get; set; }
public string[] authors { get; set; }
public decimal? original_publication_year { get; set; }
public string original_title { get; set; }
public string title { get; set; }
public string language_code { get; set; }
public double? average_rating { get; set; }
public decimal? ratings_count { get; set; }
public decimal? work_ratings_count { get; set; }
public decimal? work_text_reviews_count { get; set; }
public decimal? ratings_1 { get; set; }
public decimal? ratings_2 { get; set; }
public decimal? ratings_3 { get; set; }
public decimal? ratings_4 { get; set; }
public decimal? ratings_5 { get; set; }
public string image_url { get; set; }
public string small_image_url { get; set; }
}
}
Następny krok
Aby kontynuować naukę na temat programowania Wyszukiwanie AI platformy Azure, wypróbuj ten następny samouczek dotyczący indeksowania: