@@ -67,14 +67,15 @@ def _initialize_openai_client(self) -> OpenAI:
6767
6868 return OpenAI (** client_kwargs )
6969
70- def ask_llm (self , message : dict , temperature : float = 0.0 ) -> str :
70+ def ask_llm (self , message : dict , response_format : dict = None , temperature : float = 0.0 ) -> str :
7171 """
7272 Interroge le LLM avec un prompt système et utilisateur.
7373
7474 Args:
7575 message:
7676 system_prompt: Prompt système pour définir le rôle du LLM
7777 user_prompt: Prompt utilisateur avec la question
78+ response_format: Format de réponse à utiliser
7879 temperature: Température pour la génération (0.0 = déterministe)
7980
8081 Returns:
@@ -84,7 +85,8 @@ def ask_llm(self, message: dict, temperature: float = 0.0) -> str:
8485 response = self .client .chat .completions .create (
8586 model = self .llm_model ,
8687 messages = message ,
87- temperature = temperature
88+ temperature = temperature ,
89+ response_format = response_format if response_format else None
8890 )
8991
9092 return response .choices [0 ].message .content .strip ()
@@ -114,13 +116,14 @@ def ask_llm(self, message: dict, temperature: float = 0.0) -> str:
114116 # logger.error(f"ERREUR lors de la seconde tentative LLM: {str(e2)}")
115117 return f"Erreur lors de l'appel au LLM: { str (e )[:100 ]} "
116118
117- def analyze_content (self , context : str , question : str , temperature : float = 0.0 ) -> str :
119+ def analyze_content (self , context : str , question : str , response_format : dict = None , temperature : float = 0.0 ) -> str :
118120 """
119121 Analyse le contexte fourni en utilisant l'API LLM.
120122
121123 Args:
122124 context: Contexte à analyser (texte complet ou liste de chunks)
123125 question: Question à poser au LLM
126+ response_format: Format de réponse à utiliser
124127 temperature: Température pour la génération (0.0 = déterministe)
125128
126129 Returns:
@@ -134,7 +137,7 @@ def analyze_content(self, context: str, question: str, temperature: float = 0.0)
134137 {"role" : "user" , "content" : user_prompt }
135138 ]
136139
137- return self .ask_llm (message , temperature )
140+ return self .ask_llm (message , response_format , temperature )
138141
139142# Fonction pour extraire le JSON de la réponse
140143def parse_json_response (response_text ):
@@ -179,6 +182,22 @@ def get_prompt_from_attributes(dfAttributes: pd.DataFrame ):
179182 question += f"""{ consigne } """
180183 return question
181184
185+ def create_response_format (dfAttributes , classification ):
186+ l_output_field = select_attr (dfAttributes , classification ).output_field .tolist ()
187+ response_format = {
188+ "type" : "json_schema" ,
189+ "json_schema" : {
190+ "name" : f"{ classification } " ,
191+ "strict" : True ,
192+ "schema" : {
193+ "type" : "object" ,
194+ "properties" : {output_field : {"type" : "string" } for output_field in l_output_field },
195+ "required" : list (dfAttributes .output_field )
196+ }
197+ }
198+ }
199+ return response_format
200+
182201def df_analyze_content (api_key ,
183202 base_url ,
184203 llm_model ,
@@ -220,6 +239,7 @@ def process_row(idx):
220239 classification = row ['classification' ]
221240 try :
222241 question = get_prompt_from_attributes (select_attr (dfAttributes , classification ))
242+ response_format = create_response_format (dfAttributes , classification )
223243 context = row ['relevant_content' ]
224244
225245 if (context == "" ):
@@ -228,8 +248,9 @@ def process_row(idx):
228248 with log_execution_time (f"df_analyze_content({ row .filename } )" ):
229249 response = llm_env .analyze_content (context = context ,
230250 question = question ,
251+ response_format = response_format ,
231252 temperature = temperature )
232-
253+ print ( response )
233254 data , error = parse_json_response (response )
234255
235256 result = {
@@ -279,93 +300,3 @@ def process_row(idx):
279300def save_df_analyze_content_result (df : pd .DataFrame ):
280301 bulk_update_attachments (df , ['llm_response' , 'json_error' ])
281302
282-
283- def questionDesignation (row ):
284- context = ' ET ' .join (row ['infos' ])
285- question = f"""
286- Création d'une désignation et d'une description détaillée de la dépense
287-
288- Voici les informations disponibles sur la dépense :
289-
290- { context }
291- A partir de ces informations, tu dois produire :
292- 1. Un description, appelée "designation" : une description de la dépense en une phrase.
293- 2. Une description détaillée, appelée "description" : une description plus longue qui reprend tous les éléments importants fournis dans les informations ci-dessus.
294- 3. Tu dois IMPÉRATIVEMENT répondre UNIQUEMENT avec un JSON valide, sans aucun autre texte, avec cette structure exacte:
295- {{
296- "designation": la description en un paragraphe,
297- "description": la description detaillée à partir des éléments importants fournis dans les informations ci-dessus
298- }}
299- 4. Les informations seront fournies sous format JSON séparés par ET avec parfois de la redondance.
300- """
301- return question
302-
303- def df_create_designation (api_key ,
304- base_url ,
305- llm_model ,
306- df : pd .DataFrame ,
307- temperature : float = 0.0 ,
308- max_workers : int = 4 ,
309- save_path : str = None ,
310- directory_path : str = None ) -> pd .DataFrame :
311- dfResult = df [df ['json_error' ].isna ()].query ('nb_mot > 0' )\
312- .groupby ('num_EJ' )\
313- .agg ({
314- 'llm_response' : list
315- }).reset_index ()\
316- .rename (columns = {'llm_response' : 'infos' })\
317- .sort_values (by = 'num_EJ' , ascending = True )\
318- .copy ()
319- dfResult ['llm_response' ] = None
320- dfResult ['json_error' ] = None
321-
322- def process_row (idx ):
323- row = dfResult .iloc [idx ]
324- question = questionDesignation (row )
325- try :
326- llm_env = LLMEnvironment (
327- api_key = api_key ,
328- base_url = base_url ,
329- llm_model = llm_model
330- )
331- system_prompt = "Vous êtes un assistant IA qui analyse des documents juridiques."
332- message = [
333- {"role" : "system" , "content" : system_prompt },
334- {"role" : "user" , "content" : question }
335- ]
336- response = llm_env .ask_llm (message = message ,
337- temperature = temperature )
338- data , error = parse_json_response (response )
339-
340- result = {
341- 'llm_response' : response ,
342- 'json_error' : error
343- }
344-
345- if not error :
346- result .update ({
347- 'designation' : data .get ('designation' , '' ),
348- 'description' : data .get ('description' , '' )
349- })
350- else :
351- print (f"Erreur lors de l'analyse du fichier { row ['num_EJ' ]} : { error } " )
352- except Exception as e :
353- result = {
354- 'llm_response' : None ,
355- 'json_error' : f"Erreur lors de l'analyse: { str (e )} "
356- }
357- return idx , result
358-
359- with ThreadPoolExecutor (max_workers = max_workers ) as executor :
360- futures = [executor .submit (process_row , i ) for i in range (len (dfResult ))]
361-
362- for future in tqdm (futures , total = len (futures ), desc = "Traitement des désignations" ):
363- idx , result = future .result ()
364- for key , value in result .items ():
365- dfResult .at [idx , key ] = value
366-
367- if (save_path != None ):
368- dfResult .to_csv (f'{ save_path } /Designation_{ directory_path .split ("/" )[- 1 ]} _{ getDate ()} .csv' , index = False )
369- print (f"Liste des fichiers sauvegardées dans { save_path } /Designation_{ directory_path .split ("/" )[- 1 ]} _{ getDate ()} .csv" )
370-
371- return dfResult
0 commit comments