copilotdatast / app.py
Ludovicollin's picture
Upload app.py
75b834f
raw
history blame
20 kB
import os
import time
from operator import itemgetter
from collections import Counter
from langchain.schema.runnable import Runnable, RunnablePassthrough, RunnableLambda
from langchain.schema.runnable.config import RunnableConfig
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.chains import ConversationalRetrievalChain
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from langchain.chains import LLMChain
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.schema import StrOutputParser
from langchain.chains.conversational_retrieval.prompts import CONDENSE_QUESTION_PROMPT
from langchain.chains.question_answering import load_qa_chain
from langchain.chains.qa_with_sources import load_qa_with_sources_chain
from langchain.vectorstores import Pinecone
import pinecone
from langchain.memory import ChatMessageHistory, ConversationBufferMemory
import pandas as pd
import numpy as np
from anthropic import Anthropic, HUMAN_PROMPT, AI_PROMPT
from langchain.chat_models import ChatAnthropic
import chainlit as cl
from chainlit.input_widget import TextInput
from chainlit import user_session
from offres_emploi import Api
from offres_emploi.utils import dt_to_str_iso
import datetime
@cl.author_rename
def rename(orig_author: str):
rename_dict = {"ConversationalRetrievalChain": "💬 Assistant conversationnel", "Retriever": "Agent conversationnel", "StuffDocumentsChain": "Chaîne de documents", "LLMChain": "Agent", "ChatAnthropic": "🤖 IA"}
return rename_dict.get(orig_author, orig_author)
@cl.action_callback("download")
async def on_action(action):
content = []
content.append(action.value)
arrayContent = np.array(content)
df = pd.DataFrame(arrayContent)
with open('.chainlit/' + action.description + '.txt', 'wb') as csv_file:
df.to_csv(path_or_buf=csv_file, index=False,header=False, encoding='utf-8')
elements = [
cl.File(
name= action.description + ".txt",
path="./.chainlit/" + action.description + ".txt",
display="inline",
),
]
await cl.Message(
author="🌐🌐🌐", content="[Lien] 🔗", elements=elements
).send()
await action.remove()
@cl.action_callback("close_button")
async def on_action(action):
time.sleep(0.5)
track = user_session.get("tracker")
await track.remove()
@cl.action_callback("action_button")
async def on_action(action):
task_list = cl.TaskList()
# Create the TaskList
# Create a task and put it in the running state
task1 = cl.Task(title="Processing data Processing data Processing data Processing data Processing data Processing data Processing data Processing data Processing data Processing data Processing data Processing data Processing data Processing data Processing data Processing data Processing data Processing data Processing data Processing data Processing data \n\n Processing data", status=cl.TaskStatus.READY)
await task_list.add_task(task1)
task2 = cl.Task(title=action.value, status=cl.TaskStatus.READY)
await task_list.add_task(task2)
# Perform some action on your end
await task_list.send()
tracking = user_session.set("tracker", task_list)
others = [
cl.Action(name="close_button", value="closed", label="Fermer", description="Fermer le volet d'information!")
]
await cl.Message(author="🌐🌐🌐",content="Fermer le panneau d'information", actions=others).send()
@cl.cache
def to_cache(file):
#time.sleep(5) # Simulate a time-consuming process
return "https://cipen.univ-gustave-eiffel.fr/fileadmin/CIPEN/datas/assets/docs/" + file + ".csv"
@cl.set_chat_profiles
async def chat_profile():
return [
cl.ChatProfile(name="OF - Offre de formation",markdown_description="Requêter sur l'offre de formation - OF",icon="./public/favicon.png",),
cl.ChatProfile(name="Emplois - En direct de Pole Emploi",markdown_description="Emplois - En direct de Pole Emploi",icon="./public/favicon.png",),
cl.ChatProfile(name="K1902 - LP MDAI",markdown_description="K1902 - LP MDAI : requête sur les offres d'emploi",icon="./public/favicon.png",),
cl.ChatProfile(name="M1802-I1401-M1810-M1801-M1805 - Licence Maths-Info",markdown_description="M1802-I1401-M1810-M1801-M1805 - Licence Maths-Info : requête sur les offres d'emploi",icon="./public/favicon.png",),
cl.ChatProfile(name="K1207-G1202-G1204 - Licence STAPS",markdown_description="K1207-G1202-G1204 - Licence STAPS : requête sur les offres d'emploi",icon="./public/favicon.png",),
]
@cl.on_chat_start
async def start():
chat_profile = cl.user_session.get("chat_profile")
chatProfile = chat_profile.split(' - ')
if chatProfile[0] == 'OF':
connexion = cl.TaskList()
connexion.status = "Running..."
# Create a task and put it in the running state
task1 = cl.Task(title="Chargement des données, en attente...", status=cl.TaskStatus.RUNNING)
await connexion.add_task(task1)
await connexion.send()
logo = [
cl.Image(name="Logo", size="small", display="inline", path="./public/logo_light.png")
]
await cl.Message(author="🌐🌐🌐",content="", elements=logo).send()
await cl.Message(
author="🌐🌐🌐",content=f"Commencez à poser vos questions sur les données \"{chat_profile}\"\n\n💡Voici des exemples de question \n\t1️⃣ Basée sur les formations : Quelles sont toutes les formations licences générales?\n\t2️⃣ Basée sur les compétences : Quelles sont les compétences de la licence Economie et gestion?\n\t3️⃣ Basée sur les métiers : Quelles sont les métiers possibles de la licence Economie et gestion?\n\t4️⃣ Basée sur un souhait : Quelles formations si je veux travailler dans la vente?\n\t5️⃣ Basée sur un savoir-être : Quelles formations si j'aime travailler en équipe?\n\t6️⃣ Basée sur un état : Quelles formations si je suis créatif?\n\t7️⃣ Question multi-critère : Quelles sont les activités, les compétences et les métiers possibles de la licence Economie et gestion?"
).send()
settings = await cl.ChatSettings(
[
TextInput(id="AgentName", label="Renseigner votre code ROME", initial=""),
]
).send()
value = settings["AgentName"]
task1.status = cl.TaskStatus.DONE
await cl.sleep(0.5)
await connexion.remove()
if value:
await cl.Message(author="🌐🌐🌐",content=settings["AgentName"]).send()
index_name = os.environ['PINECONE_INDEX_NAME']
embeddings = HuggingFaceEmbeddings()
pinecone.init(
api_key=os.environ['PINECONE_API_KEY'],
environment=os.environ['PINECONE_ENVIRONMENT']
)
vectorstore = Pinecone.from_existing_index(
index_name=index_name, embedding=embeddings
)
os.environ['ANTHROPIC_API_KEY'] = os.environ['ANTHROPIC_API_KEY']
retriever = vectorstore.as_retriever(search_type="similarity_score_threshold", search_kwargs={"score_threshold": .7, "k": 60,"filter": {'categorie': {'$eq': 'OF'}}})
########## Chain with streaming ##########
message_history = ChatMessageHistory()
memory = ConversationBufferMemory(
memory_key="chat_history",
output_key="answer",
chat_memory=message_history,
return_messages=True,
)
llm = ChatAnthropic()
streaming_llm = ChatAnthropic(
streaming=True,
temperature=1,
max_tokens=4000
)
question_generator = LLMChain(llm=llm, prompt=CONDENSE_QUESTION_PROMPT)
doc_chain = load_qa_chain(streaming_llm, chain_type="stuff")
qa = ConversationalRetrievalChain(
retriever=retriever,
combine_docs_chain=doc_chain,
question_generator=question_generator,
memory=memory,
return_source_documents=True,
)
cl.user_session.set("conversation_chain", qa)
elif chatProfile[0] == 'Emplois':
poleemploi = cl.TaskList()
poleemploi.status = "Running..."
# Create a task and put it in the running state
task1 = cl.Task(title="Chargement des données du marché de l'emploi, en attente...", status=cl.TaskStatus.RUNNING)
await poleemploi.add_task(task1)
await poleemploi.send()
logo = [
cl.Image(name="Logo", size="small", display="inline", path="./public/logo_light.png")
]
await cl.Message(author="🌐🌐🌐",content="", elements=logo).send()
#file = to_cache(chatProfile[0])
await cl.Message(author="🌐🌐🌐",content=f"💻😊 Vous pouvez rechercher des \"{chat_profile}\"!").send()
await cl.Message(
author="🌐🌐🌐",content=f"💡Voici des exemples de requête \n\t1️⃣ Basée sur un code ROME : M1403\n\t2️⃣ Basée sur une appellation métier : Coach sportif"
).send()
task1.status = cl.TaskStatus.DONE
await cl.sleep(0.5)
await poleemploi.remove()
cl.user_session.set("memory", ConversationBufferMemory(return_messages=True))
memory = cl.user_session.get("memory")
cl.user_session.set("runnable", memory)
else:
emploi = cl.TaskList()
emploi.status = "Running..."
# Create a task and put it in the running state
task1 = cl.Task(title="Chargement des données du marché de l'emploi, en attente...", status=cl.TaskStatus.RUNNING)
await emploi.add_task(task1)
await emploi.send()
logo = [
cl.Image(name="Logo", size="small", display="inline", path="./public/logo_light.png")
]
await cl.Message(author="🌐🌐🌐",content="", elements=logo).send()
file = to_cache(chatProfile[0])
await cl.Message(author="🌐🌐🌐",content=f"💻😊 Vous pouvez poser vos questions concernant le marché de l'emploi de \"{chat_profile}\"!").send()
await cl.Message(author="🌐🌐🌐",content=f"📈 Le marché de l'emploi se présente comme un gros tableau structuré comme suit :\n1. Emplois\n2. Type de contrat\n3. Expérience\n4. Compétences professionnelles\n5. Salaire\n6. Qualification\n7. Localisation").send()
await cl.Message(
author="🌐🌐🌐",content=f"💡Voici des exemples de question \n\t1️⃣ Peux-tu créer une liste de 5 emplois différents les plus listés ?\n\t2️⃣ Peux-tu créer une liste de 5 emplois différents les plus listés avec leur salaire moyen correspondant à chacun de ces emplois?\n\t3️⃣ Peux-tu créer une liste de 5 emplois différents les plus listés et leur répartition dans les localisations suivantes, le 75, ou le 77, ou le 78, ou le 91, ou le 92, ou le 93, ou le 94, ou le 95?\n\t4️⃣ Peux-tu créer une liste de 5 emplois différents les plus listés pour un niveau de qualification Bac+2 Bac+3?\n\t5️⃣ Peux-tu créer une liste de 5 emplois différents les plus listés et associer 2 compétences professionnelles à chaque emploi, de type activités professionnelles, sans lister les compétences transversales?\n\t6️⃣ Peux-tu créer une liste de 5 emplois différents les plus listés avec un contrat en CDI?\n\t7️⃣ Quelles sont les compétences professionnelles les mieux payées?"
).send()
model = ChatAnthropic(model="claude-2.1",top_p=0.9,temperature=1,max_tokens_to_sample=4097,streaming=True)
df = pd.read_csv(file, sep=",")
df = df.replace(np.nan, '', regex=True)
df['combined'] = 'Emploi ' + df['Poste'] + '; type de contrat : ' + df['Contrat'] + '; Compétences professionnelles : ' + df['Savoir'] + '; Salaire : ' + df['Salaire'] + '; Niveau de qualification : ' + df['Niveau'] + '; Localisation : ' + df['Localisation']
context = []
for i, row in df.iterrows():
context.append(row['combined'])
context = "\n".join(context)
context = context[0:590000]
task1.status = cl.TaskStatus.DONE
await cl.sleep(5)
await emploi.remove()
cl.user_session.set("memory", ConversationBufferMemory(return_messages=True))
memory = cl.user_session.get("memory")
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
f"Contexte : Vous êtes un spécialiste du marché de l'emploi en fonction du niveau de qualification, des compétences professionnelles, des compétences transversales, du salaire et de l'expérience. Vous êtes doué pour faire des analyses sur les métiers les plus demandés grâce à votre aptitude à synthétiser les informations en fonction des critères définis ci-avant. En fonction des informations suivantes et du contexte suivant seulement et strictement. En fonction des informations suivantes et du contexte suivant seulement et strictement. Contexte et document : {context}. Réponds en langue française strictement à la question suivante en respectant strictement les données du document. Si vous ne pouvez pas répondre à la question sur la base des informations, dites que vous ne trouvez pas de réponse ou que vous ne parvenez pas à trouver de réponse. Essayez donc de comprendre en profondeur le contexte et répondez uniquement en vous basant sur les informations fournies. Ne générez pas de réponses non pertinentes.",
),
MessagesPlaceholder(variable_name="history"),
("human", "{question}, d'après le document en vous réferrant strictement aux données du contexte fixé sans faire de recherche dans vos connaissances ou sur le web? Si les emplois sont différents aux données, recommencez votre liste. Réponse sous forme d'une liste. Si tu ne peux pas donner la liste, fais une projection par emplois."),
]
)
#runnable = prompt | model | StrOutputParser()
runnable = (
RunnablePassthrough.assign(
history=RunnableLambda(memory.load_memory_variables) | itemgetter("history")
)
| prompt
| model
| StrOutputParser()
)
cl.user_session.set("runnable", runnable)
@cl.on_message
async def main(message: cl.Message):
chat_profile = cl.user_session.get("chat_profile")
chatProfile = chat_profile.split(' - ')
if chatProfile[0] == "OF":
chain = cl.user_session.get("conversation_chain")
cb = cl.AsyncLangchainCallbackHandler()
res = await chain.acall("Contexte : Réponds à la question suivante de la manière la plus pertinente, la plus exhaustive et la plus détaillée possible, avec au minimum 3000 tokens jusqu'à 4000 tokens, seulement et strictement dans le contexte et les informations fournies. Question : " + message.content, callbacks=[cb])
answer = res["answer"]
source_documents = res["source_documents"]
text_elements = []
metadatas = ''
if source_documents:
for source_idx, source_doc in enumerate(source_documents[::-1]):
numSource = source_idx + 1
source_name = f"Source n°{numSource}"
text_elements.append(
cl.Text(content="Formations : " + source_doc.metadata['ABREGE_LIBELLES'] + " " + source_doc.metadata['INTITULE'] + "\n\nROME : " + source_doc.metadata['CODES_ROME'] + "\nLibellés ROME : " + source_doc.metadata['LIBELLES_ROME'] + "\n\nActivités : " + source_doc.metadata['ACTIVITES_VISEES'].replace('œ','oe') + "\n\nEmplois accessibles : " + source_doc.metadata['TYPE_EMPLOI_ACCESSIBLES'] + "\n\nCompétences : " + source_doc.metadata['CAPACITES_ATTESTEES'].replace('œ','oe').replace('…','oe'), name=source_name)
)
source_names = [text_el.name for countMetadata, text_el in enumerate(text_elements) if countMetadata < 10]
if source_names:
metadatas += ', '.join(source_names)
else:
metadatas += "\n\nPas de source trouvée!"
actions = [
cl.Action(name="download", value="Question : " + message.content + "\n\nRéponse : " + answer, description="download_offre_formation")
]
await cl.Message(author="🌐🌐🌐",content=answer).send()
await cl.Message(author="🌐🌐🌐",content="Download", actions=actions).send()
if metadatas:
await cl.Message(author="🌐🌐🌐",content="Sources : " + metadatas, elements=text_elements).send()
elif chatProfile[0] == "Emplois":
client = Api(client_id=os.environ['POLE_EMPLOI_CLIENT_ID'],
client_secret=os.environ['POLE_EMPLOI_CLIENT_SECRET'])
runnable = cl.user_session.get("runnable")
memory = cl.user_session.get("memory")
msg = cl.Message(author="🌐🌐🌐",content="")
todayDate = datetime.datetime.today()
month, year = (todayDate.month-1, todayDate.year) if todayDate.month != 1 else (12, todayDate.year-1)
start_dt = todayDate.replace(day=1, month=month, year=year)
end_dt = datetime.datetime.today()
params = {"motsCles": message.content,'lieux':'75D','minCreationDate': dt_to_str_iso(start_dt),'maxCreationDate': dt_to_str_iso(end_dt),'range':'0-149'}
search_on_big_data = client.search(params=params)
results = search_on_big_data["resultats"]
emplois = []
text_elements = []
for i in range(0,len(results)):
emplois.append("✔️ Emploi : " + results[i]['intitule'] + "\nCode ROME : " + results[i]['romeCode'] + "\nLien vers Pôle Emploi : https://candidat.pole-emploi.fr/offres/recherche/detail/" + results[i]['id'] + "\n\nDescription : " + results[i]['description'] + "\n\n")
emplois_list = ''.join(emplois)
await msg.stream_token(emplois_list)
await msg.send()
listEmplois_name = f"Liste des emplois"
text_elements.append(
cl.Text(content="Question : " + message.content + "\n\nRéponse :\n" + msg.content, name=listEmplois_name)
)
actions = [
cl.Action(name="download", value="Question : " + message.content + "\n\nRéponse : " + msg.content, description="download_emplois")
]
await cl.Message(author="🌐🌐🌐",content="Download", actions=actions).send()
await cl.Message(author="🌐🌐🌐",content="Source Pôle Emploi : " + listEmplois_name, elements=text_elements).send()
memory.chat_memory.add_user_message(message.content)
memory.chat_memory.add_ai_message(msg.content)
else:
memory = cl.user_session.get("memory")
runnable = cl.user_session.get("runnable") # type: Runnable
msg = cl.Message(author="🌐🌐🌐",content="")
text_elements = []
async for chunk in runnable.astream(
{"question": message.content},
config=RunnableConfig(callbacks=[cl.LangchainCallbackHandler()]),
):
await msg.stream_token(chunk)
await msg.send()
QA_Emplois_name = f"Question-réponse sur les emplois"
text_elements.append(
cl.Text(content="Question : " + message.content + "\n\nRéponse :\n" + msg.content, name=QA_Emplois_name)
)
actions = [
cl.Action(name="download", value="Question : " + message.content + "\n\nRéponse : " + msg.content, description="download_QA_emplois")
]
await cl.Message(author="🌐🌐🌐",content="Download", actions=actions).send()
await cl.Message(author="🌐🌐🌐",content="Marché Emploi : " + QA_Emplois_name, elements=text_elements).send()
memory.chat_memory.add_user_message(message.content)
memory.chat_memory.add_ai_message(msg.content)