jpbernardo commited on
Commit
33bdbae
·
verified ·
1 Parent(s): f6f9a51

Upload 4 files

Browse files
Files changed (5) hide show
  1. .gitattributes +1 -0
  2. Dockerfile +20 -0
  3. Regimento.pdf +3 -0
  4. app.py +244 -0
  5. requirements.txt +21 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ Regimento.pdf filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10
2
+
3
+ ENV PYTHONIOENCODING=utf-8
4
+
5
+ # Diretório de trabalho
6
+ WORKDIR /app
7
+
8
+ # Copia requisitos e instala
9
+ COPY requirements.txt .
10
+ RUN pip install --no-cache-dir -r requirements.txt
11
+
12
+ # Copia o app e o PDF
13
+ COPY app.py .
14
+ COPY Regimento.pdf ./Regimento.pdf
15
+
16
+ # Expor porta do Gradio
17
+ EXPOSE 7860
18
+
19
+ # Rodar app automaticamente
20
+ CMD ["python", "app.py"]
Regimento.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b987432f9f88e8de1384c7f27c94f75b02af946bede2d39c0779135f90ea7c8a
3
+ size 1639996
app.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """app.ipynb
3
+
4
+ Automatically generated by Colab.
5
+
6
+ Original file is located at
7
+ https://colab.research.google.com/drive/1qA2X2N5BFz5EHDp4nbroVT1WNAWp8kaw
8
+ """
9
+
10
+ # -*- coding: utf-8 -*-
11
+ import os
12
+ import torch
13
+ import gradio as gr
14
+
15
+ from pypdf import PdfReader
16
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
17
+ from langchain_core.documents import Document
18
+ from langchain_community.vectorstores import Chroma
19
+ from langchain_community.embeddings import HuggingFaceEmbeddings
20
+ from langchain_core.prompts import PromptTemplate
21
+ from transformers import pipeline, AutoModelForCausalLM, AutoTokenizer
22
+
23
+
24
+ CAMINHO_PDF = "Regimento.pdf"
25
+ CAMINHO_DB = "db"
26
+
27
+
28
+ # ==========================
29
+ # 1. FUNÇÕES DE CONSTRUÇÃO DO RAG
30
+ # ==========================
31
+
32
+ def carregar_pdf(caminho):
33
+ reader = PdfReader(caminho)
34
+ textos = []
35
+ for i, pagina in enumerate(reader.pages):
36
+ texto = pagina.extract_text()
37
+ if texto:
38
+ textos.append(Document(page_content=texto, metadata={"page": i + 1}))
39
+ return textos
40
+
41
+
42
+ def dividir_em_chunks(documentos):
43
+ splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
44
+ chunks = splitter.split_documents(documentos)
45
+ print(f"Dividido em {len(chunks)} chunks")
46
+ return chunks
47
+
48
+
49
+ def vetorizar_em_lotes(chunks):
50
+ embeddings = HuggingFaceEmbeddings(
51
+ model_name="intfloat/multilingual-e5-small",
52
+ model_kwargs={"device": "cpu"},
53
+ encode_kwargs={"batch_size": 32}
54
+ )
55
+
56
+ db = Chroma(collection_name="regimento", embedding_function=embeddings, persist_directory=CAMINHO_DB)
57
+ db.add_documents(chunks)
58
+ return db
59
+
60
+
61
+ def criar_db(caminho_pdf):
62
+ documentos = carregar_pdf(caminho_pdf)
63
+ chunks = dividir_em_chunks(documentos)
64
+ db = vetorizar_em_lotes(chunks)
65
+ return db
66
+
67
+
68
+ # ==============================
69
+ # 2. GARANTIR QUE O DB EXISTE
70
+ # ==============================
71
+ if not os.path.exists(CAMINHO_DB):
72
+ print("DB não encontrado. Criando base vetorial...")
73
+ criar_db(CAMINHO_PDF)
74
+ else:
75
+ print("DB encontrado. Usando base existente.")
76
+
77
+
78
+ # ==============================
79
+ # 3. CARREGAR RAG
80
+ # ==============================
81
+
82
+ # Embeddings
83
+ _emb = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-small")
84
+
85
+ # Base vetorial
86
+ _db = Chroma(
87
+ collection_name="regimento",
88
+ persist_directory=CAMINHO_DB,
89
+ embedding_function=_emb
90
+ )
91
+
92
+ # Carregar modelo LLM
93
+ MODEL = "Qwen/Qwen2.5-1.5B-Instruct"
94
+ tok = AutoTokenizer.from_pretrained(MODEL)
95
+
96
+ mdl = AutoModelForCausalLM.from_pretrained(
97
+ MODEL,
98
+ device_map="auto",
99
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
100
+ )
101
+
102
+ generator = pipeline(
103
+ "text-generation",
104
+ model=mdl,
105
+ tokenizer=tok,
106
+ max_new_tokens=300,
107
+ temperature=0.2,
108
+ do_sample=False,
109
+ pad_token_id=tok.eos_token_id,
110
+ return_full_text=False
111
+ )
112
+
113
+
114
+ # Prompt template
115
+ prompt_template = """
116
+ Primeiramente, inicie a resposta com "Oi, querido!".
117
+ Depois responda a pergunta do usuário:
118
+ {pergunta}
119
+ Com base somente nessas informações:
120
+ {base_conhecimento}
121
+ Caso não encontre a resposta, diga: "não sei te dizer isso".
122
+ """
123
+
124
+ _prompt = PromptTemplate(
125
+ template=prompt_template,
126
+ input_variables=["pergunta", "base_conhecimento"]
127
+ )
128
+
129
+
130
+ def _listar_fontes(resultados):
131
+ pags = []
132
+ for doc, score in resultados:
133
+ p = doc.metadata.get("page")
134
+ if p and p not in pags:
135
+ pags.append(p)
136
+ return ", ".join([f"p.{p}" for p in pags])
137
+
138
+
139
+ # ================
140
+ # 4. RAG CHAT
141
+ # ================
142
+ def rag_chat(user_msg, history):
143
+ resultados = _db.similarity_search_with_relevance_scores(user_msg, k=3)
144
+
145
+ if not resultados or resultados[0][1] < 0.7:
146
+ resp = "Oi, querido! Não consegui encontrar algo relevante na base para responder com segurança."
147
+ return history + [(user_msg, resp)]
148
+
149
+ textos = [f"(p.{doc.metadata.get('page')}) {doc.page_content}" for doc, score in resultados]
150
+ base_conhecimento = "\n\n----\n\n".join(textos)
151
+
152
+ mensagem = _prompt.format(pergunta=user_msg, base_conhecimento=base_conhecimento)
153
+
154
+ messages = [
155
+ {"role": "system", "content": "Você é um assistente útil. Responda em PT-BR e fiel ao contexto."},
156
+ {"role": "user", "content": mensagem},
157
+ ]
158
+
159
+ prompt_chat = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
160
+
161
+ out = generator(prompt_chat, return_full_text=False)[0]["generated_text"].strip()
162
+
163
+ fontes = _listar_fontes(resultados)
164
+ if fontes:
165
+ out += f"\n\nFontes: {fontes}"
166
+
167
+ return history + [(user_msg, out)]
168
+
169
+
170
+ # ================
171
+ # 5. FRONT (GRADIO)
172
+ # ================
173
+ with gr.Blocks(
174
+ title="CHAT IEPG",
175
+ css="""
176
+ .gradio-container {max-width: 760px; margin: auto;}
177
+ #title {text-align: center; font-size: 32px; font-weight: 700;}
178
+ #subtitle {text-align: center; margin-bottom: 25px;}
179
+ """
180
+ ) as demo:
181
+
182
+ gr.Markdown("<h1 id='title'>CHAT IEPG</h1>")
183
+ gr.Markdown(
184
+ "<p id='subtitle'>Faça perguntas sobre o Regimento/Documentos.<br>"
185
+ "Chatbot usando RAG (Chroma + E5) + Qwen 2.5 1.5B Instruct.</p>"
186
+ )
187
+
188
+ chat = gr.Chatbot(height=450, bubble_full_width=False)
189
+
190
+ txt = gr.Textbox(
191
+ placeholder="Digite sua pergunta e pressione Enter…",
192
+ label="Pergunta",
193
+ lines=1,
194
+ autofocus=True
195
+ )
196
+
197
+ btn = gr.Button("🚀 Enviar", variant="primary")
198
+ clear = gr.Button("🧹 Limpar", variant="secondary")
199
+
200
+ def _respond(user_msg, history):
201
+ return rag_chat(user_msg, history), gr.update(value="")
202
+
203
+ txt.submit(_respond, [txt, chat], [chat, txt])
204
+ btn.click(_respond, [txt, chat], [chat, txt])
205
+ clear.click(lambda: ([], ""), None, [chat, txt])
206
+
207
+ # --- Uma pequena caixa informativa para debug (opcional)
208
+ gr.Markdown("### Status: App carregado")
209
+
210
+
211
+ # ==============================
212
+ # 6. REGISTRAR ROTA DE API PARA O SPACES (evita "No API found")
213
+ # ==============================
214
+
215
+ # API wrapper que o Hugging Face Spaces vai reconhecer
216
+ @gr.wrap_api
217
+ def api_respond(payload: dict):
218
+ """
219
+ Espera payload no formato:
220
+ { "user_msg": "texto", "history": [] }
221
+ Retorna o mesmo formato que rag_chat espera (history atualizado).
222
+ """
223
+ user_msg = payload.get("user_msg", "") if isinstance(payload, dict) else ""
224
+ history = payload.get("history", []) if isinstance(payload, dict) else []
225
+ # Garante que retornamos em um formato simples serializável
226
+ return rag_chat(user_msg, history)
227
+
228
+
229
+ # Adiciona rota explícita que o HF Spaces detecta como API
230
+ # Rota: POST /ask
231
+ demo.add_api_route("/ask", api_respond)
232
+
233
+
234
+ # ==============================
235
+ # 7. LAUNCH (sem forçar host/port no Spaces)
236
+ # ==============================
237
+ # if __name__ == "__main__":
238
+ # # No ambiente local, pode usar porta do env ou 7860
239
+ # port = int(os.getenv("PORT", 7860))
240
+ # # Use launch padrão (Spaces gerencia host/port), mas deixamos explícito para local
241
+ # demo.launch(server_name="0.0.0.0", server_port=port)
242
+
243
+
244
+ demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", 7860)))
requirements.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- Interface ---
2
+ gradio==4.44.1
3
+
4
+ # --- PDF ---
5
+ pypdf==4.2.0
6
+
7
+ # --- Embeddings e modelos ---
8
+ sentence-transformers==3.0.1
9
+ transformers==4.44.2
10
+ accelerate==0.33.0
11
+ huggingface-hub==0.24.0
12
+ torch==2.2.2
13
+
14
+ # --- LangChain ecossistema estável ---
15
+ langchain==0.2.9
16
+ langchain-core==0.2.20
17
+ langchain-community==0.2.0
18
+ langchain-text-splitters==0.2.2
19
+
20
+ # --- Vector Store antigo (compatível com LC 0.2.x) ---
21
+ chromadb==0.4.24