Automatisation des interactions par LLM :liaison entre compréhension linguistique et exécution d'action (code) - POC
Go to file
2024-10-08 14:46:36 +02:00
doc_assets Update 2024-10-08 14:46:36 +02:00
rasa Update 2024-10-08 14:46:15 +02:00
.DS_Store Update 2024-10-08 14:46:36 +02:00
.gitignore Update 2024-10-08 14:46:36 +02:00
logs.txt Update 2024-10-08 14:46:36 +02:00
README.md Update 2024-10-08 14:46:36 +02:00
requirements.txt Update 2024-10-08 14:46:36 +02:00

Information générale

Description du PoC

Ce projet se concentre sur la mise en place d'un PoC visant à déclencher des actions concrètes à partir d'un NLP/LLM, afin d'exécuter des tâches spécifiques en fonction de l'intention par déclenchement (scripts python).

Objectif du projet

L'objectif est de communiquer avec un chatbot sous l'environnement Rasa, permettant :

  • La communication avec le LLM Llama3 (installé en local au travers de Ollama) lors d'une communication générale.
  • Le déclenchement d'actions Python (scripts) lorsque les intentions suivantes sont reconnues :
    • Intention de connaître les informations d'une ville suisse depuis le NPA, déclenchant l'accès à une API afin de récupérer ces informations.
    • Intention de connaître la signification d'un prénom, interrogeant les connaissances du LLM llama3 avec une demande de prompt personnalisé afin de donner la signification ainsi que trois personnes historiquement célèbre ayant porté ce nom.
    • Intention de générer un profil d'une personne aléatoirement, en interrogeant une API.

En fonction des intentions détectées par le NLU, les différentes actions sont routées et jouées.

Réalisation

  • Installer et configurer Ollama
  • Créer et configurer l'environnement Rasa
    • Créer les intentions, les stories, les règles et leur liaison aux actions.
    • Créer les actions déclenchées en Python.
  • Entraîner le modèle sur les intentions dans Rasa.
  • Monter l'infrastructure.
  • Interagir avec le chatbot et tester la bonne exécution des actions.

Réalisation du PoC

Prérequis

Les technologies utilisées sont accessibles librement ou gratuitement (API, technologies).

Les programmes suivants doivent être installés :

  • Python 3.10 doit être installé sur la machine.

Installation d'Ollama

Ollama est une plateforme conçue pour faciliter l'utilisation et le déploiement de modèles de langage de type LLM (Large Language Models) localement sur des machines. Ollama propose une interface simple pour interagir avec différents modèles de langage.

L'installation d'Ollama va nous permettre d'utiliser localement un LLM. Ce concept étant fort intéressant pour la création d'un privacy LLM.

  • Installer Ollama depuis cet https://ollama.com/.

  • Téléchargez le modèle Llama 3.2 (par défaut, le modèle contenant 3,2 milliards de paramètres est téléchargé).

    ollama pull llama3.2
    
  • Essayer d'exécuter et de converser avec le modèle.

    ollama run llama3.2
    >>> Hello, comment ça va ? 
    'Bonjour ! Je vais bien, merci pour ta question. C est un plaisir de discuter avec toi en français ! Comment puis-je t aider 
    aujourd hui ?'
    

Ollama offre l'accès à sont API sur http://localhost:11434/api/generate

Créer le projet rasa

Rasa est une plateforme open source de développement de chatbots et d'assistants virtuels qui utilise le traitement du langage naturel (NLP) pour comprendre les intentions des utilisateurs et gérer les dialogues. Elle permet de créer des modèles personnalisés et d'exécuter des actions en réponse aux demandes des utilisateurs, tout en offrant des fonctionnalités avancées comme la gestion des dialogues contextuels et l'intégration avec diverses API.

Créer un environnement virtuel python

python3.10 -m venv ./venv
source ./venv/bin/activate

Dans cet exemple, j'utilise la version 3.10 de Python, car c'est celle qui est le mieux supportée.

Installer les dépendances

pip insatll -r requirements.txt

Création d'un nouveau projet rasa

mkdir rasa && cd rasa
rasa init

.
├── actions
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-38.pyc
│   │   └── actions.cpython-38.pyc
│   └── actions.py
├── config.yml
├── credentials.yml
├── data
│   ├── nlu.yml
│   ├── rules.yml
│   └── stories.yml
├── domain.yml
├── endpoints.yml
├── models
│   └── 20241008-123922-metal-event.tar.gz
└── tests
    └── test_stories.yml

Construire le projet

nlu.yml

Le fichier nlu.ymlpermet de spécifier les intentions qui seront entraîner par le modèle NLU (Natural Language Processing) :

Nous avons donc 4 intentions :

  • Générer un profil utilisateur par défaut (API)
  • Discuter de manière générale avec le LLM
  • Demander au LLM la signification d'un prénom
  • Récupérer les informations d'une ville Suisse depuis un NPA (API)
version: "3.1"

nlu:
- intent: intent_randomuser
  examples: |
    - Génère moi un profil utilisateur
    - Crée un profil d'utilisateur
    - Peux-tu me donner un profil utilisateur aléatoire ?
    - J'ai besoin d'un profil utilisateur
    - Donne-moi un exemple de profil utilisateur
    - Fais-moi un profil utilisateur
    - Génère un profil utilisateur au hasard
    - Crée un utilisateur aléatoire pour moi
    - Est-ce que tu peux créer un profil utilisateur ?    

- intent: intent_ask_llama
  examples: |
    - Qu'est-ce que tu sais sur les modèles de langage ?
    - Comment fonctionne un ordinateur ?
    - Quelle est la capitale de la France ?    

- intent: intent_firstname_signification_by_llama
  examples: |
    - Que signifie mon prénom [Guillaume](firstname) ?
    - Peux-tu me dire ce que signifie le prénom [Juliette](firstname) ?
    - Quelle est la signification de [Emma](firstname) ?
    - Explique-moi la signification du prénom [Lucas](firstname).
    - Que veut dire le prénom [Sophie](firstname) ?
    - Quelle est l'origine du prénom [Maxime](firstname) ?
    - Dis-moi la signification de [Chloé](firstname).
    - Que représente le prénom [Thomas](firstname) ?
    - Je voudrais savoir ce que signifie le prénom [Camille](firstname).
    - Que signifie le prénom [Alice](firstname) ?    

- intent: intent_swiss_zipcode
  examples: |
    - Quel est le code postal en Suisse de ce numéro [1977](zipcode)
    - Donne-moi le code postal de [1977](zipcode)
    - Quel est le code postal pour [1977](zipcode) ?    

À noter que dans des expressions telle que [1977](zipcode), le modèle NLU sera capable de reconnaître que 1977 représente une valeur variable, ce qui lui permettra d'attribuer les valeurs de code postal fournies à la variable zipcode.

stories.yml

Le fichier stories.yml permet de définir les scénarios d'interaction en spécifiant les séquences d'actions et d'intentions que le modèle doit apprendre pour simuler un dialogue.

Nous sommes face à des stories très simple, soit :

  • Reconnaître l'intention
  • Exécuter l'action associée
version: "3.1"
stories:
  - story: request_randomuser
    steps:
      - intent: intent_randomuser
      - action: action_randomuser

  - story: request_ask_llama
    steps:
      - intent: intent_ask_llama
      - action: action_ask_llama

  - story: request_firstname_signification_by_llama
    steps:
      - intent: intent_firstname_signification_by_llama
      - action: action_firstname_signification_by_llama

  - story: request_swiss_zipcode
    steps:
    - intent: intent_swiss_zipcode
    - action: action_swiss_zipcode

Il est tout à fait possible de complexifier les stories comme :

  • Rajouter des réponses dans le scénario de conversation

rules.yml

Le fichier rules.yml permet de spécifier les règles qui guideront le comportement du modèle, en définissant des séquences d'actions et de conditions à suivre lors des interactions avec les utilisateurs.:

Nous avons donc une séquence, qui dans l'ordre va repérer les intentions pour :

  1. Générer un profil utilisateur par défaut (API)
  2. Discuter de manière générale avec le LLM
  3. Demander au LLM la signification d'un prénom
  4. Récupérer les informations d'une ville Suisse depuis un NPA (API)
version: "3.1"
rules:
  - rule: rule__randomuser
    steps:
      - intent: intent_randomuser
      - action: action_randomuser

  - rule: rule_swiss_zipcode
    steps:
      - intent: intent_swiss_zipcode
      - action: action_swiss_zipcode

  - rule: rule_firstname_signification_by_llama
    steps:
      - intent: intent_firstname_signification_by_llama
      - action: action_firstname_signification_by_llama

  - rule: General question redirect to LLM
    steps:
      - intent: intent_ask_llama
      - action: action_ask_llama

Il est tout à fait possible de complexifier les rules comme :

  • Créer des boucles (remplissage de formulaires, input
  • Rajouter des conditions

domain.yml

Le fichier domain.ym définit les éléments essentiels de l'application Rasa, y compris les intentions, les entités, les actions, les réponses et les configurations de dialogue, permettant ainsi au modèle de gérer les interactions avec les utilisateurs.

Il faut renseigner les éléments dans le fichier domain.ym.

version: "3.1"

intents:
  - intent_randomuser
  - intent_ask_llama
  - intent_swiss_zipcode
  - intent_firstname_signification_by_llama

actions:
  - action_randomuser
  - action_ask_llama
  - action_swiss_zipcode
  - action_firstname_signification_by_llama

actions.py

Le fichier actions.py contient les actions personnalisées que le modèle Rasa peut exécuter en réponse à des requêtes utilisateur. Ce fichier permet de définir des fonctions qui réalisent des tâches spécifiques, comme l'appel à des API, le traitement de données ou l'interaction avec des bases de données.

On retrouve nos fonctions :

  • L'appel à action_ask_llamapour les questions générales

    # Action to ask to the Llama model
    class ActionAskLlama(Action):
        def name(self):
            return "action_ask_llama"
    
        def run(self, dispatcher, tracker, domain):
            user_input = tracker.latest_message.get('text')
    
            # Call API (Streaming)
            url = "http://localhost:11434/api/generate"
            payload = {
                "model": "llama3.1",
                "prompt": user_input
            }
    
            try:
                response = requests.post(url, json=payload, stream=True)
    
                if response.status_code == 200:
                    llm_response = ""
                    # Read the response line by line
                    for line in response.iter_lines():
                        if line:
                            json_line = line.decode('utf-8')
                            json_data = json.loads(json_line)
    
                            # Done flag for defining the end of the response
                            if json_data.get("done"):
                                break
                            llm_response += json_data.get("response", "")
                    dispatcher.utter_message(text=llm_response.strip())
                else:
                    dispatcher.utter_message(text=f"Erreur lors de l'appel à Ollama: {response.status_code} - {response.text}")
            except Exception as e:
                dispatcher.utter_message(text=f"Une exception est survenue : {str(e)}")
    
            return []
    
  • L'appel à action_randomuserpour générer un profil aléatoire (API)

    class ActionRandomuser(Action):
    def name(self):
        return "action_randomuser"
        ...
    
  • L'appel à action_swiss_zipcodepour récupérer les information d'une ville Suisse depuis un NPA

    class ActionGetZipcode(Action):
    
    def name(self) -> str:
        return "action_swiss_zipcode"
    
    def run(self, dispatcher, tracker, domain):
        zipcode = next(tracker.get_latest_entity_values("zipcode"), None)
        with open('../logs.txt', 'a') as f:
            f.write(zipcode + '\n') 
    
    
        if zipcode:
            url = f"https://api.zippopotam.us/ch/{zipcode}"
            response = requests.get(url)
    
            if response.status_code == 200:
                data = response.json()
                places = data.get("places", [])
                if places:
                    place_info = places[0]
                    city = place_info.get("place name")
                    state = place_info.get("state")
                    message = f"Le code postal {zipcode} correspond à {city}, {state}."
                else:
                    message = f"Aucune information trouvée pour le code postal {zipcode}."
            else:
                message = f"Erreur lors de l'appel à l'API : {response.status_code}."
        else:
            message = "Je n'ai pas pu extraire le code postal."
    
        dispatcher.utter_message(text=message)
        return []
    
  • L'appel à action_firstname_signification_by_llamapour connaître la signification d'un prénom.

    class ActionFirstnameSignificationLlama(Action):
        def name(self):
            return "action_firstname_signification_by_llama"
    

Entraîner le modèle rasa

Pour entraîner le modèle Rasa, utilisez la commande suivante :

rasa train

Tester le chatbot

Lancer le chatbot

Pour teser le projet, exécuter la commande suivantes :

rasa run actions & rasa shell

Cette commande :

  • Démarre le serveur des actions
  • Exécuter un shell interractif

Tester le chabot

chatbot_test

À noter qu'une des fonctionnalités d'un NLU est l'automatisation et la transparence de la langue. C'est pourquoi la question en anglais est comprise, même si l'intention est en français.