rdassignies commited on
Commit
b4c2b4c
·
verified ·
1 Parent(s): d437d63

Upload 7 files

Browse files
Files changed (8) hide show
  1. .gitattributes +1 -0
  2. app.py +153 -0
  3. bodacc.json +3 -0
  4. graph.py +153 -0
  5. models.py +129 -0
  6. nodes.py +166 -0
  7. prompts.py +73 -0
  8. requirements.txt +58 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ bodacc.json filter=lfs diff=lfs merge=lfs -text
app.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Sun Sep 22 15:43:16 2024
5
+
6
+ @author: Raphaël d'Assignies (rdassignies@protonmail.ch)
7
+ """
8
+ import json
9
+ from typing import Literal, Optional, List, Union, Any
10
+ from langchain_openai import ChatOpenAI
11
+ import pandas as pd
12
+ from langchain_core.prompts import ChatPromptTemplate
13
+ from langgraph.graph import END, StateGraph, START
14
+ from langchain_core.output_parsers import StrOutputParser
15
+ from pydantic import BaseModel, Field
16
+ from models import NatureJugement
17
+ from nodes import (GradeResults, GraphState, generate_query_node,
18
+ generate_results_node, query_feedback_node,
19
+ evaluate_query_node, evaluate_results_node)
20
+ import streamlit as st
21
+
22
+
23
+
24
+
25
+ # Instanciate pipeline
26
+ pipeline = StateGraph(GraphState)
27
+
28
+ pipeline.add_node('generate_query', generate_query_node)
29
+ pipeline.add_node('generate_results', generate_results_node)
30
+ pipeline.add_node('query_feedback', query_feedback_node)
31
+
32
+ # Only query
33
+ #pipeline.add_edge(START,'generate_query')
34
+ #pipeline.add_edge('generate_query', generate_query_node)
35
+ #pipeline.add_edge('generate_query', END)
36
+
37
+ # Full scenario
38
+ pipeline.add_edge(START,'generate_query')
39
+ pipeline.add_conditional_edges(
40
+ 'generate_query',
41
+ evaluate_query_node,
42
+ {'error_query' : 'generate_query',
43
+ 'ok' : 'generate_results'
44
+ })
45
+
46
+ pipeline.add_conditional_edges(
47
+ 'generate_results',
48
+ evaluate_results_node,
49
+ {
50
+ "yes": END,
51
+ "no": 'query_feedback',
52
+ "max_generation_reached": END
53
+
54
+ }
55
+ )
56
+
57
+
58
+ # Création du graph
59
+ graph = pipeline.compile()
60
+
61
+ # Load le dataframe
62
+ df = pd.read_json('bodacc.json', orient='table')
63
+
64
+ # Initialise le dictionnaire
65
+ inputs = {
66
+ 'df_head': df.head().to_csv(),
67
+ 'df': df
68
+ }
69
+
70
+ # Créé un dictionnaire des sorties vide
71
+ outputs = {}
72
+
73
+
74
+ # Titre de l'application
75
+ st.title("Chat with BODACC !")
76
+
77
+ # Message d'avertissement
78
+ warning_message = (f"Cet outil, purement pédagogique, est basé sur des données réelles allant de {df['dateparution'].min()} "
79
+ f"à {df['dateparution'].max()}, et permet d'interroger le BODACC en langage naturel. Compte tenu de la variabilité des modèles, nous ne pouvons pas garantir la fiabilité des réponses.")
80
+
81
+ st.warning(warning_message)
82
+ # Interface utilisateur pour entrer la requête
83
+ user_query = st.text_input("Entrez votre requête:", "Trouve moi les restaurants à reprendre en Bretagne dans les 30 derniers jours")
84
+
85
+
86
+ # Afficher les résultats avec Streamlit
87
+ inputs["instructions"] = user_query
88
+
89
+
90
+ # Afficher un bouton pour démarrer la recherche
91
+ if st.button("Lancer la recherche"):
92
+ config = {"configurable": {"thread_id": "2"}}
93
+
94
+ # Étape 1 : Afficher le message "Je réfléchis..."
95
+ st.write("Je réfléchis...")
96
+
97
+ # Stream des résultats au fur et à mesure
98
+ with st.spinner('Recherche en cours...'):
99
+ for output in graph.stream(inputs, stream_mode='values', debug=False):
100
+ # Ajouter les résultats au dictionnaire outputs
101
+ for k, v in output.items():
102
+ if k not in outputs:
103
+ outputs[k] = []
104
+ outputs[k].append(v)
105
+
106
+ # Ne pas afficher les messages pour les clés non pertinentes (comme error_query)
107
+ if 'query' in output and len(output['query'])>0:
108
+ st.write(f"query : {output['query']}")
109
+ #st.write(outputs.get('query_feedbacks', 'pas de feedback'))
110
+ #st.write(outputs.get('results_feedbacks', 'pas de resultfeedback'))
111
+ if "results" in output and len(output["results"]) > 0:
112
+ records = json.loads(output['results'])
113
+ st.write(f"Résultats intermédiaires trouvés : {len(records)} résultats jusqu'à présent.")
114
+
115
+ # Après la fin du traitement
116
+ if "results" in outputs and len(outputs["results"]) > 0:
117
+ # Agréger tous les résultats accumulés
118
+ all_results = []
119
+ for res in outputs["results"]:
120
+ json_data = json.loads(res) # Convertir chaque ensemble de résultats en JSON
121
+ all_results.extend(json_data) # Accumuler tous les résultats
122
+
123
+ results_df = pd.DataFrame(all_results) # Créer un DataFrame avec tous les résultats accumulés
124
+ # Afficher un aperçu des résultats (jusqu'à 5 premiers)
125
+ num_results = len(results_df)
126
+ st.write(f"J'ai trouvé {num_results} résultats.")
127
+ if num_results > 0:
128
+ preview_count = min(5, num_results) # Gérer le cas où il y a moins de 5 résultats
129
+ st.write(f"Voici un aperçu des {preview_count} premiers résultats :")
130
+ st.write(results_df.head(preview_count))
131
+
132
+ trunc = outputs.get('truncated', 'pas de traunc')
133
+
134
+ if trunc[0] == True:
135
+ st.warning("Les résultats de votre recherche ont été tronqués car celle-ci était trop large ! ")
136
+
137
+ # Convertir tous les résultats en CSV
138
+ csv = results_df.to_csv(index=False)
139
+
140
+ # Ajouter un bouton pour télécharger tous les résultats
141
+ st.download_button(
142
+ label="Télécharger le résultat complet au format CSV",
143
+ data=csv,
144
+ file_name="results.csv",
145
+ mime="text/csv"
146
+ )
147
+
148
+ else:
149
+ # Si aucun résultat n'est trouvé
150
+ st.write("Aucun résultat trouvé.")
151
+
152
+
153
+
bodacc.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a5172c9d6946aef8bbf0d9c17be1159ffbb64e773bf800b65335797e35ffff1a
3
+ size 25389210
graph.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Sun Sep 22 15:43:16 2024
5
+
6
+ @author: Raphaël d'Assignies (rdassignies@protonmail.ch)
7
+ """
8
+ import json
9
+ from typing import Literal, Optional, List, Union, Any
10
+ from langchain_openai import ChatOpenAI
11
+ import pandas as pd
12
+ from langchain_core.prompts import ChatPromptTemplate
13
+ from langgraph.graph import END, StateGraph, START
14
+ from langchain_core.output_parsers import StrOutputParser
15
+ from pydantic import BaseModel, Field
16
+ from models import NatureJugement
17
+ from nodes import (GradeResults, GraphState, generate_query_node,
18
+ generate_results_node, query_feedback_node,
19
+ evaluate_query_node, evaluate_results_node)
20
+ import streamlit as st
21
+
22
+
23
+
24
+
25
+ # Instanciate pipeline
26
+ pipeline = StateGraph(GraphState)
27
+
28
+ pipeline.add_node('generate_query', generate_query_node)
29
+ pipeline.add_node('generate_results', generate_results_node)
30
+ pipeline.add_node('query_feedback', query_feedback_node)
31
+
32
+ # Only query
33
+ #pipeline.add_edge(START,'generate_query')
34
+ #pipeline.add_edge('generate_query', generate_query_node)
35
+ #pipeline.add_edge('generate_query', END)
36
+
37
+ # Full scenario
38
+ pipeline.add_edge(START,'generate_query')
39
+ pipeline.add_conditional_edges(
40
+ 'generate_query',
41
+ evaluate_query_node,
42
+ {'error_query' : 'generate_query',
43
+ 'ok' : 'generate_results'
44
+ })
45
+
46
+ pipeline.add_conditional_edges(
47
+ 'generate_results',
48
+ evaluate_results_node,
49
+ {
50
+ "yes": END,
51
+ "no": 'query_feedback',
52
+ "max_generation_reached": END
53
+
54
+ }
55
+ )
56
+
57
+
58
+ # Création du graph
59
+ graph = pipeline.compile()
60
+
61
+ # Load le dataframe
62
+ df = pd.read_json('bodacc.json', orient='table')
63
+
64
+ # Initialise le dictionnaire
65
+ inputs = {
66
+ 'df_head': df.head().to_csv(),
67
+ 'df': df
68
+ }
69
+
70
+ # Créé un dictionnaire des sorties vide
71
+ outputs = {}
72
+
73
+
74
+ # Titre de l'application
75
+ st.title("Chat with BODACC !")
76
+
77
+ # Message d'avertissement
78
+ warning_message = (f"Cet outil, purement pédagogique, est basé sur des données réelles allant de {df['dateparution'].min()} "
79
+ f"à {df['dateparution'].max()}, et permet d'interroger le BODACC en langage naturel. Compte tenu de la variabilité des modèles, nous ne pouvons pas garantir la fiabilité des réponses.")
80
+
81
+ st.warning(warning_message)
82
+ # Interface utilisateur pour entrer la requête
83
+ user_query = st.text_input("Entrez votre requête:", "Trouve moi les restaurants à reprendre en Bretagne dans les 30 derniers jours")
84
+
85
+
86
+ # Afficher les résultats avec Streamlit
87
+ inputs["instructions"] = user_query
88
+
89
+
90
+ # Afficher un bouton pour démarrer la recherche
91
+ if st.button("Lancer la recherche"):
92
+ config = {"configurable": {"thread_id": "2"}}
93
+
94
+ # Étape 1 : Afficher le message "Je réfléchis..."
95
+ st.write("Je réfléchis...")
96
+
97
+ # Stream des résultats au fur et à mesure
98
+ with st.spinner('Recherche en cours...'):
99
+ for output in graph.stream(inputs, stream_mode='values', debug=False):
100
+ # Ajouter les résultats au dictionnaire outputs
101
+ for k, v in output.items():
102
+ if k not in outputs:
103
+ outputs[k] = []
104
+ outputs[k].append(v)
105
+
106
+ # Ne pas afficher les messages pour les clés non pertinentes (comme error_query)
107
+ if 'query' in output and len(output['query'])>0:
108
+ st.write(f"query : {output['query']}")
109
+ #st.write(outputs.get('query_feedbacks', 'pas de feedback'))
110
+ #st.write(outputs.get('results_feedbacks', 'pas de resultfeedback'))
111
+ if "results" in output and len(output["results"]) > 0:
112
+ records = json.loads(output['results'])
113
+ st.write(f"Résultats intermédiaires trouvés : {len(records)} résultats jusqu'à présent.")
114
+
115
+ # Après la fin du traitement
116
+ if "results" in outputs and len(outputs["results"]) > 0:
117
+ # Agréger tous les résultats accumulés
118
+ all_results = []
119
+ for res in outputs["results"]:
120
+ json_data = json.loads(res) # Convertir chaque ensemble de résultats en JSON
121
+ all_results.extend(json_data) # Accumuler tous les résultats
122
+
123
+ results_df = pd.DataFrame(all_results) # Créer un DataFrame avec tous les résultats accumulés
124
+ # Afficher un aperçu des résultats (jusqu'à 5 premiers)
125
+ num_results = len(results_df)
126
+ st.write(f"J'ai trouvé {num_results} résultats.")
127
+ if num_results > 0:
128
+ preview_count = min(5, num_results) # Gérer le cas où il y a moins de 5 résultats
129
+ st.write(f"Voici un aperçu des {preview_count} premiers résultats :")
130
+ st.write(results_df.head(preview_count))
131
+
132
+ trunc = outputs.get('truncated', 'pas de traunc')
133
+
134
+ if trunc[0] == True:
135
+ st.warning("Les résultats de votre recherche ont été tronqués car celle-ci était trop large ! ")
136
+
137
+ # Convertir tous les résultats en CSV
138
+ csv = results_df.to_csv(index=False)
139
+
140
+ # Ajouter un bouton pour télécharger tous les résultats
141
+ st.download_button(
142
+ label="Télécharger le résultat complet au format CSV",
143
+ data=csv,
144
+ file_name="results.csv",
145
+ mime="text/csv"
146
+ )
147
+
148
+ else:
149
+ # Si aucun résultat n'est trouvé
150
+ st.write("Aucun résultat trouvé.")
151
+
152
+
153
+
models.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Thu Dec 28 10:32:52 2023
5
+
6
+ @author: raphael
7
+ """
8
+ import json
9
+ from typing import List, Optional, Dict
10
+ from pydantic import BaseModel, Field, UUID4
11
+ from enum import Enum, unique
12
+
13
+ @unique
14
+ class FamilleJugement(str, Enum):
15
+ ARRET_APPEL = "Arrêt de la Cour d'Appel"
16
+ AVIS_DEPOT = 'Avis de dépôt'
17
+ EXTRAIT_JUGEMENT = 'Extrait de jugement'
18
+ JUGEMENT_OUVERTURE_ouverture = "Jugement d'ouverture"
19
+ JUGEMENT_CLOTURE = 'Jugement de clôture'
20
+ JUGEMENT = 'Jugement prononçant'
21
+
22
+ @unique
23
+ class NatureJugement(str, Enum):
24
+ ARRET_COUR_APPEL_INFIRMANT = "Arrêt de la cour d'appel infirmant une décision soumise à publicité"
25
+ AUTRE_ARRET_COUR_APPEL = "Autre arrêt de la Cour d'Appel"
26
+ AUTRE_AVIS_DE_DEPOT = "Autre avis de dépôt"
27
+ AUTRE_JUGEMENT_OUVERTURE = "Autre jugement d'ouverture"
28
+ AUTRE_JUGEMENT_CLOTURE = "Autre jugement de clôture"
29
+ AUTRE_JUGEMENT_ORDONNANCE = "Autre jugement et ordonnance"
30
+ AUTRE_JUGEMENT_PRONONCANT = "Autre jugement prononçant"
31
+ DEPOT_ETAT_COLLOCATION = "Dépôt de l'état de collocation"
32
+ DEPOT_ETAT_CREANCES = "Dépôt de l'état des créances"
33
+ DEPOT_ETAT_CREANCES_1985 = "Dépôt de l'état des créances Loi de 1985"
34
+ DEPOT_PROJET_REPARTITION = "Dépôt du projet de répartition"
35
+ JUGEMENT_PLAN_SAUVEGARDE = "Jugement arrêtant le plan de sauvegarde"
36
+ JUGEMENT_PLAN_CESSION = "Jugement arrêtant un plan de cession"
37
+ EXTENSION_LIQUIDATION_JUDICIAIRE = "Jugement d'extension de liquidation judiciaire"
38
+ INTERDICTION_GERER = "Jugement d'interdiction de gérer"
39
+ OUVERTURE_PROCEDURE_RESTRUCTURATION = "Jugement d'ouverture d'une procédure de redressement judiciaire"
40
+ OUVERTURE_PROCEDURE_SAUVEGARDE = "Jugement d'ouverture d'une procédure de sauvegarde"
41
+ OUVERTURE_LIQUIDATION_JUDICIAIRE = "Jugement d'ouverture de liquidation judiciaire"
42
+ CLOTURE_PROCEDURE_SAUVEGARDE = "Jugement de clôture de la procédure de sauvegarde"
43
+ CLOTURE_EXTINCTION_PASSIF = "Jugement de clôture pour extinction du passif"
44
+ CLOTURE_INSUFFISANCE_ACTIF = "Jugement de clôture pour insuffisance d'actif"
45
+ CONVERSION_LIQUIDATION_JUDICIAIRE = "Jugement de conversion en liquidation judiciaire"
46
+ CONVERSION_LIQUIDATION_SAUVEGARDE = "Jugement de conversion en liquidation judiciaire de la procédure de sauvegarde"
47
+ CONVERSION_RESTRUCTURATION_SAUVEGARDE = "Jugement de conversion en redressement judiciaire de la procédure de sauvegarde"
48
+ JUGEMENT_FAILLITE_PERSONNELLE = "Jugement de faillite personnelle"
49
+ JUGEMENT_PLAN_RESTRUCTURATION = "Jugement de plan de redressement"
50
+ REPRISE_PROCEDURE_LIQUIDATION = "Jugement de reprise de la procédure de liquidation judiciaire"
51
+ FIN_PROCEDURE_RESTRUCTURATION = "Jugement mettant fin à la procédure de redressement judiciaire"
52
+ FIN_PROCEDURE_SAUVEGARDE = "Jugement mettant fin à la procédure de sauvegarde"
53
+ MODIFICATION_DATE_CESSATION_PAIEMENTS = "Jugement modifiant la date de cessation des paiements"
54
+ MODIFICATION_PLAN_RESTRUCTURATION = "Jugement modifiant le plan de redressement"
55
+ MODIFICATION_PLAN_SAUVEGARDE = "Jugement modifiant le plan de sauvegarde"
56
+ MODIFICATION_PLAN_TRAITEMENT_SORTIE_CRISE = "Jugement modifiant le plan de traitement de sortie de crise"
57
+ RESOLUTION_PLAN_RESTRUCTURATION_LIQUIDATION = "Jugement prononçant la résolution du plan de redressement et la liquidation judiciaire"
58
+ RESOLUTION_PLAN_SAUVEGARDE_LIQUIDATION = "Jugement prononçant la résolution du plan de sauvegarde et la liquidation judiciaire"
59
+ RESOLUTION_PLAN_SAUVEGARDE_RESTRUCTURATION = "Jugement prononçant la résolution du plan de sauvegarde et le redressement judiciaire"
60
+ RESOLUTION_PLAN_SORTIE_CRISE_LIQUIDATION = "Jugement prononçant la résolution du plan de traitement de sortie de crise et la liquidation judiciaire"
61
+ LISTE_CREANCES_POST_OUVERTURE_LIQUIDATION = "Liste des créances nées après le jugement d'ouverture d'une procédure de liquidation judiciaire"
62
+ LISTE_CREANCES_POST_OUVERTURE_RESTRUCTURATION = "Liste des créances nées après le jugement d'ouverture d'une procédure de redressement judiciaire"
63
+
64
+ # Méthode pour obtenir l'énumération à partir de la chaîne
65
+ @staticmethod
66
+ def from_string(s):
67
+ for member in NatureJugement:
68
+ if member.value == s:
69
+ return member
70
+ raise ValueError(f"{s} n'est pas une valeur valide de TypeJugement")
71
+
72
+ class Personne(BaseModel):
73
+ typePersonne: str
74
+ numeroImmatriculation: Optional[Dict] = Field(default=None)
75
+ denomination: Optional[str] = Field(default=None)
76
+ activite: Optional[str] = Field(default=None)
77
+ formeJuridique: Optional[str] = Field(default=None)
78
+ adresseSiegeSocial: Optional[Dict] = Field(default=None)
79
+
80
+ class Jugement(BaseModel):
81
+ type:Optional[str] = None
82
+ famille:Optional[str] = None
83
+ nature:Optional[str] = None
84
+ #nature: NatureJugement = None
85
+ date: Optional[str] = None
86
+ complementJugement: Optional[str] = None
87
+
88
+ class Annonce(BaseModel):
89
+ #uuid: UUID4
90
+ publicationavis: Optional[str]
91
+ publicationavis_facette: Optional[str]
92
+ parution: Optional[int]
93
+ dateparution: Optional[str]
94
+ numeroannonce: int
95
+ typeavis: Optional[str]
96
+ typeavis_lib: Optional[str]
97
+ familleavis: Optional[str]
98
+ familleavis_lib: Optional[str]
99
+ numerodepartement: Optional[str]
100
+ departement_nom_officiel: Optional[str]
101
+ region_code: int
102
+ region_nom_officiel: Optional[str]
103
+ tribunal: Optional[str]
104
+ commercant: Optional[str]
105
+ ville: Optional[str]
106
+ registre: Optional[List[str]] # Rendre le champ optionnel
107
+ cp: Optional[str]
108
+ pdf_parution_subfolder: int
109
+ ispdf_unitaire: str
110
+ listepersonnes: Optional[List[Personne]] # Liste d'instances de Personne
111
+ listeetablissements: Optional[None]
112
+ jugement: Optional[Jugement] # JSON string or None
113
+ acte: Optional[None]
114
+ modificationsgenerales: Optional[None]
115
+ radiationaurcs: Optional[None]
116
+ depot: Optional[None]
117
+ listeprecedentexploitant: Optional[None]
118
+ listeprecedentproprietaire: Optional[None]
119
+ divers: Optional[None]
120
+ parutionavisprecedent: Optional[None]
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
nodes.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Sun Oct 13 10:30:56 2024
5
+
6
+ @author: legalchain
7
+ """
8
+ from typing import Literal, Optional, List, Union, Any
9
+ from langchain_openai import ChatOpenAI
10
+ import pandas as pd
11
+ from langchain_core.prompts import ChatPromptTemplate
12
+ from langgraph.graph import END, StateGraph, START
13
+ from langchain_core.output_parsers import StrOutputParser
14
+ from pydantic import BaseModel, Field
15
+ from models import NatureJugement
16
+ from prompts import df_prompt, feed_back_prompt, reflection_prompt
17
+ from dotenv import load_dotenv
18
+ load_dotenv()
19
+
20
+ llm = ChatOpenAI(model="gpt-4o-mini")
21
+ MAX_GENERATIONS = 2
22
+ MAX_ROWS: int = 10
23
+
24
+ class Query(BaseModel):
25
+ query:str = Field(..., title="Requête pour filtrer les résultats du dataframe entourée avec des gullemets de type \" ")
26
+
27
+ def clean_query(self):
28
+ # Correction des échappements dans la chaîne de la requête
29
+ corrected_query = self.query.replace("\\'", "\\'")
30
+ # Extraire la condition à l'intérieur des crochets
31
+ import re
32
+ condition = re.search(r"df\[(.*)\]", corrected_query).group(1)
33
+ return condition
34
+
35
+ class GradeResults(BaseModel):
36
+ binary_score: Literal["yes", "no"] = Field(
37
+ description="Les résultats sont satisfaisants -> 'yes' ou il y une erreur ou pas de résultats ou les résultats sont améliorables -> 'no'"
38
+ )
39
+
40
+ class GraphState(BaseModel):
41
+
42
+ df : Any
43
+ df_head:str
44
+ instructions: Optional[str] = None
45
+ nature_jugement: List = ', '.join([e.value for e in NatureJugement])
46
+ region:str = ''
47
+ dep:str = ''
48
+ query: Optional[str] = None
49
+ results :Union[str, List[str]] = []
50
+ query_feedbacks: Optional[str] = None
51
+ results_feedbacks: bool = None
52
+ generation_num: int = 0
53
+ retrieval_num: int = 0
54
+ search_mode: Literal["vectorstore", "websearch", "QA_LM"] = "QA_LM"
55
+ error_query: Optional[Any] = ""
56
+ error_results: Optional[Any] = ""
57
+ truncated: bool = False
58
+
59
+ # Méthode pour récupérer le DataFrame
60
+ def get_df(self) -> pd.DataFrame:
61
+ return pd.read_json(self.df)
62
+
63
+ # Surcharger l'initialisation pour créer les champs 'region' et 'dep'
64
+ def __init__(self, **data):
65
+ super().__init__(**data)
66
+
67
+ # Extraire le DataFrame
68
+ #df = self.get_df()
69
+
70
+ # Générer les chaînes pour les régions et départements
71
+ distinct_regions = self.df['region_nom_officiel'].dropna().unique().tolist()
72
+ distinct_departements = self.df['departement_nom_officiel'].dropna().unique().tolist()
73
+
74
+ # Convertir en chaînes séparées par des virgules
75
+ self.region = ', '.join(distinct_regions)
76
+ self.dep = ', '.join(distinct_departements)
77
+
78
+ def generate_query_node(state: GraphState):
79
+
80
+ prompt = ChatPromptTemplate.from_messages(messages = df_prompt)
81
+ generate_df_query = prompt | llm.with_structured_output(
82
+ Query,
83
+ include_raw=True, # permet de checker les erreurs en sortie
84
+ )
85
+ # Ajouter le retour erreur de parse_error
86
+ try :
87
+ query_generate = generate_df_query.invoke({
88
+ 'df_head' : state.df_head,
89
+ 'instructions' : state.instructions,
90
+ 'feedback' : state.query_feedbacks,
91
+ 'error' : state.error_query,
92
+ 'nature_jugement' : state.nature_jugement,
93
+ 'dep' : state.dep,
94
+ 'region': state.region
95
+ })
96
+ query_final = query_generate['parsed'].clean_query()
97
+ return {
98
+ "query": query_final,
99
+ "error_query" : "" # si il ya une erreur cela remet le compteur à zéro
100
+ }
101
+ except Exception as e:
102
+ return {'error_query' : e}
103
+
104
+ def evaluate_query_node(state:GraphState):
105
+ if state.error_query != "":
106
+ return "Il y a une erreur dans la requête. Je me suis sûrement trompé. Veuillez réessayer."
107
+ else:
108
+ return "ok"
109
+
110
+
111
+ def generate_results_node(state:GraphState):
112
+ try :
113
+ query = state.query
114
+ print("query ", query)
115
+ print('je suis dans generate', type(state.df))
116
+ query = eval(query, {"df": state.df})
117
+ new_df = state.df[query]
118
+ print("new_df", new_df.empty)
119
+ if new_df.empty:
120
+ return {
121
+ "generation_num": state.generation_num + 1}
122
+ elif len(new_df)> MAX_ROWS:
123
+ return {'results' : new_df.head(MAX_ROWS).to_json(orient='records'),
124
+ "generation_num": state.generation_num + 1,
125
+ "truncated": True
126
+ }
127
+ else:
128
+ return {'results' : new_df.to_json(orient='records'),
129
+ "generation_num": state.generation_num + 1,
130
+
131
+ }
132
+ except Exception as e :
133
+ return {'error_results' : e,
134
+ "generation_num": state.generation_num + 1}
135
+
136
+
137
+ def evaluate_results_node(state:GraphState):
138
+ prompt_eval = ChatPromptTemplate.from_messages(messages=reflection_prompt)
139
+ generate_eval = prompt_eval | llm.with_structured_output(
140
+ GradeResults,
141
+ include_raw=False, # permet de checker les erreurs en sortie
142
+ )
143
+
144
+ evaluation = generate_eval.invoke({'df_head' : state.df_head,
145
+ 'results' :state.results,
146
+ 'instructions' : state.instructions})
147
+
148
+ if state.generation_num > MAX_GENERATIONS:
149
+ return "max_generation_reached"
150
+
151
+ return evaluation.binary_score
152
+
153
+ def query_feedback_node(state: GraphState):
154
+ prompt_feed_back = ChatPromptTemplate.from_messages(messages=feed_back_prompt)
155
+ query_feedback_chain = prompt_feed_back| llm |StrOutputParser()
156
+
157
+ feedback = query_feedback_chain.invoke({
158
+ "df_head" : state.df_head,
159
+ "instructions": state.instructions,
160
+ "results": state.results,
161
+ "query": state.query
162
+ })
163
+
164
+ feedback = f"Evaluation de la recherche : {feedback}"
165
+ print(feedback)
166
+ return {"query_feedbacks": feedback}
prompts.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Sun Oct 13 10:32:26 2024
5
+
6
+ @author: legalchain
7
+ """
8
+
9
+ df_prompt = [
10
+ (
11
+ "system",
12
+ "Tu travailles avec dees pandas dataframes en Python. Le nom du dataframe est `df`."
13
+ " Voici les instructions à connaître pour effectuer une recherche performante :"
14
+ "1. La colonne 'jugement_nature' renseigne sur le type de jugement. C'est dans cette colonne que tu peux trouver les sociétés ou les personnes en liquidation judiciaire ou en redressement judiciaire"
15
+ " La liste des valeurs que peut prendre ce champ est la suivante : {nature_jugement}"
16
+ "2. La colonne 'activite' indique le type d'activité comme restauration, hotelerie, avocat, etc. Ce champ est libre. Il faudra souvent élargir la recherche initiale pour trouver la bonne activité."
17
+ "3. Les colonnes 'departement_nom_officiel, 'region_nom_officiel' servent à localiser l'entreprise concernée"
18
+ " Voici la liste des valeurs possibles pour les régions : \n {region} \n"
19
+ " Voici la liste des valeurs possibles pour les départements : \n {dep} \n"
20
+ "4. La colonne 'jugement_complementJugement' sert à obtenir des détails sur le jugement comme le nom du mandataires par exemple."
21
+ "5. Si on te demande une durée depuis la date de jugement utilise la colonne 'temps_ecoule' qui contient la durée en jours entre aujourd'hui et la date de jugement"
22
+ "La recherche est souvent formulée en des termes communs et a une correspondance juridique dans le dataframe"
23
+ "Par exemple le mot faillite ne s'applique qu'aux faillites personnelles, on parlera de liquidation judiciaire "
24
+ "Voici le résultat de la fonction `print(df.head())`:{df_head}"
25
+ ,
26
+ ),
27
+ ("human",
28
+ "{instructions}"
29
+ "### Ta tâche : \n"
30
+ "Ecris la requêtes pour extraire les informations pertinentes du dataframe : \n\n "
31
+ "Si on te demande une durée depuis la date de jugement utilise la colonne 'temps_ecoule' qui contient la durée en jours entre aujourd'hui et la date de jugement"
32
+ "Voici l'évaluation d'une précédente recherche : {feedback}"
33
+ "Corrige éventuellement la requête précédente qui a généré cette erreur {error}"
34
+
35
+ ),
36
+ ]
37
+
38
+ reflection_prompt = [
39
+ (
40
+ "system",
41
+ "Tu travailles avec dees pandas dataframes en Python."
42
+ " Le nom du dataframe est df "
43
+ "Voici le résultat de la fonction `print(df.head())`:{df_head}"
44
+ " Tu es chargé d'évaluer les résultats d'une requête/recherche sur le dataframe"
45
+
46
+
47
+ ),
48
+ ("human",
49
+ "Voici l'instruction donnée pour la recherche dans le dataframe {instructions} \n"
50
+ "Voici un extrait (df.head() )des résultats de la recherche : {results} \n"
51
+ "### Ta tâche : \n"
52
+ " Tu dois évaluer les résultats de la recherche. \n"
53
+ ),
54
+ ]
55
+
56
+ feed_back_prompt = [
57
+ (
58
+ "system",
59
+ "Tu travailles avec dees pandas dataframes en Python."
60
+ " Le nom du dataframe est df "
61
+ "Voici le résultat de la fonction `print(df.head())`:{df_head}"
62
+ " Tu es chargé d'évaluer les résultats d'une requête/recherche sur le dataframe"
63
+
64
+ ),
65
+ ("human",
66
+ "Voici l'instructions de recherche: {instructions}"
67
+ "Voici la recherche dans le dataframe : {query}"
68
+ "Voici les resultats insatisfaisants : {results} "
69
+ "### Ta tâche : \n"
70
+ " Tu dois évaluer la recherche et donner des instructions pour la modifier en fonction "
71
+ " de ton diagnostique. "
72
+ ),
73
+ ]
requirements.txt ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ altair==5.4.1
2
+ annotated-types==0.7.0
3
+ anyio==4.6.0
4
+ attrs==24.2.0
5
+ blinker==1.8.2
6
+ cachetools==5.5.0
7
+ certifi==2024.8.30
8
+ charset-normalizer==3.4.0
9
+ click==8.1.7
10
+ gitdb==4.0.11
11
+ GitPython==3.1.43
12
+ h11==0.14.0
13
+ httpcore==1.0.6
14
+ httpx==0.27.2
15
+ idna==3.10
16
+ Jinja2==3.1.4
17
+ jsonpatch==1.33
18
+ jsonpointer==3.0.0
19
+ jsonschema==4.23.0
20
+ jsonschema-specifications==2024.10.1
21
+ langchain-core==0.3.10
22
+ langgraph==0.2.35
23
+ langgraph-checkpoint==2.0.1
24
+ langsmith==0.1.134
25
+ markdown-it-py==3.0.0
26
+ MarkupSafe==3.0.1
27
+ mdurl==0.1.2
28
+ msgpack==1.1.0
29
+ narwhals==1.9.3
30
+ numpy==2.1.2
31
+ orjson==3.10.7
32
+ packaging==24.1
33
+ pandas==2.2.3
34
+ pillow==10.4.0
35
+ protobuf==5.28.2
36
+ pyarrow==17.0.0
37
+ pydantic==2.9.2
38
+ pydantic_core==2.23.4
39
+ pydeck==0.9.1
40
+ Pygments==2.18.0
41
+ python-dateutil==2.9.0.post0
42
+ pytz==2024.2
43
+ PyYAML==6.0.2
44
+ referencing==0.35.1
45
+ requests==2.32.3
46
+ requests-toolbelt==1.0.0
47
+ rich==13.9.2
48
+ rpds-py==0.20.0
49
+ six==1.16.0
50
+ smmap==5.0.1
51
+ sniffio==1.3.1
52
+ streamlit==1.39.0
53
+ tenacity==8.5.0
54
+ toml==0.10.2
55
+ tornado==6.4.1
56
+ typing_extensions==4.12.2
57
+ tzdata==2024.2
58
+ urllib3==2.2.3