Word Embeddings Donner à votre ChatBot du contexte pour de meilleures réponses
Word Embeddings pour un ChatBot plus contextuel et des réponses améliorées
Il ne fait aucun doute que ChatGPT d’OpenAI est exceptionnellement intelligent – il a réussi le test du barreau des avocats, il possède des connaissances similaires à celles d’un médecin et certains tests ont mesuré son QI à 155. Cependant, il a tendance à inventer des informations plutôt que de reconnaître son ignorance. Cette tendance, associée au fait que ses connaissances s’arrêtent en 2021, pose des défis pour la création de produits spécialisés à l’aide de l’API GPT.
Comment pouvons-nous surmonter ces obstacles ? Comment pouvons-nous transmettre de nouvelles connaissances à un modèle comme GPT-3 ? Mon objectif est de répondre à ces questions en construisant un bot de questions-réponses en utilisant Python, l’API OpenAI et des embeddings de mots.
Ce que je vais construire
Je prévois de créer un bot qui génère des pipelines d’intégration continue à partir d’une invite, qui, comme vous le savez peut-être, sont formulés avec YAML dans Semaphore CI/CD.
Voici un exemple du bot en action :
- Les chercheurs d’Apple proposent un nouveau modèle de décomposition tensorielle pour le filtrage collaboratif avec des commentaires implicites.
- Comment les solutions GenAI révolutionnent l’automatisation des entreprises Découverte des applications LLM pour les cadres
- Classification avec le Perceptron de Rosenblatt
Capture d’écran du programme en cours d’exécution. À l’écran, la commande python query.py "Créer un pipeline CI qui construit et téléverse une image Docker vers Docker Hub"
est exécutée, et le programme affiche du YAML correspondant à un pipeline CI qui effectue l’action demandée.
Dans l’esprit de projets tels que DocsGPT, My AskAI et Libraria, j’ai l’intention d'”enseigner” au modèle GPT-3 ce qu’est Semaphore et comment générer des fichiers de configuration de pipeline. Je vais y parvenir en exploitant la documentation existante.
Je ne partirai pas du principe que vous avez des connaissances préalables en matière de construction de bot et je maintiendrai un code propre afin que vous puissiez l’adapter à vos besoins.
Prérequis
Vous n’avez pas besoin d’expérience en codage de bot ou de connaissances en réseaux neuronaux pour suivre ce tutoriel. Cependant, vous aurez besoin de :
-
Python 3.
-
Un compte Pinecone (inscrivez-vous gratuitement au plan Starter).
-
Une clé API OpenAI (payée, nécessite une carte de crédit) ; les nouveaux utilisateurs peuvent expérimenter avec 5 $ de crédit gratuit pendant les 3 premiers mois.
Mais ChatGPT ne peut-il pas apprendre ?
ChatGPT, ou plus précisément GPT-3 et GPT-4, les modèles de langage de grande taille (LLMs) qui les alimentent, ont été entraînés sur un ensemble de données massif avec une date de coupure vers septembre 2021.
En essence, GPT-3 sait très peu de choses sur les événements postérieurs à cette date. Nous pouvons le vérifier avec une simple invite :
Alors que certains modèles OpenAI peuvent être soumis à un affinage, les modèles les plus avancés, tels que ceux qui nous intéressent, ne le peuvent pas ; nous ne pouvons pas augmenter leurs données d’entraînement.
Comment pouvons-nous obtenir des réponses de GPT-3 au-delà de ses données d’entraînement ? Une méthode consiste à exploiter ses capacités de compréhension de texte ; en améliorant l’invite avec un contexte pertinent, nous pouvons probablement obtenir la réponse correcte.
Dans l’exemple ci-dessous, je fournis un contexte provenant du site officiel de la FIFA, et la réponse diffère significativement :
Nous pouvons déduire que le modèle peut répondre à n’importe quelle invite s’il dispose d’un contexte pertinent suffisant. La question reste : comment pouvons-nous savoir ce qui est pertinent pour une invite arbitraire ? Pour répondre à cette question, nous devons explorer ce que sont les embeddings de mots.
Qu’est-ce que les embeddings de mots ?
Dans le contexte des modèles de langage, un embedding est une façon de représenter des mots, des phrases ou des documents entiers sous forme de vecteurs ou de listes de nombres.
Pour calculer des embeddings, nous aurons besoin d’un réseau neuronal tel que word2vec ou text-embedding-ada-002. Ces réseaux ont été entraînés sur de très grandes quantités de texte et peuvent trouver des relations entre les mots en analysant les fréquences auxquelles des motifs spécifiques apparaissent dans les données d’entraînement.
Supposons que nous ayons les mots suivants :
-
Chat
-
Chien
-
Balle
-
Maison
Imaginons que nous utilisions l’un de ces réseaux d’embeddings pour calculer les vecteurs pour chaque mot. Par exemple :
Une fois que nous avons les vecteurs pour chaque mot, nous pouvons les utiliser pour représenter la signification du texte. Par exemple, la phrase “Le chat a poursuivi la balle” peut être représentée par le vecteur [0.1, 0.2, 0.3, 0.4, 0.5] + [0.2, 0.4, 0.6, 0.8, 1.0] = [0.3, 0.6, 0.9, 1.2, 1.5]. Ce vecteur représente une phrase qui parle d’un animal poursuivant un objet.
Les embeddings de mots peuvent être visualisés comme des espaces multidimensionnels où les mots ou les phrases ayant des significations similaires sont proches les uns des autres. Nous pouvons calculer la “distance” entre les vecteurs pour trouver des significations similaires pour n’importe quel texte d’entrée.
Représentation en 3D des embeddings en tant qu’espaces vectoriels. En réalité, ces espaces peuvent avoir des centaines ou des milliers de dimensions. Source: Meet AI’s Multitool: Vector Embeddings
Les mathématiques réelles derrière tout cela dépassent le cadre de cet article. Cependant, la principale conclusion est que les opérations vectorielles nous permettent de manipuler ou de déterminer la signification en utilisant les mathématiques. Prenez le vecteur qui représente le mot “reine”, soustrayez le vecteur “femme” et ajoutez le vecteur “homme”. Le résultat devrait être un vecteur à proximité de “roi”. Si nous ajoutons “fils”, nous devrions obtenir quelque chose de proche de “prince”.
Embedding des réseaux neuronaux avec des tokens
Jusqu’à présent, nous avons discuté des réseaux neuronaux d’embedding qui prennent des mots en entrée et produisent des nombres en sortie. Cependant, de nombreux réseaux modernes sont passés du traitement des mots au traitement des tokens.
Un token est la plus petite unité de texte qui peut être traitée par le modèle. Les tokens peuvent être des mots, des caractères, des signes de ponctuation, des symboles ou des parties de mots.
Nous pouvons voir comment les mots sont convertis en tokens en expérimentant avec le tokenizer en ligne d’OpenAI, qui utilise le codage Byte-Pair (BPE) pour convertir le texte en tokens et représenter chacun avec un nombre :
Il existe souvent une relation de 1 à 1 entre les tokens et les mots. La plupart des tokens incluent le mot et un espace devant. Cependant, il y a des cas spéciaux comme “embedding”, qui se compose de deux tokens, “embed” et “ding”, ou “capabilities”, qui se compose de quatre tokens. Si vous cliquez sur “IDs de token”, vous pouvez voir la représentation numérique du modèle de chaque token.
Conception d’un bot plus intelligent en utilisant des embeddings
Maintenant que nous comprenons ce que sont les embeddings, la question suivante est : comment peuvent-ils nous aider à construire un bot plus intelligent ?
Tout d’abord, considérons ce qui se passe lorsque nous utilisons directement l’API GPT-3. L’utilisateur soumet une demande et le modèle répond du mieux qu’il peut.
Cependant, lorsque nous ajoutons du contexte à l’équation, les choses changent. Par exemple, lorsque j’ai demandé à ChatGPT le vainqueur de la Coupe du Monde après avoir fourni du contexte, cela a fait toute la différence.
Ainsi, le plan pour construire un bot plus intelligent est le suivant :
-
Intercepter la demande de l’utilisateur.
-
Calculer les embeddings pour cette demande, ce qui donne un vecteur.
-
Rechercher dans une base de données les documents proches du vecteur, car ils devraient être sémantiquement pertinents pour la demande initiale.
-
Envoyer la demande originale à GPT-3, avec tout contexte pertinent.
-
Transmettre la réponse de GPT-3 à l’utilisateur.
Commençons comme la plupart des projets, en concevant la base de données.
Création d’une base de connaissances avec des embeddings
Notre base de données de contexte doit inclure la documentation originale et leurs vecteurs respectifs. En principe, nous pouvons utiliser n’importe quel type de base de données pour cette tâche, mais une base de données vectorielle est l’outil optimal pour le travail.
Les bases de données vectorielles sont des bases de données spécialisées conçues pour stocker et récupérer des données vectorielles de haute dimension. Au lieu d’utiliser un langage de requête tel que SQL pour la recherche, nous fournissons un vecteur et demandons les N voisins les plus proches.
Pour générer les vecteurs, nous utiliserons le modèle text-embedding-ada-002 d’OpenAI, car il est le plus rapide et le plus rentable qu’ils proposent. Le modèle convertit le texte d’entrée en tokens et utilise un mécanisme d’attention appelé Transformer pour apprendre leurs relations. La sortie de ce réseau neuronal est des vecteurs représentant la signification du texte.
Pour créer une base de données de contexte, je vais :
-
Collecter toute la documentation source.
-
Filtrer les documents non pertinents.
-
Calculer les embeddings pour chaque document.
-
Stocker les vecteurs, le texte original et toute autre métadonnée pertinente dans la base de données.
Conversion de documents en vecteurs
Tout d’abord, je dois initialiser un fichier d’environnement avec la clé API OpenAI. Ce fichier ne doit jamais être inclus dans le contrôle de version, car la clé API est privée et liée à votre compte.
export OPENAI_API_KEY=VOTRE_CLÉ_API
Ensuite, je vais créer un environnement virtuel pour mon application Python :
$ virtualenv venv
$ source venv/bin/activate
$ source .env
Et installer le package OpenAI :
```bash
$ pip install openai numpy
Essayons maintenant de calculer l’incorporation pour la chaîne “Docker Container”. Vous pouvez exécuter cela sur le REPL Python ou en tant que script Python :
$ python
>>> import openai
>>> embeddings = openai.Embedding.create(input="Docker Containers", engine="text-embedding-ada-002")
>>> embeddings
JSON: {
"data": [
{
"embedding": [
-0.00530336843803525,
0.0013223182177171111,
... 1533 autres éléments ...,
-0.015645816922187805
],
"index": 0,
"object": "embedding"
}
],
"model": "text-embedding-ada-002-v2",
"object": "list",
"usage": {
"prompt_tokens": 2,
"total_tokens": 2
}
}
Comme vous pouvez le voir, le modèle d’OpenAI renvoie une liste d’« incorporations » contenant 1536 éléments, qui est la taille du vecteur pour text-embedding-ada-002.
Stocker les incorporations dans Pinecone
Il existe plusieurs moteurs de bases de données vectorielles parmi lesquels choisir, comme Chroma, qui est open-source. J’ai choisi Pinecone parce que c’est une base de données gérée avec une offre gratuite, ce qui simplifie les choses. Leur plan Starter est plus que capable de gérer toutes les données dont j’aurai besoin.
Après avoir créé mon compte Pinecone et récupéré ma clé d’API et mon environnement, j’ajoute les deux valeurs à mon fichier .env
.
Maintenant, .env
devrait contenir mes secrets Pinecone et OpenAI.
export OPENAI_API_KEY=VOTRE_CLÉ_API
# Secrets Pinecone
export PINECONE_API_KEY=VOTRE_CLÉ_API
export PINECONE_ENVIRONMENT=VOTRE_CENTRE_DE_DONNÉES_PINECONE
Ensuite, j’installe le client Pinecone pour Python :
$ pip install pinecone-client
Je dois initialiser une base de données ; voici le contenu du script db_create.py
:
# db_create.py
import pinecone
import openai
import os
nom_index = "sémaphore"
modèle_incorp = "text-embedding-ada-002"
clé_api = os.getenv("PINECONE_API_KEY")
environnement = os.getenv("PINECONE_ENVIRONMENT")
pinecone.init(api_key=clé_api, environment=environnement)
incorporation = openai.Embedding.create(
input=[
"Voici le texte de l'exemple du document",
"il y aura plusieurs phrases dans chaque lot"
], engine=modèle_incorp
)
if nom_index not in pinecone.list_indexes():
print("Création de l'index Pinecone : " + nom_index)
pinecone.create_index(
nom_index,
dimension=len(incorporation['data'][0]['embedding']),
metric='cosine',
metadata_config={'indexed': ['source', 'id']}
)
Le script peut prendre quelques minutes pour créer la base de données.
$ python db_create.py
Ensuite, j’installerai le package tiktoken. Je l’utiliserai pour calculer combien de jetons les documents sources contiennent. C’est important car le modèle d’incorporation ne peut gérer que jusqu’à 8191 jetons.
$ pip install tiktoken
Pendant l’installation des packages, installons également tqdm
pour afficher une barre de progression agréable.
$ pip install tqdm
Maintenant, je dois télécharger les documents dans la base de données. Le script pour cela s’appellera index_docs.py
. Commençons par importer les modules requis et définir certaines constantes :
# index_docs.py
# Nom de la base de données Pinecone et taille de lot de téléchargement
nom_index = 'sémaphore'
taille_lot_mise_à_jour = 20
# Modèles d'incorporation et de tokenization OpenAI
modèle_incorp = "text-embedding-ada-002"
modèle_encodage = "cl100k_base"
modèle_max_jetons = 8191
Ensuite, nous aurons besoin d’une fonction pour compter les jetons. Il y a un exemple de compteur de jetons sur la page d’OpenAI :
import tiktoken
def num_tokens_from_string(string: str) -> int:
"""Retourne le nombre de jetons dans une chaîne de texte."""
encodage = tiktoken.get_encoding(encoding_model)
num_tokens = len(encodage.encode(string))
return num_tokens
Enfin, j’aurai besoin de certaines fonctions de filtrage pour convertir le document original en exemples utilisables. La plupart des exemples dans la documentation se trouvent entre des barrières de code, je vais donc extraire tout le code YAML de chaque fichier:
import re
def extract_yaml(text: str) -> str:
"""Retourne une liste contenant tous les blocs de code YAML trouvés dans le texte."""
correspondances = [m.group(1) for m in re.finditer("```yaml([\w\W]*?)```", text)]
return correspondances
J’ai terminé avec les fonctions. Ensuite, cela chargera les fichiers en mémoire et extraira les exemples:
from tqdm import tqdm
import sys
import os
import pathlib
repo_path = sys.argv[1]
repo_path = os.path.abspath(repo_path)
repo = pathlib.Path(repo_path)
markdown_files = list(repo.glob("**/*.md")) + list(
repo.glob("**/*.mdx")
)
print(f"Extraction du YAML à partir des fichiers Markdown dans {repo_path}")
new_data = []
for i in tqdm(range(0, len(markdown_files))):
markdown_file = markdown_files[i]
with open(markdown_file, "r") as f:
relative_path = markdown_file.relative_to(repo_path)
text = str(f.read())
if text == '':
continue
yamls = extract_yaml(text)
j = 0
for y in yamls:
j = j+1
new_data.append({
"source": str(relative_path),
"text": y,
"id": f"github.com/semaphore/docs/{relative_path}[{j}]"
})
À ce stade, tous les YAML devraient être stockés dans la liste new_data
. La dernière étape consiste à télécharger les embeddings dans Pinecone.
import pinecone
import openai
api_key = os.getenv("PINECONE_API_KEY")
env = os.getenv("PINECONE_ENVIRONMENT")
pinecone.init(api_key=api_key, enviroment=env)
index = pinecone.Index(index_name)
print(f"Création des embeddings et téléchargement des vecteurs dans la base de données")
for i in tqdm(range(0, len(new_data), upsert_batch_size)):
i_end = min(len(new_data), i+upsert_batch_size)
meta_batch = new_data[i:i_end]
ids_batch = [x['id'] for x in meta_batch]
texts = [x['text'] for x in meta_batch]
embedding = openai.Embedding.create(input=texts, engine=embed_model)
embeds = [record['embedding'] for record in embedding['data']]
# nettoyer les métadonnées avant la mise à jour
meta_batch = [{
'id': x['id'],
'text': x['text'],
'source': x['source']
} for x in meta_batch]
to_upsert = list(zip(ids_batch, embeds, meta_batch))
index.upsert(vectors=to_upsert)
À titre de référence, vous pouvez trouver le fichier index_docs.py complet dans le référentiel de démonstration.
Exécutons le script d’indexation pour terminer la configuration de la base de données:
$ git clone https://github.com/semaphoreci/docs.git /tmp/docs
$ source .env
$ python index_docs.py /tmp/docs
Tester la base de données
Le tableau de bord Pinecone devrait afficher des vecteurs dans la base de données.
Nous pouvons interroger la base de données avec le code suivant, que vous pouvez exécuter en tant que script ou directement dans le REPL Python:
$ python
>>> import os
>>> import pinecone
>>> import openai
# Calculer les embeddings pour la chaîne "Docker Container"
>>> embeddings = openai.Embedding.create(input="Docker Containers", engine="text-embedding-ada-002")
# Connexion à la base de données
>>> index_name = "semaphore"
>>> api_key = os.getenv("PINECONE_API_KEY")
>>> env = os.getenv("PINECONE_ENVIRONMENT")
>>> pinecone.init(api_key=api_key, environment=env)
>>> index = pinecone.Index(index_name)
# Interroger la base de données
>>> correspondances = index.query(embeddings['data'][0]['embedding'], top_k=1, include_metadata=True)
>>> correspondances['matches'][0]
{'id': 'github.com/semaphore/docs/docs/ci-cd-environment/docker-authentication.md[3]',
'metadata': {'id': 'github.com/semaphore/docs/docs/ci-cd-environment/docker-authentication.md[3]',
'source': 'docs/ci-cd-environment/docker-authentication.md',
'text': '\n'
'# .semaphore/semaphore.yml\n'
'version: v1.0\n'
'name: Using a Docker image\n'
'agent:\n'
' machine:\n'
' type: e1-standard-2\n'
' os_image: ubuntu1804\n'
'\n'
'blocks:\n'
' - name: Run container from Docker Hub\n'
' task:\n'
' jobs:\n'
' - name: Authenticate docker pull\n'
' commands:\n'
' - checkout\n'
' - echo $DOCKERHUB_PASSWORD | docker login '
'--username "$DOCKERHUB_USERNAME" --password-stdin\n'
' - docker pull /\n'
' - docker images\n'
' - docker run /\n'
' secrets:\n'
' - name: docker-hub\n'},
'score': 0.796259582,
'values': []}
Comme vous pouvez le voir, la première correspondance est le fichier YAML pour une pipeline Semaphore qui récupère une image Docker et l’exécute. C’est un bon début car cela est pertinent pour notre chaîne de recherche “Docker Containers”.
Construction du Bot
Nous avons les données et nous savons comment les interroger. Mettons-les au travail dans le bot.
Les étapes de traitement de l’invite sont :
-
Prenez l’invite de l’utilisateur.
-
Calculez son vecteur.
-
Récupérez le contexte pertinent depuis la base de données.
-
Envoyez l’invite de l’utilisateur avec le contexte à GPT-3.
-
Transmettez la réponse du modèle à l’utilisateur.
Comme d’habitude, je commencerai par définir quelques constantes dans complete.py
, le script principal du bot :
# complete.py
# Nom de la base de données Pinecone, nombre de correspondances à récupérer
# Score de similarité de coupure, et combien de jetons comme contexte
index_name = 'semaphore'
context_cap_per_query = 30
match_min_score = 0.75
context_tokens_per_query = 3000
# Paramètres du modèle OpenAI LLM
chat_engine_model = "gpt-3.5-turbo"
max_tokens_model = 4096
temperature = 0.2
embed_model = "text-embedding-ada-002"
encoding_model_messages = "gpt-3.5-turbo-0301"
encoding_model_strings = "cl100k_base"
import pinecone
import os
# Connexion à la base de données Pinecone et à l'index
api_key = os.getenv("PINECONE_API_KEY")
env = os.getenv("PINECONE_ENVIRONMENT")
pinecone.init(api_key=api_key, environment=env)
index = pinecone.Index(index_name)
Ensuite, j’ajouterai des fonctions pour compter les jetons comme indiqué dans les exemples OpenAI. La première fonction compte les jetons dans une chaîne, tandis que la deuxième compte les jetons dans les messages. Nous verrons les messages en détail dans un instant. Pour l’instant, disons simplement que c’est une structure qui conserve l’état de la conversation en mémoire.
import tiktoken
def num_tokens_from_string(string: str) -> int:
"""Retourne le nombre de jetons dans une chaîne de texte."""
encoding = tiktoken.get_encoding(encoding_model_strings)
num_tokens = len(encoding.encode(string))
return num_tokens
def num_tokens_from_messages(messages):
"""Retourne le nombre de jetons utilisés par une liste de messages. Compatible avec le modèle """
try:
encoding = tiktoken.encoding_for_model(encoding_model_messages)
except KeyError:
encoding = tiktoken.get_encoding(encoding_model_strings)
num_tokens = 0
for message in messages:
num_tokens += 4 # chaque message suit {rôle/nom}\n{contenu}\n
for key, value in message.items():
num_tokens += len(encoding.encode(value))
if key == "name": # s'il y a un nom, le rôle est omis
num_tokens += -1 # le rôle est toujours requis et toujours 1 jeton
num_tokens += 2 # chaque réponse est amorcée avec assistant
return num_tokens
La fonction suivante prend les chaînes d’invite et de contexte d’origine pour renvoyer une invite enrichie pour GPT-3 :
def get_prompt(query: str, context: str) -> str:
"""Retourne l'invite avec la requête et le contexte."""
return (
f"Créez le code YAML de la pipeline d'intégration continue pour accomplir la tâche demandée.\n" +
f"Vous trouverez ci-dessous un contexte qui peut être utile. Ignorez-le s'il semble sans rapport.\n\n" +
f"Contexte :\n{context}" +
f"\n\nTâche : {query}\n\nCode YAML :"
)
La fonction get_message
formate l’invite dans un format compatible avec l’API :
def get_message(role: str, content: str) -> dict:
"""Génère un message pour la complétion de l'API OpenAI."""
return {"role": role, "content": content}
Il existe trois types de rôles qui affectent la réaction du modèle :
-
Utilisateur : pour l’invite originale de l’utilisateur.
-
Système : aide à définir le comportement de l’assistant. Bien qu’il y ait une certaine controverse concernant son efficacité, il semble être plus efficace lorsqu’il est envoyé à la fin de la liste des messages.
-
Assistant : représente les réponses passées du modèle. L’API OpenAI n’a pas de “mémoire” ; nous devons plutôt renvoyer les réponses précédentes du modèle lors de chaque interaction pour maintenir la conversation.
Maintenant, passons à la partie intéressante. La fonction get_context
prend le prompt, interroge la base de données et génère une chaîne de contexte jusqu’à ce que l’une de ces conditions soit remplie :
-
Le texte complet dépasse
context_tokens_per_query
, l’espace que j’ai réservé pour le contexte. -
La fonction de recherche récupère toutes les correspondances demandées.
-
Les correspondances dont le score de similarité est inférieur à
match_min_score
sont ignorées.
import openai
def get_context(query: str, max_tokens: int) -> list:
"""Génère un message pour le modèle OpenAI. Ajoute du contexte jusqu'à atteindre la limite de `context_token_limit`. Renvoie une chaîne prompt."""
embeddings = openai.Embedding.create(
input=[query],
engine=embed_model
)
# recherche dans la base de données
vectors = embeddings['data'][0]['embedding']
embeddings = index.query(vectors, top_k=context_cap_per_query, include_metadata=True)
matches = embeddings['matches']
# filtrer et agréger le contexte
usable_context = ""
context_count = 0
for i in range(0, len(matches)):
source = matches[i]['metadata']['source']
if matches[i]['score'] < match_min_score:
# ignorer le contexte avec un score de similarité faible
continue
context = matches[i]['metadata']['text']
token_count = num_tokens_from_string(usable_context + '\n---\n' + context)
if token_count < context_tokens_per_query:
usable_context = usable_context + '\n---\n' + context
context_count = context_count + 1
print(f"Trouvé {context_count} contextes pour votre requête")
return usable_context
La fonction suivante et dernière, complete
, envoie la requête API à OpenAI et renvoie la réponse du modèle.
def complete(messages):
"""Interroge le modèle OpenAI. Renvoie la première réponse."""
res = openai.ChatCompletion.create(
model=chat_engine_model,
messages=messages,
temperature=temperature
)
return res.choices[0].message.content.strip()
C’est tout ; maintenant, je n’ai plus qu’à gérer les arguments de ligne de commande et appeler les fonctions dans le bon ordre :
import sys
query = sys.argv[1]
context = get_context(query, context_tokens_per_query)
prompt = get_prompt(query, context)
# initialise la liste des messages à envoyer à l'API OpenAI
messages = []
messages.append(get_message('user', prompt))
messages.append(get_message('system', 'You are a helpful assistant that writes YAML code for Semaphore continuous integration pipelines and explains them. Return YAML code inside code fences.'))
if num_tokens_from_messages(messages) >= max_tokens_model:
raise Exception('Limite de taille des jetons du modèle atteinte')
print("Traitement de votre requête... ")
answer = complete(messages)
print("Réponse :\n")
print(answer)
messages.append(get_message('assistant', answer))
Il est temps d’exécuter le script et de voir comment cela se passe :
$ python complete.py "Créez un pipeline CI qui construit et télécharge une image Docker sur Docker Hub"
Le résultat est :
version: v1.0
name: Docker Build and Push
agent:
machine:
type: e1-standard-2
os_image: ubuntu1804
blocks:
- name: "Build and Push Docker Image"
task:
jobs:
- name: "Docker Build and Push"
commands:
- checkout
- docker build -t /: .
- echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
- docker push /:
promotions:
- name: Deploy to production
pipeline_file: deploy-production.yml
auto_promote:
when: "result = 'passed' and branch = 'master'"
C’est le premier bon résultat. Le modèle a déduit la syntaxe à partir des exemples de contexte que nous avons fournis.
Réflexions sur l’extension des capacités du bot
N’oubliez pas que j’ai commencé avec un objectif modeste : créer un assistant pour écrire des pipelines YAML. Avec un contenu plus riche dans ma base de vecteurs, je peux généraliser le bot pour répondre à toutes les questions sur Semaphore (ou sur n’importe quel produit – n’oubliez pas de cloner la documentation dans /tmp
?).
La clé pour obtenir de bonnes réponses est – sans surprise – un contexte de qualité. Le simple fait de télécharger tous les documents dans la base de vecteurs est peu susceptible de donner de bons résultats. La base de données de contexte doit être organisée, étiquetée avec des métadonnées descriptives et concise. Sinon, nous risquons de remplir le quota de jetons dans le prompt avec un contexte non pertinent.
Donc, en un sens, il y a un art – et beaucoup d’essais et d’erreurs – impliqué dans le réglage fin du bot pour répondre à nos besoins. Nous pouvons expérimenter avec la limite de contexte, supprimer le contenu de faible qualité, résumer et filtrer le contexte non pertinent en ajustant le score de similarité.
Mise en œuvre d’un chatbot approprié
Vous avez peut-être remarqué que mon bot ne nous permet pas d’avoir une véritable conversation comme ChatGPT. Nous posons une question et obtenons une réponse.
Transformer le bot en un chatbot complet n’est, en principe, pas trop difficile. Nous pouvons maintenir la conversation en renvoyant les réponses précédentes au modèle avec chaque requête API. Les réponses précédentes de GPT-3 sont renvoyées sous le rôle “assistant”. Par exemple:
messages = []
while True:
query = input('Tapez votre requête:\n')
context = get_context(query, context_tokens_per_query)
prompt = get_prompt(query, context)
messages.append(get_message('utilisateur', prompt))
messages.append(get_message('système', 'Vous êtes un assistant utile qui écrit du code YAML pour les pipelines d'intégration continue de Semaphore et les explique. Renvoyez le code YAML entre des clôtures de code.'))
if num_tokens_from_messages(messages) >= max_tokens_model:
raise Exception('Limite de taille des jetons du modèle atteinte')
print("Travail sur votre requête... ")
answer = complete(messages)
print("Réponse:\n")
print(answer)
# supprimer le message du système et ajouter la réponse du modèle
messages.pop()
messages.append(get_message('assistant', answer))
Malheureusement, cette implémentation est plutôt rudimentaire. Elle ne prend pas en charge les conversations étendues car le nombre de jetons augmente à chaque interaction. Nous atteindrons bientôt la limite de 4096 jetons pour GPT-3, empêchant tout dialogue ultérieur.
Nous devons donc trouver un moyen de maintenir la requête dans les limites des jetons. Quelques stratégies suivent:
-
Supprimer les anciens messages. Bien que cela soit la solution la plus simple, cela limite la “mémoire” de la conversation aux messages les plus récents.
-
Résumer les messages précédents. Nous pouvons utiliser “Demander au modèle” pour condenser les messages antérieurs et les substituer aux questions et réponses d’origine. Bien que cette approche augmente le coût et le délai entre les requêtes, elle peut produire des résultats supérieurs par rapport à la simple suppression des messages passés.
-
Fixer une limite stricte sur le nombre d’interactions.
-
Attendre la disponibilité générale de l’API GPT-4, qui est non seulement plus intelligente mais a une capacité de jetons doublée.
-
Utiliser un modèle plus récent comme “gpt-3.5-turbo-16k” qui peut gérer jusqu’à 16k jetons.
Conclusion
Améliorer les réponses du bot est possible avec les embeddings de mots et une bonne base de données de contexte. Pour cela, nous avons besoin d’une documentation de bonne qualité. Il y a une quantité substantielle d’essais et d’erreurs impliquée dans le développement d’un bot qui semble avoir une compréhension du sujet.
J’espère que cette exploration approfondie des embeddings de mots et des grands modèles de langage vous aidera à construire un bot plus puissant, adapté à vos besoins.
Bonne construction !
We will continue to update IPGirl; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles
- Thèmes par classe utilisant BERTopic
- Quoi de neuf au sommet Ai X Business et Innovation
- Cette recherche en IA présente Point-Bind un modèle multi-modalité 3D alignant des nuages de points avec une image 2D, du langage, de l’audio et de la vidéo.
- Le Congrès tiendra de nouvelles audiences sur l’IA
- Introduction à l’ingénierie de l’apprentissage automatique et aux LLMOps avec OpenAI et LangChain
- Les programmes pilotes d’IA visent à réduire la consommation d’énergie et les émissions sur le campus du MIT
- Améliorer la santé des actifs et la résilience du réseau grâce à l’apprentissage automatique