4 — Eksplorowanie kodu wyszukiwania w języku Python
W poprzednich lekcjach dodano wyszukiwanie do statycznej aplikacji internetowej. W tej lekcji przedstawiono podstawowe kroki, które ustanawiają integrację. Jeśli szukasz ściągawki dotyczącej sposobu integrowania wyszukiwania z aplikacją w języku Python, w tym artykule wyjaśniono, co musisz wiedzieć.
Aplikacja jest dostępna:
Zestaw Azure SDK azure-search-documents
Aplikacja funkcji używa zestawu Azure SDK dla usługi Azure AI Search:
Aplikacja funkcji uwierzytelnia się za pośrednictwem zestawu SDK do opartego na chmurze interfejsu API wyszukiwania sztucznej inteligencji platformy Azure przy użyciu nazwy zasobu, klucza interfejsu API i nazwy indeksu. Wpisy tajne są przechowywane w ustawieniach statycznej aplikacji internetowej i pobierane do funkcji jako zmienne środowiskowe.
Konfigurowanie wpisów tajnych w pliku konfiguracji
Zmienne środowiskowe ustawień aplikacji funkcji platformy Azure są pobierane z pliku udostępnionego __init__.py
między trzema funkcjami interfejsu API.
import os
def azure_config():
configs = {}
configs["search_facets"] = os.environ.get("SearchFacets", "")
configs["search_index_name"] = os.environ.get("SearchIndexName", "")
configs["search_service_name"] = os.environ.get("SearchServiceName", "")
configs["search_api_key"] = os.environ.get("SearchApiKey", "")
return configs
Funkcja platformy Azure: wyszukiwanie w wykazie
Interfejs API wyszukiwania przyjmuje termin wyszukiwania i wyszukuje je w dokumentach w indeksie wyszukiwania, zwracając listę dopasowań.
Funkcja platformy Azure pobiera informacje o konfiguracji wyszukiwania i spełnia zapytanie.
import logging
import azure.functions as func
from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient
from shared_code import azure_config
import json
environment_vars = azure_config()
# Set Azure Search endpoint and key
endpoint = f'https://{environment_vars["search_service_name"]}.search.windows.net'
key = environment_vars["search_api_key"]
# Your index name
index_name = "good-books"
# Create Azure SDK client
search_client = SearchClient(endpoint, index_name, AzureKeyCredential(key))
# returns obj like {authors: 'array', language_code:'string'}
def read_facets(facetsString):
facets = facetsString.split(",")
output = {}
for x in facets:
if x.find("*") != -1:
newVal = x.replace("*", "")
output[newVal] = "array"
else:
output[x] = "string"
return output
# creates filters in odata syntax
def create_filter_expression(filter_list, facets):
i = 0
filter_expressions = []
return_string = ""
separator = " and "
while i < len(filter_list):
field = filter_list[i]["field"]
value = filter_list[i]["value"]
if facets[field] == "array":
print("array")
filter_expressions.append(f"{field}/any(t: search.in(t, '{value}', ','))")
else:
print("value")
filter_expressions.append(f"{field} eq '{value}'")
i += 1
return_string = separator.join(filter_expressions)
return return_string
def new_shape(docs):
old_api_shape = list(docs)
client_side_expected_shape = []
for item in old_api_shape:
new_document = {}
new_document["score"] = item["@search.score"]
new_document["highlights"] = item["@search.highlights"]
new_api_shape = {}
new_api_shape["id"] = item["id"]
new_api_shape["goodreads_book_id"] = item["goodreads_book_id"]
new_api_shape["best_book_id"] = item["best_book_id"]
new_api_shape["work_id"] = item["work_id"]
new_api_shape["books_count"] = item["books_count"]
new_api_shape["isbn"] = item["isbn"]
new_api_shape["isbn13"] = item["isbn13"]
new_api_shape["authors"] = item["authors"]
new_api_shape["original_publication_year"] = item["original_publication_year"]
new_api_shape["original_title"] = item["original_title"]
new_api_shape["title"] = item["title"]
new_api_shape["language_code"] = item["language_code"]
new_api_shape["average_rating"] = item["average_rating"]
new_api_shape["ratings_count"] = item["ratings_count"]
new_api_shape["work_ratings_count"] = item["work_ratings_count"]
new_api_shape["work_text_reviews_count"] = item["work_text_reviews_count"]
new_api_shape["ratings_1"] = item["ratings_1"]
new_api_shape["ratings_2"] = item["ratings_2"]
new_api_shape["ratings_3"] = item["ratings_3"]
new_api_shape["ratings_4"] = item["ratings_4"]
new_api_shape["ratings_5"] = item["ratings_5"]
new_api_shape["image_url"] = item["image_url"]
new_api_shape["small_image_url"] = item["small_image_url"]
new_document["document"] = new_api_shape
client_side_expected_shape.append(new_document)
return list(client_side_expected_shape)
bp=func.Blueprint()
@bp.function_name("search")
@bp.route(route="search", methods=[func.HttpMethod.GET, func.HttpMethod.POST] )
def main(req: func.HttpRequest) -> func.HttpResponse:
# variables sent in body
req_body = req.get_json()
q = req_body.get("q")
top = req_body.get("top") or 8
skip = req_body.get("skip") or 0
filters = req_body.get("filters") or []
facets = environment_vars["search_facets"]
facetKeys = read_facets(facets)
search_filter = ""
if len(filters):
search_filter = create_filter_expression(filters, facetKeys)
if q:
logging.info(f"/Search q = {q}")
search_results = search_client.search(
search_text=q,
top=top,
skip=skip,
facets=facetKeys,
filter=search_filter,
include_total_count=True,
)
returned_docs = new_shape(search_results)
# format the React app expects
full_response = {}
full_response["count"] = search_results.get_count()
full_response["facets"] = search_results.get_facets()
full_response["results"] = returned_docs
return func.HttpResponse(
body=json.dumps(full_response), mimetype="application/json", status_code=200
)
else:
return func.HttpResponse("No query param found.", status_code=200)
Klient: wyszukiwanie z katalogu
Wywołaj funkcję platformy Azure w kliencie React przy użyciu następującego kodu.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
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;
useEffect(() => {
setIsLoading(true);
setSkip((currentPage-1) * top);
const body = {
q: q,
top: top,
skip: skip,
filters: filters
};
axios.post( '/api/search', body)
.then(response => {
console.log(JSON.stringify(response.data))
setResults(response.data.results);
setFacets(response.data.facets);
setResultCount(response.data.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) => {
//console.log(searchTerm);
setQ(searchTerm);
}
var body;
if (isLoading) {
body = (
<div className="col-md-9">
<CircularProgress />
</div>);
} else {
body = (
<div className="col-md-9">
<Results documents={results} top={top} skip={skip} count={resultCount}></Results>
<Pager className="pager-style" currentPage={currentPage} resultCount={resultCount} resultsPerPage={resultsPerPage} setCurrentPage={setCurrentPage}></Pager>
</div>
)
}
return (
<main className="main main--search container-fluid">
<div className="row">
<div className="col-md-3">
<div className="search-bar">
<SearchBar postSearchHandler={postSearchHandler} q={q}></SearchBar>
</div>
<Facets facets={facets} filters={filters} setFilters={setFilters}></Facets>
</div>
{body}
</div>
</main>
);
}
Funkcja platformy Azure: sugestie z wykazu
Interfejs Suggest
API przyjmuje termin wyszukiwania, gdy użytkownik wpisuje i sugeruje terminy wyszukiwania, takie jak tytuły książek i autorzy w dokumentach w indeksie wyszukiwania, zwracając niewielką listę dopasowań.
Sugestor sg
wyszukiwania , jest zdefiniowany w pliku schematu używanym podczas przekazywania zbiorczego.
import logging
import azure.functions as func
from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient
from shared_code import azure_config
import json
environment_vars = azure_config()
# curl --header "Content-Type: application/json" \
# --request POST \
# --data '{"q":"code","top":"5", "suggester":"sg"}' \
# http://localhost:7071/api/Suggest
# Set Azure Search endpoint and key
service_name = environment_vars["search_service_name"]
endpoint = f"https://{service_name}.search.windows.net"
key = environment_vars["search_api_key"]
# Your index name
index_name = "good-books"
# Create Azure SDK client
search_client = SearchClient(endpoint, index_name, AzureKeyCredential(key))
bp=func.Blueprint()
@bp.function_name("suggest")
@bp.route(route="suggest", methods=[func.HttpMethod.GET, func.HttpMethod.POST] )
def main(req: func.HttpRequest) -> func.HttpResponse:
# variables sent in body
req_body = req.get_json()
q = req_body.get("q")
top = req_body.get("top") or 5
suggester = req_body.get("suggester") or "sg"
if q:
logging.info("/Suggest q = %s", q)
suggestions = search_client.suggest(search_text=q, suggester_name=suggester, top=top)
# format the React app expects
full_response = {}
full_response["suggestions"] = suggestions
logging.debug(suggestions)
return func.HttpResponse(
body=json.dumps(full_response), mimetype="application/json", status_code=200
)
else:
return func.HttpResponse("No query param found.", status_code=200)
Klient: sugestie z wykazu
Interfejs API funkcji Suggest jest wywoływany w aplikacji React w client\src\components\SearchBar\SearchBar.js
ramach inicjowania składnika:
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import Suggestions from './Suggestions/Suggestions';
import "./SearchBar.css";
export default function SearchBar(props) {
let [q, setQ] = useState("");
let [suggestions, setSuggestions] = useState([]);
let [showSuggestions, setShowSuggestions] = useState(false);
const onSearchHandler = () => {
props.postSearchHandler(q);
setShowSuggestions(false);
}
const suggestionClickHandler = (s) => {
document.getElementById("search-box").value = s;
setShowSuggestions(false);
props.postSearchHandler(s);
}
const onEnterButton = (event) => {
if (event.keyCode === 13) {
onSearchHandler();
}
}
const onChangeHandler = () => {
var searchTerm = document.getElementById("search-box").value;
setShowSuggestions(true);
setQ(searchTerm);
// use this prop if you want to make the search more reactive
if (props.searchChangeHandler) {
props.searchChangeHandler(searchTerm);
}
}
useEffect(_ =>{
const timer = setTimeout(() => {
const body = {
q: q,
top: 5,
suggester: 'sg'
};
if (q === '') {
setSuggestions([]);
} else {
axios.post( '/api/suggest', body)
.then(response => {
console.log(JSON.stringify(response.data))
setSuggestions(response.data.suggestions);
} )
.catch(error => {
console.log(error);
setSuggestions([]);
});
}
}, 300);
return () => clearTimeout(timer);
}, [q, props]);
var suggestionDiv;
if (showSuggestions) {
suggestionDiv = (<Suggestions suggestions={suggestions} suggestionClickHandler={(s) => suggestionClickHandler(s)}></Suggestions>);
} else {
suggestionDiv = (<div></div>);
}
return (
<div >
<div className="input-group" onKeyDown={e => onEnterButton(e)}>
<div className="suggestions" >
<input
autoComplete="off" // setting for browsers; not the app
type="text"
id="search-box"
className="form-control rounded-0"
placeholder="What are you looking for?"
onChange={onChangeHandler}
defaultValue={props.q}
onBlur={() => setShowSuggestions(false)}
onClick={() => setShowSuggestions(true)}>
</input>
{suggestionDiv}
</div>
<div className="input-group-btn">
<button className="btn btn-primary rounded-0" type="submit" onClick={onSearchHandler}>
Search
</button>
</div>
</div>
</div>
);
};
Funkcja platformy Azure: uzyskiwanie określonego dokumentu
Interfejs Lookup
API przyjmuje identyfikator i zwraca obiekt dokumentu z indeksu wyszukiwania.
import logging
import azure.functions as func
from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient
from shared_code import azure_config
import json
environment_vars = azure_config()
# Set Azure Search endpoint and key
endpoint = f'https://{environment_vars["search_service_name"]}.search.windows.net'
key = environment_vars["search_api_key"]
# Your index name
index_name = "good-books"
# Create Azure SDK client
search_client = SearchClient(endpoint, index_name, AzureKeyCredential(key))
bp = func.Blueprint()
@bp.function_name("lookup")
@bp.route(route="lookup", methods=[func.HttpMethod.GET, func.HttpMethod.POST])
def main(req: func.HttpRequest) -> func.HttpResponse:
# http://localhost:7071/api/Lookup?id=100
docid = req.params.get("id")
if docid:
logging.info(f"/Lookup id = {docid}")
returnedDocument = search_client.get_document(key=docid)
full_response = {}
full_response["document"] = returnedDocument
return func.HttpResponse(
body=json.dumps(full_response), mimetype="application/json", status_code=200
)
else:
return func.HttpResponse("No doc id param found.", status_code=200)
Klient: Uzyskiwanie określonego dokumentu
Ten interfejs API funkcji jest wywoływany w aplikacji React w client\src\pages\Details\Detail.js
ramach 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 axios from 'axios';
import "./Details.css";
export default function Details() {
let { id } = useParams();
const [document, setDocument] = useState({});
const [selectedTab, setTab] = useState(0);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
setIsLoading(true);
// console.log(id);
axios.get('/api/lookup?id=' + id)
.then(response => {
console.log(JSON.stringify(response.data))
const doc = response.data.document;
setDocument(doc);
setIsLoading(false);
})
.catch(error => {
console.log(error);
setIsLoading(false);
});
}, [id]);
// View default is loading with no active tab
let detailsBody = (<CircularProgress />),
resultStyle = "nav-link",
rawStyle = "nav-link";
if (!isLoading && document) {
// View result
if (selectedTab === 0) {
resultStyle += " active";
detailsBody = (
<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>
);
}
// View raw data
else {
rawStyle += " active";
detailsBody = (
<div className="card-body text-left">
<pre><code>
{JSON.stringify(document, null, 2)}
</code></pre>
</div>
);
}
}
return (
<main className="main main--details container fluid">
<div className="card text-center result-container">
<div className="card-header">
<ul className="nav nav-tabs card-header-tabs">
<li className="nav-item"><button className={resultStyle} onClick={() => setTab(0)}>Result</button></li>
<li className="nav-item"><button className={rawStyle} onClick={() => setTab(1)}>Raw Data</button></li>
</ul>
</div>
{detailsBody}
</div>
</main>
);
}
Następne kroki
Opinia
https://aka.ms/ContentUserFeedback.
Dostępne już wkrótce: W 2024 r. będziemy stopniowo wycofywać zgłoszenia z serwisu GitHub jako mechanizm przesyłania opinii na temat zawartości i zastępować go nowym systemem opinii. Aby uzyskać więcej informacji, sprawdź:Prześlij i wyświetl opinię dla