GaetanoParente commited on
Commit
cfc197c
·
verified ·
1 Parent(s): a6db9bb

Update src/extraction/extractor.py

Browse files
Files changed (1) hide show
  1. src/extraction/extractor.py +72 -46
src/extraction/extractor.py CHANGED
@@ -5,8 +5,10 @@ from typing import List, Optional
5
  from pydantic import BaseModel, Field, ValidationError
6
  from langchain_core.prompts import ChatPromptTemplate
7
  from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
 
 
8
  from langchain_ollama import ChatOllama
9
- from langchain_huggingface import HuggingFaceEmbeddings
10
  from sklearn.metrics.pairwise import cosine_similarity
11
 
12
  # --- 1. DEFINIZIONE DELLO SCHEMA ---
@@ -24,19 +26,40 @@ class KnowledgeGraphExtraction(BaseModel):
24
  # --- 2. ESTRATTORE DINAMICO (Dynamic Few-Shot) ---
25
  class NeuroSymbolicExtractor:
26
  def __init__(self, model_name="llama3", temperature=0, gold_standard_path=None):
27
- print(f"🦙 Inizializzazione Local LLM: {model_name}...")
28
-
29
- # 1. LLM per l'inferenza
30
- self.llm = ChatOllama(
31
- model=model_name,
32
- temperature=temperature,
33
- format="json",
34
- base_url="http://localhost:11434"
35
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  # 2. Modello Embedding per la selezione dinamica
38
  print("🧠 Caricamento modello embedding per Dynamic Selection...")
39
- # Nota: Usiamo lo stesso modello dello splitter per coerenza
40
  self.embedding_model = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
41
 
42
  # 3. Caricamento e Indicizzazione Gold Standard
@@ -47,12 +70,13 @@ class NeuroSymbolicExtractor:
47
  print(f"🌟 Indicizzazione vettoriale Gold Standard da: {gold_standard_path}")
48
  self._index_examples(gold_standard_path)
49
  else:
 
50
  print("⚠️ Nessun Gold Standard trovato. Modalità Zero-Shot.")
51
 
52
- # Template Specializzato per Canusium xCH (CIDOC-CRM + Ontology Layers)
53
  self.system_template_base = """Sei l'Agente Cognitivo (AC) del sistema Canusium xCH.
54
  Il tuo compito è trasformare il testo non strutturato in un Digital Twin Graph (RDF).
55
-
56
  SCHEMA JSON RICHIESTO:
57
  {{
58
  "reasoning": "Spiega brevemente perché hai scelto queste classi/relazioni...",
@@ -60,23 +84,24 @@ class NeuroSymbolicExtractor:
60
  {{"subject": "Entità", "predicate": "prefix:Relazione", "object": "Entità", "confidence": 0.95}}
61
  ]
62
  }}
63
-
64
  ONTOLOGIA DI RIFERIMENTO (Usa questi prefissi):
65
  - xchh: (Heritage) -> Per oggetti fisici, siti, reperti (es. xchh:HeritageObject, xchh:Site).
66
  - crm: (CIDOC-CRM) -> Per relazioni standard (es. crm:P55_has_current_location, crm:P4_has_time-span).
67
  - xche: (Experience) -> Per sessioni AR/VR, visitatori, interazioni (es. xche:ExperienceSession).
68
  - xcha: (Agents) -> Per agenti umani o artificiali.
69
  - skos: -> Per concetti generici o gerarchie.
70
-
71
  ESEMPI CONTESTUALI (Dynamic Few-Shot):
72
  {selected_examples}
73
-
74
  REGOLE DI CONFIDENZA (Trust Layer):
75
  - 1.0 (Fatto Curato): Informazione esplicita e certa nel testo.
76
  - 0.8 - 0.9 (Inferenza): Deduzione logica forte ma non esplicita.
77
  - < 0.7 (Ipotesi): Associazione probabile ma incerta (da marcare per revisione umana).
78
 
79
  Canonicalizza i nomi (es. "Il Parco" -> "Parco Archeologico di Canne").
 
80
  """
81
 
82
  def _index_examples(self, path: str):
@@ -115,20 +140,21 @@ class NeuroSymbolicExtractor:
115
  sim_score = similarities[idx]
116
  formatted_text += f"\n--- ESEMPIO RILEVANTE #{i+1} (Sim: {sim_score:.2f}) ---\n"
117
  formatted_text += f"INPUT: {ex['text']}\n"
118
- formatted_text += f"OUTPUT: {json.dumps({'triples': ex['triples']}, ensure_ascii=False)}\n"
 
 
119
 
120
  return formatted_text
121
 
122
  def extract(self, text_chunk: str, source_id: str = "unknown", max_retries=3) -> KnowledgeGraphExtraction:
123
- print(f"🧠 Processing {source_id} con Llama 3 (Dynamic Mode)...")
124
 
125
- # --- FASE DINAMICA: Selezione Esempi ---
126
  relevant_examples_str = self._get_relevant_examples(text_chunk, k=2)
127
 
128
- # Costruzione Prompt Finale (usando .format per iniettare gli esempi scelti)
129
  final_sys_text = self.system_template_base.format(selected_examples=relevant_examples_str)
130
 
131
- # Creazione del SystemMessage 'raw' per evitare problemi di parsing delle graffe
132
  sys_msg = SystemMessage(content=final_sys_text)
133
 
134
  prompt = ChatPromptTemplate.from_messages([
@@ -141,13 +167,28 @@ class NeuroSymbolicExtractor:
141
  for attempt in range(max_retries):
142
  try:
143
  response = chain.invoke({"text": text_chunk})
144
- data = json.loads(response.content)
 
 
 
 
 
 
 
 
 
 
145
 
146
  # Normalizzazione output
147
  if isinstance(data, list):
148
  validated_data = KnowledgeGraphExtraction(triples=data, reasoning="Direct list output")
149
  else:
150
- validated_data = KnowledgeGraphExtraction(**data)
 
 
 
 
 
151
 
152
  for t in validated_data.triples:
153
  t.source = source_id
@@ -157,35 +198,20 @@ class NeuroSymbolicExtractor:
157
  except (json.JSONDecodeError, ValidationError) as e:
158
  print(f"⚠️ Errore Validazione (Tentativo {attempt+1}/{max_retries}): {e}")
159
 
160
- # SELF-CORRECTION LOOP (Mantenuto dalla tua versione robusta)
 
 
161
  correction_prompt = ChatPromptTemplate.from_messages([
162
  sys_msg,
163
  HumanMessage(content=text_chunk),
164
- AIMessage(content=response.content), # La risposta sbagliata
165
- HumanMessage(content=f"Errore nel JSON precedente: {e}. Correggi e restituisci SOLO JSON valido.")
166
  ])
167
 
168
  chain = correction_prompt | self.llm
169
-
170
  except Exception as e:
171
  print(f"❌ Errore critico: {e}")
172
  break
173
-
174
- return KnowledgeGraphExtraction(triples=[])
175
-
176
- # --- TEST ---
177
- if __name__ == "__main__":
178
- # Testiamo se seleziona l'esempio giusto
179
- chunk_arte = "Il dipinto mostra una tecnica a olio sopraffina."
180
- chunk_storia = "Il senato elesse il nuovo capo di stato nel 1200."
181
-
182
- # Nota: Assicurati che il percorso del file JSON sia corretto
183
- extractor = NeuroSymbolicExtractor(gold_standard_path="data/gold_standard/examples.json")
184
-
185
- print("\n--- TEST SELEZIONE DINAMICA (ARTE) ---")
186
- # Dovrebbe pescare l'esempio della Primavera o Restauro
187
- print(extractor._get_relevant_examples(chunk_arte, k=1))
188
-
189
- print("\n--- TEST SELEZIONE DINAMICA (STORIA/POLITICA) ---")
190
- # Dovrebbe pescare l'esempio del Doge o Colosseo
191
- print(extractor._get_relevant_examples(chunk_storia, k=1))
 
5
  from pydantic import BaseModel, Field, ValidationError
6
  from langchain_core.prompts import ChatPromptTemplate
7
  from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
8
+
9
+ # Gestione Multi-Backend (Locale vs Cloud)
10
  from langchain_ollama import ChatOllama
11
+ from langchain_huggingface import HuggingFaceEmbeddings, ChatHuggingFace, HuggingFaceEndpoint
12
  from sklearn.metrics.pairwise import cosine_similarity
13
 
14
  # --- 1. DEFINIZIONE DELLO SCHEMA ---
 
26
  # --- 2. ESTRATTORE DINAMICO (Dynamic Few-Shot) ---
27
  class NeuroSymbolicExtractor:
28
  def __init__(self, model_name="llama3", temperature=0, gold_standard_path=None):
29
+
30
+ hf_token = os.getenv("HF_TOKEN")
31
+
32
+ if hf_token:
33
+ print("☁️ Rilevato ambiente Cloud (HF Spaces). Utilizzo HuggingFace Inference API.")
34
+ repo_id = "meta-llama/Meta-Llama-3-8B-Instruct"
35
+
36
+ try:
37
+ endpoint = HuggingFaceEndpoint(
38
+ repo_id=repo_id,
39
+ task="text-generation",
40
+ max_new_tokens=1024,
41
+ temperature=0.1,
42
+ huggingfacehub_api_token=hf_token
43
+ )
44
+ self.llm = ChatHuggingFace(llm=endpoint)
45
+ print(f"✅ Connesso a {repo_id} via API.")
46
+ except Exception as e:
47
+ print(f"❌ Errore connessione HF API: {e}. Fallback su CPU locale (sconsigliato).")
48
+ raise e
49
+ else:
50
+ print(f"🏠 Ambiente Locale rilevato. Inizializzazione Ollama: {model_name}...")
51
+ try:
52
+ self.llm = ChatOllama(
53
+ model=model_name,
54
+ temperature=temperature,
55
+ format="json",
56
+ base_url="http://localhost:11434"
57
+ )
58
+ except Exception as e:
59
+ print(f"⚠️ Errore Ollama: {e}")
60
 
61
  # 2. Modello Embedding per la selezione dinamica
62
  print("🧠 Caricamento modello embedding per Dynamic Selection...")
 
63
  self.embedding_model = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
64
 
65
  # 3. Caricamento e Indicizzazione Gold Standard
 
70
  print(f"🌟 Indicizzazione vettoriale Gold Standard da: {gold_standard_path}")
71
  self._index_examples(gold_standard_path)
72
  else:
73
+ # Crea una lista vuota per evitare crash se il path non esiste
74
  print("⚠️ Nessun Gold Standard trovato. Modalità Zero-Shot.")
75
 
76
+ # Template Specializzato (Prompt Engineering)
77
  self.system_template_base = """Sei l'Agente Cognitivo (AC) del sistema Canusium xCH.
78
  Il tuo compito è trasformare il testo non strutturato in un Digital Twin Graph (RDF).
79
+
80
  SCHEMA JSON RICHIESTO:
81
  {{
82
  "reasoning": "Spiega brevemente perché hai scelto queste classi/relazioni...",
 
84
  {{"subject": "Entità", "predicate": "prefix:Relazione", "object": "Entità", "confidence": 0.95}}
85
  ]
86
  }}
87
+
88
  ONTOLOGIA DI RIFERIMENTO (Usa questi prefissi):
89
  - xchh: (Heritage) -> Per oggetti fisici, siti, reperti (es. xchh:HeritageObject, xchh:Site).
90
  - crm: (CIDOC-CRM) -> Per relazioni standard (es. crm:P55_has_current_location, crm:P4_has_time-span).
91
  - xche: (Experience) -> Per sessioni AR/VR, visitatori, interazioni (es. xche:ExperienceSession).
92
  - xcha: (Agents) -> Per agenti umani o artificiali.
93
  - skos: -> Per concetti generici o gerarchie.
94
+
95
  ESEMPI CONTESTUALI (Dynamic Few-Shot):
96
  {selected_examples}
97
+
98
  REGOLE DI CONFIDENZA (Trust Layer):
99
  - 1.0 (Fatto Curato): Informazione esplicita e certa nel testo.
100
  - 0.8 - 0.9 (Inferenza): Deduzione logica forte ma non esplicita.
101
  - < 0.7 (Ipotesi): Associazione probabile ma incerta (da marcare per revisione umana).
102
 
103
  Canonicalizza i nomi (es. "Il Parco" -> "Parco Archeologico di Canne").
104
+ Rispondi ESCLUSIVAMENTE con un JSON valido.
105
  """
106
 
107
  def _index_examples(self, path: str):
 
140
  sim_score = similarities[idx]
141
  formatted_text += f"\n--- ESEMPIO RILEVANTE #{i+1} (Sim: {sim_score:.2f}) ---\n"
142
  formatted_text += f"INPUT: {ex['text']}\n"
143
+ # Gestione sicura nel caso triples manchi
144
+ triples_out = ex.get('triples', [])
145
+ formatted_text += f"OUTPUT: {json.dumps({'triples': triples_out}, ensure_ascii=False)}\n"
146
 
147
  return formatted_text
148
 
149
  def extract(self, text_chunk: str, source_id: str = "unknown", max_retries=3) -> KnowledgeGraphExtraction:
150
+ print(f"🧠 Processing {source_id} (Dynamic Mode)...")
151
 
152
+ # Selezione Esempi
153
  relevant_examples_str = self._get_relevant_examples(text_chunk, k=2)
154
 
155
+ # Costruzione Prompt Finale
156
  final_sys_text = self.system_template_base.format(selected_examples=relevant_examples_str)
157
 
 
158
  sys_msg = SystemMessage(content=final_sys_text)
159
 
160
  prompt = ChatPromptTemplate.from_messages([
 
167
  for attempt in range(max_retries):
168
  try:
169
  response = chain.invoke({"text": text_chunk})
170
+
171
+ # Parsing della risposta (diversa tra Ollama e HF)
172
+ content = response.content
173
+
174
+ # Pulizia base se il modello chiacchiera prima del JSON
175
+ if "```json" in content:
176
+ content = content.split("```json")[1].split("```")[0].strip()
177
+ elif "```" in content:
178
+ content = content.split("```")[1].split("```")[0].strip()
179
+
180
+ data = json.loads(content)
181
 
182
  # Normalizzazione output
183
  if isinstance(data, list):
184
  validated_data = KnowledgeGraphExtraction(triples=data, reasoning="Direct list output")
185
  else:
186
+ # Filtra campi extra che il modello potrebbe inventare
187
+ triples = [GraphTriple(**t) for t in data.get("triples", [])]
188
+ validated_data = KnowledgeGraphExtraction(
189
+ reasoning=data.get("reasoning", "N/A"),
190
+ triples=triples
191
+ )
192
 
193
  for t in validated_data.triples:
194
  t.source = source_id
 
198
  except (json.JSONDecodeError, ValidationError) as e:
199
  print(f"⚠️ Errore Validazione (Tentativo {attempt+1}/{max_retries}): {e}")
200
 
201
+ # SELF-CORRECTION LOOP
202
+ prev_content = locals().get('content', 'No content')
203
+
204
  correction_prompt = ChatPromptTemplate.from_messages([
205
  sys_msg,
206
  HumanMessage(content=text_chunk),
207
+ AIMessage(content=prev_content),
208
+ HumanMessage(content=f"Errore nel JSON precedente: {e}. Correggi e restituisci SOLO JSON valido senza markdown.")
209
  ])
210
 
211
  chain = correction_prompt | self.llm
212
+
213
  except Exception as e:
214
  print(f"❌ Errore critico: {e}")
215
  break
216
+
217
+ return KnowledgeGraphExtraction(triples=[])