openprompt-lang 1.2.6 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -8
- package/docs/EMBEDDINGS.md +214 -0
- package/docs/FRAMEWORK.md +52 -0
- package/docs/ONBOARDING_WORKFLOW.md +151 -0
- package/docs/OPL-ERRORES.md +504 -0
- package/docs/OPL_ACADEMIC_ISSUES.md +158 -0
- package/docs/WEB_SCRAPER_PLAN.md +454 -0
- package/package.json +7 -1
- package/scripts/postinstall.js +37 -0
- package/src/cli/commands-knowledge.js +1 -0
- package/src/cli/commands-opl.js +79 -1
- package/src/cli/commands-work.js +3 -1
- package/src/cli/commands-workflow.js +125 -6
- package/src/commands/init-core.js +188 -12
- package/src/commands/init-existing.js +13 -6
- package/src/commands/init-helpers.js +20 -14
- package/src/commands/knowledge-ops.js +52 -0
- package/src/commands/opl-embeddings.js +556 -0
- package/src/commands/opl-help.js +26 -2
- package/src/commands/opl-search.js +106 -2
- package/src/commands/opl-webscrape.js +390 -0
- package/src/commands/work-context.js +17 -0
- package/src/commands/workflow/close/index.js +2 -1
- package/src/commands/workflow/delivery/index.js +4 -0
- package/src/commands/workflow/discovery/index.js +4 -0
- package/src/commands/workflow/epic-cli.js +192 -0
- package/src/commands/workflow/select.js +146 -0
- package/src/commands/workflow/specification/index.js +4 -0
- package/src/commands/workflow/sprint-cli.js +174 -0
- package/src/core/engine/sandbox.js +7 -3
- package/src/core/webscrape/analyzer.js +481 -0
- package/src/core/webscrape/deep-scraper.js +1027 -0
- package/src/core/workflow/epic-manager.js +845 -0
- package/src/core/workflow/gates.js +180 -1
- package/src/core/workflow/selector.js +707 -0
- package/src/embeddings/chunker.js +450 -0
- package/src/embeddings/embedder.js +431 -0
- package/src/embeddings/index-pipeline.js +320 -0
- package/src/embeddings/vector-store.js +505 -0
- package/src/mcp-plan-server.js +12 -5
- package/src/mcp-shared-state.js +25 -0
- package/src/mcp-refactor/mcp-server.js +0 -171
- package/src/mcp-server-backup.js +0 -1913
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
# Web Scraper Evolution — Plan Ollama-Driven
|
|
2
|
+
|
|
3
|
+
> **Versión:** 2.0 (revisado)
|
|
4
|
+
> **Objetivo:** Reemplazar `opl webscrape` (Readability.js + Turndown) por un extractor
|
|
5
|
+
> **inteligente asistido por Ollama** que analiza la estructura de cada página y genera
|
|
6
|
+
> un plan de extracción personalizado.
|
|
7
|
+
>
|
|
8
|
+
> **Stack:** 100% Node.js + Ollama (local) → Sin dependencia de Python.
|
|
9
|
+
> **Principio:** Cada página es única → cada página merece un plan de extracción único.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Filosofía: Por qué IA en vez de Reglas Fijas
|
|
14
|
+
|
|
15
|
+
### Enfoque tradicional (hardcoded)
|
|
16
|
+
```python
|
|
17
|
+
# Reglas fijas que fallan en el 50% de las páginas
|
|
18
|
+
extract_og_tags(html)
|
|
19
|
+
extract_metadata(html)
|
|
20
|
+
extract_articles(html)
|
|
21
|
+
extract_tables(html)
|
|
22
|
+
```
|
|
23
|
+
❌ Cada sitio tiene markup distinto → reglas imposibles de mantener
|
|
24
|
+
❌ No entiende el **significado** de las secciones
|
|
25
|
+
❌ No distingue contenido principal de navegación/publicidad
|
|
26
|
+
❌ Cada nuevo tipo de página requiere más reglas
|
|
27
|
+
|
|
28
|
+
### Enfoque Ollama-driven
|
|
29
|
+
```
|
|
30
|
+
fetch(url) → HTML + layout estructurado
|
|
31
|
+
↓
|
|
32
|
+
🧠 Ollama analiza: "¿qué hay aquí?"
|
|
33
|
+
↓
|
|
34
|
+
← plan de extracción JSON ←
|
|
35
|
+
↓
|
|
36
|
+
Ejecuta el plan (selectores dinámicos)
|
|
37
|
+
↓
|
|
38
|
+
knowledge/<domain>/<slug>/
|
|
39
|
+
```
|
|
40
|
+
✅ Se adapta a cualquier página
|
|
41
|
+
✅ Entiende semántica del contenido
|
|
42
|
+
✅ Decide QUÉ extraer según el tipo de página
|
|
43
|
+
✅ Sin reglas hardcodeadas — el LLM decide
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 2. Arquitectura General
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
51
|
+
│ Input │
|
|
52
|
+
│ url → fetchPage(url) │
|
|
53
|
+
│ ├── simple: node-fetch (rápido, HTML estático) │
|
|
54
|
+
│ └── headless: @playwright (SPA, JS-heavy, lazy-load) │
|
|
55
|
+
│ │
|
|
56
|
+
│ Resultado: PageSnapshot { html, text, links, meta, ...} │
|
|
57
|
+
└──────────────────┬───────────────────────────────────────────────┘
|
|
58
|
+
↓
|
|
59
|
+
┌──────────────────┴───────────────────────────────────────────────┐
|
|
60
|
+
│ PASO 1: 🧠 Escaneo con Ollama (analyzePage) │
|
|
61
|
+
│ │
|
|
62
|
+
│ Envía a Ollama: │
|
|
63
|
+
│ - Título de la página │
|
|
64
|
+
│ - Meta tags básicos │
|
|
65
|
+
│ - HTML estructural resumido (árbol de secciones, no el HTML │
|
|
66
|
+
│ completo — eso sería demasiado tokens) │
|
|
67
|
+
│ - Texto visible (primeros ~2000 chars) │
|
|
68
|
+
│ - Cantidad y tipos de elementos (cuántos <article>, <section>, │
|
|
69
|
+
│ <table>, <img>, <form>, cards, listas, etc.) │
|
|
70
|
+
│ │
|
|
71
|
+
│ Ollama responde: │
|
|
72
|
+
│ { │
|
|
73
|
+
│ pageType: "article" | "product" | "docs" | "blog" | │
|
|
74
|
+
│ "catalog" | "landing" | "profile" | "generic", │
|
|
75
|
+
│ title: "Título detectado", │
|
|
76
|
+
│ description: "Descripción detectada", │
|
|
77
|
+
│ contentBlocks: [ │
|
|
78
|
+
│ { │
|
|
79
|
+
│ type: "main-content" | "sidebar" | "nav" | "footer" | │
|
|
80
|
+
│ "cards" | "table" | "form" | "gallery", │
|
|
81
|
+
│ selector: "main article", ← selector CSS sugerido │
|
|
82
|
+
│ importance: 1-10, ← qué tan importante │
|
|
83
|
+
│ extractMode: "markdown" | "json" | "table" | "cards", │
|
|
84
|
+
│ description: "El artículo principal con su contenido" │
|
|
85
|
+
│ } │
|
|
86
|
+
│ ], │
|
|
87
|
+
│ metadata: { │
|
|
88
|
+
│ og: true, twitter: true, jsonld: true, microdata: false │
|
|
89
|
+
│ }, │
|
|
90
|
+
│ specialFeatures: ["scroll-infinity", "load-more", │
|
|
91
|
+
│ "pagination", "tabs", "accordion"], │
|
|
92
|
+
│ extractionPlan: { │
|
|
93
|
+
│ steps: ["extract-main-content", "extract-og", │
|
|
94
|
+
│ "extract-images", "extract-tables"], │
|
|
95
|
+
│ outputFormat: "markdown+json" │
|
|
96
|
+
│ } │
|
|
97
|
+
│ } │
|
|
98
|
+
└──────────────────┬───────────────────────────────────────────────┘
|
|
99
|
+
↓
|
|
100
|
+
┌──────────────────┴───────────────────────────────────────────────┐
|
|
101
|
+
│ PASO 2: Ejecutar el plan (executePlan) │
|
|
102
|
+
│ │
|
|
103
|
+
│ Según el plan de Ollama, ejecuta SOLO los extractores │
|
|
104
|
+
│ necesarios para esta página: │
|
|
105
|
+
│ │
|
|
106
|
+
│ ├── main-content: Readability.js + limpieza │
|
|
107
|
+
│ ├── og/twitter/jsonld: extractores específicos (jsdom) │
|
|
108
|
+
│ ├── images: src, alt, caption, dimensiones │
|
|
109
|
+
│ ├── tables: caption + headers + rows (markdown/JSON) │
|
|
110
|
+
│ ├── cards: tarjetas de producto/artículo/post │
|
|
111
|
+
│ ├── forms: action, method, inputs │
|
|
112
|
+
│ └── special: auto-scroll, load-more, pagination │
|
|
113
|
+
│ │
|
|
114
|
+
│ Resultado: ExtractedPage { ... } │
|
|
115
|
+
└──────────────────┬───────────────────────────────────────────────┘
|
|
116
|
+
↓
|
|
117
|
+
┌──────────────────┴───────────────────────────────────────────────┐
|
|
118
|
+
│ PASO 3: Post-procesamiento (postProcess) │
|
|
119
|
+
│ │
|
|
120
|
+
│ ├── Convertir a markdown estructurado │
|
|
121
|
+
│ ├── Clasificar contenido (dominio OPL) │
|
|
122
|
+
│ ├── Generar resumen con Ollama (opcional) │
|
|
123
|
+
│ ├── Generar embeddings vía pipeline existente │
|
|
124
|
+
│ └── Guardar en knowledge/<domain>/<slug>/ │
|
|
125
|
+
│ ├── page.md ← markdown completo │
|
|
126
|
+
│ ├── metadata.json ← datos estructurados │
|
|
127
|
+
│ ├── plan.json ← el plan que usó Ollama │
|
|
128
|
+
│ └── embeddings.json ← vectores │
|
|
129
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 3. Componentes a Implementar
|
|
135
|
+
|
|
136
|
+
### 3.1 Core (`src/core/webscrape/`)
|
|
137
|
+
|
|
138
|
+
| Archivo | Descripción | Deps |
|
|
139
|
+
|---------|-------------|------|
|
|
140
|
+
| `analyzer.js` | Envía estructura de página a Ollama, recibe plan JSON | `@xenova/transformers`? No, es puro Ollama API |
|
|
141
|
+
| `executor.js` | Ejecuta el plan de extracción paso a paso | `jsdom` |
|
|
142
|
+
| `fetcher.js` | Fetch de página (simple + headless) | `node-fetch`, `playwright` (opt) |
|
|
143
|
+
| `models.js` | PageSnapshot, ExtractPlan, ExtractedPage, PageType | — |
|
|
144
|
+
| `postprocessor.js` | Markdown, metadata, embeddings, guardado | — |
|
|
145
|
+
| `config.js` | URL de Ollama, timeouts, modelos, cache | — |
|
|
146
|
+
|
|
147
|
+
### 3.2 Extractores (`src/core/webscrape/extractors/`)
|
|
148
|
+
|
|
149
|
+
| Archivo | Descripción |
|
|
150
|
+
|---------|-------------|
|
|
151
|
+
| `extract-content.js` | Extrae contenido principal (Readability.js mejorado) |
|
|
152
|
+
| `extract-og.js` | Open Graph + Twitter Cards |
|
|
153
|
+
| `extract-jsonld.js` | JSON-LD structured data |
|
|
154
|
+
| `extract-microdata.js` | Microdata / schema.org |
|
|
155
|
+
| `extract-images.js` | Imágenes con alt, caption, dimensiones |
|
|
156
|
+
| `extract-tables.js` | Tablas con caption, headers, rows |
|
|
157
|
+
| `extract-cards.js` | Tarjetas de producto/artículo/post |
|
|
158
|
+
| `extract-forms.js` | Formularios con campos |
|
|
159
|
+
| `extract-special.js` | Load-more, paginación, scroll infinito |
|
|
160
|
+
|
|
161
|
+
### 3.3 CLI (`src/commands/opl-webscrape.js`)
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
opl webscrape <url> # Modo normal (Ollama analyze + execute)
|
|
165
|
+
opl webscrape <url> --plan-only # Solo mostrar el plan de Ollama, no ejecutar
|
|
166
|
+
opl webscrape <url> --plan <plan.json> # Usar un plan guardado (re-ejecutar)
|
|
167
|
+
opl webscrape <url> --simple # Forzar Readability.js (sin Ollama)
|
|
168
|
+
opl webscrape <url> --headless # Forzar Playwright (para SPAs)
|
|
169
|
+
opl webscrape <url> --output json # Salida JSON estructurada
|
|
170
|
+
opl webscrape <url> --no-embeddings # No generar embeddings
|
|
171
|
+
opl webscrape <url> --dry-run # Mostrar plan sin guardar
|
|
172
|
+
opl webscrape plan <url> # Shorthand para --plan-only
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 4. Prompt de Análisis (Ollama)
|
|
178
|
+
|
|
179
|
+
El prompt que se envía a Ollama es **la pieza clave**. Debe ser:
|
|
180
|
+
|
|
181
|
+
- Compacto (no enviar HTML completo — solo resumen estructural)
|
|
182
|
+
- Rico en señales semánticas (tipo de elementos, no su contenido)
|
|
183
|
+
- Determinista en la respuesta (JSON estructurado)
|
|
184
|
+
|
|
185
|
+
### Formato del prompt
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
Eres un analista de páginas web. Tu trabajo es examinar la estructura
|
|
189
|
+
de una página web y generar un plan de extracción.
|
|
190
|
+
|
|
191
|
+
## Información de la página
|
|
192
|
+
URL: {url}
|
|
193
|
+
Título: {title}
|
|
194
|
+
Meta description: {description}
|
|
195
|
+
Tamaño HTML: {htmlSize} chars
|
|
196
|
+
|
|
197
|
+
## Resumen estructural
|
|
198
|
+
- Tipo de elementos en la página:
|
|
199
|
+
{summary: article: 3, section: 5, nav: 1, main: 1, aside: 2,
|
|
200
|
+
footer: 1, table: 1, form: 0, img: 12, video: 0, article_card: 6,
|
|
201
|
+
list: 4, breadcrumb: 0}
|
|
202
|
+
- Primeros 2000 caracteres del texto visible:
|
|
203
|
+
"{truncatedText}"
|
|
204
|
+
|
|
205
|
+
## Enlaces principales (primeros 10)
|
|
206
|
+
{links: [
|
|
207
|
+
{href: "/articulo-1", text: "Cómo hacer X", type: "internal"},
|
|
208
|
+
{href: "/categoria/y", text: "Categoría Y", type: "internal"},
|
|
209
|
+
...
|
|
210
|
+
]}
|
|
211
|
+
|
|
212
|
+
## Meta tags encontrados
|
|
213
|
+
{meta: {
|
|
214
|
+
og: ["og:title", "og:description", "og:image"],
|
|
215
|
+
twitter: ["twitter:card", "twitter:site"],
|
|
216
|
+
jsonld: true,
|
|
217
|
+
microdata: false
|
|
218
|
+
}}
|
|
219
|
+
|
|
220
|
+
## Instrucciones
|
|
221
|
+
Analiza esta página y devuelve un JSON con:
|
|
222
|
+
1. pageType: qué tipo de página es
|
|
223
|
+
2. contentBlocks: qué bloques de contenido identificas y cómo extraerlos
|
|
224
|
+
3. extractionPlan: pasos concretos a seguir
|
|
225
|
+
4. metadata: qué meta tags extraer
|
|
226
|
+
5. specialFeatures: si hay scroll infinito, load-more, paginación, etc.
|
|
227
|
+
|
|
228
|
+
Responde ÚNICAMENTE con el JSON. No incluyas explicaciones.
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Respuesta esperada
|
|
232
|
+
|
|
233
|
+
```json
|
|
234
|
+
{
|
|
235
|
+
"pageType": "article",
|
|
236
|
+
"confidence": 0.92,
|
|
237
|
+
"title": "Cómo implementar un scraper con IA",
|
|
238
|
+
"description": "Guía paso a paso para construir un extractor web inteligente",
|
|
239
|
+
"contentBlocks": [
|
|
240
|
+
{
|
|
241
|
+
"type": "main-content",
|
|
242
|
+
"selector": "main article",
|
|
243
|
+
"importance": 10,
|
|
244
|
+
"extractMode": "markdown",
|
|
245
|
+
"description": "Artículo principal con contenido completo"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"type": "sidebar",
|
|
249
|
+
"selector": "aside.related-posts",
|
|
250
|
+
"importance": 3,
|
|
251
|
+
"extractMode": "links",
|
|
252
|
+
"description": "Artículos relacionados en la barra lateral"
|
|
253
|
+
}
|
|
254
|
+
],
|
|
255
|
+
"metadata": {
|
|
256
|
+
"og": true,
|
|
257
|
+
"twitter": true,
|
|
258
|
+
"jsonld": true,
|
|
259
|
+
"microdata": false,
|
|
260
|
+
"priority": ["og:title", "og:description", "og:image"]
|
|
261
|
+
},
|
|
262
|
+
"specialFeatures": [],
|
|
263
|
+
"extractionPlan": {
|
|
264
|
+
"steps": [
|
|
265
|
+
"extract-main-content",
|
|
266
|
+
"extract-og",
|
|
267
|
+
"extract-jsonld",
|
|
268
|
+
"extract-images"
|
|
269
|
+
],
|
|
270
|
+
"outputFormat": "markdown+json",
|
|
271
|
+
"notes": "Página de blog estándar con contenido principal rico en imágenes"
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 5. Integración con el Sistema OPL Existente
|
|
279
|
+
|
|
280
|
+
### Pipeline completo con embeddings
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
cli: opl webscrape https://ejemplo.com/articulo
|
|
284
|
+
↓
|
|
285
|
+
fetcher.fetch(url) → PageSnapshot { html, text, links, meta, elementCounts }
|
|
286
|
+
↓
|
|
287
|
+
analyzer.analyze(snapshot, ollama) → ExtractPlan { pageType, blocks, steps }
|
|
288
|
+
↓
|
|
289
|
+
executor.execute(html, plan) → ExtractedPage { content, metadata, media, ... }
|
|
290
|
+
↓
|
|
291
|
+
postprocessor.process(extracted) → { markdown, metadata, cards }
|
|
292
|
+
↓
|
|
293
|
+
→ knowledge/<domain>/<slug>/page.md
|
|
294
|
+
→ knowledge/<domain>/<slug>/metadata.json
|
|
295
|
+
→ knowledge/<domain>/<slug>/plan.json
|
|
296
|
+
→ knowledge/<domain>/<slug>/cards.json (si aplica)
|
|
297
|
+
↓
|
|
298
|
+
embedder.indexDocument(slug) ← pipeline de embeddings existente
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Reutilización de infraestructura existente
|
|
302
|
+
|
|
303
|
+
| Componente OPL existente | Se reutiliza |
|
|
304
|
+
|--------------------------|:------------:|
|
|
305
|
+
| `src/embeddings/pipeline.js` | ✅ Indexación automática |
|
|
306
|
+
| `src/embeddings/sqlite-store.js` | ✅ Cache + almacenamiento |
|
|
307
|
+
| `src/commands/opl-webscrape.js` | ✅ Refactorizado como entry point |
|
|
308
|
+
| `src/cli/commands-opl.js` | ✅ Flags nuevos (`--plan-only`, `--headless`) |
|
|
309
|
+
| Readability.js + Turndown | ✅ Como extractores de contenido (PASO 2) |
|
|
310
|
+
| `opl knowledge` | ✅ Integración con knowledge base |
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## 6. Estrategia de Implementación por Fases
|
|
315
|
+
|
|
316
|
+
| Fase | Qué se hace | Archivos | Esfuerzo |
|
|
317
|
+
|------|-------------|----------|:--------:|
|
|
318
|
+
| **F0** | Models + Fetcher | `models.js`, `fetcher.js`, `config.js` | 1 día |
|
|
319
|
+
| **F1** | **Analyzer (Ollama) — el corazón** | `analyzer.js`, prompt template | 2-3 días |
|
|
320
|
+
| **F2** | Executor básico (main-content + og + jsonld) | `executor.js`, `extract-content.js`, `extract-og.js`, `extract-jsonld.js` | 3-4 días |
|
|
321
|
+
| **F3** | Extractores avanzados (images, tables, cards, forms) | `extract-images.js`, `extract-tables.js`, `extract-cards.js`, `extract-forms.js` | 3-4 días |
|
|
322
|
+
| **F4** | Post-processor + integración knowledge/embeddings | `postprocessor.js`, refactor `opl-webscrape.js` | 2 días |
|
|
323
|
+
| **F5** | Headless browser (Playwright opcional) | `fetcher.js` (modo headless), `extract-special.js` | 2 días |
|
|
324
|
+
| **F6** | Tests + documentación + CLI flags | tests, docs, `commands-opl.js` | 2 días |
|
|
325
|
+
|
|
326
|
+
**Total estimado:** 2-3 semanas (trabajo parcial, OLLAMA-first)
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 7. Beneficios Clave del Enfoque IA
|
|
331
|
+
|
|
332
|
+
| Aspecto | Readability.js | Scraper Python hardcoded | **Ollama-driven (esta propuesta)** |
|
|
333
|
+
|---------|:--------------:|:------------------------:|:----------------------------------:|
|
|
334
|
+
| **Adaptabilidad** | Solo artículos | Media (reglas fijas) | **Alta (analiza cada página)** |
|
|
335
|
+
| **Clasificación** | No | Heurística | **IA entiende el tipo** |
|
|
336
|
+
| **Metadata** | Mínima | Completa | **Selectiva (solo lo relevante)** |
|
|
337
|
+
| **SPA/JS** | No | Sí (Playwright) | **Sí (Playwright opcional)** |
|
|
338
|
+
| **Mantenimiento** | Nulo | Alto (nuevas reglas) | **Bajo (mejora el prompt)** |
|
|
339
|
+
| **Precisión** | 60-70% | 70-85% | **85-95% (IA entiende contexto)** |
|
|
340
|
+
| **Cobertura** | Blogs/news | Sitios conocidos | **Cualquier página web** |
|
|
341
|
+
| **Dependencias** | Mínimas | Python + librerías | **Node.js + Ollama** |
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## 8. Riesgos y Mitigaciones
|
|
346
|
+
|
|
347
|
+
| Riesgo | Probabilidad | Impacto | Mitigación |
|
|
348
|
+
|--------|:------------:|:-------:|------------|
|
|
349
|
+
| Ollama no disponible | Alta | Alto | Fallback automático a Readability.js + Turndown |
|
|
350
|
+
| Prompts largos (>4K tokens) | Media | Medio | Resumir HTML estructuralmente (árbol, no HTML crudo) |
|
|
351
|
+
| Respuesta JSON malformada de Ollama | Media | Medio | Validar JSON + reintentar con `--strict` |
|
|
352
|
+
| Páginas con anti-bot | Media | Alto | Modo headless + User-Agent rotación |
|
|
353
|
+
| Latencia (Ollama analyse = 2-5s) | Alta | Bajo | Cache de planes por dominio + extracción async |
|
|
354
|
+
| Precisión del plan depende del modelo | Alta | Medio | Usar modelos >= 7B (phi-4, llama3.2, qwen2.5) |
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## 9. Comandos Futuros (Post-F6)
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
# Extracción básica con IA
|
|
362
|
+
opl webscrape https://ejemplo.com
|
|
363
|
+
|
|
364
|
+
# Solo ver el plan que Ollama generaría (sin extraer)
|
|
365
|
+
opl webscrape https://ejemplo.com --plan-only
|
|
366
|
+
|
|
367
|
+
# Re-ejecutar con un plan guardado
|
|
368
|
+
opl webscrape https://ejemplo.com --plan ./plan.json
|
|
369
|
+
|
|
370
|
+
# Crawl completo de un sitio con IA por página
|
|
371
|
+
opl webscrape https://ejemplo.com --crawl --depth 2
|
|
372
|
+
|
|
373
|
+
# Forzar Playwright para SPAs pesados
|
|
374
|
+
opl webscrape https://spa-ejemplo.com --headless
|
|
375
|
+
|
|
376
|
+
# Salida JSON para pipeline
|
|
377
|
+
opl webscrape https://ejemplo.com --output json
|
|
378
|
+
|
|
379
|
+
# Forzar Readability.js (sin IA)
|
|
380
|
+
opl webscrape https://ejemplo.com --simple
|
|
381
|
+
|
|
382
|
+
# Extraer diseño visual (colores, tipografía, componentes)
|
|
383
|
+
opl webscrape https://ejemplo.com --extract-design
|
|
384
|
+
|
|
385
|
+
# Ver el prompt que se envía a Ollama
|
|
386
|
+
opl webscrape https://ejemplo.com --debug-prompt
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## 10. Primeros Pasos Concretos
|
|
392
|
+
|
|
393
|
+
### Fase 0: Preparación
|
|
394
|
+
|
|
395
|
+
1. Verificar que Ollama corre localmente:
|
|
396
|
+
```bash
|
|
397
|
+
curl http://localhost:11434/api/tags
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
2. Elegir modelo base (recomendado: `phi-4:14b` o `llama3.2:8b`):
|
|
401
|
+
```bash
|
|
402
|
+
ollama pull phi-4:14b # Buen balance calidad/velocidad
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
3. Crear estructura de directorios:
|
|
406
|
+
```
|
|
407
|
+
src/core/webscrape/
|
|
408
|
+
├── index.js # Re-export
|
|
409
|
+
├── config.js # Ollama URL, modelo, timeouts
|
|
410
|
+
├── models.js # PageSnapshot, ExtractPlan, ExtractedPage
|
|
411
|
+
├── fetcher.js # node-fetch + Playwright opcional
|
|
412
|
+
├── analyzer.js # Ollama prompt + parseo JSON
|
|
413
|
+
├── executor.js # Ejecuta plan paso a paso
|
|
414
|
+
├── postprocessor.js # Markdown + metadata + guardado
|
|
415
|
+
└── extractors/
|
|
416
|
+
├── extract-content.js
|
|
417
|
+
├── extract-og.js
|
|
418
|
+
├── extract-jsonld.js
|
|
419
|
+
├── extract-images.js
|
|
420
|
+
├── extract-tables.js
|
|
421
|
+
└── ...
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
4. Implementar `models.js` + `config.js` + `fetcher.js` (F0, 1 día)
|
|
425
|
+
|
|
426
|
+
### Fase 1: Analyzer (el corazón)
|
|
427
|
+
|
|
428
|
+
5. Implementar `analyzer.js`:
|
|
429
|
+
- Construir resumen estructural del HTML (árbol de secciones, conteo de elementos)
|
|
430
|
+
- Extraer texto visible (primeros ~2000 chars)
|
|
431
|
+
- Detectar meta tags OG, Twitter, JSON-LD
|
|
432
|
+
- Enviar prompt a Ollama (`POST /api/generate`)
|
|
433
|
+
- Parsear y validar respuesta JSON
|
|
434
|
+
- Cachear plan por URL en SQLite
|
|
435
|
+
|
|
436
|
+
6. Probar con 5 tipos de página distintos (article, product, docs, blog, landing)
|
|
437
|
+
|
|
438
|
+
### Fase 2: Executor básico
|
|
439
|
+
|
|
440
|
+
7. Implementar extractores core + `executor.js`
|
|
441
|
+
8. Probar end-to-end con `opl webscrape <url>`
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## 11. Medición de Éxito
|
|
446
|
+
|
|
447
|
+
| Métrica | Objetivo | Cómo se mide |
|
|
448
|
+
|---------|:--------:|--------------|
|
|
449
|
+
| Precisión del plan | >90% | Validación manual de 50 páginas |
|
|
450
|
+
| Tiempo total por URL | <10s | Benchmark automático |
|
|
451
|
+
| Fallback a Readability | <20% de los casos | Log de modo usado |
|
|
452
|
+
| Tokens por análisis | <3000 | Prompt logging |
|
|
453
|
+
| Cobertura de tipos de página | >80% | Test con 100 URLs diversas |
|
|
454
|
+
| Embeddings generados por scraper | >90% de URLs | Pipeline count |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openprompt-lang",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "PromptLang CLI — Context Engine de anotaciones para desarrollo asistido por IA",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./bin/cli.js",
|
|
@@ -72,14 +72,17 @@
|
|
|
72
72
|
"node": ">=18"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
+
"@mozilla/readability": "^0.6.0",
|
|
75
76
|
"ajv": "^8.20.0",
|
|
76
77
|
"better-sqlite3": "^12.10.0",
|
|
77
78
|
"chalk": "^5.3.0",
|
|
78
79
|
"commander": "^12.0.0",
|
|
79
80
|
"handlebars": "^4.7.9",
|
|
80
81
|
"inquirer": "^9.3.0",
|
|
82
|
+
"jsdom": "^29.1.1",
|
|
81
83
|
"pdf-parse": "^2.4.5",
|
|
82
84
|
"tesseract.js": "^7.0.0",
|
|
85
|
+
"turndown": "^7.2.4",
|
|
83
86
|
"typescript": "^6.0.3"
|
|
84
87
|
},
|
|
85
88
|
"devDependencies": {
|
|
@@ -105,5 +108,8 @@
|
|
|
105
108
|
"*.{ts,tsx,js,jsx}": [
|
|
106
109
|
"openprompt-lang lint-file"
|
|
107
110
|
]
|
|
111
|
+
},
|
|
112
|
+
"optionalDependencies": {
|
|
113
|
+
"@xenova/transformers": "^2.17.2"
|
|
108
114
|
}
|
|
109
115
|
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -90,6 +90,43 @@ try {
|
|
|
90
90
|
console.log(" 💡 O instálalo manualmente según tu plataforma.")
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
+
// ──────────────────────────────────────────────
|
|
94
|
+
// Información sobre embeddings y Ollama
|
|
95
|
+
// ──────────────────────────────────────────────
|
|
96
|
+
try {
|
|
97
|
+
const { execSync } = await import("child_process")
|
|
98
|
+
const ollamaPath = execSync("which ollama 2>/dev/null || echo ''", {
|
|
99
|
+
encoding: "utf-8",
|
|
100
|
+
}).trim()
|
|
101
|
+
|
|
102
|
+
if (ollamaPath) {
|
|
103
|
+
const models = execSync("ollama list 2>/dev/null || echo ''", {
|
|
104
|
+
encoding: "utf-8",
|
|
105
|
+
}).trim()
|
|
106
|
+
const hasEmbedModel = models.toLowerCase().includes("nomic-embed-text")
|
|
107
|
+
|
|
108
|
+
if (hasEmbedModel) {
|
|
109
|
+
console.log("✅ Ollama + nomic-embed-text listos para embeddings semánticos")
|
|
110
|
+
} else {
|
|
111
|
+
console.log("📦 Ollama detectado. Para embeddings semánticos ejecuta:")
|
|
112
|
+
console.log(" ollama pull nomic-embed-text")
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
console.log("📦 Ollama no detectado. Usando Transformers.js como fallback.")
|
|
116
|
+
console.log(" 💡 Instala Ollama para mejor rendimiento: https://ollama.com")
|
|
117
|
+
console.log(" 💡 Luego: ollama pull nomic-embed-text")
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// No crítico
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.log("")
|
|
124
|
+
console.log("🔍 Búsqueda semántica disponible:")
|
|
125
|
+
console.log(' opl search "consulta" --mode vector')
|
|
126
|
+
console.log("")
|
|
127
|
+
console.log("📊 Indexa documentos en el vector store:")
|
|
128
|
+
console.log(" opl embeddings index <docId>")
|
|
129
|
+
console.log("")
|
|
93
130
|
console.log("💡 Ejecuta 'opl init' para crear un nuevo proyecto con planificación")
|
|
94
131
|
} catch (err) {
|
|
95
132
|
console.log(` ⚠️ opencode: ${err.message}`)
|
|
@@ -23,6 +23,7 @@ export function register(program) {
|
|
|
23
23
|
.option("--auto-ocr", "Detectar automáticamente si necesita OCR (baja densidad de texto)")
|
|
24
24
|
.option("--force", "Re-procesar PDF aunque ya exista en el índice")
|
|
25
25
|
.option("--verbose", "Mostrar progreso detallado (especialmente OCR)")
|
|
26
|
+
.option("--no-embed", "Saltar la generación automática de embeddings")
|
|
26
27
|
.action(async (file, options) => {
|
|
27
28
|
const { ingest } = await import("../commands/knowledge.js")
|
|
28
29
|
await ingest(file, options)
|
package/src/cli/commands-opl.js
CHANGED
|
@@ -61,7 +61,7 @@ export function register(program) {
|
|
|
61
61
|
.command("search <query>")
|
|
62
62
|
.description("Buscar en el conocimiento: tags + texto completo + semántica")
|
|
63
63
|
.usage("<consulta>")
|
|
64
|
-
.option("--mode <mode>", "Modo de búsqueda: tags, fulltext, semantic, hybrid (default)")
|
|
64
|
+
.option("--mode <mode>", "Modo de búsqueda: tags, fulltext, semantic, vector, hybrid (default)")
|
|
65
65
|
.action(async (query, options) => {
|
|
66
66
|
const { search } = await import("../commands/opl-search.js")
|
|
67
67
|
await search(query, options)
|
|
@@ -249,6 +249,84 @@ export function register(program) {
|
|
|
249
249
|
await contractSearch(query)
|
|
250
250
|
})
|
|
251
251
|
|
|
252
|
+
// ──────────────────────────────────────────────
|
|
253
|
+
// opl embeddings — Gestión de vectores de embeddings
|
|
254
|
+
// ──────────────────────────────────────────────
|
|
255
|
+
const embeddings = program
|
|
256
|
+
.command("embeddings")
|
|
257
|
+
.description("Gestionar embeddings vectoriales: indexar, consultar estado, eliminar")
|
|
258
|
+
|
|
259
|
+
embeddings
|
|
260
|
+
.command("index [docId]")
|
|
261
|
+
.description("Indexar un documento: chunking → embedding → almacenamiento vectorial")
|
|
262
|
+
.usage("<docId>")
|
|
263
|
+
.option("--strategy <s>", "Estrategia de chunking: section, paragraph, fixed", "section")
|
|
264
|
+
.option("--provider <p>", "Proveedor de embeddings: ollama, transformers")
|
|
265
|
+
.option("--dry-run", "Simular indexación sin persistir")
|
|
266
|
+
.option("--resume", "Saltar chunks ya indexados")
|
|
267
|
+
.action(async (docId, options) => {
|
|
268
|
+
const { embeddings } = await import("../commands/opl-embeddings.js")
|
|
269
|
+
await embeddings("index", { ...options, docId })
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
embeddings
|
|
273
|
+
.command("status")
|
|
274
|
+
.description("Mostrar estado del índice de embeddings")
|
|
275
|
+
.action(async () => {
|
|
276
|
+
const { embeddings } = await import("../commands/opl-embeddings.js")
|
|
277
|
+
await embeddings("status")
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
embeddings
|
|
281
|
+
.command("remove <docId>")
|
|
282
|
+
.description("Eliminar embeddings de un documento")
|
|
283
|
+
.usage("<docId>")
|
|
284
|
+
.action(async (docId) => {
|
|
285
|
+
const { embeddings } = await import("../commands/opl-embeddings.js")
|
|
286
|
+
await embeddings("remove", { docId })
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
embeddings
|
|
290
|
+
.command("config")
|
|
291
|
+
.description("Ver o configurar el proveedor de embeddings")
|
|
292
|
+
.option("--provider <p>", "Establecer proveedor: ollama, transformers")
|
|
293
|
+
.action(async (options) => {
|
|
294
|
+
const { embeddings } = await import("../commands/opl-embeddings.js")
|
|
295
|
+
await embeddings("config", options)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
// ──────────────────────────────────────────────
|
|
299
|
+
// opl webscrape — Extraer contenido web e indexar
|
|
300
|
+
// ──────────────────────────────────────────────
|
|
301
|
+
program
|
|
302
|
+
.command("webscrape <url>")
|
|
303
|
+
.description("Extraer contenido de una web, guardarlo como conocimiento e indexar embeddings")
|
|
304
|
+
.usage("<url>")
|
|
305
|
+
.option("--domain <d>", "Dominio de conocimiento destino (default: web)")
|
|
306
|
+
.option("--no-embed", "No indexar embeddings automáticamente")
|
|
307
|
+
.option("--clean", "Limpiar contenido con Ollama")
|
|
308
|
+
.option("--deep", "Deep scrape: navega categorías y subcategorías recursivamente")
|
|
309
|
+
.option("--max-depth <n>", "Profundidad máxima para deep scrape (default: 2)", parseInt)
|
|
310
|
+
.option("--concurrency <n>", "Fetchs simultáneos para deep scrape (default: 3)", parseInt)
|
|
311
|
+
.option("--verbose", "Log detallado del proceso")
|
|
312
|
+
.option("--no-ollama", "No usar Ollama para analizar páginas (solo heurísticas)")
|
|
313
|
+
.action(async (url, options) => {
|
|
314
|
+
if (options.deep) {
|
|
315
|
+
const { deepScrape } = await import("../core/webscrape/deep-scraper.js")
|
|
316
|
+
await deepScrape(url, {
|
|
317
|
+
maxDepth: options.maxDepth || 2,
|
|
318
|
+
concurrency: options.concurrency || 3,
|
|
319
|
+
domain: options.domain || "web",
|
|
320
|
+
noEmbed: options.noEmbed === true,
|
|
321
|
+
verbose: options.verbose === true,
|
|
322
|
+
ollama: options.ollama !== false, // default: true
|
|
323
|
+
})
|
|
324
|
+
} else {
|
|
325
|
+
const { webscrape } = await import("../commands/opl-webscrape.js")
|
|
326
|
+
await webscrape(url, options)
|
|
327
|
+
}
|
|
328
|
+
})
|
|
329
|
+
|
|
252
330
|
// ──────────────────────────────────────────────
|
|
253
331
|
// opl upgrade — Actualizar config a versión actual
|
|
254
332
|
// ──────────────────────────────────────────────
|
package/src/cli/commands-work.js
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
export function register(program) {
|
|
7
7
|
const workContext = program
|
|
8
8
|
.command("work-context")
|
|
9
|
-
.description(
|
|
9
|
+
.description(
|
|
10
|
+
"Gestionar contexto de trabajo (integrado con 'opl workflow' — start≈delivery, close≈close)"
|
|
11
|
+
)
|
|
10
12
|
|
|
11
13
|
workContext
|
|
12
14
|
.command("init")
|