File size: 20,213 Bytes
2a7178c
 
 
 
 
 
 
 
 
 
 
 
 
 
547460d
2a7178c
 
 
 
 
 
 
 
 
 
61d1ad7
5ccbefb
61d1ad7
ed9e96d
2a7178c
 
 
 
 
 
 
 
 
 
 
74631d0
2a7178c
 
 
 
74631d0
2a7178c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a964a99
2a7178c
 
 
 
1fd02ed
a7da315
9d8afbe
aa6ca59
9d8afbe
a7da315
9d8afbe
 
 
 
a7da315
1fd02ed
 
 
a7da315
96b05a8
1fd02ed
2a7178c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79ef33a
2a7178c
 
 
 
 
 
 
 
 
 
 
 
 
 
2c8b688
2a7178c
2c8b688
 
 
 
 
 
 
5ccbefb
2a7178c
6faecbe
 
 
 
 
 
 
2a7178c
 
 
 
 
f8af325
 
ba82520
f8af325
2a7178c
1009eb6
 
2a7178c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79832e2
2a7178c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
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.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.schema import StrOutputParser
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

os.environ["TOKENIZERS_PARALLELISM"] = os.environ["TOKENIZERS_PARALLELISM"]
os.environ['ANTHROPIC_API_KEY'] = os.environ['ANTHROPIC_API_KEY']

@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('./' + 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="./" + 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):
    return "https://cipen.univ-gustave-eiffel.fr/fileadmin/CIPEN/datas/assets/docs/" + file + ".csv"

@cl.cache
def retriever_to_cache():
    index_name = os.environ['PINECONE_INDEX_NAME']
    time.sleep(5)
    embeddings = HuggingFaceEmbeddings()
    time.sleep(5)
    pinecone.init(
        api_key=os.environ['PINECONE_API_KEY'],
        environment="us-west4-gcp-free"
    )
    time.sleep(5)
    vectorstore = Pinecone.from_existing_index(
        index_name=index_name, embedding=embeddings
    )
    time.sleep(10)
    retriever = vectorstore.as_retriever(search_type="similarity_score_threshold", search_kwargs={"score_threshold": .7, "k": 30,"filter": {'categorie': {'$eq': 'OF'}}})
    return retriever

@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 : Quels 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()
    
        from langchain_core.prompts.prompt import PromptTemplate

        _template = """Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language.

                    Chat History:
                    {chat_history}
                    Follow Up Input: {question}
                    Standalone question:"""
        CONDENSE_QUESTION_PROMPT = PromptTemplate.from_template(_template)
        
        ########## Chain with streaming ##########
        message_history = ChatMessageHistory()
        memory = ConversationBufferMemory(
            memory_key="chat_history",
            output_key="answer",
            chat_memory=message_history,
            return_messages=True,
        )
        streaming_llm = ChatAnthropic(
            streaming=True,
            temperature=1,
            max_tokens=4000
        )
        qa = ConversationalRetrievalChain.from_llm(
            streaming_llm,
            memory=memory,
            chain_type="stuff",
            return_source_documents=True,
            verbose=False,
            retriever=retriever_to_cache()
        )
        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):
                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)