diff --git a/scripts/pipelines/samples_generator/DOCUMENTATION.md b/scripts/pipelines/samples_generator/DOCUMENTATION.md new file mode 100644 index 0000000..9628638 --- /dev/null +++ b/scripts/pipelines/samples_generator/DOCUMENTATION.md @@ -0,0 +1,341 @@ +# AVAP MAP-Elites Dataset Pipeline — Documentación + +> Scripts para la síntesis de datasets de benchmarks AVAP mediante cobertura de gramática garantizada y priors estadísticos extraídos de código real de producción. + +--- + +## Índice + +- [Visión general del sistema](#visión-general-del-sistema) +- [Requisitos e instalación](#requisitos-e-instalación) +- [construct_prior.py](#construct_priorpy) + - [Uso](#uso-construct_prior) + - [Cómo funciona](#cómo-funciona-construct_prior) +- [generate_mbap_v2.py](#generate_mbap_v2py) + - [Uso](#uso-generate_mbap_v2) + - [Cómo funciona](#cómo-funciona-generate_mbap_v2) +- [Flujo de trabajo recomendado](#flujo-de-trabajo-recomendado) +- [Archivos generados](#archivos-generados) + +--- + +## Visión general del sistema + +El pipeline consta de dos scripts que trabajan en conjunto: + +``` +construct_prior.py → construct_map.yaml + ↓ ↓ +generate_mbap_v2.py ←─────────────────┘ + ↓ + dataset .json + coverage_stats .json +``` + +1. **`construct_prior.py`** analiza codebases reales en GitHub para extraer con qué frecuencia co-ocurren los 38 comandos AVAP en código de producción real. El resultado es un fichero `construct_map.yaml` con pesos estadísticos. + +2. **`generate_mbap_v2.py`** usa esos pesos para dirigir un generador MAP-Elites que llama a la API de Claude, garantizando cobertura uniforme de todas las combinaciones de pares y tríos del DSL AVAP. + +--- + +## Requisitos e instalación + +```bash +pip install requests pyyaml anthropic +pip install datasets huggingface_hub # solo si usas --prior-sources huggingface + +export ANTHROPIC_API_KEY=sk-ant-... +export GITHUB_TOKEN=ghp_... # opcional pero recomendado +``` + +El parser AVAP debe estar en ejecución si se desea validación AST real (opcional; si no está disponible, se usa keyword scanning como fallback): + +```bash +# El parser debe escuchar en el puerto configurado (por defecto 8080) +# Ejemplo: http://localhost:8080 +``` + +--- + +## `construct_prior.py` + +### Uso (construct_prior) + +El script tiene dos modos de operación: **generación** del mapa y **verificación** de uno existente. + +#### Generar `construct_map.yaml` (ejecutar una vez, o cuando cambie la gramática AVAP) + +```bash +# Ejecución básica (sin token: límite de 10 req/min en GitHub) +python construct_prior.py --generate-map + +# Con token de GitHub (recomendado — 30 req/min) +python construct_prior.py --generate-map --github-token ghp_... + +# Analizar más ficheros para un prior más rico +python construct_prior.py --generate-map --max-files 200 --github-token ghp_... + +# Ruta de salida personalizada +python construct_prior.py --generate-map --output /ruta/construct_map.yaml +``` + +#### Verificar un `construct_map.yaml` existente + +```bash +python construct_prior.py --verify --map construct_map.yaml +``` + +#### Uso como módulo desde `generate_mbap_v2.py` + +```python +from construct_prior import ConstructPrior + +# Cargar desde YAML generado +prior = ConstructPrior.from_yaml("construct_map.yaml") + +# Consultar el peso de una celda (par o trío de comandos AVAP) +w = prior.cell_weight(frozenset({"try", "ormAccessSelect"})) + +# Fallback estático (sin necesidad de YAML) +prior = ConstructPrior.static_fallback() +``` + +#### Parámetros CLI + +| Parámetro | Tipo | Por defecto | Descripción | +|---|---|---|---| +| `--generate-map` | flag | — | Activa el modo de extracción desde GitHub | +| `--verify` | flag | — | Carga y muestra estadísticas de un YAML existente | +| `--github-token` | str | `$GITHUB_TOKEN` | Token de acceso personal de GitHub | +| `--max-files` | int | `100` | Número máximo de ficheros a analizar | +| `--output` | str | `construct_map.yaml` | Ruta de salida del YAML | +| `--map` | str | `construct_map.yaml` | Ruta del YAML a verificar (solo con `--verify`) | + +--- + +### Cómo funciona (construct_prior) + +El objetivo del script es responder a la pregunta: **¿qué combinaciones de comandos AVAP aparecen juntas con más frecuencia en código API real de producción?** Esa información sirve para sesgar el generador de datasets hacia patrones realistas. + +#### 1. Vocabulario AVAP (`AVAP_NODE_NAMES`) + +Se define una lista canónica de 38 comandos AVAP organizados por categoría (variables, ORM, HTTP, criptografía, concurrencia, etc.). Esta lista es la única fuente de verdad del módulo y es importada también por `generate_mbap_v2.py`. + +#### 2. Tabla de equivalencias de lenguaje (`LANGUAGE_MAPPINGS`) + +Cada comando AVAP se mapea a sus equivalentes en Python (detección por AST), Go (keywords) y SQL. Esta tabla, verificada manualmente, define *qué buscar* al escanear los codebases. Por ejemplo: + +- `ormAccessSelect` → llamadas como `.fetchall()`, `.query()`, `.filter()` +- `RequestPost` → `requests.post`, `httpx.post`, `session.post` +- `try` → nodo `ast.Try` en el AST de Python + +#### 3. Extracción desde GitHub (`GitHubFetcher`) + +Se lanzan 16 queries predefinidas contra la GitHub Code Search API, cada una orientada a un patrón típico de microservicio (ORM + manejo de errores, clientes HTTP, autenticación con crypto, concurrencia async, JSON, fechas, etc.). Los ficheros encontrados se descargan en base64 y se decodifican en memoria. + +El fetcher respeta automáticamente el rate limit de la API (10 req/min sin token, 30 req/min con token) con esperas adaptativas entre peticiones. + +#### 4. Detección AST de Python (`PythonASTDetector`) + +Cada fichero descargado se parsea con el módulo estándar `ast` de Python. El walker del AST recorre todos los nodos y detecta qué comandos AVAP están presentes según su equivalente estructural: + +- `ast.Try` → `"try"` + `"exception"` +- `ast.FunctionDef` → `"function"` + `"addParam"` (si tiene argumentos) +- `ast.For` / `ast.AsyncFor` → `"startLoop"` +- `ast.Call` con callee `.fetchall` → `"ormAccessSelect"` +- etc. + +Este enfoque es **AST-level**, no keyword scanning, lo que elimina falsos positivos por nombres de variable, strings o comentarios. Si el fichero tiene errores de sintaxis, cae automáticamente a un keyword fallback. + +#### 5. Acumulación de co-ocurrencias (`CooccurrenceExtractor`) + +Por cada fichero analizado se obtiene un conjunto de comandos AVAP detectados. Se calculan todas las combinaciones de pares y tríos posibles de ese conjunto y se incrementan sus contadores. Por ejemplo, si un fichero contiene `{try, ormAccessSelect, return}`, se incrementan los contadores de `(try, ormAccessSelect)`, `(try, return)`, `(ormAccessSelect, return)` y el trío `(try, ormAccessSelect, return)`. + +#### 6. Normalización y escritura del YAML (`generate_construct_map`) + +Los contadores de co-ocurrencia se normalizan a `[0, 1]` dividiéndolos por el máximo observado. El resultado se escribe en `construct_map.yaml` con dos secciones: + +- `language_mappings` — la tabla de equivalencias (trazabilidad) +- `pair_weights` / `trio_weights` — pesos empíricos extraídos + +#### 7. Propagación de pesos a subsets (`_propagate_subset_weights`) + +Los tríos que contienen pares con alto peso heredan el 60% del peso del par más relevante que contienen. Esto garantiza que los tríos formados por pares comunes sean visitados antes que tríos de combinaciones raras. + +#### 8. Fallback estático + +Si no se dispone de conexión a GitHub o de un YAML previo, `ConstructPrior.static_fallback()` retorna un conjunto de 40+ pesos hard-coded basados en conocimiento experto (por ejemplo, `(try, exception)` = 1.0, `(function, return)` = 0.98). + +--- + +## `generate_mbap_v2.py` + +### Uso (generate_mbap_v2) + +#### Invocación básica + +```bash +# Modo por defecto: MAP-Elites + ConstructPrior (Candidato F) +python generate_mbap_v2.py --lrm avap.md --parser http://localhost:8080 + +# Generar 5000 ejemplos con celdas de hasta 3 comandos +python generate_mbap_v2.py --lrm avap.md --parser http://localhost:8080 \ + --problems 5000 --cell-size 3 --mode map-elites-prior +``` + +#### Opciones del prior + +```bash +# Usar datos reales de GitHub para el prior (AST-level, requiere red) +python generate_mbap_v2.py --lrm avap.md --mode map-elites-prior \ + --prior-sources github + +# Guardar el prior tras la extracción (para reutilizarlo después) +python generate_mbap_v2.py --lrm avap.md --mode map-elites-prior \ + --prior-sources github --prior-save prior_weights.json + +# Cargar un prior pre-extraído (sin peticiones a GitHub) +python generate_mbap_v2.py --lrm avap.md --mode map-elites-prior \ + --prior-load prior_weights.json + +# Usar el prior generado por construct_prior.py +python generate_mbap_v2.py --lrm avap.md --mode map-elites-prior \ + --prior-map construct_map.yaml +``` + +#### Modos de generación + +```bash +# Candidato F: MAP-Elites + ConstructPrior (por defecto, recomendado) +python generate_mbap_v2.py --lrm avap.md --mode map-elites-prior + +# Candidato E: MAP-Elites con pesos uniformes (baseline sin prior) +python generate_mbap_v2.py --lrm avap.md --mode map-elites + +# Candidato A: CW-Reward pool (no implementado en v2, usar generate_mbap.py) +python generate_mbap_v2.py --lrm avap.md --mode reward +``` + +#### Parámetros CLI completos + +| Parámetro | Tipo | Por defecto | Descripción | +|---|---|---|---| +| `--lrm` | str | `avap.md` | Ruta al Language Reference Manual de AVAP | +| `--output` | str | `output/mbpp_avap_v2.json` | Ruta del dataset de salida | +| `--problems` | int | `5000` | Número de ejemplos a generar | +| `--parser` | str | `http://localhost:8080` | URL del parser AVAP | +| `--cell-size` | int | `3` | Tamaño máximo de celda: 2=solo pares, 3=pares+tríos | +| `--quality-threshold` | float | `0.80` | Calidad mínima para considerar una celda "buena" | +| `--alpha` | float | `0.30` | Peso de los comandos bonus en la calidad | +| `--beta` | float | `0.20` | Peso de la calidad de los tests en la calidad | +| `--gamma` | float | `0.10` | Peso de la riqueza del código en la calidad | +| `--mode` | choice | `map-elites-prior` | Modo de generación (ver arriba) | +| `--prior-map` | str | `construct_map.yaml` | Ruta al YAML generado por `construct_prior.py` | +| `--prior-epsilon` | float | `0.05` | Peso mínimo para celdas cola (tail cells) | +| `--prior-phase3-threshold` | float | `0.70` | Calidad a partir de la cual se activa la Fase 3 (tail) | +| `--api-key` | str | `$ANTHROPIC_API_KEY` | API key de Anthropic | + +--- + +### Cómo funciona (generate_mbap_v2) + +El generador implementa un algoritmo de **Quality-Diversity (QD)** llamado MAP-Elites aplicado a la generación de ejemplos de código AVAP. El objetivo es producir un dataset que cubra de forma garantizada todas las combinaciones de pares y tríos de los 38 comandos del DSL, sin sesgos de distribución. + +#### 1. Vocabulario y detección de constructs (`AVAP_NODE_TYPES`, `CellValidator`) + +Se define un diccionario que mapea cada comando AVAP a sus patrones de keyword. `CellValidator` detecta qué comandos están presentes en un ejemplo generado usando dos estrategias: + +- **Desde el AST del parser AVAP**: se recorre el árbol recursivamente buscando nodos por `type`. +- **Desde el código fuente** (fallback): se buscan los patrones de keyword del diccionario. `if_mode2` se comprueba antes que `if_mode1` para evitar ambigüedad. + +Además calcula una **puntuación de calidad** compuesta: + +``` +quality = fidelity + + alpha * bonus_constructs_ratio + + beta * test_quality + + gamma * code_richness +``` + +- `fidelity`: fracción de los constructs requeridos por la celda que están presentes (componente principal). +- `bonus_ratio`: constructs adicionales más allá de los requeridos. +- `test_quality`: proporción de tests con patrón `re.match()` y longitud > 10. +- `richness`: número de líneas normalizadas a 30 (proxy de complejidad). + +#### 2. Mapa de cobertura MAP-Elites (`CoverageMap`) + +Es la estructura central del algoritmo. Mantiene **una celda por cada combinación posible de 2 o 3 comandos AVAP** (con `cell_size=3`: 703 pares + N tríos de un total de 38 comandos). Cada celda almacena el mejor ejemplo encontrado hasta el momento para esa combinación. + +Una celda solo se considera válida si el ejemplo que contiene usa **todos** los constructs de su clave. El mapa expone métricas en tiempo real: tasa de llenado, entropía de distribución (Shannon), y celdas de baja calidad. + +#### 3. Selector de celdas — Candidato E (`CellSelector`) + +Implementa la estrategia de selección sin prior en tres fases: + +- **Fase 1**: Celdas vacías (round-robin aleatorio con semilla fija para reproducibilidad). +- **Fase 2**: Celdas con calidad por debajo del umbral. +- **Fase 3**: UCB (Upper Confidence Bound) sobre todas las celdas — equilibrio entre explotar celdas de alta calidad y explorar celdas poco visitadas. + +#### 4. Selector con prior — Candidato F (`CellSelectorPrior`) + +Extiende `CellSelector` incorporando los pesos de `ConstructPrior`: + +- **Fase 1**: Las celdas vacías con peso de prior alto (> 1.5× epsilon) se priorizan mediante muestreo ponderado. Esto hace que el dataset sea útil para RAG desde los primeros ejemplos (los patrones más frecuentes en producción se cubren primero). +- **Fase 2**: Las celdas de baja calidad se seleccionan con UCB multiplicado por el peso del prior. +- **Fase 3**: Una vez que las celdas de prior alto alcanzan el umbral de calidad, se activa la cobertura de las celdas cola (tail cells, prior ≈ epsilon). + +#### 5. Construcción del prompt y llamada a la API (`Generator`) + +Para cada celda seleccionada se construye un prompt estructurado que incluye el LRM completo de AVAP y una especificación precisa: "genera UN problema de benchmark estilo MBPP que use **exactamente** estos constructs: `{cell}`". El generador nunca pide exploración libre — cada llamada tiene una especificación de cobertura forzada. + +La respuesta se parsea esperando JSON con campos `prompt`, `code`, y `tests`. + +#### 6. Validación y actualización del mapa + +Cada ejemplo generado pasa por `CellValidator`: + +1. Se intenta parsear con el parser AVAP (si está disponible). +2. Se detectan los constructs presentes. +3. Se calcula la puntuación de calidad. +4. Si la calidad supera el ejemplo actual de la celda (o la celda está vacía), el mapa se actualiza. + +#### 7. Checkpoints y métricas + +Cada 100 llamadas a la API se imprime un checkpoint con: tamaño del dataset, tasa de éxito, estado del mapa, entropía de distribución, y divergencia KL entre el dataset y el prior (KL = 0 significa alineación perfecta con patrones de producción). + +#### 8. Guardado (`_save`) + +Al finalizar se guardan dos ficheros: + +- `.json` — el dataset completo. +- `_coverage_stats.json` — estadísticas de cobertura, entropía, frecuencia por nodo, y KL-divergence respecto al prior. + +--- + +## Flujo de trabajo recomendado + +```bash +# Paso 1: Generar el prior estadístico (una sola vez, o al actualizar la gramática) +python construct_prior.py --generate-map --github-token $GITHUB_TOKEN --max-files 200 + +# Paso 2: Verificar el prior generado +python construct_prior.py --verify --map construct_map.yaml + +# Paso 3: Generar el dataset +python generate_mbap_v2.py \ + --lrm avap.md \ + --parser http://localhost:8080 \ + --prior-map construct_map.yaml \ + --problems 5000 \ + --output output/dataset_v2.json \ + --mode map-elites-prior +``` + +--- + +## Archivos generados + +| Fichero | Generado por | Descripción | +|---|---|---| +| `construct_map.yaml` | `construct_prior.py` | Pesos de co-ocurrencia de pares/tríos AVAP extraídos de GitHub | +| `.json` | `generate_mbap_v2.py` | Dataset de ejemplos AVAP estilo MBPP | +| `_coverage_stats.json` | `generate_mbap_v2.py` | Estadísticas de cobertura, entropía y KL-divergence |