O Alibaba Cloud Milvus integra o tipo geoespacial GEOMETRY às AI Functions, permitindo que uma única busca execute filtragem por intervalo geoespacial e recuperação semântica na mesma Collection. Este tópico descreve como executar simultaneamente a filtragem "próximo" e a recuperação "adequado ao gosto", aplicando análise de sentimento e geração de resumo para construir um ranking explicável de estabelecimentos locais.
Visão geral da solução
A pergunta "o que comer perto daqui" é a solicitação mais frequente em cenários de service locais. Para respondê-la adequadamente, o sistema precisa atender a três condições simultaneamente:
Proximidade: lojas dentro de um raio definido a partir da localização atual do usuário ou dentro do bairro comercial onde ele se encontra.
Adequação ao gosto: solicitações como "um restaurante de Sichuan adequado para famílias" ou "um café tranquilo para conversar sobre negócios", impossíveis de listar com precisão apenas por tags.
-
Confiabilidade: o conteúdo das avaliações reais, a taxa de avaliações positivas e os pontos fortes ou reclamações recorrentes.
Implementações tradicionais geralmente conectam três sistemas: um banco de dados espacial para filtragem geoespacial, um banco de dados vetorial para recuperação semântica e um service de NLP para classificação de sentimento e sumarização. Cada sistema mantém sua própria cópia dos dados, tornando custosas as junções entre sistemas, a sincronização de dados e a manutenção da consistência. Além disso, uma única consulta precisa encadear vários service e múltiplas idas e vindas na rede.
|
Dimensão |
Solução tradicional (banco de dados espacial + banco de dados vetorial + service de NLP) |
Alibaba Cloud Milvus |
|
Número de sistemas |
3 ou mais, cada um com sua própria cópia dos dados |
1, com uma única source de dados |
|
Fusão geoespacial e semântica |
Junções entre sistemas ou múltiplas rodadas de recuperação |
Uma única chamada |
|
Compreensão de avaliações |
Modelos de sentimento e sumarização próprios ou externos |
|
|
Caminho de vetorização |
A aplicação chama o embedding primeiro e depois grava os dados |
Vetorizado na inserção |
|
Consistência e sincronização |
Mantidas manualmente |
Naturalmente consistentes em um único banco de dados |
O Alibaba Cloud Milvus converge essas capacidades em uma única Collection e uma única busca:
|
Capacidade |
Implementação |
|
Filtragem por intervalo geoespacial |
Armazene longitude e latitude em um campo |
|
Busca semântica em avaliações |
O |
|
Reordenação por relevância |
O |
|
Quantificação de reputação |
Use o |
A filtragem geoespacial e a busca semântica ocorrem em uma única chamada search(): a busca vetorial ANN cuida da "adequação ao gosto" e as funções espaciais no filter cuidam da "proximidade". O sistema resolve ambas conjuntamente em uma única requisição e aplica a reordenação em seguida para produzir diretamente os candidatos do ranking.
Este tópico utiliza as seguintes AI Functions:
|
Função |
O que faz |
Papel no ranking |
Como é chamada |
|
|
Converte o texto da avaliação em vetores automaticamente na inserção, eliminando a necessidade de a aplicação chamar um modelo previamente. |
Permite que solicitações semânticas como "um restaurante de Sichuan adequado para famílias" sejam atendidas por busca vetorial. |
Declarada como Function no schema da Collection ( |
|
|
Reordena os candidatos recuperados pela relevância em relação à consulta. |
Move as lojas que melhor atendem à solicitação para o topo do ranking. |
Anexada como ranker na |
|
|
Determina se uma avaliação é positiva, negativa ou neutra. |
Agrega a taxa de avaliações positivas de uma loja. |
Chamada via endpoint REST |
|
|
Condensa um grande volume de avaliações em um resumo curto. |
Gera o comentário de uma linha exibido no ranking. |
Chamada via endpoint REST |
Você pode declarar o AI_EMBEDDING e o AI_RERANK na Collection ou anexá-los a uma busca. Já o AI_SENTIMENT e o AI_SUMMARIZE estão disponíveis apenas pela interface REST.
Pré-requisitos
Instância do Milvus 2.6 criada. O tipo
GEOMETRYe as AI Functions exigem o kernel 2.6. Após criar a instância, declare a Function diretamente na Collection; não é necessário vincular um service de modelo separadamente.Para acessar a instância pela internet, ative o Public Network Access na aba Security Configuration da página de detalhes da instância e adicione o IP de saída do cliente à lista de permissões de acesso público.
pymilvus instalado. Os exemplos deste tópico foram validados com o pymilvus 3.0.0.
Limitações e notas de uso
Revise os comportamentos abaixo antes de copiar o código, pois vários deles produzem resultados silenciosos ou vazios em vez de erros:
Semântica de limites geoespaciais — Quando um objeto geométrico está exatamente sobre o limite de um polígono, o
ST_WITHINretorna falso e oST_INTERSECTSretorna verdadeiro. Por exemplo, ao aproximar um círculo de raio por um polígono, oST_WITHINnão corresponde a uma loja situada exatamente na circunferência. Se a lógica do seu negócio precisar incluir o limite, utilize oST_INTERSECTS. Para umMULTIPOINT, oST_WITHINretorna falso assim que um de seus pontos estiver sobre o limite.Precisão da aproximação de raio — O
circle_to_wktconverte longitude e latitude com uma aproximação planar; portanto, o erro aumenta conforme a latitude cresce. Esse método serve para filtragem grosseira por raio. Caso a lógica do seu negócio seja sensível à precisão do limite, aumente a contagem de segmentosnum_segou verifique novamente a distância exata na aplicação.Porta da interface REST — A interface RESTful compartilha a porta 19530 com o
gRPC. Inclua a porta explicitamente no endereço ao chamar a interface REST, por exemplohttp://c-xxx.milvus.aliyuncs.com:19530. Se você omitir a porta, a requisição irá para a porta 80 por padrão e a conexão expirará.Parâmetros de rerank via REST — Na interface REST de rerank,
documentsdeve ser um array de strings equerydeve ser uma string no nível superior. O parâmetrotop_nnão é suportado; enviá-lo retornaparam [top_n] is not supported. A interface sempre retorna uma pontuação por candidato. Se precisar truncar a lista, ordene pela pontuação e selecione os N primeiros na aplicação.Sentimento indeterminado — O
AI_SENTIMENTpode retornar um valor vazio, indicando que o modelo não conseguiu determinar o sentimento. Filtre os valores vazios antes de agregar a taxa de avaliações positivas para que amostras indeterminadas não sejam contadas no denominador.
Procedimento
Execute as etapas em ordem dentro da mesma sessão. Os trechos de código formam um script contínuo: as etapas posteriores reutilizam o cliente, as funções auxiliares e as variáveis definidas nas etapas anteriores (por exemplo, circle da Etapa 3, além de QUERY e geo_filter da Etapa 4). Se copiar uma única etapa isoladamente, defina primeiro suas variáveis dependentes.
Etapa 0: Preparar o código compartilhado (obrigatório)
O código a seguir contém as configurações de conexão, um wrapper para a interface REST e uma função utilitária que aproxima um círculo de raio como um polígono. Todas as etapas subsequentes dependem deste código. Substitua MILVUS_HOST e MILVUS_TOKEN pelas informações da sua própria instância.
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)}))"
O valor de MILVUS_HOST é usado como URI do cliente, enquanto MILVUS_REST_BASE_URL acrescenta a porta 19530 para as chamadas REST nas etapas seguintes. A exigência de porta explícita aplica-se à interface REST.
Etapa 1: Criar uma collection com campo geoespacial e função de embedding
O campo location usa DataType.GEOMETRY para armazenar o ponto de coordenadas de uma loja. O campo review carrega uma função AI_EMBEDDING que gera automaticamente um vetor de 1.024 dimensões no momento da inserção. Simultaneamente, crie um índice HNSW para o campo vetorial e um índice R-Tree para o campo geoespacial.
O código abaixo executa drop_collection antes de criar a Collection, o que exclui permanentemente qualquer Collection existente chamada shop_reviews e todos os seus dados. Em uma instância que já contenha dados de produção, use um nome de Collection de teste separado ou remova a ramificação 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,
)
A dimensão de review_embedding deve corresponder ao valor dim nos parâmetros da Function. Criar um índice RTREE para o campo GEOMETRY acelera as consultas espaciais ST_ e é recomendado quando o volume de dados é grande.
Os valores de model_name neste tópico (qwen3.7-text-embedding aqui, e os modelos de rerank, sentimento e sumarização nas etapas seguintes) são exemplos. Substitua cada um por um modelo disponível na sua própria instância.
Etapa 2: Inserir dados de lojas e avaliações
Insira as coordenadas das lojas e o texto bruto das avaliações. O review_embedding é gerado automaticamente pela 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)
Chame flush() após inserir os dados; caso contrário, uma busca executada imediatamente depois poderá retornar resultados vazios. O load_collection() carrega a Collection na memória, operação obrigatória antes de buscar dados.
Etapa 3: Filtrar por intervalo geoespacial
Escolha o método de intervalo com base no formato da área alvo:
Distrito administrativo ou bairro comercial — Represente a área como um
POLYGONe use oST_WITHIN.Área ao redor da localização atual do usuário — Aproxime um círculo de raio como um polígono com o
circle_to_wkte depois use oST_WITHIN.-
Lógica que deve incluir o limite ou detectar áreas sobrepostas — Utilize o
ST_INTERSECTS, pois oST_WITHINnão corresponde a uma geometria localizada exatamente no limite.O código a seguir mostra tanto o polígono do bairro comercial quanto o círculo de raio. O
circle_to_wktaproxima o círculo de raio como um polígono.
# ==================== 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']}")
Os dados de exemplo possuem 4 registros de avaliação em 3 lojas (Bashu Homestead tem 2 avaliações), então ambas as consultas retornam 4 registros. Para gerar uma linha por loja, deduplique por shop_id na aplicação.
Etapa 4: Combinar busca semântica com filtragem geoespacial
Esta etapa funde "proximidade" (funções espaciais no filter) e "adequação ao gosto" (busca vetorial ANN) em uma única chamada search(). Ela reutiliza o circle da Etapa 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]}")
O exemplo a seguir mostra os resultados retornados. "Bashu Homestead Family Sichuan" aparece em primeiro lugar porque suas avaliações mencionam "high chairs" e "the servers are patient", termos semanticamente próximos de "family-friendly":
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
Etapa 5: Reordenar candidatos com AI_RERANK
A similaridade vetorial reflete proximidade semântica, o que não equivale exatamente à relevância de um resultado para a consulta. Reordenar os candidatos recuperados com o AI_RERANK move as lojas que melhor atendem à intenção da consulta para as primeiras posições. Esta etapa reutiliza QUERY e geo_filter da Etapa 4.
Duas opções estão disponíveis; escolha a adequada para o seu cenário:
Reordenar dentro do pipeline de recuperação — Anexe um ranker na
search()(usado em 5.1). O modelo reordena os candidatos que a busca ANN recupera da Collection.-
Reordenar uma lista existente de textos — Chame a interface REST stateless
/v2/vectordb/ai/rerank(usado em 5.2). Esse caminho recebe uma lista de textos diretamente e não depende de uma busca.As duas subseções abaixo são alternativas, não etapas sequenciais.
# ==================== 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]}")
O exemplo a seguir mostra a saída após a reordenação. Ambas as avaliações de "Bashu Homestead Family Sichuan" ficam à frente de "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
A interface REST retorna seus resultados em data.output.results, onde cada item corresponde a um candidato e contém index (a posição do candidato no parâmetro de requisição documents) e relevance_score (a pontuação de relevância). Ordenar pela pontuação em ordem decrescente fornece o resultado reordenado:
1. [relevance 0.6986] Came with my kid. High chairs
2. [relevance 0.5214] A paradise for bold flavors,
O texto do candidato deve cobrir todas as dimensões semânticas da consulta. A consulta neste exemplo, "family-friendly Sichuan restaurants nearby", abrange tanto a dimensão "family-friendly" quanto a dimensão "Sichuan cuisine". Um candidato que omitir qualquer uma delas perde uma quantidade significativa de pontuação. Tome o primeiro candidato como exemplo:
|
Texto do candidato |
Pontuação de relevância |
|
Came with my kid. High chairs and small bowls are available, the servers are patient. (sem informação sobre culinária de Sichuan) |
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 |
Adicionar o nome de um único prato de Sichuan eleva a pontuação de 0,4723 para 0,6666. Em contraste, "A paradise for bold flavors, the shuizhuyu is punchy ... it does not suit kids." pontua na dimensão culinária de Sichuan porque menciona "shuizhuyu", e sua relevância permanece em 0,5214. Portanto, se o primeiro candidato não tivesse informações sobre culinária de Sichuan, seria classificado em uma posição inferior.
Em produção, envie o texto original completo como candidato para reordenação. Não remova informações-chave que possam carregar relevância, como nomes de pratos ou palavras de categoria, apenas para encurtar o texto. Ao avaliar a qualidade da reordenação, utilize também o mesmo texto completo usado em produção.
Etapa 6: Calcular a taxa de avaliações positivas e gerar resumos de avaliações
Utilize o AI_SENTIMENT para classificar o sentimento de todas as avaliações de uma loja e agregue a taxa de avaliações positivas na aplicação. Em seguida, use o AI_SUMMARIZE para condensar as avaliações em um comentário de uma linha. Esta etapa lê os dados inseridos na Etapa 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]}")
Tomando o shop_id 1001 como exemplo: ambas as avaliações são classificadas como positivas, a taxa de avaliações positivas é de 100% e o resumo gerado é "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."
A agregação compara cada rótulo com os valores passados no parâmetro de requisição categories. O AI_SENTIMENT também pode retornar um valor vazio quando o modelo não consegue determinar o sentimento; filtre os valores vazios antes de agregar, conforme demonstrado pela lista valid.
Tipos de geometria e funções espaciais
Esta seção descreve o tipo GEOMETRY e as funções de relação espacial ST_ utilizadas no procedimento.
GEOMETRY e WKT
Se você armazenar longitude e latitude como dois campos DOUBLE, o banco de dados os tratará como dois números e não conseguirá determinar "qual está dentro de qual" ou "se duas áreas se interceptam". O GEOMETRY é o tipo geoespacial fornecido pelo Milvus 2.6. Ele armazena pontos, linhas e polígonos como objetos geométricos, o que possibilita operações de relação espacial e permite que um índice geoespacial acelere as consultas.
Os valores de um campo GEOMETRY usam o formato WKT (Well-Known Text). Os tipos comuns são:
|
Tipo WKT |
Exemplo |
Significado |
Correspondente neste tópico |
|
|
|
Um único ponto |
As coordenadas de uma loja |
|
|
|
Uma polilinha |
Um segmento de estrada ou rota de entrega |
|
|
|
Uma área fechada cujas primeira e última coordenadas são idênticas |
Um bairro comercial ou área de entrega |
|
|
|
Múltiplos pontos |
Várias lojas de uma mesma marca |
|
|
|
Múltiplas áreas |
Vários distritos administrativos |
As coordenadas seguem a ordem longitude primeiro e latitude segundo, ou seja, POINT(lng lat), que é o inverso da ordem falada "latitude ... longitude ...". Todas as coordenadas neste tópico utilizam o sistema de coordenadas WGS84.
Funções de relação espacial ST_
Funções que começam com ST_ determinam a relação espacial entre dois objetos geométricos. Escreva-as em uma expressão filter; elas retornam verdadeiro ou falso. Este tópico utiliza principalmente as três seguintes:
|
Função |
Descrição |
Uso neste tópico |
|
|
Se A está inteiramente dentro de B. |
Determina se uma loja está dentro de um polígono de bairro comercial ou dentro de um raio definido. |
|
|
Se A contém inteiramente B, o inverso de |
Determina se um bairro comercial contém uma determinada loja, para análise de atribuição. |
|
|
Se A e B têm alguma interseção ou contato. |
Determina se a área de entrega de uma loja intercepta a célula de grade onde o usuário está. |
As funções a seguir também são suportadas e podem ser usadas conforme necessário:
|
Função |
Descrição |
Cenário |
|
|
Dois objetos geométricos são idênticos. |
Deduplicação de geometrias. |
|
|
Apenas os limites se tocam e os interiores não se sobrepõem. |
Determina se bairros comerciais adjacentes fazem fronteira entre si. |
|
|
Sobreposição parcial sem contenção. |
Determina se áreas de entrega se cruzam. |
|
|
Interseção de travessia. |
Determina se uma estrada atravessa um bairro comercial. |
Lógica de classificação do ranking
Encadear as etapas anteriores resulta em um fluxo completo de geração de ranking: a filtragem geoespacial restringe os candidatos a "próximos", a busca semântica identifica os "adequados ao gosto", a reordenação ajusta a ordem, e o sentimento somado à sumarização preenche a "reputação". Para produzir uma linha por loja, deduplique os candidatos por shop_id na aplicação.
A posição final pode fundir três sinais:
Sinal geoespacial: estar dentro do raio ou do bairro comercial atua como limiar de admissão; lojas que não se qualificam são filtradas diretamente. As lojas qualificadas recebem então um peso base de acordo com a distância.
Relevância semântica: o
relevance_scoreretornado peloAI_RERANK, que mede o quão bem as avaliações de uma loja atendem à solicitação do usuário.-
Reputação: a taxa de avaliações positivas agregada pelo
AI_SENTIMENT.Combine esses sinais com uma fusão ponderada no formato
w1 · relevance score + w2 · positive-review rate + w3 · distance decaye ajuste os pesos conforme seus objetivos de negócio. Sob essa fusão, uma loja pode alcançar uma alta classificação geral porque corresponde fortemente à semântica "family-friendly" e porque essa correspondência é combinada com uma alta taxa de avaliações positivas e uma distância menor, e não apenas pela avaliação em si.
Extensões adicionais
Busca multimodal de lojas: utilize o
qwen3-vl-embeddingpara vetorizar fotos de lojas e pratos, permitindo buscas por imagem como "um lugar próximo com este tipo de decoração". Essa abordagem também pode ser fundida com filtragem geoespacial em uma única busca.Pré-computação offline de reputação: quando uma loja possui um grande volume de avaliações, use métodos em lote como o
AI_BATCHpara pré-computar offline a taxa de avaliações positivas e o resumo das avaliações e persisti-los. Assim, as consultas do ranking apenas leem os resultados, equilibrando custo e latência.Relações espaciais mais ricas: utilize o
ST_INTERSECTSpara determinar se a área de entrega de uma loja intercepta a célula de grade onde o usuário está, e use oST_CONTAINSpara análise de atribuição de bairro comercial. Isso estende a fusão "geoespacial + IA" da busca de lojas para despacho, seleção de locais e outras etapas.