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 :

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 :

  1. Intercepter la demande de l’utilisateur.

  2. Calculer les embeddings pour cette demande, ce qui donne un vecteur.

  3. Rechercher dans une base de données les documents proches du vecteur, car ils devraient être sémantiquement pertinents pour la demande initiale.

  4. Envoyer la demande originale à GPT-3, avec tout contexte pertinent.

  5. 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 :

  1. Collecter toute la documentation source.

  2. Filtrer les documents non pertinents.

  3. Calculer les embeddings pour chaque document.

  4. 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 :

  1. Prenez l’invite de l’utilisateur.

  2. Calculez son vecteur.

  3. Récupérez le contexte pertinent depuis la base de données.

  4. Envoyez l’invite de l’utilisateur avec le contexte à GPT-3.

  5. 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!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

AI

Six Étapes vers la Sécurité de l'IA

Dans le sillage de ChatGPT, chaque entreprise essaie de définir sa stratégie en matière d’IA, ce qui soulève ra...

AI

Cet article sur l'IA propose d'injecter le monde en 3D dans les grands modèles de langage et d'introduire une toute nouvelle famille de modèles de langage en 3D (3D-LLMs).

Au cours des dernières années, on a constaté une augmentation des grands modèles de langage (LLMs) (comme GPT4) qui e...

Actualités sur l'IA

Google Cloud aide la banque Macquarie à améliorer ses capacités de banque d'IA.

Le groupe de banque et de services financiers de Macquarie s’est associé à Google Cloud pour exploiter la puiss...

Science des données

La variable PATH pour le scientifique de données confus Comment la gérer

Comprendre ce qu'est PATH et comment ajouter des chemins à celui-ci dans les systèmes Windows et de type Unix.

AI

Cet article sur l'IA de GSAi China présente une étude complète des agents autonomes basés sur LLM.

Les agents autonomes représentent des systèmes auto-opérants qui présentent des degrés d’indépendance variables...