Spaces:
Build error
Build error
Upload 7 files
Browse files- INSTRUCTIONS.md +94 -515
- QUICKSTART.txt +1 -4
- filter_fields.py +44 -0
- query_engine.py +184 -0
- rag_builder.py +105 -0
INSTRUCTIONS.md
CHANGED
|
@@ -1,591 +1,170 @@
|
|
| 1 |
-
# 📘
|
| 2 |
|
| 3 |
-
## 🎯 Objetivo
|
| 4 |
|
| 5 |
-
|
| 6 |
|
| 7 |
-
|
| 8 |
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
-
|
| 12 |
|
| 13 |
-
1.
|
| 14 |
-
- Criar em: https://huggingface.co/join
|
| 15 |
|
| 16 |
-
|
| 17 |
-
- Repositório com chunks `.tar.gz`
|
| 18 |
-
- Exemplo: `github.com/caarleexx/para-ai-data`
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
---
|
| 27 |
-
|
| 28 |
-
## 🚀 PARTE 1: Deploy Rápido (5 minutos)
|
| 29 |
|
| 30 |
-
###
|
| 31 |
|
| 32 |
```bash
|
| 33 |
-
# Instalar Hugging Face CLI
|
| 34 |
-
pip install huggingface-hub[cli]
|
| 35 |
-
|
| 36 |
# Login
|
| 37 |
huggingface-cli login
|
| 38 |
|
| 39 |
-
# Criar
|
| 40 |
huggingface-cli repo create para-ai-rag-0301 --type space --space_sdk docker
|
| 41 |
```
|
| 42 |
|
| 43 |
-
|
| 44 |
-
```
|
| 45 |
-
✓ Space criado: https://huggingface.co/spaces/seu-usuario/para-ai-rag-0301
|
| 46 |
-
```
|
| 47 |
-
|
| 48 |
-
---
|
| 49 |
-
|
| 50 |
-
### Passo 2: Clonar e Configurar
|
| 51 |
|
| 52 |
```bash
|
| 53 |
-
|
| 54 |
-
git clone https://huggingface.co/spaces/seu-usuario/para-ai-rag-0301
|
| 55 |
-
cd para-ai-rag-0301
|
| 56 |
-
|
| 57 |
-
# Copiar arquivos deste exemplo
|
| 58 |
-
cp -r /caminho/para/hf_space_rag_example/* .
|
| 59 |
-
|
| 60 |
-
# Editar config.yaml
|
| 61 |
-
nano config.yaml
|
| 62 |
-
```
|
| 63 |
-
|
| 64 |
-
**Edite estas linhas em `config.yaml`:**
|
| 65 |
-
|
| 66 |
-
```yaml
|
| 67 |
-
cluster_id: "RAG-0301" # Identificador único
|
| 68 |
-
chunk_start: 301 # Primeiro chunk
|
| 69 |
-
chunk_end: 600 # Último chunk
|
| 70 |
-
github_repo: "https://github.com/SEU-USUARIO/para-ai-data.git" # Seu repo
|
| 71 |
-
```
|
| 72 |
-
|
| 73 |
-
---
|
| 74 |
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
# Adicionar arquivos
|
| 79 |
-
git add .
|
| 80 |
|
| 81 |
# Commit
|
| 82 |
-
git
|
|
|
|
| 83 |
|
| 84 |
-
# Push
|
| 85 |
-
git push
|
| 86 |
```
|
| 87 |
|
| 88 |
-
|
| 89 |
-
1. Detectar `Dockerfile`
|
| 90 |
-
2. Construir container
|
| 91 |
-
3. Executar `entrypoint.sh`
|
| 92 |
-
4. Clonar chunks do GitHub
|
| 93 |
-
5. Construir ChromaDB
|
| 94 |
-
6. Iniciar FastAPI
|
| 95 |
|
| 96 |
-
|
|
|
|
| 97 |
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
### Passo 4: Verificar Status
|
| 101 |
-
|
| 102 |
-
Acesse: `https://huggingface.co/spaces/seu-usuario/para-ai-rag-0301`
|
| 103 |
-
|
| 104 |
-
**Você verá:**
|
| 105 |
-
- 🟢 **Building** (5-10 min) → Container sendo construído
|
| 106 |
-
- 🟡 **Running** (5-10 min) → Clonando dados e construindo ChromaDB
|
| 107 |
-
- 🟢 **Ready** → API disponível!
|
| 108 |
-
|
| 109 |
-
**Testar API:**
|
| 110 |
|
| 111 |
```bash
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
**Resposta esperada:**
|
| 116 |
-
```json
|
| 117 |
-
{
|
| 118 |
-
"cluster_id": "RAG-0301",
|
| 119 |
-
"chunk_range": [301, 600],
|
| 120 |
-
"total_records": 295432,
|
| 121 |
-
"status": "ready"
|
| 122 |
-
}
|
| 123 |
-
```
|
| 124 |
-
|
| 125 |
-
---
|
| 126 |
-
|
| 127 |
-
## 🔧 PARTE 2: Customização Avançada
|
| 128 |
-
|
| 129 |
-
### Opção 1: Mudar Modelo de Embedding
|
| 130 |
-
|
| 131 |
-
**Em `config.yaml`:**
|
| 132 |
-
|
| 133 |
-
```yaml
|
| 134 |
-
# Modelo atual (leve, rápido)
|
| 135 |
-
embedding_model: "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
|
| 136 |
-
embedding_dim: 384
|
| 137 |
-
|
| 138 |
-
# Alternativas:
|
| 139 |
-
|
| 140 |
-
# Modelo maior (melhor qualidade, mais lento)
|
| 141 |
-
embedding_model: "sentence-transformers/paraphrase-multilingual-mpnet-base-v2"
|
| 142 |
-
embedding_dim: 768
|
| 143 |
-
|
| 144 |
-
# Modelo português-específico
|
| 145 |
-
embedding_model: "neuralmind/bert-base-portuguese-cased"
|
| 146 |
-
embedding_dim: 768
|
| 147 |
-
```
|
| 148 |
-
|
| 149 |
-
**⚠️ Atenção:**
|
| 150 |
-
- Modelos maiores usam mais RAM
|
| 151 |
-
- Verifique se cabe no free tier (16GB)
|
| 152 |
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
**Editar `config.yaml`:**
|
| 158 |
-
|
| 159 |
-
```yaml
|
| 160 |
-
campos_filter:
|
| 161 |
-
- id
|
| 162 |
-
- ementa
|
| 163 |
-
- data_decisao # Adicionar
|
| 164 |
-
- relator # Adicionar
|
| 165 |
-
- orgao_julgador # Adicionar
|
| 166 |
-
```
|
| 167 |
-
|
| 168 |
-
**Editar `filter_fields.py`:**
|
| 169 |
-
|
| 170 |
-
Já está pronto! Ele lê automaticamente de `config.yaml`.
|
| 171 |
-
|
| 172 |
-
**Editar `query_engine.py`:**
|
| 173 |
-
|
| 174 |
-
Se quiser buscar por campos adicionais:
|
| 175 |
-
|
| 176 |
-
```python
|
| 177 |
-
def search_by_metadata(self, field: str, value: str):
|
| 178 |
-
"""Busca por campo de metadata"""
|
| 179 |
-
results = self.collection.query(
|
| 180 |
-
query_texts=[""],
|
| 181 |
-
n_results=100,
|
| 182 |
-
where={field: value}
|
| 183 |
-
)
|
| 184 |
-
return results
|
| 185 |
-
```
|
| 186 |
-
|
| 187 |
-
---
|
| 188 |
-
|
| 189 |
-
### Opção 3: Ajustar Performance
|
| 190 |
-
|
| 191 |
-
**Em `config.yaml`:**
|
| 192 |
-
|
| 193 |
-
```yaml
|
| 194 |
-
# Batch size para embeddings
|
| 195 |
-
embedding_batch_size: 64 # Padrão
|
| 196 |
-
# Aumentar para 128 se tiver RAM disponível
|
| 197 |
-
# Diminuir para 32 se ficar sem RAM
|
| 198 |
-
|
| 199 |
-
# Workers
|
| 200 |
-
max_workers: 2 # Padrão (free tier tem 2 vCPU)
|
| 201 |
-
```
|
| 202 |
-
|
| 203 |
-
**Em `app.py` (FastAPI):**
|
| 204 |
-
|
| 205 |
-
```python
|
| 206 |
-
# Aumentar workers do Uvicorn
|
| 207 |
-
uvicorn.run(app, host="0.0.0.0", port=7860, workers=2) # Padrão: 1
|
| 208 |
```
|
| 209 |
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
---
|
| 213 |
-
|
| 214 |
-
## 📊 PARTE 3: Múltiplos Clusters
|
| 215 |
|
| 216 |
-
Para cobrir
|
| 217 |
|
| 218 |
-
| Space
|
| 219 |
-
|
| 220 |
-
|
|
| 221 |
-
|
|
| 222 |
-
|
|
| 223 |
-
|
|
| 224 |
-
|
|
| 225 |
-
| RAG-4201 | 4201-4500 | ~300k | /para-ai-rag-4201 |
|
| 226 |
|
| 227 |
-
|
| 228 |
|
| 229 |
```bash
|
| 230 |
#!/bin/bash
|
| 231 |
-
# deploy_all_clusters.sh
|
| 232 |
-
|
| 233 |
for START in 1 301 601 901 1201 1501 1801 2101 2401 2701 3001 3301 3601 3901 4201; do
|
| 234 |
END=$((START + 299))
|
| 235 |
-
CLUSTER_ID=$(printf "RAG-%04d" $START)
|
| 236 |
SPACE_NAME="para-ai-rag-$(printf "%04d" $START)"
|
| 237 |
|
| 238 |
-
echo "Criando $SPACE_NAME (chunks $START-$END)..."
|
| 239 |
-
|
| 240 |
-
# Criar Space
|
| 241 |
huggingface-cli repo create $SPACE_NAME --type space --space_sdk docker
|
| 242 |
-
|
| 243 |
-
# Clonar
|
| 244 |
-
git clone https://huggingface.co/spaces/seu-usuario/$SPACE_NAME
|
| 245 |
cd $SPACE_NAME
|
| 246 |
|
| 247 |
-
# Copiar template
|
| 248 |
cp -r ../hf_space_rag_example/* .
|
| 249 |
-
|
| 250 |
-
# Atualizar config
|
| 251 |
-
sed -i "s/cluster_id: .*/cluster_id: "$CLUSTER_ID"/" config.yaml
|
| 252 |
sed -i "s/chunk_start: .*/chunk_start: $START/" config.yaml
|
| 253 |
sed -i "s/chunk_end: .*/chunk_end: $END/" config.yaml
|
| 254 |
|
| 255 |
-
# Deploy
|
| 256 |
git add .
|
| 257 |
-
git commit -m "Deploy $
|
| 258 |
git push
|
| 259 |
-
|
| 260 |
cd ..
|
| 261 |
-
|
| 262 |
-
echo "✅ $SPACE_NAME deployed!"
|
| 263 |
-
sleep 5 # Esperar 5s para não sobrecarregar HF
|
| 264 |
done
|
| 265 |
-
|
| 266 |
-
echo "🎉 Todos os 15 clusters deployados!"
|
| 267 |
-
```
|
| 268 |
-
|
| 269 |
-
---
|
| 270 |
-
|
| 271 |
-
## 🌐 PARTE 4: Gateway Agregador (Opcional)
|
| 272 |
-
|
| 273 |
-
Para buscar em **todos os clusters** ao mesmo tempo, crie um **Space Gateway**:
|
| 274 |
-
|
| 275 |
-
### Arquitetura do Gateway
|
| 276 |
-
|
| 277 |
-
```python
|
| 278 |
-
# gateway_app.py
|
| 279 |
-
from fastapi import FastAPI
|
| 280 |
-
import asyncio
|
| 281 |
-
import httpx
|
| 282 |
-
|
| 283 |
-
app = FastAPI(title="Para.AI Gateway")
|
| 284 |
-
|
| 285 |
-
# Lista de todos os clusters
|
| 286 |
-
CLUSTERS = [
|
| 287 |
-
"https://seu-usuario-para-ai-rag-0001.hf.space",
|
| 288 |
-
"https://seu-usuario-para-ai-rag-0301.hf.space",
|
| 289 |
-
"https://seu-usuario-para-ai-rag-0601.hf.space",
|
| 290 |
-
# ... todos os 15
|
| 291 |
-
]
|
| 292 |
-
|
| 293 |
-
@app.post("/search/embedding")
|
| 294 |
-
async def search_all_clusters(query: str, top_k: int = 10):
|
| 295 |
-
"""Busca em todos os clusters e agrega resultados"""
|
| 296 |
-
|
| 297 |
-
async with httpx.AsyncClient(timeout=30.0) as client:
|
| 298 |
-
tasks = [
|
| 299 |
-
client.post(
|
| 300 |
-
f"{cluster}/search/embedding",
|
| 301 |
-
json={"query": query, "top_k": top_k}
|
| 302 |
-
)
|
| 303 |
-
for cluster in CLUSTERS
|
| 304 |
-
]
|
| 305 |
-
|
| 306 |
-
responses = await asyncio.gather(*tasks, return_exceptions=True)
|
| 307 |
-
|
| 308 |
-
# Agregar resultados
|
| 309 |
-
all_results = []
|
| 310 |
-
for resp in responses:
|
| 311 |
-
if isinstance(resp, Exception):
|
| 312 |
-
continue
|
| 313 |
-
data = resp.json()
|
| 314 |
-
all_results.extend(data['results'])
|
| 315 |
-
|
| 316 |
-
# Ordenar por score
|
| 317 |
-
all_results.sort(key=lambda x: x['score'], reverse=True)
|
| 318 |
-
|
| 319 |
-
return {
|
| 320 |
-
"clusters_consulted": len(CLUSTERS),
|
| 321 |
-
"total_found": len(all_results),
|
| 322 |
-
"results": all_results[:top_k] # Top K global
|
| 323 |
-
}
|
| 324 |
-
```
|
| 325 |
-
|
| 326 |
-
**Deploy do Gateway:**
|
| 327 |
-
|
| 328 |
-
```bash
|
| 329 |
-
huggingface-cli repo create para-ai-gateway --type space --space_sdk docker
|
| 330 |
-
# ... copiar gateway_app.py, Dockerfile, etc.
|
| 331 |
```
|
| 332 |
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
## 🧪 PARTE 5: Testes Locais (Desenvolvimento)
|
| 336 |
-
|
| 337 |
-
### Testar sem Deploy
|
| 338 |
-
|
| 339 |
-
```bash
|
| 340 |
-
# Entrar na pasta
|
| 341 |
-
cd hf_space_rag_example
|
| 342 |
-
|
| 343 |
-
# Instalar dependências
|
| 344 |
-
pip install -r requirements.txt
|
| 345 |
|
| 346 |
-
|
| 347 |
-
# config.yaml: chunk_start: 301, chunk_end: 305 (apenas 5 chunks!)
|
| 348 |
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
# 1. Filtrar campos
|
| 352 |
-
python filter_fields.py --input /tmp/test.jsonl --output /tmp/filtered.jsonl
|
| 353 |
-
|
| 354 |
-
# 2. Build ChromaDB
|
| 355 |
-
python rag_builder.py --input /tmp/filtered.jsonl
|
| 356 |
-
|
| 357 |
-
# 3. Iniciar API
|
| 358 |
-
python app.py
|
| 359 |
-
# ou
|
| 360 |
-
uvicorn app:app --reload
|
| 361 |
-
```
|
| 362 |
-
|
| 363 |
-
**Testar API localmente:**
|
| 364 |
-
|
| 365 |
-
```bash
|
| 366 |
-
# Teste 1: Info do cluster
|
| 367 |
-
curl http://localhost:7860/cluster/info
|
| 368 |
-
|
| 369 |
-
# Teste 2: Busca semântica
|
| 370 |
-
curl -X POST http://localhost:7860/search/embedding \
|
| 371 |
-
-H "Content-Type: application/json" \
|
| 372 |
-
-d '{"query": "despejo", "top_k": 3}'
|
| 373 |
-
|
| 374 |
-
# Teste 3: Busca por keywords
|
| 375 |
-
curl -X POST http://localhost:7860/search/keywords \
|
| 376 |
-
-H "Content-Type: application/json" \
|
| 377 |
-
-d '{"keywords": ["despejo", "aluguel"], "operator": "AND"}'
|
| 378 |
-
```
|
| 379 |
-
|
| 380 |
-
---
|
| 381 |
-
|
| 382 |
-
## 🐛 PARTE 6: Troubleshooting
|
| 383 |
-
|
| 384 |
-
### Problema 1: Build Timeout no HF
|
| 385 |
-
|
| 386 |
-
**Sintoma:** Space fica em "Building" por >1h e falha
|
| 387 |
-
|
| 388 |
-
**Causa:** Download de chunks muito grande
|
| 389 |
-
|
| 390 |
-
**Solução:**
|
| 391 |
-
|
| 392 |
-
1. Verificar se sparse checkout está funcionando:
|
| 393 |
-
|
| 394 |
-
```bash
|
| 395 |
-
# No entrypoint.sh, adicionar debug:
|
| 396 |
-
echo "Chunks encontrados:"
|
| 397 |
-
find chunks_dados -name "*.tar.gz" | wc -l
|
| 398 |
-
```
|
| 399 |
-
|
| 400 |
-
2. Reduzir intervalo de chunks temporariamente:
|
| 401 |
|
| 402 |
```yaml
|
| 403 |
-
|
| 404 |
-
|
|
|
|
|
|
|
|
|
|
| 405 |
```
|
| 406 |
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
### Problema 2: Out of Memory (OOM)
|
| 410 |
-
|
| 411 |
-
**Sintoma:** Space reinicia continuamente
|
| 412 |
-
|
| 413 |
-
**Causa:** Modelo de embedding muito grande ou muitos chunks
|
| 414 |
-
|
| 415 |
-
**Soluções:**
|
| 416 |
-
|
| 417 |
-
1. **Usar modelo menor:**
|
| 418 |
|
| 419 |
```yaml
|
| 420 |
-
|
|
|
|
| 421 |
embedding_dim: 384
|
| 422 |
-
```
|
| 423 |
-
|
| 424 |
-
2. **Reduzir batch size:**
|
| 425 |
-
|
| 426 |
-
```yaml
|
| 427 |
-
embedding_batch_size: 32 # Ao invés de 64
|
| 428 |
-
```
|
| 429 |
-
|
| 430 |
-
3. **Reduzir número de chunks:**
|
| 431 |
-
|
| 432 |
-
```yaml
|
| 433 |
-
chunk_end: 400 # Ao invés de 600
|
| 434 |
-
```
|
| 435 |
-
|
| 436 |
-
---
|
| 437 |
-
|
| 438 |
-
### Problema 3: Git Clone Muito Lento
|
| 439 |
-
|
| 440 |
-
**Sintoma:** Clonagem demora >30min
|
| 441 |
-
|
| 442 |
-
**Causa:** Sparse checkout não funcionou
|
| 443 |
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
```bash
|
| 449 |
-
# No entrypoint.sh
|
| 450 |
-
echo "Pattern de sparse checkout:"
|
| 451 |
-
echo "$PATTERN"
|
| 452 |
-
|
| 453 |
-
# Deve mostrar algo como:
|
| 454 |
-
# chunks_dados/chunk_dados_0301.tar.gz chunks_dados/chunk_dados_0302.tar.gz ...
|
| 455 |
-
```
|
| 456 |
-
|
| 457 |
-
Se estiver errado, corrigir geração do pattern:
|
| 458 |
-
|
| 459 |
-
```bash
|
| 460 |
-
# Bash expansion correta
|
| 461 |
-
for i in $(seq -f "%04g" $CHUNK_START $CHUNK_END); do
|
| 462 |
-
echo "chunks_dados/chunk_dados_$i.tar.gz"
|
| 463 |
-
done
|
| 464 |
-
```
|
| 465 |
-
|
| 466 |
-
---
|
| 467 |
-
|
| 468 |
-
### Problema 4: API Retorna 503
|
| 469 |
-
|
| 470 |
-
**Sintoma:** API acessível mas sempre retorna erro 503
|
| 471 |
-
|
| 472 |
-
**Causa:** ChromaDB não foi construído ou está corrompido
|
| 473 |
-
|
| 474 |
-
**Solução:**
|
| 475 |
-
|
| 476 |
-
1. Ver logs do Space no HF
|
| 477 |
-
2. Verificar se `/app/chromadb` existe e tem conteúdo
|
| 478 |
-
3. Forçar rebuild:
|
| 479 |
-
|
| 480 |
-
```bash
|
| 481 |
-
# No entrypoint.sh, remover check de persistência:
|
| 482 |
-
# if [ -d "/app/chromadb" ] && [ "$(ls -A /app/chromadb)" ]; then
|
| 483 |
-
# ...
|
| 484 |
-
# fi
|
| 485 |
-
|
| 486 |
-
# Comentar essas linhas para sempre rebuildar
|
| 487 |
-
```
|
| 488 |
-
|
| 489 |
-
---
|
| 490 |
-
|
| 491 |
-
## 📈 PARTE 7: Monitoramento
|
| 492 |
-
|
| 493 |
-
### Logs do Space
|
| 494 |
-
|
| 495 |
-
Acesse: `https://huggingface.co/spaces/seu-usuario/para-ai-rag-0301/logs`
|
| 496 |
-
|
| 497 |
-
**O que procurar:**
|
| 498 |
-
|
| 499 |
-
```
|
| 500 |
-
✅ BOM:
|
| 501 |
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 502 |
-
✓ 300 chunks clonados
|
| 503 |
-
✓ 295432 registros concatenados
|
| 504 |
-
✓ Campos filtrados
|
| 505 |
-
✓ ChromaDB pronto!
|
| 506 |
-
✓ Para.AI RAG Cluster RAG-0301 ONLINE
|
| 507 |
-
|
| 508 |
-
❌ RUIM:
|
| 509 |
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 510 |
-
✗ Erro ao clonar repositório
|
| 511 |
-
✗ Out of memory
|
| 512 |
-
✗ ChromaDB corrupted
|
| 513 |
-
```
|
| 514 |
-
|
| 515 |
-
---
|
| 516 |
-
|
| 517 |
-
### Métricas de Performance
|
| 518 |
-
|
| 519 |
-
Adicionar em `app.py`:
|
| 520 |
-
|
| 521 |
-
```python
|
| 522 |
-
from prometheus_client import Counter, Histogram, generate_latest
|
| 523 |
-
|
| 524 |
-
# Métricas
|
| 525 |
-
search_requests = Counter('search_requests_total', 'Total search requests')
|
| 526 |
-
search_latency = Histogram('search_latency_seconds', 'Search latency')
|
| 527 |
-
|
| 528 |
-
@app.get("/metrics")
|
| 529 |
-
async def metrics():
|
| 530 |
-
return generate_latest()
|
| 531 |
```
|
| 532 |
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
## 🎓 PARTE 8: Próximos Passos
|
| 536 |
-
|
| 537 |
-
Após ter os clusters funcionando:
|
| 538 |
-
|
| 539 |
-
1. **Integrar com aplicação front-end**
|
| 540 |
-
- Criar Gradio UI que consulta os clusters
|
| 541 |
-
- Deploy em outro HF Space
|
| 542 |
-
|
| 543 |
-
2. **Adicionar cache**
|
| 544 |
-
- Redis para cachear queries frequentes
|
| 545 |
-
- Reduz latência e custo
|
| 546 |
-
|
| 547 |
-
3. **Fine-tuning do modelo**
|
| 548 |
-
- Treinar embedding model específico para jurisprudência
|
| 549 |
-
- Melhora qualidade dos resultados
|
| 550 |
-
|
| 551 |
-
4. **Adicionar re-ranking**
|
| 552 |
-
- Cross-encoder para re-ranquear top results
|
| 553 |
-
- Aumenta precisão
|
| 554 |
-
|
| 555 |
-
---
|
| 556 |
-
|
| 557 |
-
## 📚 Recursos Adicionais
|
| 558 |
-
|
| 559 |
-
- **Documentação HF Spaces:** https://huggingface.co/docs/hub/spaces
|
| 560 |
-
- **ChromaDB Docs:** https://docs.trychroma.com/
|
| 561 |
-
- **Sentence Transformers:** https://www.sbert.net/
|
| 562 |
-
- **FastAPI Docs:** https://fastapi.tiangolo.com/
|
| 563 |
|
| 564 |
-
|
|
|
|
|
|
|
| 565 |
|
| 566 |
-
|
|
|
|
|
|
|
|
|
|
| 567 |
|
| 568 |
-
|
|
|
|
|
|
|
| 569 |
|
| 570 |
-
|
| 571 |
-
- [ ] `/cluster/info` retorna dados corretos
|
| 572 |
-
- [ ] Busca semântica funciona
|
| 573 |
-
- [ ] Busca por keywords funciona
|
| 574 |
-
- [ ] Busca por ID funciona
|
| 575 |
-
- [ ] Latência < 200ms para queries
|
| 576 |
-
- [ ] Logs sem erros
|
| 577 |
-
- [ ] README.md atualizado com URL do Space
|
| 578 |
-
- [ ] Testado com queries reais
|
| 579 |
|
| 580 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 581 |
|
| 582 |
-
##
|
| 583 |
|
| 584 |
-
|
| 585 |
|
| 586 |
-
|
| 587 |
-
✅ Buscar em <100ms
|
| 588 |
-
✅ Custo zero (free tier)
|
| 589 |
-
✅ Escalável para milhões de registros
|
| 590 |
|
| 591 |
-
|
|
|
|
| 1 |
+
# 📘 Para.AI RAG Cluster - Instructions
|
| 2 |
|
| 3 |
+
## 🎯 Objetivo
|
| 4 |
|
| 5 |
+
Deploy de micro-cluster RAG no Hugging Face Spaces (free tier) para indexar ~300k jurisprudências do TJPR.
|
| 6 |
|
| 7 |
+
## 📋 Arquivos do Projeto
|
| 8 |
|
| 9 |
+
| Arquivo | Função |
|
| 10 |
+
|---------|---------|
|
| 11 |
+
| `config.yaml` | Configuração (EDITE AQUI!) |
|
| 12 |
+
| `Dockerfile` | Container Docker |
|
| 13 |
+
| `entrypoint.sh` | Script de inicialização |
|
| 14 |
+
| `requirements.txt` | Dependências Python |
|
| 15 |
+
| `filter_fields.py` | Filtrar campos JSONL |
|
| 16 |
+
| `rag_builder.py` | Construir ChromaDB |
|
| 17 |
+
| `query_engine.py` | Engine de busca |
|
| 18 |
+
| `app.py` | FastAPI REST API |
|
| 19 |
+
| `README.md` | Documentação básica |
|
| 20 |
+
| `.gitignore` | Arquivos ignorados |
|
| 21 |
|
| 22 |
+
## 🚀 Deploy Step-by-Step
|
| 23 |
|
| 24 |
+
### 1. Preparar Configuração
|
|
|
|
| 25 |
|
| 26 |
+
Editar `config.yaml`:
|
|
|
|
|
|
|
| 27 |
|
| 28 |
+
```yaml
|
| 29 |
+
cluster_id: "RAG-0301" # Seu ID único
|
| 30 |
+
chunk_start: 301 # Primeiro chunk
|
| 31 |
+
chunk_end: 600 # Último chunk (300 chunks = ~300k registros)
|
| 32 |
+
github_repo: "https://github.com/SEU-USUARIO/para-ai-data.git"
|
| 33 |
+
```
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
+
### 2. Criar Space no HF
|
| 36 |
|
| 37 |
```bash
|
|
|
|
|
|
|
|
|
|
| 38 |
# Login
|
| 39 |
huggingface-cli login
|
| 40 |
|
| 41 |
+
# Criar Space
|
| 42 |
huggingface-cli repo create para-ai-rag-0301 --type space --space_sdk docker
|
| 43 |
```
|
| 44 |
|
| 45 |
+
### 3. Fazer Upload
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
```bash
|
| 48 |
+
cd hf_space_rag_example
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
+
# Inicializar Git
|
| 51 |
+
git init
|
| 52 |
+
git remote add origin https://huggingface.co/spaces/SEU-USUARIO/para-ai-rag-0301
|
|
|
|
|
|
|
| 53 |
|
| 54 |
# Commit
|
| 55 |
+
git add .
|
| 56 |
+
git commit -m "Initial deployment"
|
| 57 |
|
| 58 |
+
# Push
|
| 59 |
+
git push origin main
|
| 60 |
```
|
| 61 |
|
| 62 |
+
### 4. Aguardar Build
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
+
- Acesse: https://huggingface.co/spaces/SEU-USUARIO/para-ai-rag-0301
|
| 65 |
+
- Status: Building (5-10 min) → Running (5-10 min) → Ready ✅
|
| 66 |
|
| 67 |
+
### 5. Testar
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
```bash
|
| 70 |
+
# Info do cluster
|
| 71 |
+
curl https://SEU-USUARIO-para-ai-rag-0301.hf.space/cluster/info
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
+
# Busca semântica
|
| 74 |
+
curl -X POST https://SEU-USUARIO-para-ai-rag-0301.hf.space/search/embedding \
|
| 75 |
+
-H "Content-Type: application/json" \
|
| 76 |
+
-d '{"query": "despejo falta pagamento", "top_k": 5}'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
```
|
| 78 |
|
| 79 |
+
## 🌐 Arquitetura Distribuída
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
+
Para cobrir todos os 4.5M registros, crie 15 Spaces:
|
| 82 |
|
| 83 |
+
| Space | Chunks | Config |
|
| 84 |
+
|-------|--------|--------|
|
| 85 |
+
| para-ai-rag-0001 | 1-300 | chunk_start: 1, chunk_end: 300 |
|
| 86 |
+
| para-ai-rag-0301 | 301-600 | chunk_start: 301, chunk_end: 600 |
|
| 87 |
+
| para-ai-rag-0601 | 601-900 | chunk_start: 601, chunk_end: 900 |
|
| 88 |
+
| ... | ... | ... |
|
| 89 |
+
| para-ai-rag-4201 | 4201-4500 | chunk_start: 4201, chunk_end: 4500 |
|
|
|
|
| 90 |
|
| 91 |
+
**Script de deploy automático** (bash):
|
| 92 |
|
| 93 |
```bash
|
| 94 |
#!/bin/bash
|
|
|
|
|
|
|
| 95 |
for START in 1 301 601 901 1201 1501 1801 2101 2401 2701 3001 3301 3601 3901 4201; do
|
| 96 |
END=$((START + 299))
|
|
|
|
| 97 |
SPACE_NAME="para-ai-rag-$(printf "%04d" $START)"
|
| 98 |
|
|
|
|
|
|
|
|
|
|
| 99 |
huggingface-cli repo create $SPACE_NAME --type space --space_sdk docker
|
| 100 |
+
git clone https://huggingface.co/spaces/SEU-USUARIO/$SPACE_NAME
|
|
|
|
|
|
|
| 101 |
cd $SPACE_NAME
|
| 102 |
|
| 103 |
+
# Copiar template e atualizar config
|
| 104 |
cp -r ../hf_space_rag_example/* .
|
|
|
|
|
|
|
|
|
|
| 105 |
sed -i "s/chunk_start: .*/chunk_start: $START/" config.yaml
|
| 106 |
sed -i "s/chunk_end: .*/chunk_end: $END/" config.yaml
|
| 107 |
|
|
|
|
| 108 |
git add .
|
| 109 |
+
git commit -m "Deploy cluster $START-$END"
|
| 110 |
git push
|
|
|
|
| 111 |
cd ..
|
|
|
|
|
|
|
|
|
|
| 112 |
done
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
```
|
| 114 |
|
| 115 |
+
## 🔧 Customização
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
+
### Adicionar Mais Campos
|
|
|
|
| 118 |
|
| 119 |
+
Em `config.yaml`:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
```yaml
|
| 122 |
+
campos_filter:
|
| 123 |
+
- id
|
| 124 |
+
- ementa
|
| 125 |
+
- data_decisao # Adicionar
|
| 126 |
+
- relator # Adicionar
|
| 127 |
```
|
| 128 |
|
| 129 |
+
### Trocar Modelo de Embedding
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
```yaml
|
| 132 |
+
# Opção 1: Mais leve (padrão)
|
| 133 |
+
embedding_model: "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
|
| 134 |
embedding_dim: 384
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
+
# Opção 2: Melhor qualidade
|
| 137 |
+
embedding_model: "sentence-transformers/paraphrase-multilingual-mpnet-base-v2"
|
| 138 |
+
embedding_dim: 768
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
```
|
| 140 |
|
| 141 |
+
## 🐛 Troubleshooting
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
+
### Build muito lento
|
| 144 |
+
- Verificar se sparse checkout está funcionando
|
| 145 |
+
- Reduzir número de chunks temporariamente
|
| 146 |
|
| 147 |
+
### Out of Memory
|
| 148 |
+
- Usar modelo menor: `all-MiniLM-L6-v2`
|
| 149 |
+
- Reduzir `embedding_batch_size: 32`
|
| 150 |
+
- Diminuir intervalo de chunks
|
| 151 |
|
| 152 |
+
### API retorna 503
|
| 153 |
+
- Ver logs do Space no HF
|
| 154 |
+
- Verificar se ChromaDB foi construído
|
| 155 |
|
| 156 |
+
## 📊 Recursos Utilizados
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
+
| Recurso | Usado | Disponível (Free) |
|
| 159 |
+
|---------|-------|-------------------|
|
| 160 |
+
| RAM | ~2GB | 16GB ✅ |
|
| 161 |
+
| Disco | ~2.6GB | 50GB ✅ |
|
| 162 |
+
| CPU | 1 core | 2 cores ✅ |
|
| 163 |
|
| 164 |
+
## ⚖️ Sobre Para.AI
|
| 165 |
|
| 166 |
+
Projeto open-source para democratizar o acesso à justiça no Paraná usando IA.
|
| 167 |
|
| 168 |
+
🐝 **InJustiça não para o Paraná!**
|
|
|
|
|
|
|
|
|
|
| 169 |
|
| 170 |
+
📧 github.com/caarleexx/para-ai
|
QUICKSTART.txt
CHANGED
|
@@ -18,7 +18,6 @@
|
|
| 18 |
|
| 19 |
3. Deploy:
|
| 20 |
|
| 21 |
-
$ cd hf_space_rag_example
|
| 22 |
$ git init
|
| 23 |
$ git remote add origin https://huggingface.co/spaces/SEU-USUARIO/para-ai-rag-0301
|
| 24 |
$ git add .
|
|
@@ -33,8 +32,6 @@
|
|
| 33 |
|
| 34 |
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 35 |
|
| 36 |
-
📖 LEIA:
|
| 37 |
-
• README.md - Docs da API
|
| 38 |
-
• INSTRUCTIONS.md - Guia completo
|
| 39 |
|
| 40 |
✅ PRONTO! Seu RAG está online!
|
|
|
|
| 18 |
|
| 19 |
3. Deploy:
|
| 20 |
|
|
|
|
| 21 |
$ git init
|
| 22 |
$ git remote add origin https://huggingface.co/spaces/SEU-USUARIO/para-ai-rag-0301
|
| 23 |
$ git add .
|
|
|
|
| 32 |
|
| 33 |
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
| 34 |
|
| 35 |
+
📖 LEIA: INSTRUCTIONS.md para guia completo
|
|
|
|
|
|
|
| 36 |
|
| 37 |
✅ PRONTO! Seu RAG está online!
|
filter_fields.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Filtrar campos de JSONL mantendo apenas os especificados
|
| 4 |
+
"""
|
| 5 |
+
import json
|
| 6 |
+
import yaml
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
import argparse
|
| 9 |
+
from tqdm import tqdm
|
| 10 |
+
|
| 11 |
+
def filter_jsonl(input_path: str, output_path: str, keep_fields: list = None):
|
| 12 |
+
"""Filtra campos de arquivo JSONL"""
|
| 13 |
+
|
| 14 |
+
# Carregar campos da config se não especificados
|
| 15 |
+
if keep_fields is None:
|
| 16 |
+
with open('config.yaml') as f:
|
| 17 |
+
config = yaml.safe_load(f)
|
| 18 |
+
keep_fields = config['campos_filter']
|
| 19 |
+
|
| 20 |
+
print(f"📥 Input: {input_path}")
|
| 21 |
+
print(f"📤 Output: {output_path}")
|
| 22 |
+
print(f"🔧 Mantendo campos: {keep_fields}")
|
| 23 |
+
|
| 24 |
+
# Contar linhas
|
| 25 |
+
with open(input_path) as f:
|
| 26 |
+
total = sum(1 for _ in f)
|
| 27 |
+
|
| 28 |
+
# Filtrar
|
| 29 |
+
with open(input_path) as fin, open(output_path, 'w') as fout:
|
| 30 |
+
for line in tqdm(fin, total=total, desc="Filtrando"):
|
| 31 |
+
record = json.loads(line)
|
| 32 |
+
filtered = {k: record[k] for k in keep_fields if k in record}
|
| 33 |
+
fout.write(json.dumps(filtered, ensure_ascii=False) + '\n')
|
| 34 |
+
|
| 35 |
+
print(f"✅ {total} registros filtrados!")
|
| 36 |
+
|
| 37 |
+
if __name__ == "__main__":
|
| 38 |
+
parser = argparse.ArgumentParser()
|
| 39 |
+
parser.add_argument('--input', required=True)
|
| 40 |
+
parser.add_argument('--output', required=True)
|
| 41 |
+
parser.add_argument('--keep', nargs='+', default=None)
|
| 42 |
+
args = parser.parse_args()
|
| 43 |
+
|
| 44 |
+
filter_jsonl(args.input, args.output, args.keep)
|
query_engine.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Engine de busca para ChromaDB
|
| 4 |
+
"""
|
| 5 |
+
import yaml
|
| 6 |
+
import chromadb
|
| 7 |
+
from sentence_transformers import SentenceTransformer
|
| 8 |
+
from typing import List, Dict, Optional
|
| 9 |
+
import logging
|
| 10 |
+
|
| 11 |
+
logger = logging.getLogger(__name__)
|
| 12 |
+
|
| 13 |
+
class QueryEngine:
|
| 14 |
+
"""Engine de busca com ChromaDB"""
|
| 15 |
+
|
| 16 |
+
def __init__(self, config_path: str = 'config.yaml'):
|
| 17 |
+
# Carregar config
|
| 18 |
+
with open(config_path) as f:
|
| 19 |
+
self.config = yaml.safe_load(f)
|
| 20 |
+
|
| 21 |
+
# Carregar modelo de embedding
|
| 22 |
+
logger.info(f"Carregando modelo {self.config['embedding_model']}...")
|
| 23 |
+
self.model = SentenceTransformer(self.config['embedding_model'])
|
| 24 |
+
|
| 25 |
+
# Conectar ao ChromaDB
|
| 26 |
+
logger.info(f"Conectando ao ChromaDB...")
|
| 27 |
+
self.client = chromadb.PersistentClient(path=self.config['chromadb_path'])
|
| 28 |
+
self.collection = self.client.get_collection(self.config['collection_name'])
|
| 29 |
+
|
| 30 |
+
logger.info(f"✅ QueryEngine pronto ({self.collection.count():,} registros)")
|
| 31 |
+
|
| 32 |
+
def search_by_embedding(
|
| 33 |
+
self,
|
| 34 |
+
query: str,
|
| 35 |
+
top_k: int = 10,
|
| 36 |
+
return_embeddings: bool = False
|
| 37 |
+
) -> Dict:
|
| 38 |
+
"""Busca por similaridade semântica"""
|
| 39 |
+
|
| 40 |
+
# Gerar embedding da query
|
| 41 |
+
query_embedding = self.model.encode(query).tolist()
|
| 42 |
+
|
| 43 |
+
# Buscar no ChromaDB
|
| 44 |
+
results = self.collection.query(
|
| 45 |
+
query_embeddings=[query_embedding],
|
| 46 |
+
n_results=top_k,
|
| 47 |
+
include=['documents', 'metadatas', 'distances', 'embeddings'] if return_embeddings
|
| 48 |
+
else ['documents', 'metadatas', 'distances']
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
# Formatar resposta
|
| 52 |
+
formatted_results = []
|
| 53 |
+
for i in range(len(results['ids'][0])):
|
| 54 |
+
result = {
|
| 55 |
+
'id': results['ids'][0][i],
|
| 56 |
+
'ementa': results['documents'][0][i],
|
| 57 |
+
'distance': results['distances'][0][i],
|
| 58 |
+
'score': 1.0 - results['distances'][0][i] # Converter distância para score
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
if return_embeddings and 'embeddings' in results:
|
| 62 |
+
result['embedding'] = results['embeddings'][0][i]
|
| 63 |
+
|
| 64 |
+
formatted_results.append(result)
|
| 65 |
+
|
| 66 |
+
return {
|
| 67 |
+
'cluster_id': self.config['cluster_id'],
|
| 68 |
+
'chunk_range': [self.config['chunk_start'], self.config['chunk_end']],
|
| 69 |
+
'results': formatted_results,
|
| 70 |
+
'total_found': len(formatted_results)
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
def search_by_keywords(
|
| 74 |
+
self,
|
| 75 |
+
keywords: List[str],
|
| 76 |
+
operator: str = 'AND',
|
| 77 |
+
top_k: int = 20
|
| 78 |
+
) -> Dict:
|
| 79 |
+
"""Busca por termos-chave (full-text search)"""
|
| 80 |
+
|
| 81 |
+
# Construir query string
|
| 82 |
+
if operator.upper() == 'AND':
|
| 83 |
+
query_str = ' '.join(keywords)
|
| 84 |
+
else: # OR
|
| 85 |
+
query_str = '|'.join(keywords)
|
| 86 |
+
|
| 87 |
+
# Buscar usando where_document (full-text search do ChromaDB)
|
| 88 |
+
results = self.collection.query(
|
| 89 |
+
query_texts=[query_str],
|
| 90 |
+
n_results=top_k,
|
| 91 |
+
include=['documents', 'metadatas']
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
# Formatar resposta
|
| 95 |
+
formatted_results = []
|
| 96 |
+
for i in range(len(results['ids'][0])):
|
| 97 |
+
# Verificar quais keywords foram matchadas
|
| 98 |
+
doc = results['documents'][0][i].lower()
|
| 99 |
+
matched = [kw for kw in keywords if kw.lower() in doc]
|
| 100 |
+
|
| 101 |
+
formatted_results.append({
|
| 102 |
+
'id': results['ids'][0][i],
|
| 103 |
+
'ementa': results['documents'][0][i],
|
| 104 |
+
'matched_keywords': matched
|
| 105 |
+
})
|
| 106 |
+
|
| 107 |
+
return {
|
| 108 |
+
'cluster_id': self.config['cluster_id'],
|
| 109 |
+
'results': formatted_results,
|
| 110 |
+
'total_found': len(formatted_results)
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
def search_by_ids(
|
| 114 |
+
self,
|
| 115 |
+
ids: List[str],
|
| 116 |
+
return_embeddings: bool = False
|
| 117 |
+
) -> Dict:
|
| 118 |
+
"""Busca direta por ID(s)"""
|
| 119 |
+
|
| 120 |
+
# Buscar por IDs
|
| 121 |
+
try:
|
| 122 |
+
results = self.collection.get(
|
| 123 |
+
ids=ids,
|
| 124 |
+
include=['documents', 'metadatas', 'embeddings'] if return_embeddings
|
| 125 |
+
else ['documents', 'metadatas']
|
| 126 |
+
)
|
| 127 |
+
except Exception as e:
|
| 128 |
+
logger.error(f"Erro ao buscar IDs: {e}")
|
| 129 |
+
return {
|
| 130 |
+
'cluster_id': self.config['cluster_id'],
|
| 131 |
+
'results': [],
|
| 132 |
+
'not_found': ids,
|
| 133 |
+
'total_found': 0
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
# Formatar resposta
|
| 137 |
+
formatted_results = []
|
| 138 |
+
found_ids = set(results['ids'])
|
| 139 |
+
|
| 140 |
+
for i in range(len(results['ids'])):
|
| 141 |
+
result = {
|
| 142 |
+
'id': results['ids'][i],
|
| 143 |
+
'ementa': results['documents'][i]
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
if return_embeddings and 'embeddings' in results:
|
| 147 |
+
result['embedding'] = results['embeddings'][i]
|
| 148 |
+
|
| 149 |
+
formatted_results.append(result)
|
| 150 |
+
|
| 151 |
+
# IDs não encontrados
|
| 152 |
+
not_found = [id for id in ids if id not in found_ids]
|
| 153 |
+
|
| 154 |
+
return {
|
| 155 |
+
'cluster_id': self.config['cluster_id'],
|
| 156 |
+
'results': formatted_results,
|
| 157 |
+
'not_found': not_found,
|
| 158 |
+
'total_found': len(formatted_results)
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
def get_cluster_info(self) -> Dict:
|
| 162 |
+
"""Retorna informações do cluster"""
|
| 163 |
+
import os
|
| 164 |
+
|
| 165 |
+
# Calcular tamanho do ChromaDB
|
| 166 |
+
db_path = self.config['chromadb_path']
|
| 167 |
+
total_size = 0
|
| 168 |
+
for dirpath, dirnames, filenames in os.walk(db_path):
|
| 169 |
+
for f in filenames:
|
| 170 |
+
fp = os.path.join(dirpath, f)
|
| 171 |
+
total_size += os.path.getsize(fp)
|
| 172 |
+
|
| 173 |
+
db_size_mb = total_size / (1024 * 1024)
|
| 174 |
+
|
| 175 |
+
return {
|
| 176 |
+
'cluster_id': self.config['cluster_id'],
|
| 177 |
+
'chunk_range': [self.config['chunk_start'], self.config['chunk_end']],
|
| 178 |
+
'total_records': self.collection.count(),
|
| 179 |
+
'embedding_model': self.config['embedding_model'],
|
| 180 |
+
'embedding_dim': self.config['embedding_dim'],
|
| 181 |
+
'campos_disponiveis': self.config['campos_filter'],
|
| 182 |
+
'db_size_mb': round(db_size_mb, 2),
|
| 183 |
+
'status': 'ready'
|
| 184 |
+
}
|
rag_builder.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Constrói ChromaDB com embeddings a partir de JSONL filtrado
|
| 4 |
+
"""
|
| 5 |
+
import json
|
| 6 |
+
import yaml
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
import argparse
|
| 9 |
+
import chromadb
|
| 10 |
+
from sentence_transformers import SentenceTransformer
|
| 11 |
+
from tqdm import tqdm
|
| 12 |
+
import logging
|
| 13 |
+
|
| 14 |
+
logging.basicConfig(level=logging.INFO)
|
| 15 |
+
logger = logging.getLogger(__name__)
|
| 16 |
+
|
| 17 |
+
def build_chromadb(input_jsonl: str, config_path: str = 'config.yaml'):
|
| 18 |
+
"""Constrói ChromaDB a partir de JSONL"""
|
| 19 |
+
|
| 20 |
+
# Carregar config
|
| 21 |
+
with open(config_path) as f:
|
| 22 |
+
config = yaml.safe_load(f)
|
| 23 |
+
|
| 24 |
+
logger.info("="*80)
|
| 25 |
+
logger.info("🔧 CONSTRUINDO CHROMADB")
|
| 26 |
+
logger.info("="*80)
|
| 27 |
+
logger.info(f"Cluster ID: {config['cluster_id']}")
|
| 28 |
+
logger.info(f"Chunks: {config['chunk_start']} - {config['chunk_end']}")
|
| 29 |
+
logger.info(f"Embedding Model: {config['embedding_model']}")
|
| 30 |
+
|
| 31 |
+
# Carregar modelo de embedding
|
| 32 |
+
logger.info("\n📥 Carregando modelo de embedding...")
|
| 33 |
+
model = SentenceTransformer(config['embedding_model'])
|
| 34 |
+
logger.info(f"✅ Modelo carregado (dim={config['embedding_dim']})")
|
| 35 |
+
|
| 36 |
+
# Inicializar ChromaDB
|
| 37 |
+
logger.info(f"\n💾 Inicializando ChromaDB em {config['chromadb_path']}...")
|
| 38 |
+
client = chromadb.PersistentClient(path=config['chromadb_path'])
|
| 39 |
+
|
| 40 |
+
# Criar/obter collection
|
| 41 |
+
try:
|
| 42 |
+
collection = client.get_collection(config['collection_name'])
|
| 43 |
+
logger.info(f"⚠️ Collection '{config['collection_name']}' já existe! Apagando...")
|
| 44 |
+
client.delete_collection(config['collection_name'])
|
| 45 |
+
except:
|
| 46 |
+
pass
|
| 47 |
+
|
| 48 |
+
collection = client.create_collection(
|
| 49 |
+
name=config['collection_name'],
|
| 50 |
+
metadata={
|
| 51 |
+
"cluster_id": config['cluster_id'],
|
| 52 |
+
"chunk_start": config['chunk_start'],
|
| 53 |
+
"chunk_end": config['chunk_end']
|
| 54 |
+
}
|
| 55 |
+
)
|
| 56 |
+
logger.info(f"✅ Collection criada")
|
| 57 |
+
|
| 58 |
+
# Carregar registros
|
| 59 |
+
logger.info(f"\n📖 Carregando registros de {input_jsonl}...")
|
| 60 |
+
records = []
|
| 61 |
+
with open(input_jsonl) as f:
|
| 62 |
+
for line in f:
|
| 63 |
+
records.append(json.loads(line))
|
| 64 |
+
|
| 65 |
+
total = len(records)
|
| 66 |
+
logger.info(f"✅ {total:,} registros carregados")
|
| 67 |
+
|
| 68 |
+
# Processar em batches
|
| 69 |
+
batch_size = config['embedding_batch_size']
|
| 70 |
+
logger.info(f"\n🚀 Gerando embeddings em batches de {batch_size}...")
|
| 71 |
+
|
| 72 |
+
for i in tqdm(range(0, total, batch_size), desc="Embedding"):
|
| 73 |
+
batch = records[i:i+batch_size]
|
| 74 |
+
|
| 75 |
+
# IDs
|
| 76 |
+
ids = [str(r['id']) for r in batch]
|
| 77 |
+
|
| 78 |
+
# Documentos (usar ementa para embedding)
|
| 79 |
+
documents = [r.get('ementa', '') for r in batch]
|
| 80 |
+
|
| 81 |
+
# Metadatas
|
| 82 |
+
metadatas = [{'id': r['id']} for r in batch]
|
| 83 |
+
|
| 84 |
+
# Gerar embeddings
|
| 85 |
+
embeddings = model.encode(documents, show_progress_bar=False).tolist()
|
| 86 |
+
|
| 87 |
+
# Adicionar ao ChromaDB
|
| 88 |
+
collection.add(
|
| 89 |
+
ids=ids,
|
| 90 |
+
embeddings=embeddings,
|
| 91 |
+
documents=documents,
|
| 92 |
+
metadatas=metadatas
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
logger.info(f"\n✅ ChromaDB construído com sucesso!")
|
| 96 |
+
logger.info(f"📊 Total de registros: {collection.count():,}")
|
| 97 |
+
logger.info("="*80)
|
| 98 |
+
|
| 99 |
+
if __name__ == "__main__":
|
| 100 |
+
parser = argparse.ArgumentParser()
|
| 101 |
+
parser.add_argument('--input', required=True, help='JSONL filtrado')
|
| 102 |
+
parser.add_argument('--config', default='config.yaml')
|
| 103 |
+
args = parser.parse_args()
|
| 104 |
+
|
| 105 |
+
build_chromadb(args.input, args.config)
|