Spaces:
Running
Running
File size: 24,511 Bytes
675b76b ff9a8d0 675b76b ff9a8d0 675b76b ff9a8d0 675b76b 4776660 675b76b ff9a8d0 675b76b ff9a8d0 675b76b ff9a8d0 675b76b ff9a8d0 675b76b 4776660 675b76b ff9a8d0 675b76b ff9a8d0 675b76b 4776660 675b76b 4776660 247f40d 675b76b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | # PromptCraft / Prompteador — Informe de arquitectura (KB)
**Fecha**: 2026-07-02/03 · **Repo**: `huggingface.co/spaces/Malaji71/Prompteador` · **Clon local**: `~/Desktop/Prompteador`
**Rango de commits de esta reconstrucción**: `f467051` (estado heredado) → `22f26d9` (índice 100k)
Documento de referencia para sesiones futuras: qué había, qué se cambió y por qué, con los nombres exactos de clases, funciones y constantes.
---
## 1. Estado inicial (lo que había)
Aplicación Gradio en un Space privado de HF que refinaba prompts de imagen desde castellano. Estructura plana: `app.py`, `agent.py`, `semantic_database.py`, `faiss_index.bin`, `metadata.json`.
### 1.1 Componentes originales
- **`app.py` — `LlamaRefiner`**: pipeline de 1 sola llamada LLM. Traducción ES→EN con `Helsinki-NLP/opus-mt-es-en` (Inference API), recuperación FAISS, y una llamada a `Qwen/Qwen2.5-7B-Instruct:together` (vía router `https://router.huggingface.co/v1`) que debía hacer dos trabajos incompatibles a la vez: creatividad con el léxico y formato estricto.
- **`app.py` — `SDXLGenerator`**: generación de imagen con `stabilityai/stable-diffusion-xl-base-1.0` vía Inference API. *(Eliminado después por decisión del dueño: caro y de mala calidad.)*
- **`agent.py` — `ImprovedSemanticAgent`**: FAISS (`IndexFlatIP`) + `BAAI/bge-small-en-v1.5`. Cargaba un `CrossEncoder` (`cross-encoder/ms-marco-MiniLM-L6-v2`) que **nunca se usaba** (~90 MB de RAM muertos). El "patrón estructural" era un conteo de comas que elegía entre **4 plantillas fijas** con etiquetas tipo `Opening/Pose → Subject/Core → ...`.
- **`semantic_database.py`**: 541 líneas de caché SQLite que **nadie importaba** (código muerto). Eliminado.
- **README**: corrupto — contenía un transcript de chat pegado ("¡Claro, socio!...") y documentaba un modelo equivocado (LLaMA-3.2 cuando el código usaba Qwen).
### 1.2 Bugs y problemas del estado inicial
1. **Bug principal (`too_long`)**: el system prompt mostraba al LLM el template con etiquetas; Qwen las copiaba en el output ("Opening/Pose: ..."), `_clean_output` no las quitaba, el conteo de palabras se inflaba y el validador (umbral 40 palabras) rechazaba → fallback semántico pobre casi siempre.
2. **Filtro de léxico contradictorio**: solo pasaban fragmentos de 5–15 caracteres, pero las `SAFE_ATMOSPHERIC_FRAGMENTS` medían 25–44 → la priorización de frases seguras era inalcanzable.
3. **Categorías ficticias**: la UI ofrecía categorías y el código filtraba por `ex.get('category')` — **ese campo nunca existió en `metadata.json`** (claves reales: `caption`, `field` constante, `example_id`). Con categoría manual seleccionada, el filtro devolvía 0 candidatos.
4. **Índice incompleto y con receta inconsistente**: `faiss_index.bin` contenía **solo 10.000 vectores** de los 100.000 captions del metadata (el log "100,000 ejemplos cargados" contaba el JSON). Además, re-embeber esos mismos captions con bge-small daba cosenos de 0.75–0.89 contra los vectores almacenados → el índice original se construyó con **otra receta** que no casaba con la query.
5. **Requirements sin wheels para Python 3.13** (`sentencepiece>=0.1.99` podía resolver a 0.2.0 sin wheel cp313), falta de `ssr_mode=False`, sin prefijo BGE en queries.
---
## 2. Arquitectura actual: la Y (tri-LLM) con profundidad adaptativa
### 2.1 Flujo completo
```
input del usuario (ES o EN)
│
├─ LlamaRefiner._looks_english() → si es inglés, opus-mt se salta
├─ LlamaRefiner._translate_to_english() → opus-mt-es-en, con _translation_cache
│
├─ y_refiner.is_elaborate(user_en) → régimen:
│ compose (idea simple) | polish (prompt ya trabajado)
│
├─ ImprovedSemanticAgent.extract_pattern_and_lexicon(user_en, k=6, explore=bool(variación))
│ ├─ clasificación interna de la query (CATEGORY_PROTOTYPES, margen ≥ CATEGORY_MARGIN=0.03)
│ ├─ FAISS top-50 → filtro por etiqueta auto-generada (si detección confiada y ≥8 vecinos)
│ ├─ CrossEncoder rerank (30 pares → top-15) [con _retrieval_cache por concepto]
│ ├─ selección: MMR λ=0.7 (1ª generación) | muestreo ponderado por rango (regeneraciones)
│ └─ _grammar_profile(examples) → spec gramatical medida (spaCy por cláusula)
│
├─ EN PARALELO (ThreadPoolExecutor, max_workers=2):
│ α zai-org/GLM-5.2:deepinfra → canónico: spec + léxico (solo α ve el RAG)
│ γ moonshotai/Kimi-K2.6:deepinfra → concreto: SOLO el concepto, quality-tags prohibidos
│
├─ N deepseek-ai/DeepSeek-V4-Flash:deepinfra → compilador (compose) o editor (polish)
│
└─ contrato (YRefiner._check_contract) → cadena de degradación:
N intento 1 → N intento 2 → α/γ directo (y_direct) → 1 LLM (degraded_fast) → fallback semántico
```
### 2.2 `y_refiner.py` — nombres clave
- **`MODEL_REGISTRY`**: dict por rol (`alpha`, `gamma`, `normalizer`, `fast`) con cadenas `[primario, fallback...]`, `pricing` (USD/1M in-out) y `extra` (params de body por modelo).
- **`NODE_PROMPTS`**: 5 system prompts — `alpha`, `gamma`, `normalizer` (modo componer) + `alpha_polish`, `normalizer_polish` (modo pulir). γ no tiene variante polish (nunca ve el dataset).
- **`call_with_fallback(client, role, messages, max_tokens, temperature, timeout)`**: recorre la cadena del rol. 1 retry con backoff solo en transitorios (429/5xx/timeout); fallos deterministas (4xx, respuesta vacía `_EmptyResponse`) saltan al siguiente modelo. **Circuit breaker de sesión** (`_BREAKER_THRESHOLD=3` fallos consecutivos → entrada saltada hasta que vuelva a responder). Si un provider rechaza `extra` con 400 "Extra inputs", reintenta sin ellos.
- **`is_elaborate(text_en)`**: detector de régimen — `(≥25 palabras y ≥3 cláusulas) o ≥2 CRAFT_TERMS o ≥40 palabras`. `CRAFT_TERMS` = vocabulario de oficio visual ("golden hour", "85mm", "depth of field"...).
- **`VARIATION_ANGLES`**: 8 lentes (encuadre/luz/tono) que rotan en regeneraciones.
- **`YRefiner`**:
- `refine_y(prompt_es, category="auto", progress_cb=None)` — pipeline principal.
- `self.seen` — memoria anti-repetición por concepto (clave = texto normalizado); `variation = len(seen[key])` decide ángulo y temperaturas (compose: α/γ 0.7→0.95, N 0.35→0.75; polish: N 0.5→0.8, reintento SIEMPRE más caliente).
- `_concept_block()` — adjunta el **castellano original** a los 3 nodos (`ORIGINAL (Spanish): ...`) para reparar artefactos de opus-mt.
- `_check_contract(user_en, raw, polish)` — limpieza + validación; en polish añade el guardarraíl **`no_edit`**: jaccard de palabras output/input > 0.75 = incumplimiento (la edición nula era el óptimo del contrato de fidelidad v1 — lección clave).
- Degradación parcial: si cae α o γ, el superviviente ocupa ambos slots de N (la prohibición "no copies verbatim" fuerza reescritura).
- Telemetría: `self.session` (agregados: `n_ok_first/retry`, `y_direct`, `degraded_fast`, `fallback_sem`, `cost_total_usd`) + `self.records` (deque 200: latencias por nodo, `failed_node`, `final_step`, `depth`, `category`, coste).
- **`check_catalog(client=None)`**: al arrancar, 1 GET a `router.huggingface.co/v1/models` (vía `requests`; `urllib` recibe 403 de Cloudflare) y log de **cuántas entradas de CADA cadena** siguen en catálogo (`"rol: N/M entradas en catálogo"`), con WARNING nominal de las que faltan y ERROR si una cadena entera desaparece. Con **`STARTUP_PROBE=1`** añade un probe de facturación: 1 llamada real (`max_tokens=5`, `temperature=0`) al primer modelo de cada rol, con WARNING `"en catálogo pero NO facturable"` ante 401/402/403. El catálogo miente sobre facturación (fireworks salía `live` y devolvía 402), solo la llamada real lo distingue. Ningún fallo del probe impide el arranque.
- **Constantes**: `TIMEOUTS` (α/γ 30s, N 20s), `GLOBAL_BUDGET_S=75`, `MAX_TOKENS=300` los tres nodos (160 truncaba pulidos largos).
### 2.3 `agent.py` — nombres clave
- **`BGE_QUERY_PREFIX`**: `"Represent this sentence for searching relevant passages: "` — SOLO en queries; los documentos se embebieron sin prefijo.
- **Auto-etiquetado del índice** (en `_lazy_init`): `CATEGORY_PROTOTYPES` (10 categorías: portrait, landscape, urban, object, fantasy, animal, interior, food, graphic, text; 1–2 frases prototipo cada una) → se embeben los prototipos, se leen los vectores YA almacenados con `index.reconstruct_n` (por bloques de 100k) y `caption_labels = argmax(vecs @ protos.T)`. Instantáneo, sin re-embeber. Distribución del índice en producción: ver §3.1.
- **Clasificación de query**: embedding sin prefijo vs prototipos; si `margen top1−top2 ≥ CATEGORY_MARGIN (0.03)` → filtra los hits FAISS a esa etiqueta (mínimo 8 vecinos o se relaja). Si es ambiguo (dragón: ¿fantasy o animal?) **se abstiene** → recuperación semántica pura.
- **`_retrieval_cache`**: `(concepto normalizado) → (query_emb, top-15 rerankeado, categoría)`. Las regeneraciones solo re-muestrean (agente: ~3s → ~0.5s).
- **`_grammar_profile(examples)`**: mide la sintaxis real del cluster — cláusulas partidas por `[,;.]`, mediana de cláusulas (4–8), y por slot posicional: forma dominante (`_analyze_clause` con spaCy → `SHAPE_DESCRIPTIONS`: NP/ING/PP/ADJ/SENT), longitud media y una cláusula real de muestra (que debe coincidir con la forma elegida). Sustituye a las 4 plantillas fijas.
- **`_mmr_select(query_emb, candidates, k, lam=0.7)`**: Maximal Marginal Relevance con embeddings del propio índice (reconstruct; fallback re-encode).
- **`_filter_and_score`**: fragmentos de léxico 6–48 chars, sin conjunción inicial, sin meta-lenguaje de caption ("this image...", "photo"), sin PROPN largos, contra `CONTENT_BLACKLIST`.
- **Warmup** en `_lazy_init` (encode + rerank dummy) — el primer usuario ya no paga ~10s de carga.
### 2.4 `app.py` — nombres clave
- **`LlamaRefiner`** (conserva el nombre histórico; hoy es la infraestructura compartida + el escalón `fast`):
- `openai_client = OpenAI(base_url=router, max_retries=0)` — **crítico**: los retries internos del SDK se apilaban con nuestra cadena y un nodo caído tardaba ~90s en fallar.
- `_translate_to_english` con `_translation_cache` y `_looks_english` (heurística de stopwords EN vs ES).
- `_clean_output`: strip de bloques `<think>` (y apertura truncada), prefijos ("Here is"), **`_SECTION_LABEL_RE`** (etiquetas de 1 o 2 palabras: "Opening/Pose:", "Secondary Detail:", "Environmental Frame:"), flechas→comas, bullets, comas dobles/colgantes, y cadencia de fragmentos (". " entre oraciones → ", ", sin punto final).
- `_validate_output`: mínimo 10 chars, concepto presente (palabras >3 letras del user_en), términos prohibidos. **SIN límite de longitud** (decisión del dueño: los encoders modernos no se degradan; se eliminó `too_long`).
- `_call_llm` usa `call_with_fallback(role="fast")` — el modo rápido también tiene cadena de fallback.
- **UI mínima**: textbox de idea + botón "Refinar prompt" + prompt refinado + referencia estructural + estado/métricas. Sin categorías, sin proporciones, sin generador de imagen, sin banderas ni emojis en labels. Cabecera: solo "PromptCraft v3.1 / Compositional Transposition Engine."
### 2.5 Modelos y precios (reconstruido y verificado con llamadas reales, 2026-08-26)
**Incidente que forzó la reconstrucción**: todos los primarios iban por `:fireworks-ai` y devolvían **HTTP 402** `"Pay-as-you go is not enabled for provider fireworks-ai yet"` — no era falta de saldo ($17.84 en créditos), HF no puede facturar el overage por esa vía. Y casi toda la cadena de fallback estaba muerta: `GLM-5.1:together`, `Kimi-K2.5:fireworks-ai`, `Kimi-K2.6:together`, `DeepSeek-V4-Flash:fireworks-ai` → **410 deprecated**; `Llama-3.3-70B-Instruct:groq` → **404**; `Qwen2.5-7B-Instruct:together` → **400 non-serverless**. **`:fireworks-ai` queda prohibido en el registry hasta nuevo aviso.**
| Rol | Primario | Fallbacks | Precio in/out por 1M |
|---|---|---|---|
| α | `zai-org/GLM-5.2:deepinfra` | GLM-5.2:novita → GLM-5.2:baseten | $0.75/$2.40 |
| γ | `moonshotai/Kimi-K2.6:deepinfra` | Kimi-K2.6:novita → Kimi-K2.6:baseten | $0.75/$3.50 |
| N | `deepseek-ai/DeepSeek-V4-Flash:deepinfra` | DeepSeek-V4-Flash-0731:together → DeepSeek-V3.2:novita | $0.09/$0.18 |
| fast | `Qwen/Qwen3.5-9B:deepinfra` | Qwen3.5-9B:ovhcloud → Qwen2.5-72B-Instruct:novita | $0.10/$0.15 |
Las 12 entradas están verificadas con **una llamada real al router** (200 + `content` no vacío): el catálogo no basta, decía `live` para fireworks. Cada cadena usa **3 providers distintos** para que un corte de facturación de un provider no tumbe el rol entero. Descartadas por devolver 200 con `content` vacío (no honran `reasoning_effort`): `GLM-5.2:together`, `GLM-5:novita`, `DeepSeek-V4-Flash:novita`, `Qwen3-32B:nscale`; y `Qwen3.5-9B:together` por 400 `Input validation error`.
**Coste real medido** (pasada Y completa, compose, 1ª generación, prompts reales): **$0.0012** — α $0.00036 + γ $0.00078 + N $0.00006. Es ~2× más barato que el registry anterior en fireworks (α bajó de $1.40/$4.40 a $0.75/$2.40 y N de $0.14/$0.28 a $0.09/$0.18). Con los $2/mes de créditos Pro: **~1.600 refinamientos/mes** (las regeneraciones y el modo pulido suben algo por más tokens de input). **Latencia medida**: α ~1,5 s, γ ~8,5 s, N ~2,9 s.
## 3. Dataset e índice
### 3.1 RAG v2 — el índice en producción (commit `020c862`, 2026-07-03)
**Arquitectura de bucket**: el índice vive FUERA del Space, en el repo dataset **privado** `Malaji71/prompteador-rag`:
- `index_v2.faiss` — 706 MB, IndexFlatIP **459.718 × 384**, bge-small-en-v1.5 normalizado sin prefijo.
- `captions_v2.parquet` — 228 MB, columnas `caption` + `source_prompt`.
El Space los descarga al arrancar (`ImprovedSemanticAgent._load_rag_v2()`, env `RAG_REPO`, default `Malaji71/prompteador-rag`) con `hf_hub_download` y el secreto `HF_TOKEN`. **La descarga intra-datacenter tarda ~3 segundos** (medido: 318 MB/s) — el bucket no cuesta tiempo de arranque. Fallback automático al índice local del repo si el bucket no responde.
**Fuente**: `Photoroom/midjourney-v6-recap` (marzo 2026, MIT) — 1,23M imágenes **Midjourney v6** re-capturadas por 3 VLMs (`llava`, `gemini`, `qwen3`). Se usa la columna **`qwen3`** (Qwen3-VL, la de mayor calidad). Clave del enfoque: las recaptions VLM describen la imagen, así que **por construcción no contienen nombres de artistas ni parámetros MJ** (requisito ético del dueño), y declaran el medio ("photograph" vs "painting") — lo que da un filtro de fotorealismo gratuito.
**Curación** (1,23M → 459.718, ~52% de supervivencia, validada primero sobre muestra de 300 vía datasets-server):
1. `qwen3` no nulo y >150 chars.
2. Fotorealismo: descartar si las primeras ~200 chars declaran medio artístico (painting/illustration/anime/3d render/...) sin marcador fotográfico.
3. Veto temático (requisito del dueño: NO fantasy/cyberpunk): cyberpunk, dystopian, dragon, elf, wizard, sci-fi, futuristic, robot, zombie, superhero...
4. Cero referencias de estilo: patrón `in the style of` / `style of [A-Z]`.
5. Longitud 40-160 palabras; dedup exacto normalizado; máximo 2 variantes por prompt original (el dataset trae variantes de grid de un mismo prompt).
**Distribución auto-etiquetada v2**: object 26%, portrait 20%, interior 15%, landscape 14%, animal 12,5%, urban 10%, food 1,3%, fantasy 0,6%, graphic 0,2%, text 0,1%. (Las categorías débiles del v1 —animal 3%, urban 3,6%— ahora tienen masa real.)
**Build**: HF Jobs `t4-small`, `--timeout 2h` (~45 min reales). El parquet fuente pesa **193 GB** (imágenes embebidas) — imprescindible leer solo columnas de texto con **DuckDB sobre `hf://`** (proyección de columnas → ~2 GB transferidos). El job crea el repo destino (`create_repo(exist_ok=True)`) y sube con `upload_file`. Script: `build_rag_v2.py` (PEP-723 inline deps: sentence-transformers, faiss-cpu, duckdb, pyarrow, torch).
**Datasets descartados en el scouting** (para no repetir la evaluación): `vivym/midjourney-prompts` (9M pero era v5 — obsoleto según el dueño), `suriyagunasekar/midjourney-prompts` (2026 pero prompts crudos de Discord: artistas, celebrities, --params, cyberpunk — todo lo vetado), `Ashenone3/Midjourney-23M` (webdataset con imágenes, mixto).
### 3.2 Ficheros legacy en el repo del Space (solo fallback)
- **`metadata.json`** (100k, LFS): `{"caption", "field" (constante), "example_id"}`. Sin etiquetas de categoría.
- **`faiss_index.bin`** (147 MB, LFS, commit `22f26d9`): IndexFlatIP 100k × 384, reconstruido en T4 (el índice pre-existente tenía solo 10k vectores y otra receta de embedding, cosenos 0.75-0.89 vs re-embedding). Candidatos a borrarse del repo del Space cuando el bucket se considere estable.
---
## 4. Decisiones de diseño y lecciones (con contexto)
1. **`reasoning_effort: "none"` es obligatorio** para los híbridos 2026 del router (GLM-5.2, Kimi-K2.6/2.5, DeepSeek-V4, Qwen3.5): sin él, el razonamiento inline consume TODO el max_tokens y `content` sale vacío o lleno de deliberación. El router **rechaza** `chat_template_kwargs` y `reasoning.enabled` (400 "Extra inputs"). **El flag se honra por provider, no por modelo**: el mismo Kimi-K2.6 lo respeta en deepinfra/novita/baseten y no en together; GLM-5.2 lo respeta en deepinfra/novita/baseten y no en together. Cualquier entrada nueva se prueba con una llamada real antes de entrar al registry.
2. **`max_retries=0` en el cliente OpenAI**: los reintentos son de `call_with_fallback`, no del SDK — si no, se multiplican.
3. **Sin límite de longitud**: FLUX/MJ/SD3 (T5/LLM encoders) no se degradan con prompts largos. `too_long` eliminado del validador; la cadencia la marca la spec gramatical, no un techo.
4. **Fidelidad ≠ identidad** (lección del modo pulido v1): un contrato que dice "conserva la redacción, no pierdas nada" tiene como óptimo la **edición nula** — el sistema devolvía el input intacto. Arreglo: se protege el *inventario* (objetos/acciones/relaciones/tono) y el orden, nunca la redacción; guardarraíl `no_edit` (jaccard >0.75) y reintento a temperatura MÁS alta. Regla general: *cada regla nueva en un prompt puede tener un óptimo degenerado que nadie pidió*.
5. **Clasificación con abstención**: filtrar por categoría solo con margen ≥0.03; en ambigüedad real (dragón, taza de café) mejor no filtrar que filtrar mal.
6. **El castellano original viaja a los nodos**: opus-mt (2020, pequeño) comete errores ("norays" sin traducir, "pescadora"→"fisherman", "bicycle horn"→"bike horny" al recibir inglés); GLM/Kimi/DeepSeek entienden castellano y reparan con el original como fuente de verdad. *Deuda conocida*: opus-mt es candidato a jubilación total (traducir con uno de los LLMs del trío o pasar castellano directo; solo FAISS necesita inglés).
7. **Variación de regeneraciones** = 4 palancas juntas: muestreo estocástico del top-15 (`explore=True`), ángulo rotado (`VARIATION_ANGLES`), anti-repetición (`seen`, hasta 3 versiones previas inyectadas a α/γ/N), temperaturas altas. Resultado medido: Jaccard entre regeneraciones 0.19–0.43 (antes ~0.85).
---
## 5. Gotchas operativos (para no re-descubrirlos)
- **Probar el router con `urllib` da 403 de Cloudflare** (bloqueo por user-agent de Python). Con `requests` (user-agent propio) o con el SDK de OpenAI funciona. No es problema de cuenta ni de provider. `check_catalog()` usa `requests` por esto.
- **El catálogo del router NO dice si un provider te factura**: fireworks-ai figuraba `status: "live"` mientras devolvía 402 en cada llamada. Un provider "live" puede estar cortado para tu cuenta. La única verificación válida es una llamada real (de ahí `STARTUP_PROBE=1`).
- **`content` vacío con `out=max_tokens` = el provider ignora `reasoning_effort`**: el thinking se comió el presupuesto. Es un fallo de *serving*, no de modelo — el mismo modelo en otro provider suele ir bien.
- **HF Jobs requiere token con permiso `job.write`** — el token fine-grained cacheado en `~/.cache/huggingface/token` no lo tiene; el dueño tiene uno aparte con Jobs habilitado.
- **Gradio 6**: `show_copy_button` ya no existe en `Textbox` (crasheó un deploy). `ssr_mode=False` en `launch()` evita "Could not get API info".
- **`huggingface_hub>=1.0` rompe `transformers`** (exige <1.0) — cuidado al actualizar el hub para usar `hf jobs` en un entorno compartido con el runtime del Space.
- **Python 3.13 en Spaces**: `sentencepiece` solo tiene wheel cp313 en 0.2.1 exacto; spacy ≥3.8.2; torch ≥2.6. El Mac local es Intel (torch macOS x86_64 muere en 2.2.2) — los tests locales van en venv del scratchpad con `--system-site-packages`.
- **Logs del Space**: httpx silenciado a WARNING y `show_progress_bar=False` en todos los encode/predict — si vuelven las 40 líneas por refinamiento, algo re-activó el logger.
- **El RAG v2 es deliberadamente débil en fantasy (0,6%), graphic (0,2%) y text (0,1%)** — decisión del dueño (interés: fotorealismo). Conceptos de esas categorías caen en recuperación semántica sin filtro (el mínimo de 8 vecinos relaja el filtro solo) y el léxico será menos específico. Si algún día se quiere cubrir ilustración/fantasy, sería un índice aparte, no contaminar este.
- **Leer datasets HF con imágenes embebidas**: comprobar SIEMPRE el peso real con `datasets-server.huggingface.co/size` antes de `load_dataset` — `Photoroom/midjourney-v6-recap` pesa 193 GB. La proyección de columnas (DuckDB `hf://` o pyarrow) reduce la lectura a solo las columnas pedidas.
- **Muestrear sin descargar**: `datasets-server.huggingface.co/rows?dataset=...&offset=N&length=100` permite validar filtros de curación sobre muestras antes de gastar GPU.
---
## 6. Historial de commits de la reconstrucción
| Commit | Contenido |
|---|---|
| `35caeb4` | Arquitectura Y inicial + fix bug `too_long` + BGE prefix + reranker activado + MMR + requirements py3.13 + README limpio + borrado `semantic_database.py` |
| `a4ac9b2` | Y como único motor (sin toggle), cabecera mínima |
| `5faf28e` | γ primario a fireworks (together no honra `reasoning_effort`) |
| `7d8aef4` | Etiquetas de 2 palabras en `_clean_output`, léxico sin basura, escalón `y_direct` |
| `8092b99`/`159c513` | Fix crash Gradio 6 (`show_copy_button`), UI sin banderas/emojis |
| `018e73b` | v3.2: spec gramatical medida (spaCy), variación en regeneraciones, sin generador SDXL, sin categorías manuales, sin límite de longitud |
| `03bfd54` | `max_retries=0`, caché de traducción, caché de recuperación, logs limpios |
| `97e6f60` | Profundidad adaptativa (`is_elaborate`, prompts `*_polish`, castellano a los nodos) |
| `5450bb6` | Guardarraíl `no_edit`, max_tokens 300, detección de inglés |
| `e9a071c` | Auto-etiquetado del índice + clasificación interna de queries con abstención |
| `22f26d9` | **Índice reconstruido: 100k vectores en GPU (HF Jobs/T4), receta consistente** |
| `675b76b` | ARCHITECTURE.md (este documento) |
| `020c862` | **RAG v2: 459.718 captions fotorealistas MJ v6 desde el repo-bucket `Malaji71/prompteador-rag`** |
| `ff9a8d0` | **Registry sin fireworks (402 en toda la cuenta): 12 entradas verificadas con llamada real, 3 providers distintos por rol, `check_catalog` sobre la cadena entera + `STARTUP_PROBE`** |
---
## 7. Cómo verificar (patrones de test usados)
- **Unit del contrato**: mockear `gradio/openai/huggingface_hub/agent/y_refiner` en `sys.modules`, instanciar `LlamaRefiner` con `object.__new__`, y ejercitar `_clean_output`/`_validate_output` con los casos canónicos: etiquetas 1 y 2 palabras, flechas, `<think>`, "Here is", comas colgantes, frases con punto.
- **Integración local**: venv del scratchpad (`--system-site-packages` + spacy), `HF_TOKEN` del cache, `cwd` en el repo (FAISS relativo). Medir: `final_step`, latencias por nodo (`y.records[-1]['lat']`), jaccard entre regeneraciones (<0.5), preservación de inventario en polish (>85% de content words), coste (`y.session['cost_total_usd']`).
- **Degradación**: sustituir temporalmente `MODEL_REGISTRY[rol]` por un modelo inexistente y verificar el escalón esperado.
- **Router**: catálogo en `https://router.huggingface.co/v1/models` (público) — precios, providers live, ttft y throughput por provider.
|