Flavio Casadei Della Chiesa commited on
Commit
ad14e53
·
1 Parent(s): bfe0450

versione aggiornata con ollama

Browse files
Files changed (3) hide show
  1. app.py +94 -41
  2. ragpipeline.py +22 -38
  3. textutils.py +32 -5
app.py CHANGED
@@ -1,18 +1,12 @@
1
  import streamlit as st
2
- from ragpipeline import (RAGPipeline,Retriever,ChatBot)
3
  import tempfile
4
  import pandas as pd
5
- from textutils import ParagraphDocumentProcessor, DocumentProcessor,WholeTextDocumentProcessor
6
  from HFChatbot import HFBot
7
  import os
8
 
9
-
10
  def main():
11
- UPLOAD_DIR = "/tmp/"
12
- os.makedirs(UPLOAD_DIR, exist_ok=True)
13
-
14
- codice_tabella = f"<table><tr><td>💡AURA:</td><td> AI-Driven Unified Regulatory Audit</td></tr></table>"
15
- st.markdown(codice_tabella, unsafe_allow_html=True)
16
 
17
  if "faiss_builder" not in st.session_state:
18
  ragpipeline = RAGPipeline(numero_frammenti=10)
@@ -26,47 +20,100 @@ def main():
26
  if "indice_creato" not in st.session_state:
27
  st.session_state["indice_creato"] = False
28
 
29
- modelliLLM = [
30
- 'Almawave/Velvet-2B',
31
- 'Almawave/Velvet-14B',
32
- 'mistralai/Mistral-7B-Instruct-v0.1',
33
- 'sapienzanlp/Minerva-350M-base-v1.0',
34
- 'Qwen/Qwen3-0.6B'
35
  ]
 
 
 
 
 
 
 
 
 
36
  modelliOllama = [
37
-
38
- 'deepseek-r1:1.5b',
39
- 'qwen2.5:7b',
40
- 'deepseek-r1:7b',
41
- 'llama3.2:3b',
42
  'Almawave/Velvet:2B',
43
  'Almawave/Velvet:14b',
44
- 'mistral:latest',
45
- "vaiton/minerva",
46
- 'qwen2.5:0.5b',
47
- 'qwen3:4b',
48
- 'minerva',
49
- 'nemo',
50
- 'deepseek-r1:14b',
51
  'qwen3:14b',
52
- 'phi4-mini-reasoning',
53
- 'gemma3:12b',
54
-
55
- ]
56
-
57
- modello_scelto = st.selectbox("Seleziona un modello:", modelliLLM)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  st.write(f"Hai selezionato: {modello_scelto}")
 
 
 
 
 
 
 
 
 
 
 
 
59
 
 
60
  st.title("Suddivisione in paragrafi")
61
  docprocessor_options = {
62
- "ParagraphDocumentProcessor": ParagraphDocumentProcessor(),
63
- "WholeText": WholeTextDocumentProcessor()
 
64
  }
65
  selected_docprocessor = st.selectbox("Divisione in paragrafi", docprocessor_options.keys())
66
- docprocessor = docprocessor_options[selected_docprocessor]
67
  st.write(f"Hai selezionato: **{selected_docprocessor}**")
68
 
69
- ragpipeline.docprocessor = docprocessor
 
 
70
  if not st.session_state["indice_creato"]:
71
  st.subheader("Carica l'atto principale (Determinazione)")
72
 
@@ -125,9 +172,15 @@ def main():
125
  if domanda.strip().upper() == "FINE":
126
  st.stop()
127
 
128
- #cb = ChatBot(model_name="flaollama", model_orig=modello_scelto)
 
 
 
 
 
 
 
129
 
130
- cb = HFBot(model_name=modello_scelto)
131
 
132
  ret = Retriever(
133
  indice=ragpipeline.indice,
@@ -140,7 +193,7 @@ def main():
140
 
141
  ret.esegui_query(top_k=3)
142
 
143
- risposta = cb.generate(
144
  query=domanda,
145
  relevant_docs=ret.passaggi_rilevanti,
146
  attributi_frammenti_rilevanti=ret.attributi_rilevanti,
@@ -158,7 +211,7 @@ def main():
158
  id_frammenti_recuperati = ":".join(sorted(set(elemento['id'] for elemento in ret.attributi_rilevanti)))
159
  dump = {
160
  'timestamp': ragpipeline.timestamp,
161
- "modello": cb.model_orig,
162
  "documenti": st.session_state.get("main_pdf_nome", "non disponibile"),
163
  "file_recuperati": "",
164
  "file_gold": "",
@@ -167,7 +220,7 @@ def main():
167
  "domanda":domanda,
168
  "istruzioni":istruzione,
169
  "risposta_gold": " ",
170
- "risposta":cb.pulisci_risposta(risposta)}
171
 
172
  RAGPipeline.dump_excel(dizionario=dump,filename="dumpChatbot.xlsx")
173
 
 
1
  import streamlit as st
2
+ from ragpipeline import (RAGPipeline,Retriever,OllamaChatbot)
3
  import tempfile
4
  import pandas as pd
5
+ from textutils import ParagraphDocumentProcessor, SmallFragmentDocumentProcessor,WholeTextDocumentProcessor
6
  from HFChatbot import HFBot
7
  import os
8
 
 
9
  def main():
 
 
 
 
 
10
 
11
  if "faiss_builder" not in st.session_state:
12
  ragpipeline = RAGPipeline(numero_frammenti=10)
 
20
  if "indice_creato" not in st.session_state:
21
  st.session_state["indice_creato"] = False
22
 
23
+
24
+
25
+ modelliVelvet = [
26
+ 'Almawave/Velvet-2B',
27
+ 'Almawave/Velvet-14B',
28
+
29
  ]
30
+ modelliLLM = [
31
+ 'Almawave/Velvet-2B',
32
+ 'Almawave/Velvet-14B',
33
+
34
+ 'mistralai/Mistral-7B-Instruct-v0.1',
35
+ 'Qwen/Qwen2.5-1.5B',
36
+ 'BlackBeenie/Qwen3-30B-A3B-Q4_K_M-GGUF'
37
+ ]
38
+
39
  modelliOllama = [
 
 
 
 
 
40
  'Almawave/Velvet:2B',
41
  'Almawave/Velvet:14b',
42
+ 'llama3.1:8b-instruct-q4_K_M',
 
 
 
 
 
 
43
  'qwen3:14b',
44
+ 'qwen3:30b-a3b'
45
+
46
+ ]
47
+ ## indica se sono sullo spaces di HF (deve essere inserita uan variabile I_AM_ON_HF)
48
+ sono_su_hf =os.environ.get('I_AM_ON_HF', False)
49
+ ## se sono su Hugginh Face non uso ollama
50
+ if not sono_su_hf:
51
+ modelliLLM.append("----- USARE SOLO CON OLLAMA -----")
52
+ for mollama in modelliOllama:
53
+ modelliLLM.append(mollama)
54
+
55
+ UPLOAD_DIR="/tmp/"
56
+ if "indice_creato" not in st.session_state:
57
+ st.session_state["indice_creato"] = False
58
+ if "faiss_builder" not in st.session_state:
59
+ ragpipeline = RAGPipeline( )
60
+ codice_tabella = f"<table><tr><td>💡AURA:</td><td> AI-Utilizzata per la Regolarità Amministrativa</td></tr></table>"
61
+ st.markdown(codice_tabella, unsafe_allow_html=True)
62
+ st.title("Cosa è AURA?")
63
+ st.write("""
64
+ Questo strumento, attualmente in fase sperimentale, è stato sviluppato per eseguire controlli di
65
+ regolarità amministrativa ai sensi dell’art. 147-bis del D.Lgs. 267/2000,
66
+ con riferimento agli atti relativi al PNRR.
67
+ È in continua evoluzione. Per testarne il funzionamento, è sufficiente caricare un file PDF contenente
68
+ una determinazione dirigenziale.
69
+ AURA analizzerà il documento e fornirà risposte basate su una check-list predefinita di domande.
70
+ <p>
71
+ AURA è un sistema RAG <em>Retrieval Augmented Generation</em> che dato un atto amministrativo ed eventuali allegati
72
+ ed una o più domande contenute in una <em>check list</em> di regolarità amministrativa, ricerca nei documenti
73
+ i frammenti rilevanti per la domanda; questi assieme ad alcune istruzioni (<em>In-context learning</em>)
74
+ vengono inviati ad un LLM <em>Large Language Model</em>
75
+ al fine di generare una risposta corretta e coerente con i frammenti rilevanti.
76
+ </p>
77
+ <p>
78
+ Questa versione di AURA utilizza Velvet:2B di Almawave, rilasciato sotto
79
+ <a href="https://www.apache.org/licenses/LICENSE-2.0">Licenza Apache 2.0</a> come LLM.
80
+ </p>
81
+
82
+ """ , unsafe_allow_html=True)
83
+
84
+ st.warning("Attenzione questo tool è sperimentale. AURA può sbagliare")
85
+ if not sono_su_hf:
86
+ modello_scelto = st.selectbox("Seleziona un modello:", modelliLLM, index=0)
87
+ else:
88
+ modello_scelto = st.selectbox("Seleziona un modello:", modelliVelvet, index=0)
89
  st.write(f"Hai selezionato: {modello_scelto}")
90
+
91
+ if not sono_su_hf:
92
+ st.title("Generazione testo")
93
+ generatoriLLM = {
94
+ 'Hugging Face Transformers': "HF",
95
+ 'Ollama in locale' :"OLLAMA"
96
+ }
97
+ selected_generator= st.selectbox("Scegli lo strumento per interagire con LLM", generatoriLLM.keys())
98
+ chiave_LLM=generatoriLLM[selected_generator]
99
+ st.write(f"Hai selezionato {selected_generator}")
100
+ else:
101
+ chiave_LLM="HF"
102
 
103
+
104
  st.title("Suddivisione in paragrafi")
105
  docprocessor_options = {
106
+ "Small Fragments (più veloce ma poco preciso)": SmallFragmentDocumentProcessor(),
107
+ "ParagraphDocumentProcessor (più lento e leggermente più preciso)": ParagraphDocumentProcessor(),
108
+ "WholeText (viene generato un solo grande frammento, può confondere gli LLM)": WholeTextDocumentProcessor(),
109
  }
110
  selected_docprocessor = st.selectbox("Divisione in paragrafi", docprocessor_options.keys())
111
+ ragpipeline.docprocessor = docprocessor_options[selected_docprocessor]
112
  st.write(f"Hai selezionato: **{selected_docprocessor}**")
113
 
114
+
115
+
116
+
117
  if not st.session_state["indice_creato"]:
118
  st.subheader("Carica l'atto principale (Determinazione)")
119
 
 
172
  if domanda.strip().upper() == "FINE":
173
  st.stop()
174
 
175
+ if chiave_LLM == "HF":
176
+ LLM=HFBot(model_name=modello_scelto)
177
+ elif chiave_LLM == "OLLAMA":
178
+ LLM=OllamaChatbot(model_name="flaollama",model_orig=modello_scelto)
179
+ else:
180
+ LLM=HFBot(model_name=modello_scelto)
181
+
182
+
183
 
 
184
 
185
  ret = Retriever(
186
  indice=ragpipeline.indice,
 
193
 
194
  ret.esegui_query(top_k=3)
195
 
196
+ risposta = LLM.generate(
197
  query=domanda,
198
  relevant_docs=ret.passaggi_rilevanti,
199
  attributi_frammenti_rilevanti=ret.attributi_rilevanti,
 
211
  id_frammenti_recuperati = ":".join(sorted(set(elemento['id'] for elemento in ret.attributi_rilevanti)))
212
  dump = {
213
  'timestamp': ragpipeline.timestamp,
214
+ "modello": modello_scelto,
215
  "documenti": st.session_state.get("main_pdf_nome", "non disponibile"),
216
  "file_recuperati": "",
217
  "file_gold": "",
 
220
  "domanda":domanda,
221
  "istruzioni":istruzione,
222
  "risposta_gold": " ",
223
+ "risposta":LLM.pulisci_risposta(risposta)}
224
 
225
  RAGPipeline.dump_excel(dizionario=dump,filename="dumpChatbot.xlsx")
226
 
ragpipeline.py CHANGED
@@ -118,17 +118,32 @@ class Retriever:
118
  self.attributi_rilevanti = [self.attributi_frammenti[j] for j in indices[0]] #passaggi rilevanti
119
 
120
 
 
121
  class ChatBot:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  def __init__(self,
123
  model_name: str = "flaollama",
124
  model_orig: str = "mistral" ,
125
  model_system=(
126
- "Sei un esperto di diritto amministrativo che deve eseguire il "
127
- "controllo di regolarità amministrativa su un atto amministrativo di un comune italiano. "
128
- "Ti verranno forniti un atto amministrativo (determinazione dirigenziale) ed eventuali allegati, questi sono forniti come frammenti rilevanti. "
129
- "Utilizza solamente i frammenti che ti verranno inviati."
130
- "Rispondi in Italiano usando al massimo 50 parole. "
131
- "Basati esclusivamente sul seguente testo: "
132
  ),
133
  dump_filename="dump.csv"
134
  ):
@@ -141,31 +156,6 @@ class ChatBot:
141
  from_=model_orig,
142
  system = model_system
143
  )
144
-
145
- def dump_excel(self, dizionario, filename ):
146
- RAGPipeline.dump_excel(dizionario=dizionario, filename=filename)
147
-
148
- def dump_csv(self,dizionario):
149
- """Salva un dizionario in un file CSV con separatore '|' accodando i dati se il file esiste."""
150
- file_esiste = os.path.isfile(self.dump_filename)
151
-
152
- with open(self.dump_filename, mode="a", newline="", encoding="utf-8") as file:
153
- writer = csv.writer(file, delimiter="|")
154
-
155
- # Scrive l'intestazione solo se il file viene creato ex novo
156
- if not file_esiste:
157
- writer.writerow(dizionario.keys())
158
-
159
- # Scrive i valori come una nuova riga
160
- writer.writerow([str(val).replace("\n", "").replace("\r", "").replace("\t", "") for val in dizionario.values()])
161
-
162
- def pulisci_risposta(self,
163
- response: str):
164
- retval=re.sub(r"<think>.*?</think>", "", response, flags=re.DOTALL).strip()
165
-
166
- retval = retval.replace("\n", " ").replace("\t", " ").replace("|", " ")
167
-
168
- return retval
169
  def chat(self, domanda: str, istruzioni: str = None, frammenti =[]) -> str:
170
  prompt = f"ISTRUZIONI: {istruzioni}\n\nCONTESTO:\n" + "\n".join(frammenti) + f"\n\nDOMANDA: {domanda}"
171
 
@@ -175,13 +165,7 @@ class ChatBot:
175
 
176
  return response["message"]["content"]
177
 
178
- def generate(self,
179
- relevant_docs = [],
180
- attributi_frammenti_rilevanti = [],
181
- query="",
182
- istruzioni :str = None ##togliere
183
- ):
184
-
185
  i = 0
186
  #print (f"DIMESIONE FILE {len(relevant_files)}")
187
  #print (f"DIMESIONE TESTI {len(relevant_docs)}")
 
118
  self.attributi_rilevanti = [self.attributi_frammenti[j] for j in indices[0]] #passaggi rilevanti
119
 
120
 
121
+
122
  class ChatBot:
123
+ def pulisci_risposta(self,
124
+ response: str):
125
+ retval=re.sub(r"<think>.*?</think>", "", response, flags=re.DOTALL).strip()
126
+
127
+ retval = retval.replace("\n", " ").replace("\t", " ").replace("|", " ")
128
+
129
+ return retval
130
+
131
+ def chat(self, domanda: str, istruzioni: str = None, frammenti =[]) -> str:
132
+ raise NotImplementedError("Questo metodo deve essere implementato nelle sottoclassi.")
133
+ def generate(self, relevant_docs = [], attributi_frammenti_rilevanti = [], query="", istruzioni :str = None ) -> str:
134
+ raise NotImplementedError("Questo metodo deve essere implementato nelle sottoclassi.")
135
+
136
+ class OllamaChatbot(ChatBot):
137
  def __init__(self,
138
  model_name: str = "flaollama",
139
  model_orig: str = "mistral" ,
140
  model_system=(
141
+ """Sei un esperto di diritto amministrativo che deve eseguire il
142
+ controllo di regolarità amministrativa su un atto amministrativo di un comune italiano.
143
+ Ti verranno forniti un atto amministrativo (determinazione dirigenziale) ed eventuali allegati, questi sono forniti come frammenti rilevanti.
144
+ Utilizza solamente i frammenti che ti verranno inviati.
145
+ Rispondi in Italiano usando al massimo 50 parole.
146
+ Basati esclusivamente sul seguente testo: """
147
  ),
148
  dump_filename="dump.csv"
149
  ):
 
156
  from_=model_orig,
157
  system = model_system
158
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  def chat(self, domanda: str, istruzioni: str = None, frammenti =[]) -> str:
160
  prompt = f"ISTRUZIONI: {istruzioni}\n\nCONTESTO:\n" + "\n".join(frammenti) + f"\n\nDOMANDA: {domanda}"
161
 
 
165
 
166
  return response["message"]["content"]
167
 
168
+ def generate(self, relevant_docs = [], attributi_frammenti_rilevanti = [], query="", istruzioni :str = None ):
 
 
 
 
 
 
169
  i = 0
170
  #print (f"DIMESIONE FILE {len(relevant_files)}")
171
  #print (f"DIMESIONE TESTI {len(relevant_docs)}")
textutils.py CHANGED
@@ -2,7 +2,7 @@
2
  import pandas as pd
3
  import re
4
  import PyPDF2
5
- from dizionario_acronimi import acronimi
6
  import unicodedata
7
 
8
  class DocumentProcessor:
@@ -57,8 +57,7 @@ class DocumentProcessor:
57
  for page in reader.pages:
58
  page_text = page.extract_text() or ""
59
  full_text += page_text
60
- for acr, espansione in acronimi.items():
61
- full_text = full_text.replace(acr,espansione)
62
  return full_text
63
 
64
  def chunk_text_by_paragraph(self,text: str):
@@ -146,15 +145,43 @@ class ParagraphDocumentProcessor(DocumentProcessor):
146
 
147
  class WholeTextDocumentProcessor(DocumentProcessor) :
148
  def scomponi_in_frammenti(self, testo:str, numero_frammenti: int = 1):
149
- print("WholeTextDocumeptProcessor !!!")
150
  return [testo]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
 
153
 
154
 
155
-
156
 
157
 
158
 
 
 
159
 
160
 
 
2
  import pandas as pd
3
  import re
4
  import PyPDF2
5
+
6
  import unicodedata
7
 
8
  class DocumentProcessor:
 
57
  for page in reader.pages:
58
  page_text = page.extract_text() or ""
59
  full_text += page_text
60
+
 
61
  return full_text
62
 
63
  def chunk_text_by_paragraph(self,text: str):
 
145
 
146
  class WholeTextDocumentProcessor(DocumentProcessor) :
147
  def scomponi_in_frammenti(self, testo:str, numero_frammenti: int = 1):
 
148
  return [testo]
149
+
150
+ class SmallFragmentDocumentProcessor(DocumentProcessor):
151
+ def scomponi_in_frammenti(self, testo:str, numero_frammenti: int = 1):
152
+ return self.dividi_testo_in_frammenti(testo)
153
+
154
+ def dividi_testo_in_frammenti(self,testo, lunghezza_massima=1000):
155
+ frammenti = []
156
+ inizio = 0
157
+
158
+ while inizio < len(testo):
159
+ fine = inizio + lunghezza_massima
160
+
161
+ # Se siamo alla fine del testo, aggiungiamo e usciamo
162
+ if fine >= len(testo):
163
+ frammenti.append(testo[inizio:].strip())
164
+ break
165
+
166
+ # Cerca l'ultimo spazio prima del limite per evitare di tagliare la parola
167
+ fine_corretto = testo.rfind(" ", inizio, fine)
168
+ if fine_corretto == -1 or fine_corretto <= inizio:
169
+ # Se non troviamo spazi, tagliamo brutalmente
170
+ fine_corretto = fine
171
+
172
+ frammento = testo[inizio:fine_corretto].strip()
173
+ frammenti.append(frammento)
174
+ inizio = fine_corretto
175
+
176
+ return frammenti
177
 
178
 
179
 
180
 
 
181
 
182
 
183
 
184
+
185
+
186
 
187