Recherche basée sur l'IA et axée sur la protection de la vie privée avec LangChain et Elasticsearch
Au cours des derniers week-ends, j'ai exploré le monde fascinant de "l'ingénierie d'invite" et j'ai appris comment les bases de données vectorielles, à l'instar d'Elasticsearch®, peuvent optimiser les grands modèles de langage, comme ChatGPT, en jouant le rôle de mémoire à long terme et de boutique de connaissances sémantiques. Cependant, une chose m'a troublé (aux côtés de nombreux autres architectes de données expérimentés) : un grand nombre de tutoriels et de démonstrations sont globalement dépendants de l'envoi de vos données privées à de grandes entreprises du web et à des sociétés spécialisées dans l'intelligence artificielle basée sur le cloud.
Les données privées sont disponibles dans des formes variées et protégées pour plusieurs raisons. Pour les start-ups comme pour les entreprises, la connaissance des données privées est parfois leur avantage concurrentiel. Les données internes et de la clientèle contiennent souvent des informations personnelles, qui engendrent des conséquences juridiques et humaines concrètes si elles ne sont pas protégées. Dans les domaines de l'observabilité et de la sécurité, le manque de prudence dans l'utilisation de services tiers peut être l'origine d'une violation de données. Nous avons même entendu des accusations de violations de cybersécurité qui étaient liées à l'utilisation d'outils de messagerie instantanée basés sur l'intelligence artificielle.
Aucune conception n'est exemptée de risque ou est entièrement confidentielle, même en collaborant avec des entreprises comme Elastic qui se sont résolument engagées pour défendre la sécurité et la protection des données personnelles ou bien qui réalisent des déploiements dans un véritable air gap. Or, j'ai travaillé avec suffisamment de cas d'utilisation impliquant des données sensibles pour savoir qu'il y a une réelle valeur à améliorer la recherche basée sur l'intelligence artificielle dans le cadre d'une approche axée sur la protection de la vie privée. J'adore l'excellente présentation de mon collègue Jeff Vestal dédiée à l'utilisation des outils d'OpenAI avec Elasticsearch. Cependant, le présent article adopte un angle différent.
Dans le cadre de ce projet, j'ai deux objectifs :
- Privé. Je suis très sérieux à ce sujet. Même si je peux utiliser l'offre Elasticsearch hébergée dans le cloud si le cas d'utilisation l'exige, je souhaite qu'elle fonctionne entièrement dans un air gap. Je veux vous démontrer qu'il est possible d'exécuter la recherche basée sur l'intelligence artificielle sans envoyer d'informations privées à des parties tierces.
- Amusant. Tant qu'à faire, amusons-nous au passage. Nous utiliserons Wookieepedia, un wiki communautaire populaire dédié à Star Wars comprenant des exercices de science des données, afin de concevoir un module d'aide privé de questions-réponses qui est fondé sur l'intelligence artificielle. J'ai rédigé cet article un peu avant le 4 mai, date à laquelle de nombreux fans célèbrent la saga Star Wars. Même si l'article a été mis en ligne après cette date, je reste un fan toute l'année.
Le moyen le plus facile pour vous lancer et faire votre propre expérience est de créer une instance Elasticsearch sur Elastic Cloud et de l'exécuter grâce au bloc-notes Python fourni, qui implémentera le projet à une petite échelle. Si vous souhaitez exécuter l'ensemble de Wookieepedia composé de 180 000 paragraphes de connaissances sur Star Wars et créer une recherche dédiée parfaitement maîtrisée, vous pouvez suivre le code fourni dans ce référentiel GitHub.
Quand vous avez terminé, le résultat doit ressembler à ce qui suit.
Dans un esprit d'ouverture, utilisons deux technologies open source pour aider Elasticsearch, à savoir la bibliothèque de transformateurs Hugging Face et la nouvelle bibliothèque Python qui est amusante à utiliser et est appelée LangChain. Ensemble, elles accéléreront le recours à Elasticsearch en tant que base de données vectorielle. En prime, LangChain rend nos grands modèles de langage interchangeables sur le plan programmatique dès qu'ils sont configurés. Ainsi, nous sommes libres de mener nos expériences avec plusieurs modèles.
Fonctionnement
Qu'est-ce que LangChain ? LangChain est un framework Python et JavaScript pour le développement d'applications propulsées par de grands modèles de langage. Il fonctionne avec les API d'OpenAI et excelle à faire abstraction des différences entre les bases de données et les outils d'intelligence artificielle.
En lui-même, ChatGPT répond plutôt bien aux questions sur Star Wars. Toutefois, son ensemble de données d'entraînement a maintenant plusieurs années et nous cherchons des réponses sur les derniers événements et séries de l'univers Star Wars. En outre, nous ne devons pas oublier que ces données doivent être considérées comme privées et ne pas être partagées avec l'un des plus célèbres grands modèles de langage du cloud. Nous pouvons régler notre propre modèle à l'aide de données plus récentes. Cependant, il existe une alternative bien plus facile qui nous permet d'utiliser en permanence les informations disponibles les plus récentes.
Aujourd'hui, utilisons un grand modèle de langage de plus petite envergure et facile à autohéberger. J'ai obtenu de très bons résultats avec le modèle flan-t5-large de Google, qui compense le manque d'entraînement par une bonne capacité à analyser les réponses à partir du contexte injecté. Nous utiliserons la recherche sémantique pour récupérer nos connaissances privées. Ensuite, nous injecterons dans ce contexte une question posée à notre grand modèle de langage privé.
1. Récupérons le corpus des articles provenant de Wookieepedia et transférons les données dans des fichiers Python Pickle intermédiaires.
2A. Chargeons chaque paragraphe de ces articles dans Elasticsearch à l'aide de la bibliothèque Vectorstore intégrée de LangChain.
2B. Nous pouvons aussi comparer LangChain selon la nouvelle méthode d'hébergement des transformateurs pytorch directement dans Elasticsearch. Nous déploierons le modèle de plongement textuel dans Elasticsearch afin d'exploiter la fonctionnalité de calcul distribuée et d'accélérer le processus.
3. Quand une question est posée, nous trouverons le paragraphe le plus proche sur le plan sémantique grâce à la recherche vectorielle d'Elasticsearch. Ensuite, nous ajouterons ce paragraphe à l'invite d'un grand modèle de langage local et de petite envergure afin de donner du contexte à la question. Puis, nous laisserons la magie de l'intelligence artificielle générative opérer afin d'obtenir une courte réponse.
Configuration de l'environnement Python et Elasticsearch
Assurez-vous que votre machine soit équipée de Python 3.9 ou d'un logiciel similaire. J'ai bien utilisé cette version 3.9 pour faciliter la compatibilité des bibliothèques avec l'accélération du GPU, même si cela ne sera pas nécessaire pour ce projet. Toute version 3.X récente de Python fonctionnera.
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install beautifulsoup4 eland elasticsearch huggingface-hub langchain tqdm torch requests sentence_transformers
Si vous avez téléchargé l'exemple de code, vous pouvez plutôt extraire les versions exactes du code que j'ai utilisé avec la commande d'installation pip suivante.
pip install -r requirements.txt
Pour configurer un cluster Elasticsearch, suivez ces instructions. L'essai gratuit dans le cloud est la méthode la plus facile pour vous lancer.
Créez un fichier .env dans le dossier et chargez les informations de votre connexion pour Elasticsearch.
export ES_SERVER="YOURDESSERVERNAME.es.us-central1.gcp.cloud.es.io"
export ES_USERNAME="YOUR READ WRITE AND INDEX CREATING USER"
export ES_PASSWORD="YOUR PASSWORD"
Étape 1. Récupération des données
Le référentiel de code comprend un petit ensemble de données configuré à l'emplacement Dataset/starwars_small_sample_data.pickle. Vous pouvez passer cette étape si vous préférez travailler à une petite échelle.
Le code récupéré est une adaptation de l'excellent article de Dennis Bakhuis sur la science des données ainsi que de son projet. Je vous encourage vivement à en prendre connaissance. Seul le premier paragraphe de chaque article est extrait habituellement, mais j'ai modifié le code pour tout récupérer. Cette restriction était peut-être nécessaire pour que la taille des données puisse être contenue dans une mémoire privée. Cependant, nous n'avons pas ce problème, car nous utilisons Elasticsearch, qui devrait nous permettre de gérer facilement des pétaoctets de données.
En outre, vous pouvez très facilement intégrer votre propre source de données privées. LangChain comprend quelques bibliothèques d'utilitaire remarquables pour la décomposition de vos données textuelles en éléments plus petits.
La récupération n'est pas l'objet de cet article. Je vous conseille donc de consulter le bloc-notes Python si vous voulez lancer une exécution à petite échelle par vous-même. Vous pouvez aussi télécharger le code source et l'exécuter de la manière suivante.
source .env
python3 step-1A-scrape-urls.py
python3 step-1B-scrape-content.py
Ensuite, vous devriez pouvoir passer en revue les fichiers Pickle comme suit afin de vérifier que l'opération a été un succès.
from pathlib import Path
import pickle
bookFilePath = "starwars_*_data*.pickle"
files = sorted(Path('./Dataset').glob(bookFilePath))
for fn in files:
with open(fn,'rb') as f:
part = pickle.load(f)
for key, value in part.items():
title = value['title'].strip()
print(title)
Si vous vous passez de la récupération en ligne, il vous suffit de remplacer bookFilePath par starwars_small_sample_data.pickle afin d'utiliser l'échantillon que j'ai ajouté au référentiel GitHub.
Étape 2A. Chargement des plongements dans Elasticsearch
L'intégralité du code montre comment j'ai procédé avec LangChain uniquement. Le composant fondamental du code consiste à balayer les fichiers Pickle enregistrés, comme dans l'exemple ci-dessus, à extraire la liste des chaînes constituant des paragraphes, puis à les envoyer vers la fonction from_texts() de la bibliothèque Vectorstore de LangChain.
from langchain.vectorstores import ElasticVectorSearch
from langchain.embeddings import HuggingFaceEmbeddings
from pathlib import Path
import pickle
import os
from tqdm import tqdm
model_name = "sentence-transformers/all-mpnet-base-v2"
hf = HuggingFaceEmbeddings(model_name=model_name)
index_name = "book_wookieepedia_mpnet"
endpoint = os.getenv('ES_SERVER', 'ERROR')
username = os.getenv('ES_USERNAME', 'ERROR')
password = os.getenv('ES_PASSWORD', 'ERROR')
url = f"https://{username}:{password}@{endpoint}:443"
db = ElasticVectorSearch(embedding=hf, elasticsearch_url=url, index_name=index_name)
batchtext = []
bookFilePath = "starwars_*_data*.pickle"
files = sorted(Path('./Dataset').glob(bookFilePath))
for fn in files:
with open(fn,'rb') as f:
part = pickle.load(f)
for ix, (key, value) in tqdm(enumerate(part.items()), total=len(part)):
paragraphs = value['paragraph']
for p in paragraphs:
batchtext.append(p)
db.from_texts(batchtext,
embedding=hf,
elasticsearch_url=url,
index_name=index_name)
Étape 2B. Économies et gain de temps à l'aide des modèles entraînés et hébergés
Sur mon ancien Macbook Intel, il fallait des heures de traitement pour créer des plongements, que dis-je, plusieurs jours en réalité. Grâce aux nœuds de Machine Learning scalables sur le plan dynamique du service hébergé d'Elastic, je peux en créer plus rapidement pour un coût moins élevé. Les clusters disponibles en essai gratuit ne vous laisseront pas scaler à ce niveau. Cette étape sera sans doute plus utile à certains qu'à d'autres.
Résultat, cette approche a duré 40 minutes sur des nœuds qui coûtent 5 $ de l'heure pour une exécution dans Elastic Cloud, ce qui est bien plus rapide que ce que je peux faire en local et comparable au coût de traitement des plongements auquel s'ajoutent les frais actuels d'OpenAI pour les tokens. L'efficacité de cette opération mériterait un article à elle toute seule. Cependant, je suis impressionné par la rapidité avec laquelle j'ai pu faire fonctionner en parallèle un pipeline d'inférence dans Elastic Cloud sans devoir monter en compétences ni transférer mes données dans une API non privée.
Pour cette étape, nous allons décharger la génération de plongements au cluster Elasticsearch, qui peut héberger le modèle de plongement et l'intégrer aux paragraphes du texte de manière distribuée. Dans ce but, nous devons charger les données et utiliser des pipelines d'ingestion pour nous assurer que le formulaire final correspond bien au mapping de l'index que LangChain utilise. Exécutons la commande REST suivante dans les outils de développement de Kibana.
PUT /book_wookieepedia_mpnet
{
"settings": {
"number_of_shards": 4
},
"mappings": {
"properties": {
"metadata": {
"type": "object"
},
"text": {
"type": "text"
},
"vector": {
"type": "dense_vector",
"dims": 768
}
}
}
}
Puis, nous chargerons le modèle de plongement dans Elasticsearch à l'aide de la bibliothèque Eland de Python.
source .env
python3 step-3A-upload-model.py
Ensuite, dans la console Elastic Cloud, nous scalons notre niveau de Machine Learning à un total de 64 processeurs virtuels (soit 8 fois la puissance de mon ordinateur portable actuel).
Nous allons pouvoir déployer dans Kibana le modèle de Machine Learning entraîné. À grande échelle, le test de performance a démontré que les internautes devraient commencer avec 1 thread par attribution de modèle, puis augmenter le nombre d'allocations pour obtenir un débit plus élevé. Vous trouverez de la documentation et des conseils dédiés sur cette page. D'après mes expériences, pour cet ensemble plus petit, j'ai obtenu les meilleurs résultats avec 32 instances à 2 threads chacune. Pour reproduire cette configuration, accédez à "Stack Management" (Gestion de la suite) > Machine Learning. Utilisez la fonctionnalité Synchronize saved objects (Synchroniser les objets sauvegardés) afin que Kibana affiche le modèle intégré à Elasticsearch à l'aide du code Python. Ensuite, déployez le modèle dans le menu qui s'affiche lorsque vous cliquez dessus.
Réutilisons les outils de développement afin de créer un index et d'ingérer un pipeline qui traite le paragraphe d'un texte contenu dans un document, enregistre le résultat dans un champ de vecteur dense appelé "vector" et copie ce paragraphe dans le champ "text" attendu.
PUT /book_wookieepedia_mpnet
{
"settings": {
"number_of_shards": 4
},
"mappings": {
"properties": {
"metadata": {
"type": "object"
},
"text": {
"type": "text"
},
"vector": {
"type": "dense_vector",
"dims": 768
}
}
}
}
PUT _ingest/pipeline/sw-embeddings
{
"description": "Text embedding pipeline",
"processors": [
{
"inference": {
"model_id": "sentence-transformers__all-mpnet-base-v2",
"target_field": "text_embedding",
"field_map": {
"text": "text_field"
}
}
},
{
"set":{
"field": "vector",
"copy_from": "text_embedding.predicted_value"
}
},
{
"remove": {
"field": "text_embedding"
}
}
],
"on_failure": [
{
"set": {
"description": "Index document to 'failed-<index>'",
"field": "_index",
"value": "failed-{{{_index}}}"
}
},
{
"set": {
"description": "Set error message",
"field": "ingest.failure",
"value": "{{_ingest.on_failure_message}}"
}
}
]
}
Testez le pipeline pour vous assurer qu'il fonctionne.
POST _ingest/pipeline/sw-embeddings/_simulate
{
"docs": [
{
"_source": {
"text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"metadata": {
"a": "b"
}
}
}
]
}
Nous pouvons maintenant charger des lots de données à l'aide de la bibliothèque normale de Python pour Elasticsearch, en ciblant notre pipeline d'ingestion afin de créer correctement le plongement vectoriel et de transformer nos informations pour qu'elles répondent aux attentes de LangChain.
source .env
python3 step-3B-batch-hosted-vectorize.py
Nous avons réussi ! Les données comprennent environ 13 millions de tokens selon les termes d'OpenAI. Par conséquent, la génération de ces vecteurs dans un service cloud d'OpenAI ou équivalent devrait coûter environ 5,40 $. Avec Elastic Cloud, il a fallu 40 minutes à des machines qui coûtent 5 $ de l'heure.
Maintenant que les données sont chargées, n'oubliez pas de ramener à zéro votre modèle de Machine Learning dans le cloud ou à un niveau plus raisonnable grâce à la console Cloud.
Étape 3. Réponse à toutes les questions sur Star Wars
À présent, jouons avec le grand modèle de langage et LangChain. J'ai créé un fichier de bibliothèque intitulé lib_llm.py dans lequel j'ai enregistré le code suivant.
from langchain import PromptTemplate, HuggingFaceHub, LLMChain
from langchain.llms import HuggingFacePipeline
from transformers import AutoTokenizer, pipeline, AutoModelForSeq2SeqLM
from langchain.vectorstores import ElasticVectorSearch
from langchain.embeddings import HuggingFaceEmbeddings
import os
cache_dir = "./cache"
def getFlanLarge():
model_id = 'google/flan-t5-large'
print(f">> Prep. Get {model_id} ready to go")
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSeq2SeqLM.from_pretrained(model_id, cache_dir=cache_dir)
pipe = pipeline(
"text2text-generation",
model=model,
tokenizer=tokenizer,
max_length=100
)
llm = HuggingFacePipeline(pipeline=pipe)
return llm
local_llm = getFlanLarge()
def make_the_llm():
template_informed = """
I am a helpful AI that answers questions.
When I don't know the answer I say I don't know.
I know context: {context}
when asked: {question}
my response using only information in the context is: """
prompt_informed = PromptTemplate(
template=template_informed,
input_variables=["context", "question"])
return LLMChain(prompt=prompt_informed, llm=local_llm)
## continued below
La commande template_informed est l'étape fondamentale du processus, mais elle est aussi très facile à comprendre. Il nous suffit de formater un modèle d'invite, qui utilisera nos deux paramètres, à savoir le contexte et la question posée.
Avec ce code principal final provenant de l'exemple ci-dessus, le résultat devrait ressembler à ce qui suit.
## continued from above
topic = "Star Wars"
index_name = "book_wookieepedia_mpnet"
# Create the HuggingFace Transformer like before
model_name = "sentence-transformers/all-mpnet-base-v2"
hf = HuggingFaceEmbeddings(model_name=model_name)
## Elasticsearch as a vector db, just like before
endpoint = os.getenv('ES_SERVER', 'ERROR')
username = os.getenv('ES_USERNAME', 'ERROR')
password = os.getenv('ES_PASSWORD', 'ERROR')
url = f"https://{username}:{password}@{endpoint}:443"
db = ElasticVectorSearch(embedding=hf, elasticsearch_url=url, index_name=index_name)
## set up the conversational LLM
llm_chain_informed= make_the_llm()
def ask_a_question(question):
## get the relevant chunk from Elasticsearch for a question
similar_docs = db.similarity_search(question)
print(f'The most relevant passage: \n\t{similar_docs[0].page_content}')
informed_context= similar_docs[0].page_content
informed_response = llm_chain_informed.run(
context=informed_context,
question=question)
return informed_response
# The conversational loop
print(f'I am a trivia chat bot, ask me any question about {topic}')
while True:
command = input("User Question >> ")
response= ask_a_question(command)
print(f"\tAnswer : {response}")
Conclusion
Après avoir un peu lutté avec les données, nous avons utilisé l'intelligence artificielle sans exposer nos informations à un grand modèle de langage hébergé tiers. Le monde de l'intelligence artificielle évolue rapidement. Or, garantir la sécurité des données privées et les contrôler sont des questions que nous devons tous et toutes prendre au sérieux, à cause des conséquences réglementaires, financières et humaines des violations de données. Malheureusement, cette situation n'est pas près de changer. Notre clientèle utilise la recherche pour enquêter sur des fraudes, défendre son pays et améliorer les résultats obtenus par des patientèles vulnérables. La protection des données personnelles est importante. Pour découvrir comment les solutions d'Elastic sont utilisées dans ce domaine, consultez les liens suivants :
Vous aussi vous ne pouvez plus vous passer de LangChain maintenant ? Selon les mots d'un vieux Jedi sage, "très bien, tu as fait ton entrée dans un monde plus vaste". De nombreuses possibilités s'offrent à vous maintenant. Grâce à LangChain, l'ingénierie de l'invite basée sur l'intelligence artificielle ne s'avère pas si complexe à utiliser. Je sais qu'Elasticsearch a de nombreux autres rôles à jouer concernant la mémoire à long terme pour l'intelligence artificielle générative. J'ai donc hâte de voir ce qui va arriver dans ce domaine à l'évolution rapide.
Dans cet article, nous sommes susceptibles d'avoir utilisé des outils d'intelligence artificielle générative tiers appartenant à leurs propriétaires respectifs qui en assurent aussi le fonctionnement. Elastic n'a aucun contrôle sur les outils tiers et n'est en aucun cas responsable de leur contenu, de leur fonctionnement, de leur utilisation, ni de toute perte ou de tout dommage susceptible de survenir à cause de l'utilisation de tels outils. Lorsque vous utilisez des outils d'intelligence artificielle générative avec des informations personnelles, sensibles ou confidentielles, veuillez faire preuve de prudence. Toute donnée que vous saisissez dans ces solutions peut être utilisée pour l'entraînement de l'intelligence artificielle ou à d'autres fins. Vous n'avez aucune garantie que la sécurisation ou la confidentialité des informations renseignées sera assurée. Vous devriez vous familiariser avec les pratiques en matière de protection des données personnelles et les conditions d'utilisation de tout outil d'intelligence artificielle générative avant de l'utiliser.
Elastic, Elasticsearch et les marques associées sont des marques commerciales, des logos ou des marques déposées d'Elasticsearch N.V. aux États-Unis et dans d'autres pays. Tous les autres noms de produits et d'entreprises sont des marques commerciales, des logos ou des marques déposées appartenant à leurs propriétaires respectifs.