Chat Towards Data Science: Creare un chatbot con Zilliz Cloud
Questo articolo è la prima parte della serie di blog Chat Towards Data Science.
Nell’era dell’IA generativa, gli sviluppatori sfruttano i grandi modelli linguistici (LLM) per applicazioni più intelligenti. Tuttavia, gli LLM sono inclini alle allucinazioni a causa della loro conoscenza limitata. Retrieval Augmented Generation (RAG) affronta efficacemente questo problema integrando gli LLM con conoscenza esterna. Nella mia serie di blog Chat Towards Data Science, ti guiderò nella costruzione di un chatbot basato su RAG usando il tuo dataset come spina dorsale della conoscenza.
Nella prima parte della mia serie di blog, ti guideremo nella creazione di un chatbot per il sito web Towards Data Science, utilizzando il web scraping per creare una base di conoscenza archiviata in Zilliz Cloud, un servizio di database vettoriale completamente gestito basato su Milvus.
Scraping dei dati web con BeautifulSoup4
Il primo passo in qualsiasi progetto di machine learning (ML) è raccogliere i dati necessari. Per questo progetto, impieghiamo tecniche di web scraping per raccogliere dati per la nostra base di conoscenza. Utilizziamo la libreria requests per recuperare pagine web e poi usiamo BeautifulSoup4 (una libreria che estrae informazioni dalle pagine web) per analizzare le informazioni HTML ed estrarre i paragrafi.
Preparare BeautifulSoup4 e Requests per il Web Scraping
Per iniziare, installa BeautifulSoup eseguendo pip install beautifulsoup4 sentence-transformers. Abbiamo bisogno solo di due import per questa sezione: requests e BeautifulSoup. Successivamente, creiamo un dizionario di URL di cui vogliamo effettuare lo scraping. In questo esempio, effettuiamo lo scraping solo dei contenuti di Towards Data Science, ma puoi anche effettuare lo scraping di altri siti web. Recuperiamo i dati da ogni pagina di archivio usando il formato mostrato nel codice qui sotto.
import requests
from bs4 import BeautifulSoup
urls = {
'Towards Data Science': 'https://towardsdatascience.com/archive/{0}/{1:02d}/{2:02d}',
}
Abbiamo anche bisogno di due funzioni di supporto per il nostro web scraping. La prima funzione converte il numero di giorni dell’anno in un formato mese e giorno. La seconda recupera il numero di clap (i clap mostrano supporto per un articolo Medium) da un articolo.
La funzione di conversione del giorno è relativamente semplice. Inseriamo nel codice il numero di giorni di ciascun mese e usiamo quell’elenco per eseguire la conversione. Poiché questo progetto di web scraping prende esplicitamente di mira l’anno 2023, non dobbiamo tenere conto degli anni bisestili. Sentiti libero di modificarla per anni diversi se preferisci.
La funzione di conteggio dei clap rappresenta i clap per un articolo Medium. Alcuni articoli possono ricevere migliaia di clap, che Medium indica con la lettera "K." Pertanto, dobbiamo considerare questa rappresentazione nella nostra funzione.
# 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
Analizzare la risposta di web scraping di BeautifulSoup4
Ora che abbiamo configurato i componenti necessari, possiamo procedere con il web scraping. In questa sezione, importiamo una libreria aggiuntiva chiamata "time.” Ho aggiunto questa libreria perché ho riscontrato errori 429 (troppe richieste) durante il processo. Usando la libreria time, possiamo introdurre un ritardo tra l’invio delle richieste. Inoltre, utilizziamo la libreria sentence transformers per ottenere il nostro modello di embedding, nello specifico un modello MiniLM, da Hugging Face.
Come menzionato in precedenza, stiamo effettuando lo scraping dei dati solo per il 2023, quindi impostiamo l'anno su 2023. Inoltre, abbiamo bisogno solo dei dati dal giorno 1 (1 gennaio) al giorno 243 (30 agosto). Al momento dello scraping, era l'inizio di settembre. Col senno di poi, sarebbe stato meglio effettuare lo scraping fino al giorno 244, ma per ora procederemo con ciò che ho già fatto. Nella prima parte della nostra funzione, fino alla prima chiamata a time.sleep(), configuriamo i componenti necessari. Convertiamo l'iteratore in un mese e un giorno, lo convertiamo in una stringa di data e poi recuperiamo la risposta HTML dall'URL dell'archivio.
Dopo aver ottenuto l'HTML, lo analizziamo usando BeautifulSoup e cerchiamo elementi div con un nome di classe specifico (indicato nel codice), il che indica che si tratta di un articolo. Da lì, analizziamo il titolo, il sottotitolo, l'URL dell'articolo, il numero di clap, il tempo di lettura e il numero di risposte. Successivamente, utilizziamo di nuovo requests per recuperare l'articolo dall'URL dell'articolo.
Chiamiamo time.sleep() una seconda volta per concludere la seconda sezione di questo ciclo for. A questo punto, abbiamo ottenuto la maggior parte dei metadati richiesti per ciascun articolo. Estraiamo ogni paragrafo e il relativo embedding usando il nostro modello Hugging Face. Poi creiamo un dizionario che contiene tutti i metadati per quello specifico paragrafo e articolo.
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
})
L'ultimo passo è serializzare il nostro file con pickle.
import pickle
filename="TDS_8_30_2023"
with open(f'{filename}.pkl', 'wb') as f:
pickle.dump(data_batch, f)
Che aspetto hanno i nostri dati?
È sempre utile visualizzare i dati. Di seguito è mostrato l'aspetto dei dati in Zilliz Cloud. Presta attenzione agli embedding, che rappresentano il vettore. Generiamo questi embedding vettoriali dal testo nella sezione seguente.
Inserire i dati da TDS nel tuo database vettoriale
Una volta ottenuti i dati, il passo successivo è convogliarli in un database vettoriale. In questo progetto, ho utilizzato un notebook separato per inserire i dati in Zilliz Cloud, un servizio di database vettoriale completamente gestito basato su Milvus, anziché estrarli da Towards Data Science.
Per inserire i nostri dati in Zilliz Cloud, seguiamo questi passaggi:
Connettiti a Zilliz Cloud.
Definisci i parametri per la nostra collection.
Inserisci i dati in Zilliz Cloud.
Configurazione del Jupyter Notebook
Esegui il comando pip install pymilvus python-dotenv per configurare il nostro Jupyter Notebook e avviare il processo di inserimento dei dati. Uso la libreria dotenv per gestire le mie variabili d'ambiente come al solito. Per il pacchetto pymilvus, dobbiamo importare i seguenti moduli:
utility- per controllare lo stato delle tue collectionconnections- per connetterti alla tua istanza MilvusFieldSchema- per definire lo schema di un campoCollectionSchema- per definire lo schema di una collectionDataType- tipi di dati memorizzati in un campoCollection- il modo in cui accediamo a una collection
Poi, apriamo i dati che abbiamo precedentemente serializzato con pickle, recuperiamo le nostre variabili d'ambiente e ci connettiamo a 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
)
Configurazione del tuo database vettoriale Zilliz e inserimento dei dati
Ora dobbiamo configurare Zilliz Cloud. Dobbiamo creare una collection per archiviare e organizzare i dati che estraiamo dal sito web TDS. Sono necessarie due costanti: dimensione e nome della collection. La dimensione si riferisce al numero di dimensioni del nostro vettore. In questo caso, usiamo il modello MiniLM con 384 dimensioni. Il nome della collection è autoesplicativo.
Con la nuova funzionalità dynamic schema di Milvus, possiamo semplicemente definire i campi id ed embedding per una collection senza preoccuparci del numero di altri campi memorizzati o del loro tipo di dati richiesto. Tuttavia, è fondamentale ricordare i nomi specifici dei campi che abbiamo mantenuto per recuperarli correttamente.
Per questo esempio di progetto, utilizziamo la funzionalità dynamic schema per definire solo i campi ID ed embedding. Assegniamo quindi questi campi a uno schema e lo schema a una collection. Successivamente, specifichiamo i parametri per l'indice. In questo caso, usiamo un inverted file index (IVF) senza quantizzazione (FLAT), una distanza euclidea (L2) e 128 centroidi per l'indice.
IVF senza quantizzazione è il metodo di indicizzazione più intuitivo. In sostanza, raggruppiamo i vettori in 128 sezioni da interrogare. 128 è un numero arbitrario in questo caso. È un iperparametro che possiamo regolare a seconda dell'aspetto dei nostri risultati. Euristicamente, è un buon punto di partenza perché può fornire una buona diversità di cluster e ridurre la complessità iniziale della query. Una volta definiti questi parametri, creiamo l'indice sul campo embedding e carichiamo la collection in memoria.
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()
Una volta definita la collection e caricata in memoria, abbiamo due opzioni per inserire i dati:
Iterare attraverso il batch di dati e inserire ogni elemento singolarmente
Inserire i dati in batch
Dopo aver inserito tutti i dati, è importante eseguire il flush della collection per indicizzarla e garantire la coerenza. L'ingestione di grandi quantità di dati può richiedere del tempo.
for data in data_batch:
collection.insert([data])
collection.flush()
Eseguire query sui segmenti degli articoli TDS
Ora è tutto configurato e pronto per le query. In questo post, manterremo questa parte semplice vettorizzando la nostra query e cercando la corrispondenza più vicina in Zilliz Cloud. Nei miei prossimi post, useremo anche LlamaIndex e LangChain.
Ottieni il tuo modello HuggingFace e configura l'esecuzione di query su Zilliz
Devi ottenere il modello di embedding e configurare il database vettoriale per eseguire query sulla nostra base di conoscenza di Towards Data Science. Questo passaggio richiede l'esecuzione di un notebook separato. Userò la libreria dotenv per gestire le variabili d'ambiente. Inoltre, dobbiamo utilizzare il modello MiniLM di Sentence Transformers. Per questo passaggio, puoi riutilizzare il codice fornito nella sezione 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")
Esecuzione di una query di ricerca vettoriale
Ora devi connetterti al tuo database vettoriale ed eseguire una ricerca. In questo progetto, ci connetteremo a un'istanza Zilliz Cloud e recupereremo la collection creata in precedenza, tds_articles. Per ottenere la nostra query, chiediamo all'utente di inserire la propria domanda su TDS fino a settembre 2023.
Successivamente, utilizziamo il modello di embedding di Hugging Face per codificare la query. Questo processo converte la domanda dell'utente in una rappresentazione vettoriale a 384 dimensioni. Quindi utilizziamo questa query codificata per cercare nel database vettoriale. Durante la ricerca, dobbiamo specificare il campo dei vicini più prossimi approssimati (anns_field), i parametri dell'indice, il limite desiderato per il numero di risultati di ricerca e i campi di output che vogliamo.
In precedenza, abbiamo utilizzato la funzionalità di schema dinamico di Milvus per semplificare la definizione dello schema dei campi. Ora, mentre cerchiamo nel database vettoriale, è essenziale includere nei risultati di ricerca i campi dinamici desiderati. Questo scenario specifico comporta la richiesta del campo paragraph, che contiene il testo di ciascun paragrafo negli articoli.
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])
Ho chiesto all'app di fornire informazioni sui modelli linguistici di grandi dimensioni, e ha restituito le due risposte seguenti. Sebbene queste risposte menzionino "modelli linguistici" e includano alcune informazioni correlate, non forniscono una spiegazione dettagliata dei modelli linguistici di grandi dimensioni. La seconda risposta è semanticamente simile, ma deve essere sufficientemente vicina a quanto richiesto.
Aggiungere elementi a una knowledge base di database vettoriale
Finora, abbiamo creato una knowledge base sugli articoli di TDS usando Zilliz come nostro database vettoriale. Sebbene siamo stati in grado di recuperare facilmente risultati di ricerca semanticamente simili, solo a volte sono ciò di cui abbiamo bisogno. Il passo successivo è migliorare i nostri risultati incorporando nuovi framework e tecnologie.
Nella sezione seguente, esploreremo l’aggiunta di LlamaIndex per instradare i nostri risultati, in modo simile all’instradamento che abbiamo eseguito nel mio blog precedente su Eseguire query su più documenti usando LlamaIndex, LangChain e Milvus.
Riepilogo
Questo tutorial riguarda la creazione di un chatbot per la pubblicazione Towards Data Science. Illustriamo il processo di web scraping per creare e archiviare una knowledge base in un database vettoriale (in particolare, Zilliz Cloud). Poi dimostriamo come richiedere all’utente una query, vettorializzare la query e interrogare il database vettoriale.
Tuttavia, abbiamo scoperto che, sebbene i risultati siano semanticamente simili, non sono esattamente ciò che desideriamo. Nella prossima parte di questa serie di blog, esploreremo l’uso di LlamaIndex per instradare le query e vedere se possiamo ottenere risultati migliori. Oltre ai passaggi discussi qui, puoi anche sperimentare con Zilliz Cloud sostituendo il modello, combinando testi o usando un dataset diverso!
Per rileggere l’intera serie Chat Towards Data Science, ecco i link:
Continua a leggere

3 Easiest Ways to Use Claude Code on Your Mobile Phone
Run Claude Code from your phone with Remote Control, Happy Coder, or SSH + Tailscale. Comparison table, setup steps, and tools for typing, memory, and parallel tasks.

Introducing Zilliz MCP Server: Natural Language Access to Your Vector Database
Developers can easily manage and query vector databases with natural language via Zilliz MCP Server in AI-native environments.

Zilliz Cloud BYOC Upgrades: Bring Enterprise-Grade Security, Networking Isolation, and More
Discover how Zilliz Cloud BYOC brings enterprise-grade security, networking isolation, and infrastructure automation to vector database deployments in AWS



