roundb commited on
Commit
da2a921
·
verified ·
1 Parent(s): 6034b74

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +189 -189
app.py CHANGED
@@ -1,190 +1,190 @@
1
- #!/usr/bin/env python3
2
- """
3
- RAG Chatbot – Gradio + FAISS + NVIDIA NIM
4
- Layout com cards automáticos usando examples do ChatInterface
5
- """
6
-
7
- import os
8
- import glob
9
- from typing import List
10
-
11
- import gradio as gr
12
- import pandas as pd
13
- from openai import OpenAI
14
-
15
- from langchain_core.documents import Document
16
- from langchain_community.vectorstores import FAISS
17
- from langchain_huggingface import HuggingFaceEmbeddings
18
- from langchain_text_splitters import RecursiveCharacterTextSplitter
19
-
20
-
21
- # =========================
22
- # CONFIG
23
- # =========================
24
- DATA_DIR = os.getenv("DATA_DIR", "data")
25
-
26
- EMB_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
27
- CHUNK_SIZE = 900
28
- CHUNK_OVERLAP = 150
29
- TOP_K = 6
30
- MAX_CONTEXT_CHARS = 4500
31
-
32
- NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY", "")
33
- NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1"
34
- NVIDIA_MODEL = "meta/llama-3.3-70b-instruct"
35
-
36
- client = OpenAI(base_url=NVIDIA_BASE_URL, api_key=NVIDIA_API_KEY) if NVIDIA_API_KEY else None
37
-
38
- SYSTEM_PROMPT = """Você é um assistente que responde perguntas com base em documentos.
39
- Responda SOMENTE com base no CONTEXTO recuperado.
40
- Se não houver evidência suficiente, diga claramente.
41
- Seja objetivo.
42
- """
43
-
44
-
45
- # =========================
46
- # READ FILES
47
- # =========================
48
- SUPPORTED_EXT = {".pdf", ".docx", ".xlsx", ".xls", ".csv", ".txt"}
49
-
50
- def list_files(data_dir: str) -> List[str]:
51
- files = []
52
- for ext in SUPPORTED_EXT:
53
- files.extend(glob.glob(os.path.join(data_dir, f"**/*{ext}"), recursive=True))
54
- return sorted(set(files))
55
-
56
-
57
- def read_txt(path):
58
- try:
59
- with open(path, "r", encoding="utf-8", errors="ignore") as f:
60
- return f.read()
61
- except:
62
- return ""
63
-
64
-
65
- def read_csv(path):
66
- try:
67
- df = pd.read_csv(path)
68
- return df.head(1000).to_csv(index=False)
69
- except:
70
- return ""
71
-
72
-
73
- def read_docx(path):
74
- from docx import Document as DocxDocument
75
- doc = DocxDocument(path)
76
- return "\n".join([p.text for p in doc.paragraphs if p.text.strip()])
77
-
78
-
79
- def read_pdf(path):
80
- from pypdf import PdfReader
81
- reader = PdfReader(path)
82
- return "\n".join([p.extract_text() or "" for p in reader.pages])
83
-
84
-
85
- # =========================
86
- # BUILD VECTOR DATABASE
87
- # =========================
88
- def build_vectordb():
89
- files = list_files(DATA_DIR)
90
- if not files:
91
- raise FileNotFoundError("Nenhum arquivo encontrado na pasta data/")
92
-
93
- docs = []
94
- splitter = RecursiveCharacterTextSplitter(
95
- chunk_size=CHUNK_SIZE,
96
- chunk_overlap=CHUNK_OVERLAP
97
- )
98
-
99
- for path in files:
100
- ext = os.path.splitext(path)[1].lower()
101
- text = ""
102
-
103
- if ext == ".txt":
104
- text = read_txt(path)
105
- elif ext == ".csv":
106
- text = read_csv(path)
107
- elif ext in [".xlsx", ".xls"]:
108
- text = read_csv(path)
109
- elif ext == ".docx":
110
- text = read_docx(path)
111
- elif ext == ".pdf":
112
- text = read_pdf(path)
113
-
114
- for chunk in splitter.split_text(text):
115
- docs.append(Document(page_content=chunk, metadata={"source": path}))
116
-
117
- embedding = HuggingFaceEmbeddings(model_name=EMB_MODEL)
118
- db = FAISS.from_documents(docs, embedding)
119
- return db
120
-
121
-
122
- vectordb = build_vectordb()
123
-
124
-
125
- # =========================
126
- # SUGGESTIONS (CARDS)
127
- # =========================
128
- SUGGESTIONS = [
129
- "Resuma os principais pontos do documento.",
130
- "Quais procedimentos são descritos?",
131
- "Liste requisitos ou obrigações mencionadas.",
132
- "Explique os termos técnicos utilizados.",
133
- "Há prazos ou datas importantes?",
134
- "Existe checklist operacional?",
135
- "Quais seções são mais relevantes?",
136
- "Há diferenças entre versões?"
137
- ]
138
-
139
-
140
- # =========================
141
- # RAG FUNCTION
142
- # =========================
143
- def format_context(docs):
144
- context = "\n\n".join([d.page_content for d in docs])
145
- if len(context) > MAX_CONTEXT_CHARS:
146
- context = context[:MAX_CONTEXT_CHARS]
147
- return context
148
-
149
-
150
- def chat_rag_nvidia(message, history):
151
- if not client:
152
- return "❌ Configure NVIDIA_API_KEY."
153
-
154
- retrieved = vectordb.similarity_search(message, k=TOP_K)
155
- context = format_context(retrieved)
156
-
157
- messages = [
158
- {"role": "system", "content": SYSTEM_PROMPT},
159
- {"role": "user", "content": f"CONTEXTO:\n{context}\n\nPERGUNTA:\n{message}"}
160
- ]
161
-
162
- completion = client.chat.completions.create(
163
- model=NVIDIA_MODEL,
164
- messages=messages,
165
- temperature=0.3,
166
- max_tokens=800,
167
- )
168
-
169
- return completion.choices[0].message.content
170
-
171
-
172
- # =========================
173
- # UI (USANDO EXAMPLES NATIVOS)
174
- # =========================
175
- with gr.Blocks(title="Document RAG Assistant") as demo:
176
-
177
- gr.Markdown("""
178
- ## 📚 SOGETREL
179
- Faça perguntas sobre os documentos indexados.
180
- """)
181
-
182
- gr.ChatInterface(
183
- fn=chat_rag_nvidia,
184
- examples=SUGGESTIONS, # ← Aqui são gerados os cards automaticamente
185
- title="Assistant",
186
- description="Pergunte algo sobre os documentos."
187
- )
188
-
189
- if __name__ == "__main__":
190
  demo.launch()
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ RAG Chatbot – Gradio + FAISS + NVIDIA NIM
4
+ Layout com cards automáticos usando examples do ChatInterface
5
+ """
6
+
7
+ import os
8
+ import glob
9
+ from typing import List
10
+
11
+ import gradio as gr
12
+ import pandas as pd
13
+ from openai import OpenAI
14
+
15
+ from langchain_core.documents import Document
16
+ from langchain_community.vectorstores import FAISS
17
+ from langchain_huggingface import HuggingFaceEmbeddings
18
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
19
+
20
+ #
21
+ # =========================
22
+ # CONFIG
23
+ # =========================
24
+ DATA_DIR = os.getenv("DATA_DIR", "data")
25
+
26
+ EMB_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
27
+ CHUNK_SIZE = 900
28
+ CHUNK_OVERLAP = 150
29
+ TOP_K = 6
30
+ MAX_CONTEXT_CHARS = 4500
31
+
32
+ NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY", "")
33
+ NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1"
34
+ NVIDIA_MODEL = "meta/llama-3.3-70b-instruct"
35
+
36
+ client = OpenAI(base_url=NVIDIA_BASE_URL, api_key=NVIDIA_API_KEY) if NVIDIA_API_KEY else None
37
+
38
+ SYSTEM_PROMPT = """Você é um assistente que responde perguntas com base em documentos.
39
+ Responda SOMENTE com base no CONTEXTO recuperado.
40
+ Se não houver evidência suficiente, diga claramente.
41
+ Seja objetivo.
42
+ """
43
+
44
+
45
+ # =========================
46
+ # READ FILES
47
+ # =========================
48
+ SUPPORTED_EXT = {".pdf", ".docx", ".xlsx", ".xls", ".csv", ".txt"}
49
+
50
+ def list_files(data_dir: str) -> List[str]:
51
+ files = []
52
+ for ext in SUPPORTED_EXT:
53
+ files.extend(glob.glob(os.path.join(data_dir, f"**/*{ext}"), recursive=True))
54
+ return sorted(set(files))
55
+
56
+
57
+ def read_txt(path):
58
+ try:
59
+ with open(path, "r", encoding="utf-8", errors="ignore") as f:
60
+ return f.read()
61
+ except:
62
+ return ""
63
+
64
+
65
+ def read_csv(path):
66
+ try:
67
+ df = pd.read_csv(path)
68
+ return df.head(1000).to_csv(index=False)
69
+ except:
70
+ return ""
71
+
72
+
73
+ def read_docx(path):
74
+ from docx import Document as DocxDocument
75
+ doc = DocxDocument(path)
76
+ return "\n".join([p.text for p in doc.paragraphs if p.text.strip()])
77
+
78
+
79
+ def read_pdf(path):
80
+ from pypdf import PdfReader
81
+ reader = PdfReader(path)
82
+ return "\n".join([p.extract_text() or "" for p in reader.pages])
83
+
84
+
85
+ # =========================
86
+ # BUILD VECTOR DATABASE
87
+ # =========================
88
+ def build_vectordb():
89
+ files = list_files(DATA_DIR)
90
+ if not files:
91
+ raise FileNotFoundError("Nenhum arquivo encontrado na pasta data/")
92
+
93
+ docs = []
94
+ splitter = RecursiveCharacterTextSplitter(
95
+ chunk_size=CHUNK_SIZE,
96
+ chunk_overlap=CHUNK_OVERLAP
97
+ )
98
+
99
+ for path in files:
100
+ ext = os.path.splitext(path)[1].lower()
101
+ text = ""
102
+
103
+ if ext == ".txt":
104
+ text = read_txt(path)
105
+ elif ext == ".csv":
106
+ text = read_csv(path)
107
+ elif ext in [".xlsx", ".xls"]:
108
+ text = read_csv(path)
109
+ elif ext == ".docx":
110
+ text = read_docx(path)
111
+ elif ext == ".pdf":
112
+ text = read_pdf(path)
113
+
114
+ for chunk in splitter.split_text(text):
115
+ docs.append(Document(page_content=chunk, metadata={"source": path}))
116
+
117
+ embedding = HuggingFaceEmbeddings(model_name=EMB_MODEL)
118
+ db = FAISS.from_documents(docs, embedding)
119
+ return db
120
+
121
+
122
+ vectordb = build_vectordb()
123
+
124
+
125
+ # =========================
126
+ # SUGGESTIONS (CARDS)
127
+ # =========================
128
+ SUGGESTIONS = [
129
+ "Resuma os principais pontos do documento.",
130
+ "Quais procedimentos são descritos?",
131
+ "Liste requisitos ou obrigações mencionadas.",
132
+ "Explique os termos técnicos utilizados.",
133
+ "Há prazos ou datas importantes?",
134
+ "Existe checklist operacional?",
135
+ "Quais seções são mais relevantes?",
136
+ "Há diferenças entre versões?"
137
+ ]
138
+
139
+
140
+ # =========================
141
+ # RAG FUNCTION
142
+ # =========================
143
+ def format_context(docs):
144
+ context = "\n\n".join([d.page_content for d in docs])
145
+ if len(context) > MAX_CONTEXT_CHARS:
146
+ context = context[:MAX_CONTEXT_CHARS]
147
+ return context
148
+
149
+
150
+ def chat_rag_nvidia(message, history):
151
+ if not client:
152
+ return "❌ Configure NVIDIA_API_KEY."
153
+
154
+ retrieved = vectordb.similarity_search(message, k=TOP_K)
155
+ context = format_context(retrieved)
156
+
157
+ messages = [
158
+ {"role": "system", "content": SYSTEM_PROMPT},
159
+ {"role": "user", "content": f"CONTEXTO:\n{context}\n\nPERGUNTA:\n{message}"}
160
+ ]
161
+
162
+ completion = client.chat.completions.create(
163
+ model=NVIDIA_MODEL,
164
+ messages=messages,
165
+ temperature=0.3,
166
+ max_tokens=800,
167
+ )
168
+
169
+ return completion.choices[0].message.content
170
+
171
+
172
+ # =========================
173
+ # UI (USANDO EXAMPLES NATIVOS)
174
+ # =========================
175
+ with gr.Blocks(title="Document RAG Assistant") as demo:
176
+
177
+ gr.Markdown("""
178
+ ## 📚 SOGETREL
179
+ Faça perguntas sobre os documentos indexados.
180
+ """)
181
+
182
+ gr.ChatInterface(
183
+ fn=chat_rag_nvidia,
184
+ examples=SUGGESTIONS, # ← Aqui são gerados os cards automaticamente
185
+ title="Assistant",
186
+ description="Pergunte algo sobre os documentos."
187
+ )
188
+
189
+ if __name__ == "__main__":
190
  demo.launch()