Alibaba Cloud Milvus associe son type géospatial GEOMETRY aux fonctions d'IA afin qu'une seule recherche exécute à la fois le filtrage par plage géospatiale et la recherche sémantique au sein d'une même collection. Cette rubrique explique comment combiner le filtrage « à proximité » et la recherche sémantique « selon les goûts », puis ajouter l'analyse des sentiments et la génération de résumés pour construire un classement local de style de vie explicite.
Présentation de la solution
« Que manger dans les environs ? » est la demande la plus fréquente dans les scénarios de style de vie local. Pour y répondre efficacement, un système doit satisfaire simultanément trois conditions :
À proximité : les établissements situés dans un rayon donné autour de la position actuelle de l'utilisateur, ou dans le quartier d'affaires où il se trouve.
Selon les goûts : des requêtes telles que « un restaurant sichuanais adapté aux familles » ou « un café calme pour discuter affaires », qui ne peuvent pas être précisément énumérées à l'aide de tags.
-
Fiable : ce que disent les avis réels, le taux d'avis positifs, ainsi que les points forts et les plaintes qui reviennent régulièrement.
Une implémentation traditionnelle assemble généralement trois systèmes : une base de données spatiale pour le filtrage géospatial, une base de données vectorielle pour la recherche sémantique et un service NLP pour la classification des sentiments et la synthèse. Chaque système conserve sa propre copie des données, ce qui rend coûteuses les jointures inter-systèmes, la synchronisation des données et le maintien de la cohérence. Une seule requête doit alors enchaîner plusieurs services et plusieurs allers-retours réseau.
|
Dimension |
Solution traditionnelle (base de données spatiale + base de données vectorielle + service NLP) |
Alibaba Cloud Milvus |
|
Nombre de systèmes |
3 ou plus, chacun avec sa propre copie des données |
1, avec une source unique de données |
|
Fusion géospatiale et sémantique |
Jointures inter-systèmes ou plusieurs cycles de recherche |
Un seul appel |
|
Compréhension des avis |
Modèles de sentiment et de résumé développés en interne ou externes |
|
|
Parcours de vectorisation |
L'application appelle d'abord le modèle d'embedding, puis écrit les données |
Vectorisation lors de l'insertion |
|
Cohérence et synchronisation |
Gérées manuellement |
Naturellement cohérentes au sein d'une seule base de données |
Alibaba Cloud Milvus regroupe ces fonctionnalités au sein d'une seule collection et d'une seule recherche :
|
Fonctionnalité |
Implémentation |
|
Filtrage par plage géospatiale |
Stockez la longitude et la latitude dans un champ |
|
Recherche sémantique sur les avis |
Le texte des avis est vectorisé automatiquement lors de l'insertion grâce à |
|
Reclassement par pertinence |
Les candidats rappelés sont réordonnés selon leur pertinence par rapport à la requête via |
|
Quantification de la réputation |
Utilisez |
Le filtrage géospatial et la recherche sémantique s'exécutent lors d'un seul appel search() : la recherche vectorielle ANN gère l'aspect « selon les goûts », tandis que les fonctions spatiales dans filter gèrent l'aspect « à proximité ». Les deux aspects sont résolus conjointement au sein d'une seule requête et le reclassement est appliqué ensuite pour produire directement les candidats du classement.
Cette rubrique utilise les fonctions d'IA suivantes :
|
Fonction |
Fonctionnement |
Rôle dans le classement |
Mode d'appel |
|
|
Convertit automatiquement le texte des avis en vecteurs lors de l'insertion, évitant ainsi à l'application d'appeler préalablement un modèle. |
Permet d'associer des requêtes sémantiques telles que « un restaurant sichuanais adapté aux familles » via la recherche vectorielle. |
Déclarée en tant que Function dans le schéma de la collection ( |
|
|
Réordonne les candidats rappelés selon leur pertinence par rapport à la requête. |
Fait remonter en tête du classement les établissements qui correspondent le mieux à la demande. |
Attachée en tant que ranker dans |
|
|
Détermine si un avis est positif, négatif ou neutre. |
Agrège le taux d'avis positifs d'un établissement. |
Appelée via le point de terminaison REST |
|
|
Condense un grand nombre d'avis en un court résumé. |
Génère le commentaire en une ligne affiché dans le classement. |
Appelée via le point de terminaison REST |
AI_EMBEDDING et AI_RERANK peuvent être déclarées dans la collection ou attachées à une recherche, tandis que AI_SENTIMENT et AI_SUMMARIZE sont disponibles uniquement via l'interface REST.
Prérequis
Une instance Milvus 2.6 est créée. Le type
GEOMETRYet les fonctions d'IA nécessitent le noyau 2.6. Après la création de l'instance, vous n'avez pas besoin de lier séparément un service de modèle : déclarez simplement la Function directement dans la collection.Pour accéder à l'instance via Internet, Public Network Access est activé dans l'onglet Security Configuration de la page des détails de l'instance et l'adresse IP de sortie du client est ajoutée à la liste blanche d'accès public.
pymilvus est installé. Les exemples de cette rubrique ont été validés avec pymilvus 3.0.0.
Limitations et remarques d'utilisation
Examinez les comportements suivants avant de copier le code, car plusieurs d'entre eux produisent des résultats silencieux ou vides plutôt que des erreurs :
Sémantique des limites géospatiales — Lorsqu'un objet géométrique se trouve exactement sur la limite d'un polygone,
ST_WITHINrenvoie false etST_INTERSECTSrenvoie true. Par exemple, lorsqu'un cercle de rayon est approximé par un polygone, un établissement situé exactement sur la circonférence n'est pas identifié parST_WITHIN. Si votre logique métier doit inclure la limite, utilisezST_INTERSECTSà la place. Pour unMULTIPOINT,ST_WITHINrenvoie false dès qu'un de ses points se trouve sur la limite.Précision de l'approximation du rayon —
circle_to_wktconvertit la longitude et la latitude avec une approximation plane, de sorte que l'erreur augmente avec la latitude. Cette méthode convient au filtrage grossier par rayon. Si votre logique métier est sensible à la précision des limites, augmentez le nombre de segmentsnum_segou vérifiez à nouveau la distance exacte côté application.Port de l'interface REST — L'interface RESTful partage le port 19530 avec
gRPC. Vous devez donc inclure explicitement le port dans l'adresse lorsque vous appelez l'interface REST, par exemplehttp://c-xxx.milvus.aliyuncs.com:19530. Si vous omettez le port, la requête est dirigée vers le port 80 par défaut et la connexion expire.Paramètres de reclassement REST — Pour l'interface de reclassement REST,
documentsdoit être un tableau de chaînes etquerydoit être une chaîne de niveau supérieur. Le paramètretop_nn'est pas pris en charge : le transmettre renvoieparam [top_n] is not supported. L'interface renvoie toujours un score par candidat ; si vous devez tronquer la liste, triez par score et conservez les N premiers éléments côté application.Sentiment indéterminé —
AI_SENTIMENTpeut renvoyer une valeur vide, ce qui signifie que le modèle n'a pas pu déterminer le sentiment. Filtrez les valeurs vides avant d'agréger le taux d'avis positifs, afin que les échantillons indéterminés ne soient pas comptés au dénominateur.
Procédure
Exécutez les étapes dans l'ordre au sein de la même session. Les extraits de code forment un script continu : les étapes ultérieures réutilisent le client, les fonctions utilitaires et les variables définis par les étapes précédentes (par exemple, circle de l'étape 3 et QUERY et geo_filter de l'étape 4). Si vous copiez une seule étape isolément, définissez d'abord ses variables amont.
Étape 0 : Préparer le code partagé (obligatoire)
Le code suivant contient les paramètres de connexion, un wrapper pour l'interface REST et une fonction utilitaire qui approxime un cercle de rayon sous forme de polygone. Toutes les étapes ultérieures dépendent de ce code. Remplacez MILVUS_HOST et MILVUS_TOKEN par les informations de votre propre instance.
from __future__ import annotations
import json
import math
import time
from typing import Any
from urllib.error import HTTPError
from urllib.request import Request, urlopen
from pymilvus import DataType, Function, FunctionType, MilvusClient
# ==================== Connection settings ====================
MILVUS_HOST = "http://c-xxx.milvus.aliyuncs.com"
MILVUS_TOKEN = "root:xxx"
# The RESTful interface shares port 19530 with gRPC. Include the port explicitly,
# otherwise the request goes to port 80 by default and the connection times out.
MILVUS_REST_BASE_URL = f"{MILVUS_HOST}:19530"
client = MilvusClient(uri=MILVUS_HOST, token=MILVUS_TOKEN)
collection_name = "shop_reviews"
def post_json(path: str, body: dict[str, Any], timeout: int = 120,
retries: int = 3) -> tuple[int, dict[str, Any]]:
"""Shared wrapper for the AI_RERANK / AI_SENTIMENT / AI_SUMMARIZE REST interfaces."""
last = None
for _ in range(retries):
request = Request(
f"{MILVUS_REST_BASE_URL.rstrip('/')}{path}",
data=json.dumps(body, ensure_ascii=False).encode("utf-8"),
headers={"Authorization": f"Bearer {MILVUS_TOKEN}",
"Content-Type": "application/json"},
method="POST",
)
try:
with urlopen(request, timeout=timeout) as response:
status, data = response.status, json.loads(response.read().decode("utf-8"))
except HTTPError as exc:
status, data = exc.code, json.loads(exc.read().decode("utf-8"))
last = (status, data)
if status == 200 and data.get("code") == 0:
return status, data
time.sleep(1)
return last
def circle_to_wkt(lng: float, lat: float, radius_km: float, num_seg: int = 32) -> str:
"""Approximate the circle centered at (lng, lat) with a radius of radius_km as a regular polygon in WKT.
Note: longitude and latitude are angles. This conversion is a planar approximation,
so the error grows as the latitude increases."""
d_lat = radius_km / 111.0
d_lng = radius_km / (111.0 * math.cos(math.radians(lat)))
pts = []
for i in range(num_seg + 1):
theta = 2 * math.pi * i / num_seg
pts.append(f"{lng + d_lng * math.cos(theta):.6f} {lat + d_lat * math.sin(theta):.6f}")
return f"POLYGON(({', '.join(pts)}))"
La valeur MILVUS_HOST est utilisée comme URI du client, tandis que MILVUS_REST_BASE_URL ajoute le port 19530 pour les appels REST des étapes ultérieures. L'exigence de port explicite s'applique à l'interface REST.
Étape 1 : Créer une collection avec un champ géospatial et une fonction d'embedding
Le champ location utilise DataType.GEOMETRY pour stocker le point de coordonnées d'un établissement. Le champ review porte une fonction AI_EMBEDDING qui génère automatiquement un vecteur de 1 024 dimensions lors de l'insertion. Créez simultanément un index HNSW pour le champ vectoriel et un index R-Tree pour le champ géospatial.
Le code suivant exécute drop_collection avant de créer la collection, ce qui supprime définitivement toute collection existante nommée shop_reviews et toutes ses données. Sur une instance contenant déjà des données métier, utilisez un nom de collection de test distinct ou supprimez la branche drop_collection.
# ==================== Step 1: Create the collection ====================
if client.has_collection(collection_name):
client.drop_collection(collection_name)
schema = MilvusClient.create_schema(auto_id=True, enable_dynamic_field=False)
schema.add_field("id", DataType.INT64, is_primary=True)
schema.add_field("shop_id", DataType.INT64)
schema.add_field("shop_name", DataType.VARCHAR, max_length=256)
schema.add_field("category", DataType.VARCHAR, max_length=64)
schema.add_field("location", DataType.GEOMETRY) # Store coordinates, WKT: POINT(lng lat)
schema.add_field("review", DataType.VARCHAR, max_length=4096) # Raw review text, used as the embedding input
schema.add_field("review_embedding", DataType.FLOAT_VECTOR, dim=1024) # Review vector
# Vectorize review automatically at insert time
schema.add_function(
Function(
name="embed_review",
function_type=FunctionType.TEXTEMBEDDING,
input_field_names=["review"],
output_field_names=["review_embedding"],
params={
"provider": "aliyun_milvus",
"model_name": "qwen3.7-text-embedding",
"dim": 1024,
"max_client_batch_size": 20,
"max_concurrency": 1,
},
)
)
index_params = client.prepare_index_params()
index_params.add_index(
field_name="review_embedding",
index_type="HNSW",
metric_type="COSINE",
params={"M": 16, "efConstruction": 200},
)
# Create an R-Tree geospatial index for the GEOMETRY field to speed up spatial queries
index_params.add_index(
field_name="location",
index_type="RTREE",
)
client.create_collection(
collection_name=collection_name,
schema=schema,
index_params=index_params,
)
La dimension de review_embedding doit correspondre à la valeur dim des paramètres de la Function. La création d'un index RTREE pour le champ GEOMETRY accélère les requêtes spatiales ST_ et est recommandée lorsque le volume de données est important.
Les valeurs model_name de cette rubrique (qwen3.7-text-embedding ici, ainsi que les modèles de reclassement, de sentiment et de résumé des étapes ultérieures) sont fournies à titre d'exemple. Remplacez chacune d'elles par un modèle disponible sur votre propre instance.
Étape 2 : Insérer les données des établissements et des avis
Insérez les coordonnées des établissements et le texte brut des avis. review_embedding est généré automatiquement par la Function.
# ==================== Step 2: Insert store and review data ====================
# review_embedding is generated automatically by the Function, so you do not pass vectors manually
rows = [
{"shop_id": 1001, "shop_name": "Bashu Homestead Family Sichuan", "category": "Sichuan cuisine",
"location": "POINT(116.4612 39.9088)",
"review": "Came with my kid. High chairs and small bowls are available, the maoxuewang is not too spicy, the servers are patient, and the place is clean."},
{"shop_id": 1001, "shop_name": "Bashu Homestead Family Sichuan", "category": "Sichuan cuisine",
"location": "POINT(116.4612 39.9088)",
"review": "You wait for a table on weekends, but the dishes are consistent. Good for a family meal, and my kid enjoyed it."},
{"shop_id": 1002, "shop_name": "Mala Jianghu", "category": "Sichuan cuisine",
"location": "POINT(116.4501 39.9101)",
"review": "A paradise for bold flavors, the shuizhuyu is punchy, but the space is small and noisy, so it does not suit kids."},
{"shop_id": 1003, "shop_name": "Quiet Lane Coffee", "category": "Coffee",
"location": "POINT(116.4550 39.9075)",
"review": "Quiet enough to talk business and the pour-over is good, but it has nothing to do with a Sichuan food request."},
]
client.insert(collection_name, rows)
client.flush(collection_name)
client.load_collection(collection_name)
Appelez flush() après l'insertion des données, sinon une recherche exécutée immédiatement après risque de renvoyer des résultats vides. load_collection() charge la collection en mémoire, opération requise avant de pouvoir effectuer une recherche.
Étape 3 : Filtrer par plage géospatiale
Choisissez la méthode de plage en fonction de la forme de votre zone cible :
District administratif ou quartier d'affaires — Représentez la zone sous forme de
POLYGONet utilisezST_WITHIN.Zone autour de la position actuelle de l'utilisateur — Approximez un cercle de rayon sous forme de polygone avec
circle_to_wkt, puis utilisezST_WITHIN.-
Logique devant inclure la limite ou détecter les zones qui se chevauchent — Utilisez
ST_INTERSECTSà la place, car une géométrie située exactement sur la limite n'est pas identifiée parST_WITHIN.Le code suivant présente à la fois le polygone du quartier d'affaires et le cercle de rayon. Le cercle de rayon est approximé sous forme de polygone par
circle_to_wkt.
# ==================== Step 3: Geospatial range filtering ====================
# 3.1 Query the stores within a business district polygon
cbd_polygon = ("POLYGON((116.448 39.905, 116.465 39.905, 116.465 39.914, "
"116.448 39.914, 116.448 39.905))")
hits = client.query(
collection_name=collection_name,
filter=f"ST_WITHIN(location, '{cbd_polygon}')",
output_fields=["shop_name", "category", "location"],
limit=100,
)
print("Review records within the business district:")
for h in hits:
print(f" - {h['shop_name']} ({h['category']}) {h['location']}")
# 3.2 Query within a 3 km radius: approximate the circle as a polygon, then check whether stores fall inside it
center_lng, center_lat = 116.4600, 39.9090
circle = circle_to_wkt(center_lng, center_lat, radius_km=3.0)
nearby = client.query(
collection_name=collection_name,
filter=f"ST_WITHIN(location, '{circle}')",
output_fields=["shop_name", "category", "location"],
limit=200,
)
print(f"Review records within 3 km of the center point ({center_lng}, {center_lat}):")
for n in nearby:
print(f" - {n['shop_name']} ({n['category']}) {n['location']}")
Les données d'exemple comportent 4 enregistrements d'avis pour 3 établissements (Bashu Homestead possède 2 avis), de sorte que les deux requêtes renvoient 4 enregistrements d'avis. Pour obtenir une ligne par établissement, dédupliquez par shop_id côté application.
Étape 4 : Combiner la recherche sémantique avec le filtrage géospatial
Cette étape fusionne « à proximité » (les fonctions spatiales dans filter) et « selon les goûts » (la recherche vectorielle ANN) en un seul appel search(). Elle réutilise circle de l'étape 3.
# ==================== Step 4: Combine semantic search with geospatial filtering ====================
# The ANN search over review vectors handles "on taste", the spatial functions in filter handle "nearby",
# and both are solved jointly in one request
QUERY = "family-friendly Sichuan restaurants nearby"
geo_filter = f"category == 'Sichuan cuisine' && ST_WITHIN(location, '{circle}')"
results = client.search(
collection_name=collection_name,
data=[QUERY],
anns_field="review_embedding",
filter=geo_filter,
limit=20,
output_fields=["shop_id", "shop_name", "category", "location", "review"],
)
print(f"Query: {QUERY}")
for rank, hit in enumerate(results[0], 1):
e = hit["entity"]
print(f" {rank}. [similarity {hit['distance']:.4f}] {e['shop_name']} | {e['review'][:24]}")
L'exemple suivant montre les résultats renvoyés. « Bashu Homestead Family Sichuan » arrive en première position car ses avis mentionnent « chaises hautes » et « serveurs patients », ce qui est sémantiquement proche de « adapté aux familles » :
1. [similarity 0.6926] Bashu Homestead Family Sichuan | Came with my kid. High c
2. [similarity 0.6591] Mala Jianghu | A paradise for bold flav
3. [similarity 0.6091] Bashu Homestead Family Sichuan | You wait for a table on
Étape 5 : Reclassement des candidats avec AI_RERANK
La similarité vectorielle reflète la proximité sémantique, qui n'est pas exactement la même chose que la pertinence d'un résultat par rapport à la requête. Le reclassement des candidats rappelés avec AI_RERANK fait remonter en tête les établissements qui correspondent mieux à l'intention de la requête. Cette étape réutilise QUERY et geo_filter de l'étape 4.
Deux options sont disponibles ; choisissez celle qui correspond à votre scénario :
Reclassement au sein du pipeline de récupération — Attachez un ranker dans
search()(utilisé dans 5.1). Le modèle reclasse les candidats que la recherche ANN rappelle depuis la collection.-
Reclassement d'une liste de textes existante — Appelez l'interface REST sans état
/v2/vectordb/ai/rerank(utilisé dans 5.2). Ce chemin prend directement une liste de textes et ne dépend pas d'une recherche.Les deux sous-sections ci-dessous sont des alternatives, et non des étapes séquentielles.
# ==================== Step 5: Rerank candidates with AI_RERANK ====================
RERANK_MODEL = "qwen3-rerank"
# 5.1 Attach a RERANK ranker in search(): after ANN recall, the model reranks the candidates
reranker = Function(
name="rerank_reviews",
function_type=FunctionType.RERANK,
input_field_names=["review"],
params={
"reranker": "model",
"provider": "aliyun_milvus",
"model_name": RERANK_MODEL,
"queries": [QUERY],
"timeout_sec": 10,
},
)
results = client.search(
collection_name=collection_name,
data=[QUERY],
anns_field="review_embedding",
filter=geo_filter,
limit=20,
output_fields=["shop_id", "shop_name", "review"],
ranker=reranker,
)
for rank, hit in enumerate(results[0], 1):
e = hit["entity"]
print(f" {rank}. [rerank score {hit['distance']:.4f}] {e['shop_name']} | {e['review'][:24]}")
# 5.2 You can also use the stateless REST interface /v2/vectordb/ai/rerank
rerank_docs = [
"Came with my kid. High chairs and small bowls are available, the maoxuewang is not too spicy, the servers are patient, and the place is clean.",
"A paradise for bold flavors, the shuizhuyu is punchy, but the space is small and noisy, so it does not suit kids.",
]
status, data = post_json(
"/v2/vectordb/ai/rerank",
{
"model_name": RERANK_MODEL,
"query": QUERY,
"documents": rerank_docs,
"params": {"timeout_sec": 10},
},
)
assert status == 200 and data.get("code") == 0, data
ranked = sorted(data["data"]["output"]["results"],
key=lambda x: x["relevance_score"], reverse=True)
for rank, item in enumerate(ranked, 1):
print(f" {rank}. [relevance {item['relevance_score']:.4f}] {rerank_docs[item['index']][:28]}")
L'exemple suivant montre la sortie après reclassement. Les deux avis de « Bashu Homestead Family Sichuan » sont classés avant « Mala Jianghu » :
1. [rerank score 0.6986] Bashu Homestead Family Sichuan | Came with my kid. High c
2. [rerank score 0.5389] Bashu Homestead Family Sichuan | You wait for a table on
3. [rerank score 0.5214] Mala Jianghu | A paradise for bold flav
L'interface REST renvoie ses résultats dans data.output.results, où chaque élément correspond à un candidat et contient index (la position du candidat dans le paramètre de requête documents) et relevance_score (le score de pertinence). Un tri décroissant par score donne le résultat reclassé :
1. [relevance 0.6986] Came with my kid. High chairs
2. [relevance 0.5214] A paradise for bold flavors,
Le texte du candidat doit couvrir toutes les dimensions sémantiques de la requête. La requête de cet exemple, « family-friendly Sichuan restaurants nearby », couvre à la fois la dimension « adapté aux familles » et la dimension « cuisine sichuanaise ». Un candidat qui omet l'une ou l'autre perd une part significative de son score. Prenons le premier candidat comme exemple :
|
Texte du candidat |
Score de pertinence |
|
Came with my kid. High chairs and small bowls are available, the servers are patient. (aucune information sur la cuisine sichuanaise) |
0.4723 |
|
Came with my kid. High chairs and small bowls are available, the maoxuewang is not too spicy, the servers are patient. |
0.6666 |
|
Came with my kid. High chairs and small bowls are available, the maoxuewang is not too spicy, the servers are patient, and the place is clean. |
0.6986 |
L'ajout d'un seul nom de plat sichuanais fait passer le score de 0,4723 à 0,6666. En revanche, « A paradise for bold flavors, the shuizhuyu is punchy ... it does not suit kids. » obtient un score sur la dimension cuisine sichuanaise car il mentionne « shuizhuyu », et sa pertinence reste à 0,5214. Ainsi, si le premier candidat ne contenait aucune information sur la cuisine sichuanaise, il serait classé plus bas.
En production, envoyez donc le texte original complet comme candidat pour le reclassement et ne supprimez pas les informations clés susceptibles de porter de la pertinence, telles que les noms de plats ou les mots de catégorie, simplement pour raccourcir le texte. Lorsque vous évaluez la qualité du reclassement, utilisez également le même texte complet que celui utilisé en ligne.
Étape 6 : Calculer le taux d'avis positifs et générer des résumés d'avis
Utilisez AI_SENTIMENT pour classifier le sentiment de tous les avis d'un établissement et agrégez le taux d'avis positifs côté application. Utilisez ensuite AI_SUMMARIZE pour condenser les avis en un commentaire d'une ligne. Cette étape lit les données insérées à l'étape 2.
# ==================== Step 6: Sentiment analysis for the positive-review rate + summary generation ====================
shop_id = 1001
reviews = [r["review"] for r in client.query(
collection_name=collection_name,
filter=f"shop_id == {shop_id}",
output_fields=["review"],
limit=1000,
)]
# 6.1 Classify sentiment and aggregate the positive-review rate
status, sent = post_json("/v2/vectordb/ai/sentiment", {
"model_name": "qwen3.7-max",
"texts": reviews,
"params": {"categories": ["positive", "negative", "neutral"], "temperature": 0},
})
assert status == 200 and sent.get("code") == 0, sent
labels = sent["data"]["output"]["outputs"]
valid = [x for x in labels if x] # Empty values count as undetermined and are excluded from the aggregation
positive = sum(1 for x in valid if x == "positive")
good_rate = positive / len(valid) if valid else 0.0
print(f"Positive-review rate: {good_rate:.0%} (valid samples {len(valid)}/{len(labels)})")
# 6.2 Generate the review summary of the store
status, summ = post_json("/v2/vectordb/ai/summarize", {
"model_name": "qwen3.7-max",
"texts": ["\n".join(reviews)],
"params": {"max_words": 40, "temperature": 0,
"prompt": "Summarize the overall reviews of this store in English, highlighting the signature dishes, the diners it suits, and the atmosphere."},
})
assert status == 200 and summ.get("code") == 0, summ
print(f"Store summary: {summ['data']['output']['outputs'][0]}")
Prenons shop_id 1001 comme exemple : les deux avis sont classés comme positifs, le taux d'avis positifs est de 100 % et le résumé généré est « The place is clean and the maoxuewang is mild and palatable. The service is patient and children's tableware is available, so it suits family meals with kids, and you wait for a table on weekends. »
L'agrégation compare chaque étiquette aux valeurs transmises dans le paramètre de requête categories. AI_SENTIMENT peut également renvoyer une valeur vide lorsque le modèle ne peut pas déterminer le sentiment ; filtrez les valeurs vides avant l'agrégation, comme illustré par la liste valid.
Types Geometry et fonctions spatiales
Cette section décrit le type GEOMETRY et les fonctions de relation spatiale ST_ utilisées dans la procédure.
GEOMETRY et WKT
Si vous stockez la longitude et la latitude dans deux champs DOUBLE distincts, la base de données les traite comme deux nombres et ne peut pas déterminer « lequel est à l'intérieur de l'autre » ni « si deux zones se croisent ». GEOMETRY est le type géospatial fourni par Milvus 2.6. Il stocke les points, les lignes et les polygones sous forme d'objets géométriques, ce qui permet des opérations de relation spatiale et permet à un index géospatial d'accélérer les requêtes.
Les valeurs d'un champ GEOMETRY utilisent le format WKT (Well-Known Text). Les types courants sont les suivants :
|
Type WKT |
Exemple |
Signification |
Équivalent dans cette rubrique |
|
|
|
Un point unique |
Les coordonnées d'un établissement |
|
|
|
Une polyligne |
Un tronçon de route ou un itinéraire de livraison |
|
|
|
Une zone fermée dont les premières et dernières coordonnées sont identiques |
Un quartier d'affaires ou une zone de livraison |
|
|
|
Plusieurs points |
Plusieurs établissements d'une même marque |
|
|
|
Plusieurs zones |
Plusieurs districts administratifs |
Les coordonnées sont ordonnées longitude d'abord, latitude ensuite, soit POINT(lng lat), ce qui est l'inverse de l'ordre oral « latitude ... longitude ... ». Toutes les coordonnées de cette rubrique utilisent le système de coordonnées WGS84.
Fonctions de relation spatiale ST_
Les fonctions commençant par ST_ déterminent la relation spatiale entre deux objets géométriques. Vous les écrivez dans une expression filter et elles renvoient true ou false. Cette rubrique utilise principalement les trois suivantes :
|
Fonction |
Description |
Utilisation dans cette rubrique |
|
|
Indique si A se trouve entièrement à l'intérieur de B. |
Détermine si un établissement se trouve à l'intérieur d'un polygone de quartier d'affaires ou dans un rayon donné. |
|
|
Indique si A contient entièrement B, l'inverse de |
Détermine si un quartier d'affaires contient un établissement donné, pour une analyse d'attribution. |
|
|
Indique si A et B ont une intersection ou un contact. |
Détermine si la zone de livraison d'un établissement intersecte la cellule de grille où se trouve l'utilisateur. |
Les fonctions suivantes sont également prises en charge et peuvent être utilisées selon les besoins :
|
Fonction |
Description |
Scénario |
|
|
Deux objets géométriques sont identiques. |
Déduplication géométrique. |
|
|
Seules les limites se touchent et les intérieurs ne se chevauchent pas. |
Détermine si des quartiers d'affaires adjacents sont frontaliers. |
|
|
Chevauchement partiel sans containment. |
Détermine si les zones de livraison se croisent. |
|
|
Intersection de croisement. |
Détermine si une route traverse un quartier d'affaires. |
Logique de classement du leaderboard
L'enchaînement des étapes précédentes donne un flux complet de génération de classement : le filtrage géospatial réduit les candidats à « à proximité », la recherche sémantique associe « selon les goûts », le reclassement ajuste l'ordre et l'analyse des sentiments ainsi que la synthèse complètent la « réputation ». Pour produire une ligne par établissement, dédupliquez les candidats par shop_id côté application.
La position finale peut fusionner trois signaux :
Signal géospatial : le fait qu'un établissement se trouve dans le rayon ou le quartier d'affaires sert de seuil d'admission et les établissements non qualifiés sont directement filtrés. Les établissements qualifiés obtiennent ensuite un poids de base en fonction de la distance.
Pertinence sémantique : le
relevance_scorerenvoyé parAI_RERANK, qui mesure dans quelle mesure les avis d'un établissement correspondent à la demande de l'utilisateur.-
Réputation : le taux d'avis positifs agrégé par
AI_SENTIMENT.Vous pouvez combiner ces signaux avec une fusion pondérée sous la forme
w1 · relevance score + w2 · positive-review rate + w3 · distance decayet ajuster les poids en fonction de vos objectifs métier. Avec cette fusion, un établissement peut obtenir un rang global élevé car il correspond fortement à la sémantique « adapté aux familles » et parce que cette correspondance est combinée à un taux d'avis positifs élevé et à une distance plus courte, plutôt que par la seule note.
Extensions supplémentaires
Recherche multimodale d'établissements : utilisez
qwen3-vl-embeddingpour vectoriser les photos des établissements et des plats afin de prendre en charge la recherche d'établissements basée sur l'image, telle que « un endroit nearby avec ce type de décoration », qui peut également être fusionnée avec le filtrage géospatial en une seule recherche.Précalcul hors ligne de la réputation : lorsqu'un établissement possède un grand nombre d'avis, utilisez des méthodes par lots telles que
AI_BATCHpour précalculer hors ligne le taux d'avis positifs et le résumé des avis, puis persistez-les, de sorte que les requêtes de classement lisent uniquement les résultats, ce qui équilibre coût et latence.Relations spatiales plus riches : utilisez
ST_INTERSECTSpour déterminer si la zone de livraison d'un établissement intersecte la cellule de grille où se trouve l'utilisateur, et utilisezST_CONTAINSpour l'analyse d'attribution des quartiers d'affaires, étendant ainsi la fusion « géospatial + IA » de la recherche d'établissements à la dispatching, à la sélection de sites et à d'autres étapes.