Chat Towards Data Science : créer un chatbot avec Zilliz Cloud
Cet article est la première partie de la série de blogs Chat Towards Data Science.
À l’ère de l’IA générative, les développeurs exploitent les grands modèles de langage (LLM) pour des applications plus intelligentes. Cependant, les LLM sont sujets aux hallucinations en raison de leurs connaissances limitées. La génération augmentée par récupération (RAG) résout efficacement ce problème en complétant les LLM avec des connaissances externes. Dans ma série de blogs Chat Towards Data Science, je vous guiderai dans la création d’un chatbot basé sur la RAG en utilisant votre jeu de données comme socle de connaissances.
Dans la première partie de ma série de blogs, nous vous guiderons dans la création d’un chatbot pour le site web Towards Data Science, en utilisant le web scraping pour créer une base de connaissances stockée dans Zilliz Cloud, un service de base de données vectorielle entièrement géré construit sur Milvus.
Extraction de données web avec BeautifulSoup4
La première étape de tout projet de machine learning (ML) consiste à rassembler les données nécessaires. Pour ce projet, nous employons des techniques de web scraping afin de collecter des données pour notre base de connaissances. Nous utilisons la bibliothèque requests pour récupérer des pages web, puis BeautifulSoup4 (une bibliothèque qui extrait des informations des pages web) pour analyser les informations HTML et extraire les paragraphes.
Préparation de BeautifulSoup4 et Requests pour le web scraping
Pour commencer, installez BeautifulSoup en exécutant pip install beautifulsoup4 sentence-transformers. Nous n’avons besoin que de deux imports pour cette section : requests et BeautifulSoup. Ensuite, nous créons un dictionnaire d’URL que nous voulons scraper. Dans cet exemple, nous ne scrappons que le contenu de Towards Data Science, mais vous pouvez également scraper d’autres sites web. Nous récupérons les données de chaque page d’archive en utilisant le format indiqué dans le code ci-dessous.
import requests
from bs4 import BeautifulSoup
urls = {
'Towards Data Science': 'https://towardsdatascience.com/archive/{0}/{1:02d}/{2:02d}',
}
Nous avons également besoin de deux fonctions auxiliaires pour notre web scraping. La première fonction convertit le numéro du jour de l’année en un format mois et jour. La seconde récupère le nombre d’applaudissements (les applaudissements montrent le soutien à un article Medium) d’un article.
La fonction de conversion du jour est relativement simple. Nous codons en dur le nombre de jours de chaque mois et utilisons cette liste pour effectuer la conversion. Comme ce projet de web scraping cible explicitement l’année 2023, nous n’avons pas besoin de tenir compte des années bissextiles. N’hésitez pas à la modifier pour différentes années si vous le souhaitez.
La fonction de comptage des applaudissements représente les applaudissements pour un article Medium. Certains articles peuvent recevoir des milliers d’applaudissements, que Medium indique avec la lettre "K." Par conséquent, nous devons prendre en compte cette représentation dans notre fonction.
# takes the number day of the year and returns month, day
def convert_day(day):
month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
m = 0
d = 0
while day > 0:
d = day
day -= month_days[m]
m += 1
return (m, d)
# converts the claps string
def get_claps(claps_str):
if (claps_str is None) or (claps_str == '') or (claps_str.split is None):
return 0
split = claps_str.split('K')
claps = float(split[0])
claps = int(claps*1000) if len(split) == 2 else int(claps)
return claps
Analyse de la réponse de web scraping avec BeautifulSoup4
Maintenant que nous avons configuré les composants nécessaires, nous pouvons procéder au web scraping. Dans cette section, nous importons une bibliothèque supplémentaire appelée "time.” J’ai ajouté cette bibliothèque parce que j’ai rencontré des erreurs 429 (trop de requêtes) pendant le processus. En utilisant la bibliothèque time, nous pouvons introduire un délai entre l’envoi des requêtes. De plus, nous utilisons la bibliothèque sentence transformers pour obtenir notre modèle d’embeddings, plus précisément un modèle MiniLM, depuis Hugging Face.
Comme mentionné précédemment, nous ne récupérons que les données de 2023, nous définissons donc l’année sur 2023. De plus, nous n’avons besoin que des données du jour 1 (1er janvier) au jour 243 (30 août). Au moment du scraping, nous étions début septembre. Avec le recul, il aurait été préférable de scraper jusqu’au jour 244, mais nous allons poursuivre pour l’instant avec ce que j’ai déjà fait. Dans la première partie de notre fonction, jusqu’au premier appel à time.sleep(), nous mettons en place les composants nécessaires. Nous convertissons l’itérateur en mois et en jour, nous convertissons cela en chaîne de date, puis nous récupérons la réponse HTML depuis l’URL de l’archive.
Après avoir obtenu le HTML, nous l’analysons avec BeautifulSoup et recherchons les éléments div avec un nom de classe spécifique (indiqué dans le code), ce qui indique qu’il s’agit d’un article. À partir de là, nous analysons le titre, le sous-titre, l’URL de l’article, le nombre de claps, le temps de lecture et le nombre de réponses. Ensuite, nous utilisons à nouveau requests pour récupérer l’article depuis l’URL de l’article.
Nous appelons time.sleep() une seconde fois pour conclure la deuxième section de cette boucle for. À ce stade, nous avons obtenu la majeure partie des métadonnées requises pour chaque article. Nous extrayons chaque paragraphe et son embedding correspondant à l’aide de notre modèle Hugging Face. Nous créons ensuite un dictionnaire qui contient toutes les métadonnées pour ce paragraphe et cet article spécifiques.
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("sentence-transformers/all-MiniLM-L12-v2")
import time
# data_batch = []
year = 2023
for i in range(1, 243):
month, day = convert_day(i)
date = '{0}-{1:02d}-{2:02d}'.format(year, month, day)
for publication, url in urls.items():
response = requests.get(url.format(year, month, day), allow_redirects=True)
if not response.url.startswith(url.format(year, month, day)):
continue
time.sleep(8)
page = response.content
soup = BeautifulSoup(page, 'html.parser')
articles = soup.find_all(
"div",
class_="postArticle postArticle--short js-postArticle js-trackPostPresentation js-trackPostScrolls")
for article in articles:
title = article.find("h3", class_="graf--title")
# print(title.contents)
if title is None:
continue
title = str(title.contents[0]).replace(u'\xA0', u' ').replace(u'\u200a', u' ')
# title = title.contents[0]
subtitle = article.find("h4", class_="graf--subtitle")
subtitle = str(subtitle.contents[0]).replace(u'\xA0', u' ').replace(u'\u200a', u' ') if subtitle is not None else ''
article_url = article.find_all("a")[3]['href'].split('?')[0]
try:
claps = get_claps(article.find_all("button")[1].contents[0])
except:
claps = 0
reading_time = article.find("span", class_="readingTime")
reading_time = 0 if reading_time is None else int(reading_time['title'].split(' ')[0])
responses = article.find_all("a")
if len(responses) == 7:
responses = responses[6].contents[0].split(' ')
if len(responses) == 0:
responses = 0
else:
responses = responses[0]
else:
responses = 0
article_res = requests.get(article_url)
time.sleep(8)
soup = bs4.BeautifulSoup(article_res.text)
paragraphs = soup.select('[class*="pw-post-body-paragraph"]')
for i, paragraph in enumerate(paragraphs):
embedding = model.encode(paragraph.text)
data_batch.append({
"_id": f"{article_url}+{i}",
"article_url": article_url,
"title": title,
"subtitle": subtitle,
"claps": claps,
"responses": responses,
"reading_time": reading_time,
"publication": publication,
"date": date,
"paragraph": paragraph.text,
"embedding": embedding
})
La dernière étape consiste à sérialiser notre fichier avec pickle.
import pickle
filename="TDS_8_30_2023"
with open(f'{filename}.pkl', 'wb') as f:
pickle.dump(data_batch, f)
À quoi ressemblent nos données ?
Il est toujours utile de visualiser les données. Voici ci-dessous à quoi ressemblent les données dans Zilliz Cloud. Faites attention aux embeddings, qui représentent le vecteur. Nous générons ces embeddings vectoriels à partir du texte dans la section suivante.
Ingérer les données de TDS dans votre base de données vectorielle
Une fois que nous avons les données, l’étape suivante consiste à les acheminer vers une base de données vectorielle. Dans ce projet, j’ai utilisé un notebook distinct pour ingérer les données dans Zilliz Cloud, un service de base de données vectorielle entièrement géré construit sur Milvus, plutôt que de les scraper depuis Towards Data Science.
Pour insérer nos données dans Zilliz Cloud, nous suivons ces étapes :
Se connecter à Zilliz Cloud.
Définir les paramètres de notre collection.
Insérer les données dans Zilliz Cloud.
Configurer le Jupyter Notebook
Exécutez la commande pip install pymilvus python-dotenv pour configurer notre Jupyter Notebook et lancer le processus d’ingestion des données. J’utilise la bibliothèque dotenv pour gérer mes variables d’environnement comme d’habitude. Pour le package pymilvus, nous devons importer les modules suivants :
utility- pour vérifier l’état de vos collectionsconnections- pour vous connecter à votre instance MilvusFieldSchema- pour définir le schéma d’un champCollectionSchema- pour définir le schéma d’une collectionDataType- types de données stockés dans un champCollection- la façon dont nous accédons à une collection
Ensuite, nous ouvrons les données que nous avons précédemment sérialisées avec pickle, récupérons nos variables d’environnement et nous connectons à Zilliz Cloud.
import pickle
import os
from dotenv import load_dotenv
from pymilvus import utility, connections, FieldSchema, CollectionSchema, DataType, Collection
filename="TDS_8_30_2023"
with open(f'{filename}.pkl', 'rb') as f:
data_batch = pickle.load(f)
load_dotenv()
zilliz_uri = os.getenv("ZILLIZ_URI")
zilliz_token = os.getenv("ZILLIZ_TOKEN")
connections.connect(
uri= zilliz_uri,
token= zilliz_token
)
Configurer votre base de données vectorielle Zilliz et l’ingestion des données
Maintenant, nous devons configurer Zilliz Cloud. Nous devons créer une collection pour stocker et organiser les données que nous scrappons depuis le site web de TDS. Deux constantes sont requises : la dimension et le nom de la collection. La dimension fait référence au nombre de dimensions que possède notre vecteur. Dans ce cas, nous utilisons le modèle MiniLM avec 384 dimensions. Le nom de la collection est explicite.
Avec la nouvelle fonctionnalité de schéma dynamique de Milvus, nous pouvons simplement définir l’id et le champ d’embedding pour une collection sans nous soucier du nombre d’autres champs stockés ni de leur type de données requis. Cependant, il est essentiel de se souvenir des noms spécifiques des champs que nous avons conservés afin de les récupérer correctement.
Pour cet exemple de projet, nous utilisons la fonctionnalité de schéma dynamique pour définir uniquement les champs ID et embedding. Nous attribuons ensuite ces champs à un schéma, puis le schéma à une collection. Ensuite, nous spécifions les paramètres de l’index. Dans ce cas, nous utilisons un index de fichier inversé (IVF) sans quantification (FLAT), une distance euclidienne (L2) et 128 centroïdes pour l’index.
L’IVF sans quantification est la méthode d’indexation la plus intuitive. Essentiellement, nous regroupons les vecteurs en 128 sections à interroger. 128 est un nombre arbitraire dans ce cas. C’est un hyperparamètre que nous pouvons ajuster en fonction de l’apparence de nos résultats. Heuristiquement, c’est un bon point de départ, car il peut fournir une bonne diversité de clusters tout en réduisant la complexité initiale de la requête. Une fois ces paramètres définis, nous créons l’index sur le champ d’embedding et chargeons la collection en mémoire.
DIMENSION=384
COLLECTION_NAME="tds_articles"
fields = [
FieldSchema(name='id', dtype=DataType.VARCHAR, max_length=200, is_primary=True),
FieldSchema(name='embedding', dtype=DataType.FLOAT_VECTOR, dim=DIMENSION)
]
schema = CollectionSchema(fields=fields, enable_dynamic_field=True)
collection = Collection(name=COLLECTION_NAME, schema=schema)
index_params = {
"index_type": "IVF_FLAT",
"metric_type": "L2",
"params": {"nlist": 128},
}
collection.create_index(field_name="embedding", index_params=index_params)
collection.load()
Une fois que nous avons défini la collection et l’avons chargée en mémoire, nous avons deux options pour insérer les données :
Parcourir le lot de données en boucle et insérer chaque élément individuellement
Insérer les données par lots
Après avoir inséré toutes les données, il est important de vider la collection pour l’indexer et garantir la cohérence. L’ingestion de grandes quantités de données peut prendre un certain temps.
for data in data_batch:
collection.insert([data])
collection.flush()
Interroger les segments d’articles TDS
Maintenant, tout est configuré et prêt pour les requêtes. Dans cet article, nous garderons cette partie simple en vectorisant notre requête et en recherchant la correspondance la plus proche dans Zilliz Cloud. Dans mes prochains articles, nous utiliserons également LlamaIndex et LangChain.
Obtenir votre modèle HuggingFace et le configurer pour interroger Zilliz
Vous devez obtenir le modèle d’embedding et configurer la base de données vectorielle pour interroger notre base de connaissances Towards Data Science. Cette étape nécessite l’exécution d’un notebook séparé. J’utiliserai la bibliothèque dotenv pour gérer les variables d’environnement. De plus, nous devons utiliser le modèle MiniLM de Sentence Transformers. Pour cette étape, vous pouvez réutiliser le code fourni dans la section Web Scraping.
import os
from dotenv import load_dotenv
from pymilvus import connections, Collection
load_dotenv()
zilliz_uri = os.getenv("ZILLIZ_URI")
zilliz_token = os.getenv("ZILLIZ_TOKEN")
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("sentence-transformers/all-MiniLM-L12-v2")
Exécuter une requête de recherche vectorielle
Maintenant, vous devez vous connecter à votre base de données vectorielle et effectuer une recherche. Dans ce projet, nous nous connecterons à une instance Zilliz Cloud et récupérerons la collection que nous avons créée précédemment, tds_articles. Pour obtenir notre requête, nous demandons à l’utilisateur de saisir sa question sur TDS jusqu’en septembre 2023.
Ensuite, nous utilisons le modèle d’embedding de Hugging Face pour encoder la requête. Ce processus convertit la question de l’utilisateur en une représentation vectorielle à 384 dimensions. Nous utilisons ensuite cette requête encodée pour rechercher dans la base de données vectorielle. Pendant la recherche, nous devons spécifier le champ de plus proche voisin approximatif (anns_field), les paramètres d’index, la limite souhaitée pour le nombre de résultats de recherche et les champs de sortie que nous voulons.
Auparavant, nous avons utilisé la fonctionnalité de schéma dynamique de Milvus pour simplifier la définition du schéma des champs. Maintenant, lorsque nous recherchons dans la base de données vectorielle, il est essentiel d’inclure les champs dynamiques souhaités dans les résultats de recherche. Ce scénario spécifique implique de demander le champ paragraph, qui contient le texte de chaque paragraphe des articles.
connections.connect(uri=zilliz_uri, token=zilliz_token)
collection = Collection(name="tds_articles")
query = input("What would you like to ask Towards Data Science's 2023 publications up to September? ")
embedding = model.encode(query)
closest = collection.search([embedding],
anns_field='embedding',
param={"metric_type": "L2",
"params": {"nprobe": 16}},
limit=2,
output_fields=["paragraph"])
print(closest[0][0])
print(closest[0][1])
J’ai demandé à l’application de fournir des informations sur les grands modèles de langage, et elle a renvoyé les deux réponses suivantes. Bien que ces réponses mentionnent les "modèles de langage" et incluent certaines informations connexes, elles ne fournissent pas d’explication détaillée des grands modèles de langage. La deuxième réponse est sémantiquement similaire mais doit être suffisamment proche de ce que nous avons demandé.
Ajouter à une base de connaissances de base de données vectorielle
Jusqu’à présent, nous avons créé une base de connaissances sur les articles de TDS en utilisant Zilliz comme base de données vectorielle. Bien que nous ayons pu récupérer facilement des résultats de recherche sémantiquement similaires, ils ne correspondent pas toujours à ce dont nous avons besoin. L’étape suivante consiste à améliorer nos résultats en intégrant de nouveaux frameworks et technologies.
Dans la section suivante, nous explorerons l’ajout de LlamaIndex pour router nos résultats, de manière similaire au routage que nous avons effectué dans mon précédent article de blog sur l’interrogation de plusieurs documents avec LlamaIndex, LangChain et Milvus.
Résumé
Ce tutoriel couvre la création d’un chatbot pour la publication Towards Data Science. Nous illustrons le processus de web scraping pour créer et stocker une base de connaissances dans une base de données vectorielle (plus précisément, Zilliz Cloud). Nous montrons ensuite comment demander à l’utilisateur une requête, vectoriser la requête et interroger la base de données vectorielle.
Cependant, nous avons découvert que, bien que les résultats soient sémantiquement similaires, ils ne correspondent pas exactement à ce que nous souhaitons. Dans la prochaine partie de cette série d’articles de blog, nous explorerons l’utilisation de LlamaIndex pour router les requêtes et voir si nous pouvons obtenir de meilleurs résultats. Outre les étapes discutées ici, vous pouvez également expérimenter avec Zilliz Cloud en remplaçant le modèle, en combinant des textes ou en utilisant un jeu de données différent !
Pour relire l’intégralité de la série Chat Towards Data Science, voici les liens :
Continuer à lire

Zilliz Skills Breakdown: How AI Agents Master Vector Databases
Zilliz's Milvus Skill (pymilvus, 7 files) and Zilliz Cloud Skill (zilliz-cli, 14 modules) bring vector-DB dev and ops into one Claude Code session.

Why I’m Against Claude Code’s Grep-Only Retrieval? It Just Burns Too Many Tokens
Learn how vector-based code retrieval cuts Claude Code token consumption by 40%. Open-source solution with easy MCP integration. Try claude-context today.

Why Not All VectorDBs Are Agent-Ready
Explore why choosing the right vector database is critical for scaling AI agents, and why traditional solutions fall short in production.



