Génération d'expressions de filtre de requête Milvus à l'aide de grands modèles de langage
Dans ce tutoriel, nous allons montrer comment utiliser les grands modèles de langage (LLM) pour générer automatiquement des expressions de filtre Milvus à partir de requêtes en langage naturel. Cette approche rend l'interrogation des bases de données vectorielles plus accessible en permettant aux utilisateurs d'exprimer des conditions de filtrage complexes en anglais simple, qui sont ensuite converties en syntaxe Milvus appropriée.
Milvus prend en charge des capacités de filtrage sophistiquées, notamment
- Opérateurs de base: Opérateurs de comparaison tels que
==,!=,>,<,>=,<= - Opérateurs booléens: Opérateurs logiques tels que
and,or,notpour les conditions complexes. - Opérations sur les chaînes de caractères: Correspondance de motifs avec
likeet autres fonctions de chaînes de caractères - Opérations sur les tableaux: Travailler avec des champs de type tableau en utilisant
array_contains,array_length, etc. - Opérations JSON: Interrogation des champs JSON à l'aide d'opérateurs spécialisés
En intégrant les LLM à la documentation Milvus, nous pouvons créer un système intelligent qui comprend les requêtes en langage naturel et génère des expressions de filtre syntaxiquement correctes. Ce tutoriel décrit le processus de mise en place de ce système, en soulignant son efficacité dans divers scénarios de filtrage.
Dépendances et environnement
$ pip install --upgrade pymilvus openai requests docling beautifulsoup4
print("Environment setup complete!")
Configurer les variables d'environnement
Configurez vos identifiants API OpenAI pour permettre la génération d'embedding et la création d'expressions de filtrage basées sur LLM. Remplacez 'your_openai_api_key' par votre véritable clé API OpenAI.
import os
import openai
os.environ["OPENAI_API_KEY"] = "your_openai_api_key"
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
raise ValueError("Please set the OPENAI_API_KEY environment variable!")
openai.api_key = api_key
print("API key loaded.")
Créer une collection d'échantillons
Créons maintenant un échantillon de collection Milvus avec des données utilisateur. Cette collection contiendra à la fois des champs scalaires (pour le filtrage) et des embeddings vectoriels (pour la recherche sémantique). Nous utiliserons le modèle d'intégration de texte d'OpenAI pour générer des représentations vectorielles des informations de l'utilisateur.
from pymilvus import MilvusClient, FieldSchema, CollectionSchema, DataType
import os
from openai import OpenAI
import uuid
client = MilvusClient(uri="http://localhost:19530")
openai_client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
embedding_model = "text-embedding-3-small"
embedding_dim = 1536
fields = [
FieldSchema(
name="pk",
dtype=DataType.VARCHAR,
is_primary=True,
auto_id=False,
max_length=100,
),
FieldSchema(name="name", dtype=DataType.VARCHAR, max_length=128),
FieldSchema(name="age", dtype=DataType.INT64),
FieldSchema(name="city", dtype=DataType.VARCHAR, max_length=128),
FieldSchema(name="hobby", dtype=DataType.VARCHAR, max_length=128),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=embedding_dim),
]
schema = CollectionSchema(fields=fields, description="User data embedding example")
collection_name = "user_data_collection"
if client.has_collection(collection_name):
client.drop_collection(collection_name)
# Strong consistency waits for all loads to complete, adding latency with large datasets
# client.create_collection(
# collection_name=collection_name, schema=schema, consistency_level="Strong"
# )
client.create_collection(collection_name=collection_name, schema=schema)
index_params = client.prepare_index_params()
index_params.add_index(
field_name="embedding",
index_type="IVF_FLAT",
metric_type="COSINE",
params={"nlist": 128},
)
client.create_index(collection_name=collection_name, index_params=index_params)
data_to_insert = [
{"name": "John", "age": 23, "city": "Shanghai", "hobby": "Drinking coffee"},
{"name": "Alice", "age": 29, "city": "New York", "hobby": "Reading books"},
{"name": "Bob", "age": 31, "city": "London", "hobby": "Playing chess"},
{"name": "Eve", "age": 27, "city": "Paris", "hobby": "Painting"},
{"name": "Charlie", "age": 35, "city": "Tokyo", "hobby": "Cycling"},
{"name": "Grace", "age": 22, "city": "Berlin", "hobby": "Photography"},
{"name": "David", "age": 40, "city": "Toronto", "hobby": "Watching movies"},
{"name": "Helen", "age": 30, "city": "Sydney", "hobby": "Cooking"},
{"name": "Frank", "age": 28, "city": "Beijing", "hobby": "Hiking"},
{"name": "Ivy", "age": 26, "city": "Seoul", "hobby": "Dancing"},
{"name": "Tom", "age": 33, "city": "Madrid", "hobby": "Writing"},
]
def get_embeddings(texts):
return [
rec.embedding
for rec in openai_client.embeddings.create(
input=texts, model=embedding_model, dimensions=embedding_dim
).data
]
texts = [
f"{item['name']} from {item['city']} is {item['age']} years old and likes {item['hobby']}."
for item in data_to_insert
]
embeddings = get_embeddings(texts)
insert_data = []
for item, embedding in zip(data_to_insert, embeddings):
item_with_embedding = {
"pk": str(uuid.uuid4()),
"name": item["name"],
"age": item["age"],
"city": item["city"],
"hobby": item["hobby"],
"embedding": embedding,
}
insert_data.append(item_with_embedding)
client.insert(collection_name=collection_name, data=insert_data)
print(f"Collection '{collection_name}' has been created and data has been inserted.")
Imprimer 3 exemples de données
Le code ci-dessus crée une collection Milvus avec la structure suivante :
- pk: Champ de clé primaire (VARCHAR)
- name: Nom de l'utilisateur (VARCHAR)
- age: âge de l'utilisateur (INT64)
- city : Ville de l'utilisateur (VARCHAR) ville de l'utilisateur (VARCHAR)
- hobby: hobby de l'utilisateur (VARCHAR)
- embedding: Intégration vectorielle (FLOAT_VECTOR, 1536 dimensions)
Nous avons inséré 11 exemples d'utilisateurs avec leurs informations personnelles et nous avons généré des embeddings pour les capacités de recherche sémantique. Les informations de chaque utilisateur sont converties en un texte descriptif qui contient son nom, sa localisation, son âge et ses centres d'intérêt avant d'être intégré. Vérifions que notre collection a été créée avec succès et qu'elle contient les données attendues en interrogeant quelques enregistrements échantillons.
from pymilvus import MilvusClient
import os
from openai import OpenAI
client = MilvusClient(uri="http://localhost:19530")
collection_name = "user_data_collection"
client.load_collection(collection_name=collection_name)
result = client.query(
collection_name=collection_name,
filter="",
output_fields=["name", "age", "city", "hobby"],
limit=3,
)
for record in result:
print(record)
Collecte de la documentation sur l'expression du filtre Milvus
Pour aider le grand modèle de langage à mieux comprendre la syntaxe des expressions de filtre de Milvus, nous devons lui fournir une documentation officielle pertinente. Nous utiliserons la bibliothèque docling pour récupérer plusieurs pages clés du site Web officiel de Milvus.
Ces pages contiennent des informations détaillées sur
- les opérateurs booléens:
and,or,notpour les conditions logiques complexes - Les opérateurs de base: Opérateurs de comparaison comme
==,!=,>,<,>=,<= - Modèles de filtrage: Modèles de filtrage avancés et syntaxe
- Correspondance de chaînes: Correspondance de motifs avec
likeet autres opérations sur les chaînes.
Cette documentation servira de base de connaissances à notre LLM pour générer des expressions de filtrage précises.
import docling
from docling.document_converter import DocumentConverter
converter = DocumentConverter()
docs = [
converter.convert(url)
for url in [
"https://milvus.io/docs/boolean.md",
"https://milvus.io/docs/basic-operators.md",
"https://milvus.io/docs/filtering-templating.md",
]
]
for doc in docs[:3]:
print(doc.document.export_to_markdown())
La documentation scraping fournit une couverture complète de la syntaxe de filtrage Milvus. Cette base de connaissances permettra à notre LLM de comprendre les nuances de la construction des expressions de filtre, y compris l'utilisation correcte des opérateurs, le référencement des champs et les combinaisons de conditions complexes.
Génération de filtres par le LLM
Maintenant que nous disposons du contexte de documentation, configurons le système LLM pour générer des expressions de filtre. Nous allons créer une invite structurée qui combine la documentation récupérée avec les requêtes de l'utilisateur pour produire des expressions de filtre Milvus syntaxiquement correctes.
Notre système de génération de filtres utilise une invite soigneusement conçue qui :
- Fournit un contexte: inclut la documentation complète de Milvus comme matériel de référence
- Fixe des contraintes: garantit que le MLD n'utilise que la syntaxe et les fonctionnalités documentées
- assure la précision: Exige des expressions syntaxiquement correctes
- Maintient l'attention: Renvoie uniquement l'expression du filtre sans explications
Testons ceci avec une requête en langage naturel et voyons les performances du LLM.
from openai import OpenAI
import json
from IPython.display import display, Markdown
context = "\n".join([doc.document.export_to_markdown() for doc in docs])
prompt = f"""
You are an expert Milvus vector database engineer. Your task is to convert a user's natural language query into a valid Milvus filter expression, using the provided Milvus documentation as your knowledge base.
Follow these rules strictly:
1. Only use the provided documents as your source of knowledge.
2. Ensure the generated filter expression is syntactically correct.
3. If there isn't enough information in the documents to create an expression, state that directly.
4. Only return the final filter expression. Do not include any explanations or extra text.
---
**Milvus Documentation Context:**
{context}
---
**User Query:**
{user_query}
---
**Filter Expression:**
"""
client = OpenAI()
def generate_filter_expr(user_query):
"""
Generates a Milvus filter expression from a user query using GPT-4o-mini.
"""
completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": prompt},
{"role": "user", "content": user_query},
],
temperature=0.0,
)
return completion.choices[0].message.content
user_query = "Find people older than 30 who live in London, Tokyo, or Toronto"
filter_expr = generate_filter_expr(user_query)
print(f"Generated filter expression: {filter_expr}")
Le LLM a généré avec succès une expression de filtrage qui combine plusieurs conditions :
- Comparaison d'âge à l'aide de l'opérateur
> - Comparaison de plusieurs villes à l'aide de l'opérateur
in - Référencement des champs et syntaxe appropriés
Ceci démontre la puissance de la fourniture d'un contexte de documentation complet pour guider la génération de filtres LLM.
Test du filtre généré
Testons maintenant notre expression de filtre générée en l'utilisant dans une opération de recherche Milvus réelle. Nous allons combiner la recherche sémantique avec un filtrage précis pour trouver les utilisateurs qui correspondent à la fois à l'intention de la requête et aux critères spécifiques.
from pymilvus import MilvusClient
from openai import OpenAI
import os
client = MilvusClient(uri="http://localhost:19530")
openai_client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
clean_filter = (
filter_expr.replace("```", "").replace('filter="', "").replace('"', "").strip()
)
print(f"Using filter: {clean_filter}")
query_embedding = (
openai_client.embeddings.create(
input=[user_query], model="text-embedding-3-small", dimensions=1536
)
.data[0]
.embedding
)
search_results = client.search(
collection_name="user_data_collection",
data=[query_embedding],
limit=10,
filter=clean_filter,
output_fields=["pk", "name", "age", "city", "hobby"],
search_params={
"metric_type": "COSINE",
"params": {"nprobe": 10},
},
)
print("Search results:")
for i, hits in enumerate(search_results):
print(f"Query {i}:")
for hit in hits:
print(f" - {hit}")
print()
Analyse des résultats
Les résultats de la recherche démontrent l'intégration réussie des filtres générés par LLM avec la recherche vectorielle Milvus. Le filtre a correctement identifié les utilisateurs qui :
- sont âgés de plus de 30 ans
- vivent à Londres, Tokyo ou Toronto
- correspondent au contexte sémantique de la requête.
Cette approche combine la précision du filtrage structuré avec la flexibilité de la saisie en langage naturel, ce qui rend les bases de données vectorielles plus accessibles aux utilisateurs qui ne sont pas nécessairement familiarisés avec la syntaxe spécifique des requêtes.