Geoeasy commited on
Commit
94f5502
·
verified ·
1 Parent(s): 15ca720

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +273 -273
app.py CHANGED
@@ -1,273 +1,273 @@
1
- import os
2
- import io
3
- import re
4
- import time
5
- from pathlib import Path
6
- from typing import List, Tuple
7
-
8
- import numpy as np
9
- import faiss
10
- import gradio as gr
11
-
12
- # Para leitura do PDF
13
- try:
14
- from pypdf import PdfReader # pypdf é leve e confiável para extração de texto
15
- except Exception:
16
- # fallback simples se pypdf não estiver disponível
17
- PdfReader = None
18
-
19
- # Embeddings e LLM (NVIDIA API estilo OpenAI)
20
- from sentence_transformers import SentenceTransformer
21
- from openai import OpenAI, OpenAIError
22
-
23
- """
24
- ===============================================================================
25
- DFSORT RAG – Assistente em Português (Gradio)
26
- -------------------------------------------------------------------------------
27
- • Objetivo: responder sobre DFSORT (IBM z/OS) usando **apenas** o PDF fornecido como
28
- base de conhecimento (RAG — Retrieval Augmented Generation).
29
- • Tudo em português: interface, comentários e mensagens do sistema.
30
- • Sem conteúdos de CV ou outros temas. Foco total em DFSORT.
31
- • O app cria o índice (FAISS + embeddings MiniLM) automaticamente na primeira execução.
32
-
33
- Como usar
34
- 1) Garanta que o PDF esteja disponível. Por padrão este script usa:
35
- - PDF_PATH = "ice2ca11.pdf" (você pode alterar o caminho abaixo)
36
- 2) Execute o script. Na primeira execução, ele extrai o texto do PDF e cria:
37
- - r_docs.index (FAISS)
38
- - r_chunks.npy (lista de trechos do PDF)
39
- 3) Interaja no chat. O modelo responde **somente** com base nos trechos recuperados.
40
-
41
- Requisitos (pip):
42
- pip install gradio pypdf faiss-cpu sentence-transformers openai
43
-
44
- ==========================================================================
45
- ATENÇÃO SOBRE KEYS
46
- - Configure a variável de ambiente NV_API_KEY com a sua chave da NVIDIA
47
- (API OpenAI-compatible em https://integrate.api.nvidia.com/v1).
48
- ==========================================================================
49
- """
50
-
51
- # ===================== Configurações =====================
52
- APP_TITLE = "DFSORT RAG (PDF)"
53
- PDF_PATH = "ice2ca11.pdf" # use o PDF fornecido; altere se necessário
54
- INDEX_FILE = "r_docs.index"
55
- CHUNKS_FILE = "r_chunks.npy"
56
-
57
- # Modelo de chat na NVIDIA (pode trocar por outro suportado)
58
- CHAT_MODEL = "meta/llama3-8b-instruct"
59
- NV_API_KEY = os.environ.get("NV_API_KEY")
60
- if not NV_API_KEY:
61
- raise RuntimeError("🔒 NV_API_KEY não definido. Configure em Settings → Variables & Secrets.")
62
-
63
- client = OpenAI(base_url="https://integrate.api.nvidia.com/v1", api_key=NV_API_KEY)
64
-
65
- # Modelo de embeddings (baixa no primeiro uso)
66
- EMB_MODEL_NAME = "all-MiniLM-L6-v2"
67
- embedding_model = SentenceTransformer(EMB_MODEL_NAME)
68
-
69
- # ===================== Pipeline de Indexação =====================
70
-
71
- def _pdf_to_text_chunks(pdf_path: str, max_chunk_chars: int = 1200) -> List[str]:
72
- """Lê o PDF e cria chunks de texto amigáveis ao RAG.
73
- - Divide por páginas e quebras duplas de linha.
74
- - Faz um 'merge' simples até atingir ~max_chunk_chars.
75
- - Remove linhas vazias e normaliza espaços.
76
- """
77
- path = Path(pdf_path)
78
- if not path.exists():
79
- raise FileNotFoundError(f"PDF não encontrado: {pdf_path}")
80
-
81
- raw_pages: List[str] = []
82
- if PdfReader is None:
83
- # fallback: ler bytes e tentar split muito simples (não ideal)
84
- with open(path, "rb") as f:
85
- data = f.read()
86
- text = data.decode(errors="ignore")
87
- raw_pages = re.split(r"\f|\n\s*\n", text)
88
- else:
89
- reader = PdfReader(str(path))
90
- for pg in reader.pages:
91
- try:
92
- raw = pg.extract_text() or ""
93
- except Exception:
94
- raw = ""
95
- raw_pages.append(raw)
96
-
97
- # limpeza e chunking
98
- blocks: List[str] = []
99
- for page_txt in raw_pages:
100
- if not page_txt:
101
- continue
102
- # normalizações leves
103
- t = re.sub(r"[ \t]+", " ", page_txt)
104
- t = re.sub(r"\n{2,}", "\n\n", t).strip()
105
- # quebra por parágrafos duplos ou linhas
106
- parts = re.split(r"\n\n+|\n• |\n- ", t)
107
- blocks.extend(p.strip() for p in parts if p and p.strip())
108
-
109
- # juntar em chunks de tamanho alvo
110
- chunks: List[str] = []
111
- buf = []
112
- size = 0
113
- for b in blocks:
114
- if size + len(b) + 1 > max_chunk_chars:
115
- if buf:
116
- chunks.append("\n".join(buf))
117
- buf = [b]
118
- size = len(b)
119
- else:
120
- buf.append(b)
121
- size += len(b) + 1
122
- if buf:
123
- chunks.append("\n".join(buf))
124
-
125
- # reforço: remover pedaços muito curtos
126
- chunks = [c.strip() for c in chunks if len(c.strip()) > 50]
127
- return chunks
128
-
129
-
130
- def build_or_load_index(pdf_path: str, index_path: str, chunks_path: str) -> Tuple[faiss.IndexFlatIP, np.ndarray]:
131
- """Cria ou carrega o índice FAISS e os chunks."""
132
- if Path(index_path).exists() and Path(chunks_path).exists():
133
- index = faiss.read_index(index_path)
134
- chunks = np.load(chunks_path, allow_pickle=True)
135
- return index, chunks
136
-
137
- # construir
138
- chunks_list = _pdf_to_text_chunks(pdf_path)
139
- emb = embedding_model.encode(chunks_list, convert_to_numpy=True, normalize_embeddings=True)
140
- d = emb.shape[1]
141
- index = faiss.IndexFlatIP(d)
142
- index.add(emb)
143
- faiss.write_index(index, index_path)
144
- np.save(chunks_path, np.array(chunks_list, dtype=object))
145
- return index, np.array(chunks_list, dtype=object)
146
-
147
-
148
- # ===================== Recuperação + Chat =====================
149
-
150
- def retrieve_context(query: str, index: faiss.IndexFlatIP, chunks: np.ndarray, k: int = 6) -> str:
151
- q = embedding_model.encode([query], convert_to_numpy=True, normalize_embeddings=True)
152
- scores, idxs = index.search(q, k)
153
- parts = []
154
- for i in idxs[0]:
155
- if 0 <= i < len(chunks):
156
- parts.append(str(chunks[i]))
157
- return "\n---\n".join(parts)
158
-
159
-
160
- def nv_stream(messages, temperature: float, top_p: float, max_tokens: int):
161
- """Streaming de resposta do modelo NVIDIA (compatível com OpenAI)."""
162
- assistant_reply = ""
163
- stream = client.chat.completions.create(
164
- model=CHAT_MODEL,
165
- messages=messages,
166
- temperature=temperature,
167
- top_p=top_p,
168
- max_tokens=max_tokens,
169
- stream=True,
170
- )
171
- for chunk in stream:
172
- delta = chunk.choices[0].delta
173
- if hasattr(delta, "content") and delta.content:
174
- assistant_reply += delta.content
175
- yield assistant_reply
176
-
177
-
178
- def make_system_prompt(ctx: str) -> str:
179
- return (
180
- "Você é um assistente especializado em DFSORT (IBM z/OS).\n"
181
- "Responda **apenas** com base no contexto recuperado do PDF.\n"
182
- "Se a informação não estiver no contexto, diga que não sabe.\n\n"
183
- f"=== Contexto (trechos do PDF) ===\n{ctx}\n\n"
184
- "Ao mostrar exemplos, prefira JCL/SYSIN claros e curtos."
185
- )
186
-
187
-
188
- # ===================== UI (Gradio) =====================
189
-
190
- def chatbot_ui(user_input: str, temperature: float, top_p: float, max_tokens: int, k: int):
191
- if not user_input or not user_input.strip():
192
- return ""
193
- # garanta índice carregado
194
- global faiss_index, pdf_chunks
195
- if faiss_index is None or pdf_chunks is None:
196
- faiss_index, pdf_chunks = build_or_load_index(PDF_PATH, INDEX_FILE, CHUNKS_FILE)
197
-
198
- ctx = retrieve_context(user_input, faiss_index, pdf_chunks, k=k)
199
- sys_msg = {"role": "system", "content": make_system_prompt(ctx)}
200
- usr_msg = {"role": "user", "content": user_input}
201
-
202
- # streaming para UX fluida
203
- try:
204
- out = ""
205
- for partial in nv_stream([sys_msg, usr_msg], temperature, top_p, max_tokens):
206
- out = partial
207
- return out
208
- except OpenAIError as e:
209
- return f"⚠️ Erro da API: {e.__class__.__name__}: {e}"
210
-
211
-
212
- def rebuild_index_action():
213
- global faiss_index, pdf_chunks
214
- faiss_index, pdf_chunks = build_or_load_index(PDF_PATH, INDEX_FILE, CHUNKS_FILE)
215
- return "✅ Índice reconstruído com sucesso a partir do PDF."
216
-
217
-
218
- # Estado global carregado sob demanda
219
- faiss_index = None
220
- pdf_chunks = None
221
-
222
-
223
- custom_css = r"""
224
- :root { --primary:#2156d9; --bg:#f8fafc; --ink:#0f172a; }
225
- body { background: var(--bg); color: var(--ink); }
226
- #chatbox { height: 60vh; overflow-y: auto; }
227
- """
228
-
229
- with gr.Blocks(title=APP_TITLE, css=custom_css, theme=gr.themes.Base()) as demo:
230
- gr.Markdown(f"## {APP_TITLE}")
231
- gr.Markdown(
232
- "Este assistente responde sobre **DFSORT** usando apenas o PDF como fonte. "
233
- "Se algo não estiver no PDF, ele informa que não sabe."
234
- )
235
-
236
- with gr.Row():
237
- with gr.Column(scale=3):
238
- chat = gr.ChatInterface(
239
- fn=lambda msg, hist, t, p, mt, k: chatbot_ui(msg, t, p, mt, k),
240
- additional_inputs=[
241
- gr.Slider(0, 1, 0.4, label="Temperature"),
242
- gr.Slider(0, 1, 0.95, label="Top-p"),
243
- gr.Slider(128, 4096, 768, step=64, label="Max Tokens"),
244
- gr.Slider(2, 12, 6, step=1, label="Trechos (k)")
245
- ],
246
- multimodal=False,
247
- title="Chat DFSORT (RAG)",
248
- textbox=gr.Textbox(placeholder="Pergunte algo sobre DFSORT… ex.: Como uso INCLUDE COND?"),
249
- cache_examples=False,
250
- )
251
- with gr.Column(scale=2):
252
- gr.Markdown("### Controlo do índice")
253
- gr.Markdown(f"PDF atual: `{PDF_PATH}`")
254
- btn_rebuild = gr.Button("Reconstruir índice a partir do PDF")
255
- msg = gr.Markdown()
256
- btn_rebuild.click(lambda: rebuild_index_action(), [], [msg])
257
-
258
- gr.Markdown("---")
259
- gr.Markdown("### Dicas de consulta (direto do PDF)")
260
- gr.Markdown(
261
- "- Ex.: `Ordenar por 10 bytes a partir da posição 1 (CH, A).`\n"
262
- "- Ex.: `Como faço para eliminar duplicados com SUM FIELDS=NONE?`\n"
263
- "- Ex.: `JOINKEYS: explique o uso de REFORMAT.`\n"
264
- "- Ex.: `Exemplo de OUTFIL com cabeçalho e REMOVECC.`"
265
- )
266
-
267
- if __name__ == "__main__":
268
- # cria índice na primeira execução
269
- if not Path(INDEX_FILE).exists() or not Path(CHUNKS_FILE).exists():
270
- print("[i] Construindo índice a partir do PDF…")
271
- faiss_index, pdf_chunks = build_or_load_index(PDF_PATH, INDEX_FILE, CHUNKS_FILE)
272
- print("[i] Índice criado.")
273
- demo.launch(server_name="0.0.0.0", server_port=7861)
 
1
+ import os
2
+ import io
3
+ import re
4
+ import time
5
+ from pathlib import Path
6
+ from typing import List, Tuple
7
+
8
+ import numpy as np
9
+ import faiss
10
+ import gradio as gr
11
+
12
+ # Para leitura do PDF
13
+ try:
14
+ from pypdf import PdfReader # pypdf é leve e confiável para extração de texto
15
+ except Exception:
16
+ # fallback simples se pypdf não estiver disponível
17
+ PdfReader = None
18
+
19
+ # Embeddings e LLM (NVIDIA API estilo OpenAI)
20
+ from sentence_transformers import SentenceTransformer
21
+ from openai import OpenAI, OpenAIError
22
+
23
+ """
24
+ ===============================================================================
25
+ DFSORT RAG – Assistente em Português (Gradio)
26
+ -------------------------------------------------------------------------------
27
+ • Objetivo: responder sobre DFSORT (IBM z/OS) usando **apenas** o PDF fornecido como
28
+ base de conhecimento (RAG — Retrieval Augmented Generation).
29
+ • Tudo em português: interface, comentários e mensagens do sistema.
30
+ • Sem conteúdos de CV ou outros temas. Foco total em DFSORT.
31
+ • O app cria o índice (FAISS + embeddings MiniLM) automaticamente na primeira execução.
32
+
33
+ Como usar
34
+ 1) Garanta que o PDF esteja disponível. Por padrão este script usa:
35
+ - PDF_PATH = "ice2ca11.pdf" (você pode alterar o caminho abaixo)
36
+ 2) Execute o script. Na primeira execução, ele extrai o texto do PDF e cria:
37
+ - r_docs.index (FAISS)
38
+ - r_chunks.npy (lista de trechos do PDF)
39
+ 3) Interaja no chat. O modelo responde **somente** com base nos trechos recuperados.
40
+
41
+ Requisitos (pip):
42
+ pip install gradio pypdf faiss-cpu sentence-transformers openai
43
+
44
+ ==========================================================================
45
+ ATENÇÃO SOBRE KEYS
46
+ - Configure a variável de ambiente NV_API_KEY com a sua chave da NVIDIA
47
+ (API OpenAI-compatible em https://integrate.api.nvidia.com/v1).
48
+ ==========================================================================
49
+ """
50
+
51
+ # ===================== Configurações =====================
52
+ APP_TITLE = "DFSORT RAG (PDF)"
53
+ PDF_PATH = "ice2ca11.pdf" # use o PDF fornecido; altere se necessário
54
+ INDEX_FILE = "r_docs.index"
55
+ CHUNKS_FILE = "r_chunks.npy"
56
+
57
+ # Modelo de chat na NVIDIA (pode trocar por outro suportado)
58
+ CHAT_MODEL = "meta/llama3-8b-instruct"
59
+ NV_API_KEY = os.environ.get("NV_API_KEY")
60
+ if not NV_API_KEY:
61
+ raise RuntimeError("🔒 NV_API_KEY não definido. Configure em Settings → Variables & Secrets.")
62
+
63
+ client = OpenAI(base_url="https://integrate.api.nvidia.com/v1", api_key=NV_API_KEY)
64
+
65
+ # Modelo de embeddings (baixa no primeiro uso)
66
+ EMB_MODEL_NAME = "all-MiniLM-L6-v2"
67
+ embedding_model = SentenceTransformer(EMB_MODEL_NAME)
68
+
69
+ # ===================== Pipeline de Indexação =====================
70
+
71
+ def _pdf_to_text_chunks(pdf_path: str, max_chunk_chars: int = 1200) -> List[str]:
72
+ """Lê o PDF e cria chunks de texto amigáveis ao RAG.
73
+ - Divide por páginas e quebras duplas de linha.
74
+ - Faz um 'merge' simples até atingir ~max_chunk_chars.
75
+ - Remove linhas vazias e normaliza espaços.
76
+ """
77
+ path = Path(pdf_path)
78
+ if not path.exists():
79
+ raise FileNotFoundError(f"PDF não encontrado: {pdf_path}")
80
+
81
+ raw_pages: List[str] = []
82
+ if PdfReader is None:
83
+ # fallback: ler bytes e tentar split muito simples (não ideal)
84
+ with open(path, "rb") as f:
85
+ data = f.read()
86
+ text = data.decode(errors="ignore")
87
+ raw_pages = re.split(r"\f|\n\s*\n", text)
88
+ else:
89
+ reader = PdfReader(str(path))
90
+ for pg in reader.pages:
91
+ try:
92
+ raw = pg.extract_text() or ""
93
+ except Exception:
94
+ raw = ""
95
+ raw_pages.append(raw)
96
+
97
+ # limpeza e chunking
98
+ blocks: List[str] = []
99
+ for page_txt in raw_pages:
100
+ if not page_txt:
101
+ continue
102
+ # normalizações leves
103
+ t = re.sub(r"[ \t]+", " ", page_txt)
104
+ t = re.sub(r"\n{2,}", "\n\n", t).strip()
105
+ # quebra por parágrafos duplos ou linhas
106
+ parts = re.split(r"\n\n+|\n• |\n- ", t)
107
+ blocks.extend(p.strip() for p in parts if p and p.strip())
108
+
109
+ # juntar em chunks de tamanho alvo
110
+ chunks: List[str] = []
111
+ buf = []
112
+ size = 0
113
+ for b in blocks:
114
+ if size + len(b) + 1 > max_chunk_chars:
115
+ if buf:
116
+ chunks.append("\n".join(buf))
117
+ buf = [b]
118
+ size = len(b)
119
+ else:
120
+ buf.append(b)
121
+ size += len(b) + 1
122
+ if buf:
123
+ chunks.append("\n".join(buf))
124
+
125
+ # reforço: remover pedaços muito curtos
126
+ chunks = [c.strip() for c in chunks if len(c.strip()) > 50]
127
+ return chunks
128
+
129
+
130
+ def build_or_load_index(pdf_path: str, index_path: str, chunks_path: str) -> Tuple[faiss.IndexFlatIP, np.ndarray]:
131
+ """Cria ou carrega o índice FAISS e os chunks."""
132
+ if Path(index_path).exists() and Path(chunks_path).exists():
133
+ index = faiss.read_index(index_path)
134
+ chunks = np.load(chunks_path, allow_pickle=True)
135
+ return index, chunks
136
+
137
+ # construir
138
+ chunks_list = _pdf_to_text_chunks(pdf_path)
139
+ emb = embedding_model.encode(chunks_list, convert_to_numpy=True, normalize_embeddings=True)
140
+ d = emb.shape[1]
141
+ index = faiss.IndexFlatIP(d)
142
+ index.add(emb)
143
+ faiss.write_index(index, index_path)
144
+ np.save(chunks_path, np.array(chunks_list, dtype=object))
145
+ return index, np.array(chunks_list, dtype=object)
146
+
147
+
148
+ # ===================== Recuperação + Chat =====================
149
+
150
+ def retrieve_context(query: str, index: faiss.IndexFlatIP, chunks: np.ndarray, k: int = 6) -> str:
151
+ q = embedding_model.encode([query], convert_to_numpy=True, normalize_embeddings=True)
152
+ scores, idxs = index.search(q, k)
153
+ parts = []
154
+ for i in idxs[0]:
155
+ if 0 <= i < len(chunks):
156
+ parts.append(str(chunks[i]))
157
+ return "\n---\n".join(parts)
158
+
159
+
160
+ def nv_stream(messages, temperature: float, top_p: float, max_tokens: int):
161
+ """Streaming de resposta do modelo NVIDIA (compatível com OpenAI)."""
162
+ assistant_reply = ""
163
+ stream = client.chat.completions.create(
164
+ model=CHAT_MODEL,
165
+ messages=messages,
166
+ temperature=temperature,
167
+ top_p=top_p,
168
+ max_tokens=max_tokens,
169
+ stream=True,
170
+ )
171
+ for chunk in stream:
172
+ delta = chunk.choices[0].delta
173
+ if hasattr(delta, "content") and delta.content:
174
+ assistant_reply += delta.content
175
+ yield assistant_reply
176
+
177
+
178
+ def make_system_prompt(ctx: str) -> str:
179
+ return (
180
+ "Você é um assistente especializado em DFSORT (IBM z/OS).\n"
181
+ "Responda **apenas** com base no contexto recuperado do PDF.\n"
182
+ "Se a informação não estiver no contexto, diga que não sabe.\n\n"
183
+ f"=== Contexto (trechos do PDF) ===\n{ctx}\n\n"
184
+ "Ao mostrar exemplos, prefira JCL/SYSIN claros e curtos."
185
+ )
186
+
187
+
188
+ # ===================== UI (Gradio) =====================
189
+
190
+ def chatbot_ui(user_input: str, temperature: float, top_p: float, max_tokens: int, k: int):
191
+ if not user_input or not user_input.strip():
192
+ return ""
193
+ # garanta índice carregado
194
+ global faiss_index, pdf_chunks
195
+ if faiss_index is None or pdf_chunks is None:
196
+ faiss_index, pdf_chunks = build_or_load_index(PDF_PATH, INDEX_FILE, CHUNKS_FILE)
197
+
198
+ ctx = retrieve_context(user_input, faiss_index, pdf_chunks, k=k)
199
+ sys_msg = {"role": "system", "content": make_system_prompt(ctx)}
200
+ usr_msg = {"role": "user", "content": user_input}
201
+
202
+ # streaming para UX fluida
203
+ try:
204
+ out = ""
205
+ for partial in nv_stream([sys_msg, usr_msg], temperature, top_p, max_tokens):
206
+ out = partial
207
+ return out
208
+ except OpenAIError as e:
209
+ return f"⚠️ Erro da API: {e.__class__.__name__}: {e}"
210
+
211
+
212
+ def rebuild_index_action():
213
+ global faiss_index, pdf_chunks
214
+ faiss_index, pdf_chunks = build_or_load_index(PDF_PATH, INDEX_FILE, CHUNKS_FILE)
215
+ return "✅ Índice reconstruído com sucesso a partir do PDF."
216
+
217
+
218
+ # Estado global carregado sob demanda
219
+ faiss_index = None
220
+ pdf_chunks = None
221
+
222
+
223
+ custom_css = r"""
224
+ :root { --primary:#2156d9; --bg:#f8fafc; --ink:#0f172a; }
225
+ body { background: var(--bg); color: var(--ink); }
226
+ #chatbox { height: 60vh; overflow-y: auto; }
227
+ """
228
+
229
+ with gr.Blocks(title=APP_TITLE, css=custom_css, theme=gr.themes.Base()) as demo:
230
+ gr.Markdown(f"## {APP_TITLE}")
231
+ gr.Markdown(
232
+ "Este assistente responde sobre **DFSORT** usando apenas o PDF como fonte. "
233
+ "Se algo não estiver no PDF, ele informa que não sabe."
234
+ )
235
+
236
+ with gr.Row():
237
+ with gr.Column(scale=3):
238
+ chat = gr.ChatInterface(
239
+ fn=lambda msg, hist, t, p, mt, k: chatbot_ui(msg, t, p, mt, k),
240
+ additional_inputs=[
241
+ gr.Slider(0, 1, 0.4, label="Temperature"),
242
+ gr.Slider(0, 1, 0.95, label="Top-p"),
243
+ gr.Slider(128, 4096, 768, step=64, label="Max Tokens"),
244
+ gr.Slider(2, 12, 6, step=1, label="Trechos (k)")
245
+ ],
246
+ multimodal=False,
247
+ title="Chat DFSORT (RAG)",
248
+ textbox=gr.Textbox(placeholder="Pergunte algo sobre DFSORT… ex.: Como uso INCLUDE COND?"),
249
+ cache_examples=False,
250
+ )
251
+ with gr.Column(scale=2):
252
+ gr.Markdown("### Controlo do índice")
253
+ gr.Markdown(f"PDF atual: `{PDF_PATH}`")
254
+ btn_rebuild = gr.Button("Reconstruir índice a partir do PDF")
255
+ msg = gr.Markdown()
256
+ btn_rebuild.click(lambda: rebuild_index_action(), [], [msg])
257
+
258
+ gr.Markdown("---")
259
+ gr.Markdown("### Dicas de consulta (direto do PDF)")
260
+ gr.Markdown(
261
+ "- Ex.: `Ordenar por 10 bytes a partir da posição 1 (CH, A).`\n"
262
+ "- Ex.: `Como faço para eliminar duplicados com SUM FIELDS=NONE?`\n"
263
+ "- Ex.: `JOINKEYS: explique o uso de REFORMAT.`\n"
264
+ "- Ex.: `Exemplo de OUTFIL com cabeçalho e REMOVECC.`"
265
+ )
266
+
267
+ if __name__ == "__main__":
268
+ # cria índice na primeira execução
269
+ if not Path(INDEX_FILE).exists() or not Path(CHUNKS_FILE).exists():
270
+ print("[i] Construindo índice a partir do PDF…")
271
+ faiss_index, pdf_chunks = build_or_load_index(PDF_PATH, INDEX_FILE, CHUNKS_FILE)
272
+ print("[i] Índice criado.")
273
+ demo.launch(server_name="0.0.0.0", server_port=7860)