DHEIVER commited on
Commit
c88316d
·
verified ·
1 Parent(s): 67f4c27

Create metrology_rag.py

Browse files
Files changed (1) hide show
  1. metrology_rag.py +254 -0
metrology_rag.py ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import google.generativeai as genai
2
+ from sentence_transformers import SentenceTransformer
3
+ import numpy as np
4
+ from typing import List, Dict, Optional
5
+ import faiss
6
+ import pickle
7
+ import os
8
+ from datetime import datetime
9
+ import pdfplumber
10
+ from pathlib import Path
11
+
12
+ # Configuração inicial do Gemini
13
+ genai.configure(api_key="AIzaSyClWplmEF8_sDgmSbhg0h6xkAoFQcLU4p4")
14
+
15
+ class MetrologyGlossary:
16
+ """Glossário interno de termos metrológicos para melhorar recuperação e respostas."""
17
+ def __init__(self):
18
+ self.terms = {
19
+ "incerteza": "Medida da dispersão associada ao resultado de uma medição.",
20
+ "calibração": "Comparação de um instrumento com um padrão de referência.",
21
+ "traceability": "Propriedade de um resultado de medição que pode ser relacionado a um padrão nacional ou internacional.",
22
+ "iso/iec 17025": "Norma internacional para laboratórios de ensaio e calibração.",
23
+ # Adicione mais termos conforme necessário
24
+ }
25
+
26
+ def enhance_query(self, query: str) -> str:
27
+ """Adiciona definições ou sinônimos à consulta para maior precisão."""
28
+ for term, definition in self.terms.items():
29
+ if term.lower() in query.lower():
30
+ query += f" ({definition})"
31
+ return query
32
+
33
+ class DocumentParser:
34
+ """Extrai texto e tabelas de arquivos PDF, com foco em metrologia."""
35
+ def parse_pdf(self, file_path: str) -> Dict:
36
+ """Extrai texto e tabelas de um único PDF."""
37
+ try:
38
+ with pdfplumber.open(file_path) as pdf:
39
+ text = ""
40
+ tables = []
41
+ for page in pdf.pages:
42
+ # Extrai texto
43
+ page_text = page.extract_text() or ""
44
+ text += page_text + "\n"
45
+ # Extrai tabelas
46
+ page_tables = page.extract_tables()
47
+ for table in page_tables:
48
+ tables.append(table)
49
+
50
+ # Converte tabelas em texto estruturado
51
+ table_text = ""
52
+ for idx, table in enumerate(tables):
53
+ table_text += f"Tabela {idx + 1}:\n"
54
+ for row in table:
55
+ table_text += " | ".join([str(cell) or "" for cell in row]) + "\n"
56
+
57
+ return {
58
+ "content": (text + "\n" + table_text).strip(),
59
+ "metadata": {
60
+ "file_name": os.path.basename(file_path),
61
+ "path": file_path,
62
+ "num_pages": len(pdf.pages),
63
+ "has_tables": len(tables) > 0
64
+ }
65
+ }
66
+ except Exception as e:
67
+ print(f"Erro ao processar {file_path}: {str(e)}")
68
+ return {"content": "", "metadata": {}}
69
+
70
+ def parse_multiple_pdfs(self, pdf_paths: List[str]) -> List[Dict]:
71
+ """Extrai texto e tabelas de múltiplos PDFs."""
72
+ documents = []
73
+ for path in pdf_paths:
74
+ doc = self.parse_pdf(path)
75
+ if doc["content"]:
76
+ documents.append(doc)
77
+ return documents
78
+
79
+ class KnowledgeBase:
80
+ """Gerencia a base de conhecimento metrológico."""
81
+ def __init__(self):
82
+ self.documents: List[Dict] = []
83
+
84
+ def add_document(self, content: str, metadata: Optional[Dict] = None):
85
+ doc = {"content": content, "metadata": metadata or {}, "id": len(self.documents)}
86
+ self.documents.append(doc)
87
+
88
+ def add_documents(self, documents: List[Dict]):
89
+ for doc in documents:
90
+ self.add_document(doc["content"], doc["metadata"])
91
+
92
+ def get_document(self, doc_id: int) -> Dict:
93
+ return self.documents[doc_id]
94
+
95
+ def get_all_contents(self) -> List[str]:
96
+ return [doc["content"] for doc in self.documents]
97
+
98
+ class EmbeddingGenerator:
99
+ """Gera embeddings para textos."""
100
+ def __init__(self, model_name: str = "all-MiniLM-L6-v2"):
101
+ self.model = SentenceTransformer(model_name)
102
+
103
+ def generate(self, texts: List[str]) -> np.ndarray:
104
+ return self.model.encode(texts, convert_to_numpy=True)
105
+
106
+ class VectorStore:
107
+ """Armazena e busca embeddings usando FAISS."""
108
+ def __init__(self, dimension: int):
109
+ self.index = faiss.IndexFlatL2(dimension)
110
+ self.doc_ids = []
111
+
112
+ def add_vectors(self, embeddings: np.ndarray, doc_ids: List[int]):
113
+ self.index.add(embeddings)
114
+ self.doc_ids.extend(doc_ids)
115
+
116
+ def search(self, query_embedding: np.ndarray, k: int = 5) -> List[int]:
117
+ distances, indices = self.index.search(query_embedding, k)
118
+ return [self.doc_ids[idx] for idx in indices[0]]
119
+
120
+ class Retriever:
121
+ """Recupera documentos relevantes para uma consulta."""
122
+ def __init__(self, knowledge_base: KnowledgeBase, vector_store: VectorStore, embedding_generator: EmbeddingGenerator):
123
+ self.knowledge_base = knowledge_base
124
+ self.vector_store = vector_store
125
+ self.embedding_generator = embedding_generator
126
+
127
+ def retrieve(self, query: str, k: int = 5) -> List[Dict]:
128
+ query_embedding = self.embedding_generator.generate([query])
129
+ doc_ids = self.vector_store.search(query_embedding, k)
130
+ return [self.knowledge_base.get_document(doc_id) for doc_id in doc_ids]
131
+
132
+ class ResponseGenerator:
133
+ """Gera respostas técnicas para perguntas metrológicas."""
134
+ def __init__(self, model_name: str = "gemini-2.0-flash-thinking-exp-1219"):
135
+ self.model = genai.GenerativeModel(model_name)
136
+
137
+ def generate(self, query: str, retrieved_docs: List[Dict]) -> str:
138
+ context = "\n".join([doc["content"] for doc in retrieved_docs])
139
+ prompt = (
140
+ "Você é um especialista em metrologia, com conhecimento em normas como ISO/IEC 17025, incerteza de medição, "
141
+ "calibração e rastreabilidade. Com base no contexto fornecido, responda à pergunta de forma técnica, precisa e clara:\n\n"
142
+ f"Contexto:\n{context}\n\nPergunta: {query}\n\nResposta:"
143
+ )
144
+ try:
145
+ response = self.model.generate_content(prompt)
146
+ return response.text if response else "Desculpe, não consegui gerar uma resposta."
147
+ except Exception as e:
148
+ return f"Erro ao gerar resposta: {str(e)}"
149
+
150
+ class CacheManager:
151
+ """Gerencia cache de respostas."""
152
+ def __init__(self, cache_file: str = "metrology_cache.pkl"):
153
+ self.cache_file = cache_file
154
+ self.cache = self._load_cache()
155
+
156
+ def _load_cache(self) -> Dict:
157
+ if os.path.exists(self.cache_file):
158
+ with open(self.cache_file, "rb") as f:
159
+ return pickle.load(f)
160
+ return {}
161
+
162
+ def _save_cache(self):
163
+ with open(self.cache_file, "wb") as f:
164
+ pickle.dump(self.cache, f)
165
+
166
+ def get(self, query: str) -> Optional[str]:
167
+ return self.cache.get(query)
168
+
169
+ def set(self, query: str, response: str):
170
+ self.cache[query] = {"response": response, "timestamp": datetime.now()}
171
+ self._save_cache()
172
+
173
+ class QueryProcessor:
174
+ """Pré-processa consultas com foco em metrologia."""
175
+ def __init__(self):
176
+ self.glossary = MetrologyGlossary()
177
+
178
+ def process(self, query: str) -> str:
179
+ query = query.strip().lower()
180
+ return self.glossary.enhance_query(query)
181
+
182
+ class MetrologyRAGPipeline:
183
+ """Orquestra o agente de metrologia avançado."""
184
+ def __init__(self):
185
+ self.knowledge_base = KnowledgeBase()
186
+ self.embedding_generator = EmbeddingGenerator()
187
+ self.vector_store = VectorStore(dimension=384)
188
+ self.retriever = Retriever(self.knowledge_base, self.vector_store, self.embedding_generator)
189
+ self.response_generator = ResponseGenerator()
190
+ self.cache_manager = CacheManager()
191
+ self.query_processor = QueryProcessor()
192
+ self.document_parser = DocumentParser()
193
+
194
+ def load_pdfs(self, pdf_paths: List[str] = None, pdf_folder: Optional[str] = None):
195
+ """Carrega N arquivos PDF de uma lista de caminhos ou pasta."""
196
+ if pdf_paths and pdf_folder:
197
+ raise ValueError("Forneça apenas pdf_paths ou pdf_folder, não ambos.")
198
+
199
+ if pdf_folder:
200
+ pdf_paths = [str(p) for p in Path(pdf_folder).glob("*.pdf")]
201
+
202
+ if not pdf_paths:
203
+ print("Nenhum arquivo PDF fornecido ou encontrado.")
204
+ return
205
+
206
+ print(f"Carregando {len(pdf_paths)} arquivos PDF...")
207
+ documents = self.document_parser.parse_multiple_pdfs(pdf_paths)
208
+ if documents:
209
+ self.knowledge_base.add_documents(documents)
210
+ self._index_documents()
211
+ print(f"{len(documents)} documentos indexados com sucesso.")
212
+ else:
213
+ print("Nenhum documento válido foi extraído dos PDFs.")
214
+
215
+ def _index_documents(self):
216
+ contents = self.knowledge_base.get_all_contents()
217
+ if not contents:
218
+ return
219
+ embeddings = self.embedding_generator.generate(contents)
220
+ doc_ids = list(range(len(contents)))
221
+ self.vector_store.add_vectors(embeddings, doc_ids)
222
+
223
+ def query(self, query: str, k: int = 5) -> str:
224
+ processed_query = self.query_processor.process(query)
225
+ cached_response = self.cache_manager.get(processed_query)
226
+ if cached_response:
227
+ return f"[Resposta do cache] {cached_response}"
228
+
229
+ retrieved_docs = self.retriever.retrieve(processed_query, k)
230
+ response = self.response_generator.generate(processed_query, retrieved_docs)
231
+ self.cache_manager.set(processed_query, response)
232
+ return response
233
+
234
+ # # Exemplo de uso
235
+ if __name__ == "__main__":
236
+ # Inicializa o pipeline
237
+ rag = MetrologyRAGPipeline()
238
+
239
+ # Carrega N arquivos PDF de uma pasta
240
+ pdf_folder = "/content/" # Substitua pelo caminho real
241
+ rag.load_pdfs(pdf_folder=pdf_folder)
242
+
243
+ # Alternativamente, carrega PDFs específicos
244
+ pdf_paths = [
245
+ # "caminho/para/manual_calibrador.pdf",
246
+ # "caminho/para/iso_17025.pdf",
247
+ # Adicione mais caminhos
248
+ ]
249
+ # rag.load_pdfs(pdf_paths=pdf_paths)
250
+
251
+ # Faz uma consulta técnica
252
+ pergunta = "faça uma avaliação sobre o documento CERTIFICADO DE CALIBRAÇÃO N RBC 25/0018"
253
+ resposta = rag.query(pergunta)
254
+ print("Agente de Metrologia:", resposta)