farmax commited on
Commit
629f6d9
·
verified ·
1 Parent(s): 80ec853

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +174 -43
app.py CHANGED
@@ -1,73 +1,204 @@
 
 
1
  import gradio as gr
 
2
  from transformers import AutoTokenizer, AutoModelForQuestionAnswering, AutoModelForCausalLM, pipeline
3
 
4
- # Modelli
 
5
  MODEL_DEBERTA = "osiria/deberta-italian-question-answering"
6
  MODEL_GEPPETTO = "LorenzoDeMattei/GePpeTto"
7
 
8
- # Pipeline DeBERTa (estrattivo)
9
- tok_deb = AutoTokenizer.from_pretrained(MODEL_DEBERTA)
10
- mdl_deb = AutoModelForQuestionAnswering.from_pretrained(MODEL_DEBERTA)
11
- qa_deb = pipeline("question-answering", model=mdl_deb, tokenizer=tok_deb, device=-1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- # Pipeline GePpeTto (generativo)
14
- tok_gepp = AutoTokenizer.from_pretrained(MODEL_GEPPETTO)
15
- mdl_gepp = AutoModelForCausalLM.from_pretrained(MODEL_GEPPETTO)
16
- qa_gepp = pipeline("text-generation", model=mdl_gepp, tokenizer=tok_gepp, device=-1)
17
 
18
- def ensemble_invoice_qa(md_text: str, question: str):
19
- results = {}
 
 
 
 
 
20
 
21
- # Estrattivo (DeBERTa)
22
- try:
23
- res_deb = qa_deb(question=question, context=md_text)
24
- results["DeBERTa (estrattivo)"] = {
25
- "risposta": res_deb["answer"].strip(),
26
- "confidenza": round(res_deb["score"], 3)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
28
- except Exception as e:
29
- results["DeBERTa (estrattivo)"] = {"errore": str(e)}
30
 
31
- # Generativo (GePpeTto)
32
- try:
33
- prompt = f"Domanda: {question}\nContesto: {md_text}\nRisposta:"
34
- res_gepp = qa_gepp(prompt, max_new_tokens=64, do_sample=False)
35
- results["GePpeTto (generativo)"] = {
36
- "risposta": res_gepp[0]["generated_text"].replace(prompt, "").strip()
 
 
 
 
37
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  except Exception as e:
39
- results["GePpeTto (generativo)"] = {"errore": str(e)}
 
40
 
41
- return results
42
 
43
- # ================== UI Gradio ==================
44
  with gr.Blocks(theme=gr.themes.Base()) as demo:
45
- gr.Markdown("# 🧾 Invoice QA: Ensemble DeBERTa + GePpeTto")
46
- gr.Markdown("Confronto tra risposte estrattive (DeBERTa) e generative (GePpeTto).")
47
 
48
  with gr.Row():
49
  with gr.Column(scale=1):
50
  md_input = gr.Textbox(
51
- label="Testo Fattura (.MD) / Contesto",
52
- lines=15,
53
  placeholder="Incolla qui il contenuto Markdown/Testo della fattura..."
54
  )
55
-
56
  custom_q_input = gr.Textbox(
57
- label="Domanda (in Italiano)",
58
- placeholder="Es: Qual è l'IBAN per il pagamento?"
59
  )
60
 
61
- btn = gr.Button("🔍 Analizza Documento", variant="primary")
62
-
63
  with gr.Column(scale=1):
64
- out_json = gr.JSON(label="Risultati Ensemble (Estrattivo vs Generativo)")
 
 
65
 
66
- btn.click(
67
- fn=ensemble_invoice_qa,
68
- inputs=[md_input, custom_q_input],
69
- outputs=[out_json]
70
- )
71
 
72
  if __name__ == "__main__":
73
  demo.launch()
 
1
+ import time
2
+ import re
3
  import gradio as gr
4
+ from typing import Dict, Any, List, Tuple
5
  from transformers import AutoTokenizer, AutoModelForQuestionAnswering, AutoModelForCausalLM, pipeline
6
 
7
+ # ================= CONFIGURAZIONE =================
8
+
9
  MODEL_DEBERTA = "osiria/deberta-italian-question-answering"
10
  MODEL_GEPPETTO = "LorenzoDeMattei/GePpeTto"
11
 
12
+ # Domande standard (chiave, domanda)
13
+ DOMANDE_IT: List[Tuple[str, str]] = [
14
+ ("tipologia_fattura", "Qual è la tipologia della fattura? / Qual è la natura o il tipo del documento?"),
15
+ ("numero_fattura", "Qual è il numero della fattura? / Qual è il codice identificativo della fattura?"),
16
+ ("data_fattura", "Qual è la data della fattura? / Quando è stata emessa la fattura?"),
17
+ ("data_scadenza", "Qual è la data di scadenza del pagamento? / Entro quale termine deve essere saldata la fattura?"),
18
+ ("emittente", "Chi è l'emittente? / Qual è la ragione sociale dell'emittente?"),
19
+ ("indirizzo_emittente", "Qual è l'indirizzo dell'emittente? / Dove si trova la sede dell'emittente?"),
20
+ ("piva_emittente", "Qual è il codice fiscale o P.IVA dell'emittente? / Qual è la partita IVA dell'azienda?"),
21
+ ("email_emittente", "Qual è l'email dell'emittente? / Qual è l'indirizzo di posta elettronica dell'emittente?"),
22
+ ("telefono_emittente", "Qual è il numero di telefono dell'emittente? / Qual è il recapito telefonico dell'emittente?"),
23
+ ("beneficiario", "Chi è il beneficiario? / Qual è l'intestatario della fattura?"),
24
+ ("indirizzo_beneficiario", "Qual è l'indirizzo del beneficiario? / Dove risiede il beneficiario?"),
25
+ ("cf_beneficiario", "Qual è il codice fiscale del beneficiario? / Qual è l'identificativo fiscale del cliente?"),
26
+ ("email_beneficiario", "Qual è l'email del beneficiario? / Qual è l'indirizzo di posta elettronica del cliente?"),
27
+ ("descrizione_servizio", "Qual è la descrizione del prodotto/servizio? / Come viene denominato l’articolo o la prestazione?"),
28
+ ("quantita_servizio", "Qual è la quantità del prodotto/servizio? / Quante unità o ore sono state fatturate?"),
29
+ ("prezzo_unitario", "Qual è il prezzo unitario del prodotto/servizio? / Qual è il costo per singola unità o ora?"),
30
+ ("totale_servizio", "Qual è il totale del prodotto/servizio? / Qual è l’importo complessivo relativo all’articolo o servizio?"),
31
+ ("importo_imponibile", "Qual è l'importo imponibile? / Qual è il subtotale prima dell'IVA?"),
32
+ ("aliquota_iva", "Qual è l'aliquota IVA applicata? / Qual è la percentuale di IVA?"),
33
+ ("importo_iva", "Qual è l'importo IVA? / Qual è il valore dell'imposta applicata?"),
34
+ ("importo_totale", "Qual è l'importo totale del documento? / Qual è la somma complessiva da pagare?"),
35
+ ("condizioni_pagamento", "Quali sono le condizioni di pagamento? / Quali sono i termini di pagamento previsti?"),
36
+ ("modalita_pagamento", "Qual è la modalità di pagamento prevista? / Quale metodo di pagamento è indicato?"),
37
+ ("iban", "Qual è l'IBAN indicato? / Qual è il numero di conto per il bonifico?"),
38
+ ("bic_swift", "Qual è il BIC/SWIFT indicato? / Qual è il codice bancario internazionale?"),
39
+ ("intestatario_conto", "A chi è intestato il conto? / Chi è il titolare del conto bancario?"),
40
+ ("note", "Quali sono le note aggiuntive riportate nella fattura? / Quali osservazioni sono incluse nel documento?"),
41
+ ("firma_digitale", "Qual è la firma digitale? / Chi ha firmato digitalmente la fattura?"),
42
+ ("cig", "Qual è il CIG di riferimento? / Qual è il codice identificativo di gara?"),
43
+ ("stato_liquidazione", "Qual è lo stato di liquidazione? / Qual è la situazione del pagamento?"),
44
+ ("codice_destinatario", "Qual è il codice destinatario? / Qual è l'identificativo del destinatario?"),
45
+ ("causale", "Qual è la causale della fattura? / Qual è la motivazione o descrizione del pagamento?")
46
+ ]
47
 
48
+ # ================= CACHE MODELLI =================
49
+ LOADED: Dict[str, Any] = {}
 
 
50
 
51
+ def get_deberta_pipeline():
52
+ if "deb" in LOADED: return LOADED["deb"]
53
+ tok = AutoTokenizer.from_pretrained(MODEL_DEBERTA)
54
+ mdl = AutoModelForQuestionAnswering.from_pretrained(MODEL_DEBERTA)
55
+ qa = pipeline("question-answering", model=mdl, tokenizer=tok, handle_impossible_answer=True, top_k=1, device=-1)
56
+ LOADED["deb"] = qa
57
+ return qa
58
 
59
+ def get_geppetto_pipeline():
60
+ if "gepp" in LOADED: return LOADED["gepp"]
61
+ tok = AutoTokenizer.from_pretrained(MODEL_GEPPETTO)
62
+ mdl = AutoModelForCausalLM.from_pretrained(MODEL_GEPPETTO)
63
+ gen = pipeline("text-generation", model=mdl, tokenizer=tok, device=-1)
64
+ LOADED["gepp"] = gen
65
+ return gen
66
+
67
+ # ================= UTILITY =================
68
+
69
+ def preprocess_markdown(text: str) -> str:
70
+ if not text: return ""
71
+ text = re.sub(r'\|[\s-]+\|', ' ', text) # ripulisce separatori tabella
72
+ text = text.replace('|', ' ')
73
+ text = text.replace('**', '').replace('##', '')
74
+ # mapping semantico leggero
75
+ text = text.replace('P.IVA', 'partita IVA').replace('PIVA', 'partita IVA')
76
+ text = re.sub(r'\s+', ' ', text).strip()
77
+ return text
78
+
79
+ def chunk_text(text: str, max_chars: int = 3000, overlap: int = 200) -> List[str]:
80
+ if len(text) <= max_chars: return [text]
81
+ chunks = []
82
+ i = 0
83
+ while i < len(text):
84
+ end = min(i + max_chars, len(text))
85
+ chunks.append(text[i:end])
86
+ i = end - overlap
87
+ if i < 0: i = 0
88
+ return chunks
89
+
90
+ # ================= LOGICA PRINCIPALE =================
91
+
92
+ def analyze_invoice(md_text: str, custom_question_it: str):
93
+ logs: List[str] = []
94
+ final_output: Dict[str, Any] = {}
95
+
96
+ if len(md_text.strip()) < 10:
97
+ return {"Error": "Testo troppo breve"}, "⚠️ Inserisci almeno 10 caratteri."
98
+
99
+ clean_text = preprocess_markdown(md_text)
100
+ chunks = chunk_text(clean_text, max_chars=3000, overlap=200)
101
+ logs.append(f"📄 Testo originale: {len(md_text)} chars | Pulito: {len(clean_text)} chars | Chunks: {len(chunks)}")
102
+
103
+ qa_deb = get_deberta_pipeline()
104
+ gen_gepp = get_geppetto_pipeline()
105
+
106
+ # 1) DeBERTa: QA estrattivo su tutte le domande + opzionale
107
+ t_start_deb = time.time()
108
+ deb_res: Dict[str, Any] = {}
109
+ success_count = 0
110
+
111
+ def ask_all_chunks(question: str) -> Tuple[str, float]:
112
+ best_answer, best_score = "", 0.0
113
+ for c in chunks:
114
+ try:
115
+ r = qa_deb(question=question, context=c)
116
+ ans = r.get("answer", "").strip()
117
+ score = float(r.get("score", 0.0))
118
+ if score > best_score and ans:
119
+ best_answer, best_score = ans, score
120
+ except Exception as e:
121
+ logs.append(f"❌ Errore QA chunk: {str(e)}")
122
+ return best_answer, best_score
123
+
124
+ for key, question_text in DOMANDE_IT:
125
+ answer, score = ask_all_chunks(question_text)
126
+ status = "Successo" if score > 0.05 and answer else "Non Trovato"
127
+ if status == "Successo": success_count += 1
128
+ deb_res[key] = {
129
+ "domanda": question_text,
130
+ "risposta": answer,
131
+ "confidenza": round(score, 3),
132
+ "status": status
133
  }
 
 
134
 
135
+ custom_q = custom_question_it.strip()
136
+ if custom_q:
137
+ answer, score = ask_all_chunks(custom_q)
138
+ status = "Successo" if score > 0.05 and answer else "Non Trovato"
139
+ if status == "Successo": success_count += 1
140
+ deb_res["domanda_opzionale"] = {
141
+ "domanda": custom_q,
142
+ "risposta": answer,
143
+ "confidenza": round(score, 3),
144
+ "status": status
145
  }
146
+
147
+ t_elapsed_deb = round(time.time() - t_start_deb, 2)
148
+ final_output["DeBERTa (estrattivo)"] = deb_res
149
+ logs.append(f"✅ DeBERTa completato in {t_elapsed_deb}s | Successi: {success_count}/{len(DOMANDE_IT) + (1 if custom_q else 0)}")
150
+
151
+ # 2) GePpeTto: generativo su tutte le domande in blocco
152
+ t_start_gepp = time.time()
153
+ try:
154
+ # Costruzione prompt conciso per ridurre rumore
155
+ prompt_lines = ["Rispondi in elenco puntato alle seguenti domande sulla fattura:"]
156
+ for _, q in DOMANDE_IT:
157
+ prompt_lines.append(f"- {q}")
158
+ if custom_q:
159
+ prompt_lines.append(f"- {custom_q}")
160
+ prompt_lines.append("\nContesto:")
161
+ prompt_lines.append(clean_text[:4000]) # taglio prudenziale su CPU
162
+ prompt_lines.append("\nRisposte (usa un punto per ogni domanda, senza inventare dati):")
163
+
164
+ prompt = "\n".join(prompt_lines)
165
+ gen = gen_gepp(prompt, max_new_tokens=256, do_sample=False)
166
+ generative_text = gen[0]["generated_text"].replace(prompt, "").strip()
167
+ final_output["GePpeTto (generativo)"] = {"risposte": generative_text}
168
+ t_elapsed_gepp = round(time.time() - t_start_gepp, 2)
169
+ logs.append(f"✅ GePpeTto completato in {t_elapsed_gepp}s")
170
  except Exception as e:
171
+ final_output["GePpeTto (generativo)"] = {"errore": str(e)}
172
+ logs.append(f"❌ Errore GePpeTto: {e}")
173
 
174
+ return final_output, "\n".join(logs)
175
 
176
+ # ================== UI GRADIO ==================
177
  with gr.Blocks(theme=gr.themes.Base()) as demo:
178
+ gr.Markdown("# 🧾 Invoice QA: Domande standard + opzionale (DeBERTa estrattivo & GePpeTto generativo)")
179
+ gr.Markdown("Risposte estrattive strutturate per tutte le domande e un blocco generativo riassuntivo, con log e tempi.")
180
 
181
  with gr.Row():
182
  with gr.Column(scale=1):
183
  md_input = gr.Textbox(
184
+ label="Testo Fattura (.MD) / Contesto",
185
+ lines=18,
186
  placeholder="Incolla qui il contenuto Markdown/Testo della fattura..."
187
  )
188
+
189
  custom_q_input = gr.Textbox(
190
+ label="Domanda opzionale (in Italiano)",
191
+ placeholder="Es: Qual è il riferimento d'ordine?"
192
  )
193
 
194
+ btn = gr.Button("🔍 Analizza documento", variant="primary")
195
+
196
  with gr.Column(scale=1):
197
+ out_json = gr.JSON(label="Risultati estrattivi (DeBERTa) e generativi (GePpeTto)")
198
+ with gr.Accordion("📝 Log di Sistema (Tempi e Debug)", open=False):
199
+ out_log = gr.Textbox(label="Process Log", lines=12)
200
 
201
+ btn.click(fn=analyze_invoice, inputs=[md_input, custom_q_input], outputs=[out_json, out_log])
 
 
 
 
202
 
203
  if __name__ == "__main__":
204
  demo.launch()