File size: 7,647 Bytes
088f610
 
79466e6
6cecfcc
79466e6
 
 
 
4c60df3
 
 
 
79466e6
 
 
 
 
 
088f610
79466e6
 
 
 
 
 
d54e468
ccdd1cb
79466e6
 
 
088f610
79466e6
 
 
dd4a612
79466e6
 
a35e3d3
79466e6
 
 
 
 
 
 
6cecfcc
ccdd1cb
79466e6
 
 
 
 
 
 
 
 
d54e468
79466e6
 
 
 
 
 
 
 
 
 
 
 
088f610
79466e6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a35e3d3
79466e6
 
 
 
 
 
 
b7b37be
ccdd1cb
79466e6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
088f610
79466e6
 
dc2efd2
6cecfcc
 
 
 
 
 
 
 
 
 
 
 
dc2efd2
 
 
 
 
79466e6
 
d54e468
79466e6
 
 
 
 
 
 
 
 
088f610
 
 
79466e6
 
 
 
088f610
79466e6
 
bf0265c
 
79466e6
 
 
 
 
 
 
 
 
 
 
 
 
dc2efd2
79466e6
 
dc2efd2
79466e6
 
 
 
 
 
 
 
 
 
 
 
 
088f610
79466e6
 
6cecfcc
79466e6
dc2efd2
088f610
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# Autor: Luan Alysson de Souza

# -*- coding: utf-8 -*-
"""app"""

import gradio as gr
import os
from dotenv import load_dotenv
from langchain_community.chat_models import ChatOpenAI
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
import tempfile
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

# Carrega variáveis de ambiente
load_dotenv()
OPENROUTER_API_KEY = os.getenv("ROUTER_API_KEY")

if not OPENROUTER_API_KEY:
    raise ValueError("❌ A variável de ambiente ROUTER_API_KEY não está definida. Verifique o arquivo .env.")

# Embedding robusto
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
qa_chain = None
processed_file = None

# Carrega o PDF fixo automaticamente
def load_default_pdf():
    global qa_chain, processed_file
    try:
        loader = PyPDFLoader("LegisMiner.pdf")
        documents = loader.load()

        text_splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=300)
        docs = text_splitter.split_documents(documents)

        vectorstore = FAISS.from_documents(docs, embeddings)

        llm = ChatOpenAI(
            openai_api_key=OPENROUTER_API_KEY,
            openai_api_base="https://openrouter.ai/api/v1",
            model="mistralai/mistral-7b-instruct:free",
            temperature=0.3
        )

        qa_chain = RetrievalQA.from_chain_type(
            llm=llm,
            retriever=vectorstore.as_retriever(),
            return_source_documents=True
        )

        processed_file = "LegisMiner.pdf"
        print("✅ LegisMiner.pdf carregado automaticamente.")
    except Exception as e:
        print(f"❌ Erro ao carregar LegisMiner.pdf automaticamente: {e}")

def calculate_rag_metrics(query, response, source_docs):
    metrics = {}
    try:
        query_embedding = embeddings.embed_query(query)
        response_embedding = embeddings.embed_query(response)
        metrics["query_response_similarity"] = cosine_similarity(
            [query_embedding], [response_embedding]
        )[0][0]

        # Mantido apenas para fins internos, mas não será exibido
        doc_similarities = []
        for doc in source_docs:
            doc_embedding = embeddings.embed_query(doc.page_content[:1000])
            similarity = cosine_similarity([response_embedding], [doc_embedding])[0][0]
            doc_similarities.append(similarity)

        metrics["avg_response_source_similarity"] = np.mean(doc_similarities) if doc_similarities else 0
        metrics["max_response_source_similarity"] = max(doc_similarities) if doc_similarities else 0
        metrics["num_source_documents"] = len(source_docs)

    except Exception as e:
        metrics["error"] = str(e)

    return metrics

def process_pdf(file):
    global qa_chain, processed_file

    with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
        tmp.write(file)
        pdf_path = tmp.name

    try:
        loader = PyPDFLoader(pdf_path)
        documents = loader.load()

        text_splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=300)
        docs = text_splitter.split_documents(documents)

        vectorstore = FAISS.from_documents(docs, embeddings)

        llm = ChatOpenAI(
            openai_api_key=OPENROUTER_API_KEY,
            openai_api_base="https://openrouter.ai/api/v1",
            model="mistralai/mistral-7b-instruct:free",
            temperature=0.3
        )

        qa_chain = RetrievalQA.from_chain_type(
            llm=llm,
            retriever=vectorstore.as_retriever(),
            return_source_documents=True
        )

        processed_file = os.path.basename(pdf_path)
        return f"✅ PDF processado com sucesso: {processed_file}"

    except Exception as e:
        return f"❌ Erro ao processar PDF: {str(e)}"

def ask_question(question):
    global qa_chain

    if qa_chain is None:
        return "⚠️ Por favor, carregue um PDF primeiro", "", ""

    try:
        system_prompt = (
            "Você é um assistente especialista em mineração, legislação ambiental e políticas públicas. "
            "Seu papel é responder perguntas com base no conteúdo do PDF carregado, que trata do ambiente regulatório da mineração. "
            "Siga estas instruções com rigor:\n\n"
            "1. A resposta deve estar no mesmo idioma em que a pergunta foi feita.\n"
            "2. Utilize apenas as informações contidas no PDF como base.\n"
            "3. Nunca omita dados relevantes encontrados no conteúdo original.\n"
            "4. Mencione, sempre que possível, trechos, leis, datas ou tópicos do PDF usados como base.\n"
            "5. Se a pergunta for técnica, use linguagem técnica. Se for simples, explique de forma acessível.\n"
            "6. Caso a resposta exija algo que não está no PDF, diga claramente: "
            "\"Com base no conteúdo fornecido, não há informação direta sobre este ponto específico.\"\n"
            "7. Seja objetivo, preciso e fiel ao conteúdo carregado.\n\n"
            "Agora, responda a pergunta abaixo com base no PDF:"
        )

        resposta = qa_chain.invoke({
            "query": f"{system_prompt}\n\nPergunta: {question}"
        })

        sources = "\n\n".join(
            [f"📄 Fonte {i+1}:\n{doc.page_content[:500]}..."
             for i, doc in enumerate(resposta['source_documents'])]
        )

        metrics = calculate_rag_metrics(
            question,
            resposta['result'],
            resposta['source_documents']
        )

        # Exibe apenas o nível de confiança
        confidence = metrics.get("query_response_similarity", 0)
        metrics_text = f"🔎 Nível de Confiança da Resposta: {confidence:.2f}"

        return resposta['result'], sources, metrics_text

    except Exception as e:
        return f"❌ Erro ao processar pergunta: {str(e)}", "", ""

# Interface Gradio
with gr.Blocks(title="Chat com PDF usando RAG", theme=gr.themes.Soft()) as demo:
    gr.Markdown("# 🧠 Artificial Intelligence Applied to Regulatory Standard Processing in Mining\n###  Development of a Decision Support Tool")

    with gr.Row():
        with gr.Column(scale=1):
            file_input = gr.File(label="📤 Envie um PDF", type="binary")
            process_btn = gr.Button("Processar PDF", variant="primary")
            status_output = gr.Textbox(label="Status")

        with gr.Column(scale=2):
            question_input = gr.Textbox(label="Faça uma pergunta sobre Normas da Mineração", lines=3)
            ask_btn = gr.Button("Enviar Pergunta", variant="primary")
            answer_output = gr.Textbox(label="✅ Resposta", interactive=False)

        with gr.Accordion("📄 Fontes usadas", open=False):
            sources_output = gr.Textbox(label="Trechos relevantes", lines=10)

        with gr.Accordion("📊 Métricas RAG", open=False):
            metrics_output = gr.Textbox(label="Métricas", lines=4)

    process_btn.click(
        fn=process_pdf,
        inputs=file_input,
        outputs=status_output
    )

    ask_btn.click(
        fn=ask_question,
        inputs=question_input,
        outputs=[answer_output, sources_output, metrics_output]
    )

# Carrega o PDF fixo ao iniciar
load_default_pdf()

# Compartilhamento opcional
share = True if 'COLAB_JUPYTER_TRANSPORT' in os.environ else False
demo.launch(share=share, debug=False)