jpbernardo commited on
Commit
f08a510
·
verified ·
1 Parent(s): d08eaaf

Upload 4 files

Browse files
Files changed (5) hide show
  1. .gitattributes +1 -0
  2. Dockerfile +32 -0
  3. Regimento.pdf +3 -0
  4. app.py +218 -0
  5. requirements.txt +14 -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,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+
4
+ WORKDIR /app
5
+
6
+
7
+ # Evita issues de encoding
8
+ ENV PYTHONIOENCODING=utf-8
9
+
10
+
11
+ # Instala dependências do sistema
12
+ RUN apt-get update && apt-get install -y \
13
+ build-essential \
14
+ && rm -rf /var/lib/apt/lists/*
15
+
16
+
17
+ # Copia requirements
18
+ COPY requirements.txt .
19
+ RUN pip install --no-cache-dir -r requirements.txt
20
+
21
+
22
+ # Copia app e PDF
23
+ COPY app.py .
24
+ COPY Regimento.pdf ./Regimento.pdf
25
+
26
+
27
+ # Porta padrão do Gradio
28
+ EXPOSE 7860
29
+
30
+
31
+ # Comando para iniciar
32
+ 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,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/18vjUd8TiNpmeTVPmcgYEYrMyQlozkkzT
8
+ """
9
+
10
+ import os
11
+ import torch
12
+ import gradio as gr
13
+
14
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
15
+ from langchain.prompts import PromptTemplate
16
+ from langchain_community.embeddings import HuggingFaceEmbeddings
17
+ from langchain_community.vectorstores import Chroma
18
+ from pypdf import PdfReader
19
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
20
+ from langchain_core.documents import Document
21
+
22
+
23
+ CAMINHO_DB = "db"
24
+
25
+
26
+ # =====================================================
27
+ # PREPARAÇÃO DO PDF
28
+ # =====================================================
29
+
30
+ def carregar_pdf(caminho):
31
+ reader = PdfReader(caminho)
32
+ textos = []
33
+ for i, pagina in enumerate(reader.pages):
34
+ texto = pagina.extract_text()
35
+ if texto:
36
+ textos.append(
37
+ Document(page_content=texto, metadata={"page": i + 1})
38
+ )
39
+ return textos
40
+
41
+
42
+ def dividir_em_chunks(documentos):
43
+ splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
44
+ return splitter.split_documents(documentos)
45
+
46
+
47
+ def vetorizar_chunks(chunks):
48
+ embeddings = HuggingFaceEmbeddings(
49
+ model_name="intfloat/multilingual-e5-small",
50
+ model_kwargs={"device": "cpu"},
51
+ encode_kwargs={"batch_size": 32},
52
+ )
53
+
54
+ db = Chroma(
55
+ embedding_function=embeddings,
56
+ persist_directory=CAMINHO_DB,
57
+ )
58
+
59
+ db.add_documents(chunks)
60
+ db.persist()
61
+ return db
62
+
63
+
64
+ def criar_db(caminho_pdf="Regimento.pdf"):
65
+ documentos = carregar_pdf(caminho_pdf)
66
+ chunks = dividir_em_chunks(documentos)
67
+ return vetorizar_chunks(chunks)
68
+
69
+
70
+ # =====================================================
71
+ # CRIAR DB AUTOMATICAMENTE SE NÃO EXISTIR
72
+ # =====================================================
73
+
74
+ if not os.path.exists(CAMINHO_DB):
75
+ print("🔧 Criando base vetorial...")
76
+ criar_db()
77
+ else:
78
+ print("📚 Base vetorial encontrada. Carregando...")
79
+
80
+
81
+ # =====================================================
82
+ # PROMPT TEMPLATE
83
+ # =====================================================
84
+
85
+ prompt_template = """
86
+ Primeiramente, inicie a resposta sempre com "Oi, querido!"
87
+
88
+ E depois responda a pergunta:
89
+ {pergunta}
90
+
91
+ Com base nessas informações:
92
+ {base_conhecimento}
93
+
94
+ Se não houver resposta na base, diga apenas: não sei te dizer isso.
95
+ """
96
+
97
+ _prompt = PromptTemplate(
98
+ template=prompt_template,
99
+ input_variables=["pergunta", "base_conhecimento"],
100
+ )
101
+
102
+
103
+ # =====================================================
104
+ # EMBEDDINGS + CHROMA
105
+ # =====================================================
106
+
107
+ emb = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-small")
108
+ db = Chroma(persist_directory=CAMINHO_DB, embedding_function=emb)
109
+
110
+
111
+ # =====================================================
112
+ # CARREGAR MODELO LLM
113
+ # =====================================================
114
+
115
+ def carregar_modelo():
116
+ MODEL = "Qwen/Qwen2.5-1.5B-Instruct"
117
+
118
+ tok = AutoTokenizer.from_pretrained(MODEL)
119
+
120
+ mdl = AutoModelForCausalLM.from_pretrained(
121
+ MODEL,
122
+ device_map="auto",
123
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
124
+ )
125
+
126
+ gen = pipeline(
127
+ "text-generation",
128
+ model=mdl,
129
+ tokenizer=tok,
130
+ max_new_tokens=1000,
131
+ temperature=0.2,
132
+ do_sample=False,
133
+ pad_token_id=tok.eos_token_id,
134
+ return_full_text=False,
135
+ )
136
+
137
+ return tok, gen
138
+
139
+
140
+ tok, generator = carregar_modelo()
141
+
142
+
143
+ # =====================================================
144
+ # FUNÇÕES DO RAG
145
+ # =====================================================
146
+
147
+ def listar_fontes(resultados):
148
+ pags = []
149
+ for doc, score in resultados:
150
+ p = doc.metadata.get("page")
151
+ if p and p not in pags:
152
+ pags.append(p)
153
+ return ", ".join([f"p.{p}" for p in pags])
154
+
155
+
156
+ def rag_chat(user_msg, history):
157
+ resultados = db.similarity_search_with_relevance_scores(user_msg, k=3)
158
+
159
+ if not resultados or resultados[0][1] < 0.7:
160
+ resp = "Oi, querido! Não consegui encontrar algo relevante na base para responder com segurança."
161
+ return history + [(user_msg, resp)]
162
+
163
+ textos = [
164
+ f"(p.{doc.metadata.get('page')}) {doc.page_content}"
165
+ for doc, score in resultados
166
+ ]
167
+
168
+ base_conhecimento = "\n\n----\n\n".join(textos)
169
+
170
+ mensagem = _prompt.format(
171
+ pergunta=user_msg,
172
+ base_conhecimento=base_conhecimento,
173
+ )
174
+
175
+ messages = [
176
+ {"role": "system", "content": "Você responde em PT-BR de forma objetiva e educada."},
177
+ {"role": "user", "content": mensagem},
178
+ ]
179
+
180
+ prompt_chat = tok.apply_chat_template(
181
+ messages, tokenize=False, add_generation_prompt=True
182
+ )
183
+
184
+ out = generator(prompt_chat, return_full_text=False)[0]["generated_text"].strip()
185
+
186
+ fontes = listar_fontes(resultados)
187
+ if fontes:
188
+ out += f"\n\nFontes: {fontes}"
189
+
190
+ return history + [(user_msg, out)]
191
+
192
+
193
+ # =====================================================
194
+ # INTERFACE GRADIO
195
+ # =====================================================
196
+
197
+ with gr.Blocks(title="CHAT IEPG") as demo:
198
+
199
+ gr.Markdown("<h1 style='text-align:center;'>CHAT IEPG</h1>")
200
+ gr.Markdown(
201
+ "<p style='text-align:center;'>Faça perguntas sobre o Regimento. "
202
+ "O chatbot usa RAG (Chroma + E5) com o modelo Qwen.</p>"
203
+ )
204
+
205
+ chat = gr.Chatbot(height=450)
206
+ txt = gr.Textbox(label="Pergunta", placeholder="Digite sua pergunta...")
207
+ btn = gr.Button("Enviar")
208
+ clear = gr.Button("Limpar")
209
+
210
+ def responder(user_msg, history):
211
+ return rag_chat(user_msg, history), ""
212
+
213
+ txt.submit(responder, [txt, chat], [chat, txt])
214
+ btn.click(responder, [txt, chat], [chat, txt])
215
+ clear.click(lambda: ([], ""), None, [chat, txt])
216
+
217
+ if __name__ == "__main__":
218
+ demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", 7860)))
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ pypdf
2
+ sentence-transformers>=3.0.1
3
+ langchain-community>=0.2.0
4
+ chromadb
5
+ langchain-text-splitters
6
+ langchain-core
7
+ transformers
8
+ accelerate
9
+ huggingface_hub
10
+ torch
11
+ gradio
12
+ langchain
13
+ streamlit
14
+ streamlit-chat