Programming

Recherche d’employés avec OpenAI, Flowise et LangChain | de John Damask | septembre 2023

[ad_1]

Image à mi-parcours d’un groupe de personnes.  La plupart portent des t-shirts rouges, une personne porte du vert.
Recherche de personnes (image générée par Midjourney)

Si tu lis mon précédent unArticle, vous savez que j’ai une version bostonienne de ChatGPT dans Slack appelée W’kid Smaaht (« méchant intelligent »). Mon équipe vit dans Slack, il est donc logique d’y avoir également un robot IA. Tu peux installer W’kid Smaaht vous-même… et changez de personnalité si vous n’êtes pas fan de Boston.

Pour cet exercice, j’ai souhaité ajouter une fonctionnalité dont toute entreprise de grande taille a besoin : la recherche d’employés par compétence. Cela semblait également être un bon moyen d’en savoir plus sur la génération augmentée de récupération (RAG).

Le MVP le plus simple auquel je pouvais penser était de récupérer notre site Web public pour les biographies des employés, de les fragmenter, de les stocker et de créer un agent conversationnel. Mon objectif était d’arriver à WOW le plus rapidement possible et de ne pas me soucier de tout rendre parfait.

Pourquoi coder un grattoir d’écran alors que GPT peut le faire à ma place ? Il a suffi de quelques DM à mon ami IA. Voici quelques captures d’écran des discussions avec le script final à la fin :

Invite : écrivez un script Python qui prend une URL, supprime toutes les sous-pages et génère un fichier texte pour chaque page.  Le nom du fichier doit être le nom de la page
Code pour moi, itération 1

Cela a fonctionné, mais n’a pas extrait le bon contenu.

Invite : réécrivez pour gratter uniquement les pages sous : 

<div class=” class=”bg nh ni c” width=”700″ height=”90″ loading=”lazy”/>
Code pour moi, itération 2

Problème. Plainte auprès de GPT.

Je me plains à W'kid Smaaht que le code ne fonctionne pas.  En réponse, plusieurs options me sont proposées.
IA pour le dépannage

Le n°2 était une bonne idée : « Inspectez la structure HTML », j’ai donc donné à GPT des informations supplémentaires.

Invite : voici un extrait du code HTML.  Notez le href imbriqué — ce sont ceux-là que je veux gratter 

<div class=”people-grid__grid people-grid__grid — big” id=”people”>” class=”bg nh ni c” width=”700″ height=”209″ loading=”lazy”/></picture></div>
</div><figcaption class=Code pour moi, itération 3

Ça marche. Bon. Mais je veux juste un texte biographique. Demandez à W’kid Smaaht d’extraire uniquement le texte dans un div étiqueter.

Invite : super.  Modifiez maintenant pour extraire uniquement le texte trouvé dans 

<div class=

” class=”bg nh ni c” width=”700″ height=”89″ loading=”lazy”/>

Code pour moi, itération 4

Cool, mais je ne reçois qu’une douzaine de fichiers texte. Pourquoi? Ah, le site Web a un défilement infini. Comment puis-je gérer cela ?

Invite : parfait.  Il semble que la page Web soit dynamique, de sorte que davantage de personnes apparaissent lorsque l'utilisateur la fait défiler.  Comment puis-je faire défiler par programmation pour que ce script trouve tout le monde ?
Code pour moi, itération 5

Et voici le code :

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup
import time
import os
import requests

def scrape_site(base_url):
driver = webdriver.Chrome() # use Chrome
driver.get(base_url)
time.sleep(5) # wait for the page to load

# Scroll to the bottom of the page until no more new content is loaded
last_height = driver.execute_script("return document.body.scrollHeight")
while True:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2) # wait for new content to load
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
break
last_height = new_height

soup = BeautifulSoup(driver.page_source, 'html.parser')
people_grid = soup.find('div', {'class': 'people-grid__grid people-grid__grid--big', 'id': 'people'})
for link in people_grid.find_all('a', {'class': 'people-grid__link'}):
url = link.get('href')
if url and url.startswith('http'):
scrape_subpage(url)

driver.quit()

def scrape_subpage(url):
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
text_div = soup.find('div', {'class': 'redactor redactor--dropcap'})
if text_div:
text = text_div.get_text(strip=True)
page_name = url.split('/')(-1)
name = ' '.join(word.capitalize() for word in page_name.split('-'))
with open(f'docs/{page_name}.txt', 'w') as f:
s = f"{name}. {text}"
f.write(s)

base_url = ' # replace with your URL
scrape_site(base_url)

J’ai enregistré le code sous selenium_scrape.pyA créé un venvpip installé les bibliothèques, installé pilote chrome, et laissez-le se déchirer. Le code a bien fonctionné dès le départ, créant un fichier distinct pour chaque employé avec sa biographie extraite du site Web.

Capture d'écran de l'IDE montrant la sortie des fichiers texte pour chaque personne.
Un dossier par employé

L’étape suivante consistait à découper les bios et à les charger dans un magasin de vecteurs (base de données). Mais je n’avais jamais utilisé de magasin vectoriel auparavant, donc je ne savais pas ce que je ne savais pas. La bonne nouvelle est que nous sommes à l’ère de l’apprentissage juste à temps, et l’apprentissage des bases n’a pas pris longtemps.

Vous pouvez trouver de nombreux didacticiels en ligne sur les magasins de vecteurs, cet article ne répétera donc pas les raisons d’en utiliser un. Sachez simplement qu’ils sont utiles lorsque vous recherchez des informations sémantiquement similaires à un terme de recherche donné. Par exemple, une requête avec le terme « biologie » peut renvoyer des enregistrements pour « biologie moléculaire », « biochimie », etc.

LangChain a un super introduction sur les magasins vectoriels et prend en charge plusieurs des plus populaires. J’ai choisi Pomme de pin parce que je voulais un SaaS (pas un magasin local ou en mémoire) facile à utiliser et gratuit pour un MVP.

Après m’être inscrit, j’ai commencé à créer un index et j’ai été immédiatement perplexe ; de combien de dimensions cela a-t-il besoin ? Il s’avère que la réponse vient de Le modèle d’intégration d’OpenAI — 1536. Il y a sûrement d’autres considérations, mais cela a suffi pour continuer à avancer.

Créer un index dans Pinecone
Indice pomme de pin

Super. J’ai un magasin de contenu et de vecteurs. Comment charger les données ?

Entrez Flowise

En recherchant les didacticiels LangChain, je suis tombé sur celui de Leon van Zyl. fantastique série de vidéos sur Flowise, un outil sans code pour créer des applications LLM qui intègrent de nombreuses fonctionnalités LangChain. Suite aux vidéos et Flowise Par exemple, j’ai pu créer un chargeur Pinecone et un chatbot pour interagir avec mes données en moins d’une heure. Et encore mieux, voir comment les différents composants étaient liés m’a aidé à comprendre un peu plus LangChain.

Commencer était aussi simple que de forger Flowiseen le clonant et en le configurant selon le fichier README.

Chargeur

Pour charger les documents, j’ai créé un « flux de discussion » Flowise en suivant le tutoriel de Léon et en peaufinant les choses en cours de route.

Flowise Chatflow pour charger le bios dans Pineone
Flux de discussion Flowise pour charger le bios dans Pinecone

Vous pouvez voir à quel point c’est conceptuellement simple. Parcourez les fichiers d’un dossier, lisez et divisez selon certaines règles, créez des intégrations pour chacun et insérez.

Dans ce flux, une discussion déclenche la charge – ce qui est une UX étrange – mais peu importe, les MVP visent à faire fonctionner quelque chose rapidement, pas à l’élégance. Les biographies d’environ 300 personnes ont été traitées en quelques minutes.

Nous souhaitons créer un flux distinct pour la recherche d’employés, car il s’agit d’une conception à chargement unique et à plusieurs requêtes. Si celles-ci étaient combinées en un seul flux, chaque session déclencherait un rechargement de toutes les données.

Représentation visuelle du chatbot de recherche de personnes Flowise.
ChatBot de recherche de personnes

Cela n’aurait guère pu être plus simple.

Maintenant quoi? Flowise fonctionne sur mon Mac, mais j’en ai besoin sur un serveur Web. Idéalement, il s’agirait d’un conteneur Docker exécuté dans le même compte AWS que W’kid Smaaht, mais Flowise n’a pas encore documenté le déploiement ECS, j’ai donc opté pour une solution plus simple. Rendre.

chez Léon Tutoriel Flowise AI n°5 — Déployer pour le rendu m’a amené là où je devais aller. Après avoir créé mon compte Render gratuit et configuré les bases, je l’ai connecté à mon dépôt Flowise forké et j’ai créé un nouveau service Web Render (Node).

Déployer un service Web sur Render
Déployer un service Web sur Render

Après une dizaine de minutes, le service était en direct.

Image du rendu créant mon application Flowise avec une URL publique
Service Web en direct sur Render

Qu’est-ce qui vient de se passer? Un tas de trucs. Mais je n’avais pas besoin d’en savoir plus. Render a créé l’infrastructure, installé Flowise et l’a exposée derrière une URL publique. Désormais, je peux accéder à Flowise via Render, comme je le faisais sur mon ordinateur portable.

Ensuite, j’ai exporté ma version locale du flux de discussion FSP People QA et je l’ai importé dans Flowise, exécuté sur Render. Les clés API ne sont pas copiées lorsque vous exportez/importez un seul flux de discussion, j’ai donc recréé les informations d’identification de l’API OpenAI et Pinecone dans mon Render Flowise et les ai ajoutées au flux de discussion.

Succès!

Capture d'écran d'un chatbot de recherche d'employés Flowise fonctionnel
Un chatbot de recherche d’employés Flowise fonctionnel

API

Mais je ne veux pas d’interface graphique Web pour cela ; Je veux une API. Pour ce faire, j’ai suivi un autre tutoriel de Léon sur l’utilisation Points de terminaison Flowise.

Il était facile de sécuriser le point de terminaison avec une nouvelle clé et de récupérer le code Python de référence pratique à ajouter à mon application. Pour voir l’intégralité de la fonction, consultez le localsrc/utils.py fichier dans le 17-add-flowise-fsp-emp-directory-api-calls branche de W’kid Smaaht.

Référencez le code Python de Render pour utiliser mon nouveau point de terminaison API
Code de référence Python pour appeler mon point de terminaison de l’API de rendu

Pour le tester, j’ai ajouté un petit hook pour rechercher les messages commençant par « :fsp » et transmettre l’entrée à l’API.

Alors, W’kid Smaaht est-il plus intelligent ? Ouais.

Invite : fsp qui connaît la protéomique ?  Réponse : Les personnes ayant des connaissances en protéomique sont John et Dean.
Tester le point de terminaison dans W’kid Smaaht (Slack)

Bon. Mais ce n’est pas très utile. À moins de connaître John et Dean, nous n’obtenons pas suffisamment d’informations pour nous aider de manière significative. Ce serait mieux si cela incluait un lien vers leurs pages bio.

Il s’avère que cela peut être fait en activant l’option « Retourner les documents sources » dans le widget Conversational Retrieval QA Chain du flux de discussion et en analysant les métadonnées.

Activez l'option « Retourner les documents sources » dans le widget Conversational Retrieval QA Chain de mon chatflow Flowise.
Activez « Retourner les documents sources » dans le widget Conversational Retrieval QA Chain Flowise

Une fois activé, j’ai vérifié la structure de l’objet dans mon débogueur pour savoir quel champ contenait la source. Mais attendez… la source est le fichier qui a été fragmenté et téléchargé sur Pinecone. J’ai besoin d’une URL.

Débogueur inspectant l'objet sourceDocuments renvoyé par ma recherche de personnes Flowsise.  Le champ sourceDocuments(0).metadata.source contient le chemin du fichier du document source que j'ai chargé dans Pinecone
Inspecter l’objet sourceDocuments renvoyé par mon flux de discussion Flowise

C’est assez facile à régler. Puisque nous savons que le nom de fichier a été extrait de l’URL de la page, nous pouvons simplement le reconvertir. Divisez simplement la chaîne « source » pour récupérer le nom d’utilisateur et l’ajouter à l’URL de mon entreprise. Ensuite, nous pouvons renvoyer la liste modifiée à Slack avec la réponse textuelle.

Image me montrant en train d'effectuer une recherche de personnes depuis l'application W'kid Smaaht Slack.  La réponse comprend une phrase et une liste d’URL renvoyant vers des pages biographiques des employés.
Résultats, y compris les hyperliens bio

C’est mieux mais ça reste un peu bancal. Je préférerais que les hyperliens soient intégrés dans la chaîne. Mais coder cette logique n’est pas simple car certaines biographies incluent le nom complet de la personne alors que d’autres ne le font pas. Par exemple, le Dr Jane Doe peut apparaître comme « Dr. Jane Doe », « Dr. Doe », « Jane Doe » ou simplement « Jane » dans le texte. Comment dois-je résoudre ce problème ? Hmm….

Mdr, attends… à quoi je pense ? Les LLM sont excellents dans ce genre de choses, et LangChain facilite la tâche avec seulement quelques lignes de code.

Inclure le ChatPromptTemplate, SystemMessagePromptTemplateet HumanMessagePromptTemplate dans le code suivant :

from langchain.prompts import ChatPromptTemplate
from langchain.prompts.chat import SystemMessagePromptTemplate, HumanMessagePromptTemplate

Ensuite, ajoutez des modèles d’invite pour le système et l’humain et créez leurs invites de discussion. Notez que l’invite spécifie de renvoyer une chaîne formatée selon la convention de Slack. En effet, la manière dont Slack code les hyperliens est unique.

    system_template = """You take a string and a python list of URLs \
and do your best to attach the URLs as hyperlinks to their \
proper locations in the string. The string return is the same as the text \
string that was provided to you, but with hyperlinks inserted into the right places. \
The string you return must have its hyperlinks formatted according to \
Slack convention.

Example:
< and < \
have experience with AWS
"""

# create a prompt template for a System role
system_message_prompt_template = SystemMessagePromptTemplate.from_template(
system_template)

# create a string template for a Human role with input variables
human_template = "{text} {urls}"

# create a prompt template for a Human role
human_message_prompt_template = HumanMessagePromptTemplate.from_template(human_template)

# create chat prompt template
chat_prompt_template = ChatPromptTemplate.from_messages(
(system_message_prompt_template, human_message_prompt_template))

# generate a final prompt by passing variables (`text`, `urls`)
final_prompt = chat_prompt_template.format_prompt(text=rt, urls=sl).to_messages()

La dernière étape consiste à effectuer un autre appel à l’API d’OpenAI pour que le LLM reformate la chaîne. Pour la vitesse, et parce que cela semble être toujours correct, j’appelle gpt-3.5-turbo plutôt que gpt-4.

llm = ChatOpenAI(model_name='gpt-3.5-turbo', openai_api_key=OPENAI_API_KEY, streaming=False)
response_string = llm(final_prompt)

return response_string.content

Désormais, les résultats sont slackifiés !

La recherche d'employés renvoie désormais une chaîne avec des hyperliens intégrés
Résultats de recherche avec des noms hyperliés

Comme vous pouvez le constater, les résultats ne sont pas toujours parfaits, mais ils sont vraiment bons et surpassent certainement les URL brutes.

C’était une autre expérience pour me familiariser avec l’écriture d’applications LLM. L’ensemble d’outils a rendu le processus rapide et amusant, mais le travail acharné commence maintenant : je ne peux pas le mettre en production tel quel.

Je n’ai pas montré les problèmes que j’ai rencontrés pour obtenir des résultats cohérents et complets à partir du magasin de vecteurs et de GPT. Contrairement aux applications de données traditionnelles, cela ressemblait un peu plus à de l’art qu’à de la science, mais je reconnais que cela tient en partie à mon manque d’expérience avec elles.

De plus, les réponses de Pinecone et des LLM ne sont pas déterministes, il y a donc un tas de logique conditionnelle et d’erreur qui doivent être écrites, ce qui peut être fastidieux. Mais peut-être dois-je adapter ma réflexion, passant du codage à travers des problèmes à l’écriture de meilleures invites ou à l’utilisation d’agents pour raisonner à ma place. Nous verrons.

[ad_2]

Source link

Related Articles

Back to top button