diff --git a/README.md b/README.md index 92bec97..9959fbf 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,16 @@ graph TD ├── README.md # System documentation & Dev guide ├── changelog # Version tracking and release history ├── pyproject.toml # Python project configuration +├── docs/ +| ├── AVAP Language: ... # AVAP DSL Documentation +| | └── AVAP.md +│ ├── developer.avapfr... # Documents on developer web page +| └── LRM/ # AVAP LRM documentation +| └── avap.md ├── Docker/ │ ├── Dockerfile # Container definition for the Engine │ ├── docker-compose.yaml # Local orchestration for dev environment -│ ├── requirements.txt # Python dependencies for Docker +│ ├── requirements.txt # Python dependencies for Docker │ ├── protos/ │ │ └── brunix.proto # Protocol Buffers: The source of truth for the API │ └── src/ @@ -64,7 +70,10 @@ graph TD │ └── kubeconfig.yaml # Kubernetes cluster configuration ├── scripts/ │ └── pipelines/ +| ├── samples_generator/ # AVAP Sample generator +| | └─ generate_mbap.py │ └── flows/ # Data processing flows +| └─ elasticsearch_ingestion.py └── src/ ├── __init__.py └── utils/ @@ -202,6 +211,53 @@ python -m grpc_tools.protoc -I./protos --python_out=./src --grpc_python_out=./sr --- +## Dataset Generation & Evaluation + +The engine includes a specialized benchmarking suite to evaluate the model's proficiency in **AVAP syntax**. This is achieved through a synthetic data generator that creates problems in the MBPP (Mostly Basic Python Problems) style, but tailored for the AVAP Language Reference Manual (LRM). + +### 1. Synthetic Data Generator +The script `scripts/generate_mbpp_avap.py` leverages Claude 3.5 Sonnet to produce high-quality, executable code examples and validation tests. + +**Key Features:** +* **LRM Grounding:** Uses the provided `avap.md` as the source of truth for syntax and logic. +* **Validation Logic:** Generates `test_list` with Python regex assertions to verify the state of the AVAP stack after execution. +* **Balanced Categories:** Covers 14 domains including ORM, Concurrency (`go/gather`), HTTP handling, and Cryptography. + +### 2. Usage +Ensure you have the `anthropic` library installed and your API key configured: + +```bash +pip install anthropic +export ANTHROPIC_API_KEY="your-sk-ant-key" +``` + +Run the generator specifying the path to your LRM and the desired output: + +```bash +python scripts/generate_mbpp_avap.py \ + --lrm ingestion/docs/avap.md \ + --output evaluation/mbpp_avap.json \ + --problems 300 +``` + +### 3. Dataset Schema +The generated JSON follows this structure: + +| Field | Type | Description | +| :--- | :--- | :--- | +| `task_id` | Integer | Unique identifier for the benchmark. | +| `text` | String | Natural language description of the problem (Spanish). | +| `code` | String | The reference AVAP implementation. | +| `test_list` | Array | Python `re.match` expressions to validate execution results. | + +### 4. Integration in RAG +These generated examples are used to: +1. **Fine-tune** the local models (`qwen2.5:1.5b`) or others via the MrHouston pipeline. +2. **Evaluate** the "Zero-Shot" performance of the engine before deployment. +3. **Provide Few-Shot examples** in the RAG prompt orchestration (`src/prompts.py`). + +--- + ## Repository Standards & Architecture ### Docker & Build Context diff --git a/changelog b/changelog index f060d26..ac8e3f3 100644 --- a/changelog +++ b/changelog @@ -4,6 +4,21 @@ All notable changes to the **Brunix Assistance Engine** will be documented in th --- +## [1.4.0] - 2026-03-10 + +### Added +- **Dataset Generation Suite**: Added `scripts/generate_mbpp_avap.py` to automate the creation of synthetic AVAP training data. +- **MBPP-style Benchmarking**: Support for generating structured JSON datasets with code solutions and Python-based validation tests (`test_list`). +- **LRM Integration**: The generator now performs grounded synthesis using the `avap.md` Language Reference Manual. +- **Anthropic Claude 3.5 Sonnet Integration**: Orchestration logic for high-fidelity code generation via API. + +### Changed +- **README.md**: Added comprehensive documentation for the Evaluation & Dataset Generation pipeline. +- **Project Structure**: Integrated `evaluation/` directory for synthetic dataset storage. + +### Security +- Added explicit policy to avoid committing real Anthropic API keys, enforcing the use of environment variables. + ## [1.3.0] - 2026-03-05 ### Added diff --git a/docs/LRM/avap.md b/docs/LRM/avap.md new file mode 100644 index 0000000..2209e9d --- /dev/null +++ b/docs/LRM/avap.md @@ -0,0 +1,1045 @@ +### Prefacio Arquitectónico + +**AVAP es un DSL (Domain-Specific Language) Turing Completo, diseñado arquitectónicamente para la orquestación segura, concurrente y determinista de microservicios e I/O.** No es un lenguaje de propósito general; su motor híbrido y su gramática estricta están optimizados para el procesamiento rápido de transacciones HTTP, la manipulación de datos en memoria y la persistencia, minimizando los efectos secundarios no deseados. + +--- + +# Especificación Técnica Consolidada del Lenguaje AVAP (LRM) + +Este documento unifica la arquitectura de memoria, estructuras de control, modularidad, concurrencia asíncrona y la gramática formal (BNF) del lenguaje AVAP. Actúa como la única fuente de verdad (Single Source of Truth) para la implementación del parser, el motor de ejecución y la indexación del sistema RAG. + +--- + +## SECCIÓN I: Arquitectura, Memoria y Fundamentos Estructurales + +Esta sección sienta las bases de cómo AVAP gestiona la lógica de los servicios y la manipulación de datos en memoria. A diferencia de los lenguajes interpretados convencionales, AVAP utiliza un motor de evaluación híbrida que permite combinar comandos declarativos con expresiones dinámicas. + +### 1.1 Estructura de Archivo y Terminación de Sentencias +AVAP es un lenguaje **estrictamente orientado a líneas**. Esta decisión de diseño garantiza que el analizador sintáctico (parser) sea extremadamente rápido y determinista, evitando la ambigüedad que sufren lenguajes que permiten declaraciones en múltiples líneas. +* Cada instrucción lógica (`statement`) debe completarse en una única línea física de texto. +* El motor reconoce el salto de línea o retorno de carro (``) como el terminador absoluto de la instrucción. +* No se admite la partición de una instrucción, obligando al programador a escribir un código secuencial, limpio y fácil de depurar. + +### 1.2 Registro de Endpoints (registerEndpoint) +El comando `registerEndpoint` es la unidad atómica de configuración en AVAP. Actúa como el puente crítico entre la red externa (HTTP) y el código interno. +* **Mecánica:** Define la ruta URL, el método HTTP permitido (ej. `GET`, `POST`), y la función de entrada principal (Handler). +* **Seguridad:** El servidor AVAP rechazará automáticamente (con un Error 405) cualquier petición que no coincida con el método especificado. +* **Middlewares:** Permite inyectar una lista de funciones previas para validar tokens antes de ejecutar el bloque principal. + +### 1.3 Asignación Dinámica y Referencias (addVar) +AVAP permite una sintaxis de asignación directa mediante el símbolo `=`, otorgando flexibilidad bajo un estricto control de contexto. +* **Evaluación en tiempo real:** Cuando el intérprete lee `variable = expresión`, resuelve cualquier operación matemática o lógica utilizando el motor de evaluación subyacente. +* **El operador de desreferenciación (`$`):** Cuando se utiliza el comando nativo `addVar(copia, $original)`, el prefijo `$` indica al motor que debe buscar en la tabla de símbolos la variable llamada "original" y extraer su valor. +* **Semántica de addVar:** El comando acepta `addVar(valor, variable)` o `addVar(variable, valor)`. Si ambos argumentos son identificadores, el valor del segundo se asigna al primero. No está permitido usar dos literales como argumentos. + +### Especificación BNF (Sección I) + +```bnf + ::= ( | )* + ::= [ ] [ | ] + | ( | ) + ::= /* Retorno de carro / Salto de línea (\n o \r\n) */ + + ::= + | + | + | + | + | + | + | + | + | + | + | + | + | + + ::= "=" + +/* Llamada a función global (sin receptor de objeto) */ + ::= "(" [] ")" + +/* Llamada a método sobre un objeto conector (con receptor) */ + ::= "=" "." "(" [] ")" + + ::= | + ::= "registerEndpoint(" "," "," "," "," "," ")" +/* addVar asigna un valor a una variable. Acepta (valor, variable) o (variable, valor). + Si ambos argumentos son identificadores, el valor del segundo se asigna al primero. + No está permitido pasar dos literales como argumentos. */ + ::= "addVar(" "," ")" + ::= | | "$" +/* Restricción semántica: al menos uno de los dos debe ser */ + + ::= [a-zA-Z_] [a-zA-Z0-9_]* + +/* Variables de sistema reservadas — accesibles y asignables desde cualquier scope: + _status — código HTTP de respuesta (ej. addVar(_status, 401) o _status = 404) */ + ::= "_status" +``` + +--- + +## SECCIÓN II: Gestión de Entrada y Salida (I/O) + +Esta sección describe los mecanismos que AVAP utiliza para la ingesta de datos externos, la validación de la integridad de los parámetros y la construcción del paquete de respuesta HTTP. AVAP no posee comandos de impresión interna (como `print`); toda salida de datos se realiza a través de la interfaz HTTP. + +### 2.1 Captura Inteligente de Parámetros (addParam) +El comando `addParam(parametro, destino)` inspecciona la petición HTTP en un orden jerárquico estricto: primero en la URL (Query arguments), luego en el JSON Body, y finalmente en el Form Data. Si el parámetro solicitado no existe, la variable de destino se inicializa como `None`. + +### 2.2 Validación y Colecciones (getListLen / getQueryParamList) +* **`getListLen(fuente, destino)`**: Actúa como un inspector de volumen. Cuenta cuántos elementos hay en una lista o cadena. +* **`getQueryParamList(parametro, lista_destino)`**: Empaqueta automáticamente múltiples ocurrencias de un parámetro de URL (ej. `?filtro=A&filtro=B`) en una única estructura de lista. + +### 2.3 Construcción de Respuesta (addResult y _status) +El comando `addResult(variable)` es el encargado de registrar qué variables formarán parte del cuerpo JSON de la respuesta final. La variable de sistema `_status` permite definir explícitamente el código HTTP de salida tanto mediante asignación directa (`_status = 404`) como mediante `addVar(_status, 401)`. + +### Especificación BNF (Sección II) + +```bnf + ::= | | | + ::= "addParam(" "," ")" + ::= "getListLen(" "," ")" + ::= "getQueryParamList(" "," ")" + ::= "addResult(" ")" +``` + +--- + +## SECCIÓN III: Lógica de Control y Estructuras de Decisión + +AVAP utiliza una gramática estructural mixta. Combina la fluidez de las palabras clave para abrir bloques funcionales con la seguridad matemática de cierres estrictos. + +### 3.1 El Bloque Condicional (if() / else() / end()) +La estructura `if()` evalúa una expresión lógica o de comparación. Todo bloque condicional requiere un cierre explícito utilizando el comando `end()`. + +El comando `if()` soporta dos modos de invocación: +* **Modo 1 (comparación estructurada):** `if(variable, valor, comparador)` — evalúa la comparación entre variable y valor usando el operador indicado como string (ej. `"=="`, `">"`, `"!="`). Los dos primeros argumentos deben ser identificadores simples o literales, nunca expresiones de acceso como `dict['clave']`. Si se necesita comparar un valor extraído de una estructura, debe asignarse primero a una variable.* **Modo 2 (expresión libre):** `if(None, None, "expresion_compleja")` — evalúa directamente una expresión booleana compleja proporcionada como string. + +### 3.2 Iteraciones Estrictas y Deterministas (startLoop / endLoop) +Para garantizar el determinismo y evitar el colapso de memoria: +* Los bucles se definen mediante `startLoop(contador, inicio, fin)`. Solo iteran basándose en índices numéricos finitos. +* El bloque debe cerrarse obligatoriamente con `endLoop()`. +* La forma de salir anticipadamente es invocando el comando global `return()`. + +### 3.3 Gestión de Errores en Tiempo de Ejecución (try() / exception() / end()) +Diseñada para proteger la estabilidad del servidor ante fallos de I/O. +* Si ocurre un fallo del sistema dentro del bloque `try`, el flujo salta al bloque `exception(variable_error)`, poblando la variable con la traza para facilitar la recuperación del script. + +### Especificación BNF (Sección III) + +```bnf + ::= | | + + ::= "if(" ")" + + [ "else()" ] + "end()" + +/* if() soporta dos modos: + Modo 1 — comparación estructurada: los dos primeros argumentos deben ser + identificadores simples o literales, nunca expresiones de acceso. + Si se necesita comparar un valor extraído de una estructura (ej. dict['clave']), + debe asignarse previamente a una variable. + Modo 2 — expresión libre: None, None, expresión compleja como string */ + ::= "," "," + | "None" "," "None" "," + ::= | + + ::= "startLoop(" "," "," ")" + + "endLoop()" + + ::= "try()" + + "exception(" ")" + + "end()" + + ::= * +``` + +--- + +## SECCIÓN IV: Concurrencia y Asincronía + +Implementa un sistema avanzado basado en hilos ligeros (gorutinas), permitiendo que el servidor procese operaciones de E/S largas sin bloquear el hilo principal. + +### 4.1 Comando Lanzador (go) +* **Sintaxis:** `identificador = go nombre_funcion(parametros)`. +* **Mecánica:** Crea un nuevo contexto de ejecución aislado. Devuelve un identificador único que debe guardarse para interactuar con el hilo posteriormente. + +### 4.2 Comando Sincronizador (gather) +* **Sintaxis:** `resultado = gather(identificador, timeout)`. +* **Mecánica:** Pausa el hilo principal esperando el resultado. Si se supera el `timeout` especificado, cancela la espera y devuelve `None`. + +### Especificación BNF (Sección IV) + +```bnf + ::= | + ::= "=" "go" "(" [] ")" + ::= "=" "gather(" ["," ] ")" +``` + +--- + +## SECCIÓN V: Conectores de Terceros, Peticiones HTTP y ORM Nativo + +Agrupa todas las capacidades de interconexión hacia el exterior, permitiendo consumir integraciones de terceros, APIs externas y administrar bases de datos relacionales sin drivers adicionales. + +### 5.1 Conectores de Terceros (avapConnector) + +`avapConnector` es el mecanismo de integración con servicios de terceros configurados en la plataforma AVAP. Un conector se registra previamente mediante un UUID único. Al instanciarlo, la variable se convierte en un **objeto proxy** que encapsula credenciales y contexto, exponiendo métodos dinámicos mediante notación de punto. + +**Patrón de uso:** +```avap +// 1. Instanciar el conector usando su UUID +belvo_connector = avapConnector("20908e93260147acb2636967021fbf5d") + +// 2. Invocar métodos dinámicos (resueltos en runtime) +institutions = belvo_connector.list_institutions() +balances = belvo_connector.get_balances(link, account_id) + +// 3. Resultado tratable como variable estándar +addResult(balances) +``` + +### 5.2 Cliente HTTP Externo (RequestPost / RequestGet) + +Para evitar hilos bloqueados por latencia de red, AVAP exige un parámetro de **timeout** (en milisegundos). Si se supera, la variable destino recibe `None`. + +* **`RequestPost(url, querystring, headers, body, destino, timeout)`**: Ejecuta un POST almacenando la respuesta en `destino`. +* **`RequestGet(url, querystring, headers, destino, timeout)`**: Ejecuta un GET omitiendo el cuerpo. + +### 5.3 Conector de Bases de Datos y ORM + +AVAP utiliza `avapConnector("TOKEN")` para la hidratación segura de credenciales. Las operaciones se ejecutan sobre una tabla específica definida por el parámetro `tableName`. + +* **`ormCheckTable(tableName, varTarget)`**: Verifica la existencia de una tabla en la base de datos conectada. +* **`ormCreateTable(fields, fieldsType, tableName, varTarget)`**: Comando DDL para creación de tablas. +* **`ormAccessSelect(fields, tableName, selector, varTarget)`**: Recupera registros. `fields` acepta `*` o lista de campos. El `selector` es la cláusula WHERE (puede estar vacío). Devuelve una lista de diccionarios. +* **`ormAccessInsert(fieldsValues, tableName, varTarget)`**: Inserción parametrizada de registros en la tabla `tableName`. +* **`ormAccessUpdate(fields, fieldsValues, tableName, selector, varTarget)`**: Modifica registros existentes. El `selector` es obligatorio para delimitar el alcance del cambio en la tabla `tableName`. +* **`ormDirect(sentencia, destino)`**: Ejecución de SQL crudo para consultas analíticas complejas. + + + +--- + +### Especificación BNF (Sección V) + +```bnf +/* Instanciación de conector de terceros y llamada a sus métodos dinámicos */ + ::= | + ::= "=" "avapConnector(" ")" + ::= [ "=" ] "." "(" [] ")" + +/* Cliente HTTP con Timeout Obligatorio */ + ::= | + ::= "RequestPost(" "," "," "," "," "," ")" + ::= "RequestGet(" "," "," "," "," ")" + +/* ORM y Persistencia (Estandarizado con tableName) */ + ::= | | | | | + ::= "ormDirect(" "," ")" + ::= "ormCheckTable(" "," ")" + ::= "ormCreateTable(" "," "," "," ")" + +/* ormAccessSelect(fields, tableName, selector, varTarget) */ + ::= "ormAccessSelect(" "," "," [] "," ")" + ::= "*" | + +/* ormAccessInsert(fieldsValues, tableName, varTarget) */ + ::= "ormAccessInsert(" "," "," ")" + +/* ormAccessUpdate(fields, fieldsValues, tableName, selector, varTarget) */ + ::= "ormAccessUpdate(" "," "," "," "," ")" +``` + +> **Nota de implementación:** `` se distingue de `` (ORM) únicamente por contexto semántico: el UUID pasado como argumento determina si el adaptador resuelto es un ORM de base de datos o un proxy de terceros. La gramática los trata de forma idéntica; el motor de ejecución selecciona el adaptador apropiado en runtime. + +--- + +## SECCIÓN VI: Utilidades, Criptografía y Manipulación de Datos + +AVAP incluye un set de comandos integrados de alto nivel para manipular tipos complejos (JSON y Listas), tiempos, textos y generar hashes. + +### 6.1 Manipulación Nativa de Listas y Objetos JSON +Para extraer y mutar estructuras complejas, AVAP provee comandos nativos específicos: +* **`variableToList(elemento, destino)`**: Fuerza a que una variable escalar se convierta en una estructura iterable de lista. +* **`itemFromList(lista_origen, indice, destino)`**: Extrae de forma segura el elemento contenido en la posición `indice` de una lista. +* **`variableFromJSON(json_origen, clave, destino)`**: Parsea un objeto JSON en memoria y extrae el valor correspondiente a la `clave`. +* **`AddVariableToJSON(clave, valor, json_destino)`**: Inyecta dinámicamente una nueva propiedad dentro de un objeto JSON existente. + +### 6.2 Criptografía y Expresiones Regulares +* **`encodeSHA256` y `encodeMD5(origen, destino)`**: Funciones criptográficas que encriptan de forma irreversible un texto. Vitales para el almacenamiento seguro de contraseñas. +* **`getRegex(origen, patron, destino)`**: Aplica una Expresión Regular (`patron`) sobre la variable de origen, extrayendo las coincidencias exactas. + +### 6.3 Transformación de Tiempo y Cadenas +* **Fechas:** `getTimeStamp` (convierte un string a Epoch), `getDateTime` (Epoch a string legible), y `stampToDatetime` (Epoch a objeto datetime estructurado). Soportan formatos de calendario y cálculos con TimeDeltas. +* **Cadenas:** `replace` (saneamiento y sustitución de texto) y `randomString` (generación determinista de claves/tokens aleatorios). + +### Especificación BNF (Sección VI) + +```bnf +/* [CORRECCIÓN] Todas las subreglas de están ahora completamente expandidas. */ + ::= | | | | | | + +/* Manipulación de listas y JSON */ + ::= "variableToList(" "," ")" + | "itemFromList(" "," "," ")" + | "variableFromJSON(" "," "," ")" + | "AddVariableToJSON(" "," "," ")" + +/* Criptografía */ + ::= "encodeSHA256(" "," ")" + | "encodeMD5(" "," ")" + +/* Expresiones regulares */ + ::= "getRegex(" "," "," ")" + + ::= "getDateTime(" "," "," "," ")" +/* Argumentos: formato_salida, epoch_origen, zona_horaria, destino */ + + ::= "stampToDatetime(" "," "," "," ")" +/* Argumentos: epoch_origen, formato, timedelta, destino */ + | "getTimeStamp(" "," "," "," ")" +/* Argumentos: fecha_string, formato_entrada, timedelta, destino */ + + ::= "randomString(" "," ")" +/* Argumentos: longitud, destino */ + + ::= "replace(" "," "," "," ")" +/* Argumentos: origen, patron_busqueda, reemplazo, destino */ +``` + +--- + +## SECCIÓN VII: Arquitectura de Funciones y Ámbitos (Scopes) + +Las funciones son recintos herméticos de memoria. Al entrar en una función, AVAP crea un nuevo diccionario de variables locales aislado del contexto global. +El comando `return()` actúa como interruptor de flujo: inyecta el valor calculado al llamador, libera la memoria local, y si se usa dentro de un `startLoop`, rompe la iteración anticipadamente. + +### Especificación BNF (Sección VII) + +```bnf +/* Nota: las funciones utilizan llaves {} como delimitadores de bloque por decisión + arquitectónica explícita, diferenciándose de las estructuras de control (if, loop, try) + que usan palabras clave de cierre (end(), endLoop()). Ambos patrones coexisten + en la gramática y el parser los distingue por el token de apertura. */ + ::= "function" "(" [] ")" "{" + + "}" + ::= ("," )* + ::= "return(" [] ")" +``` + +--- + +## SECCIÓN VIII: Modularidad e Inclusiones + +* **Inclusión Estática (`include`)**: Directiva de preprocesador que pega el contenido de un fichero físico en la línea actual. +* **Librerías (`import`)**: Carga colecciones de funciones. Corchetes angulares (`import `) para nativas, comillas (`import "mis_utils"`) para locales. + +### Especificación BNF (Sección VIII) + +```bnf + ::= | + ::= "include" " " + ::= "import" " " ( "<" ">" | ) +``` + +--- + +## SECCIÓN IX: Expresiones y Gramática Léxica Estricta + +Esta sección es el corazón matemático evaluador de AVAP. Define la jerarquía exacta (Precedencia) y provee soporte nativo para características avanzadas similares a Python. + +### 9.1 Cast de Tipos Explícito +AVAP permite conversiones de tipos (Type Casting) en cualquier evaluación utilizando funciones constructoras estándar. Puedes transformar variables dinámicamente usando `int(var)`, `float(var)` o `str(var)`. + +### 9.2 Slicing y Comprensiones (Comprehensions) +* **Slicing (Cortes):** Puedes extraer fragmentos de listas o strings utilizando la notación de dos puntos. Ejemplo: `mi_lista[1:4]` (extrae desde el índice 1 hasta el 3). +* **Comprehensions:** AVAP soporta la construcción rápida de listas mediante iteradores en una sola línea, permitiendo filtrar y mapear colecciones enteras (ej. `[x * 2 for x in valores if x > 0]`). + +### 9.3 Análisis Léxico (Lexer) y Documentación +AVAP cuenta con tres niveles de descarte de texto para anotaciones humanas: +1. **Comentarios de Línea (`//`):** Ignora el texto hasta el salto de línea. +2. **Comentarios de Bloque (`/* ... */`):** Para aislar bloques enteros multilínea. +3. **Comentarios de Documentación (`///`):** Utilizados por analizadores de código o IDEs para generar documentación técnica automática (Docstrings) a partir del código fuente. + +### Especificación BNF (Sección IX) + +```bnf +/* Jerarquía de Expresiones (Precedencia de menor a mayor) */ + ::= + ::= ( "or" )* + ::= ( "and" )* + ::= "not" | + + ::= ( )* + ::= "==" | "!=" | "<" | ">" | "<=" | ">=" | "in" | "is" + + ::= ( ( "+" | "-" ) )* + ::= ( ( "*" | "/" | "%" ) )* + ::= ( "+" | "-" ) | + ::= [ "**" ] + +/* Primarios y Átomos (Accesos, Castings, Slicing, Métodos y Funciones) + La regla cubre también el acceso a métodos de objetos conector + (conector.metodo(...)) y el acceso por clave a sus resultados (resultado["key"]) */ + ::= + | "." + | "[" "]" + | "[" [] ":" [] [":" []] "]" + | "(" [] ")" + + ::= + | "$" + | + | "(" ")" + | + | + +/* Estructuras de Datos, Comprensiones y Argumentos */ + ::= "[" [] "]" + | "[" "for" "in" [] "]" + ::= "if" + ::= "{" [] "}" + ::= ( "," )* + ::= ":" + ::= ( "," )* + +/* Tipo numérico unificado */ + ::= | + +/* Literales (Tipos de Datos Primitivos Soportados) */ + ::= | | | "None" + ::= "True" | "False" + ::= [0-9]+ + ::= [0-9]+ "." [0-9]* | "." [0-9]+ + +/* Cadenas de Texto con soporte de secuencias de escape */ + ::= "\"" "\"" | "'" "'" + ::= "\\" ( "\"" | "'" | "\\" | "n" | "t" | "r" | "0" ) + ::= ( [^"\\] | )* + ::= ( [^'\\] | )* + ::= | + +/* Reglas de Comentarios para el Lexer + El lexer aplica longest-match: /// debe evaluarse ANTES que // */ + ::= "///" + ::= "//" + ::= "/*" "*/" + ::= [^\r\n]* + ::= /* Cualquier secuencia de caracteres que no contenga la subcadena "*/" */ +``` + +# APÉNDICE X: Especificación Léxica de AVAP + +Este apéndice define las reglas del **analizador léxico (lexer)** del lenguaje AVAP. +El lexer transforma el código fuente en una secuencia de **tokens**, que posteriormente son consumidos por el parser descrito en la gramática BNF. + +El análisis léxico sigue el principio de **máxima coincidencia (longest match)**: cuando múltiples reglas pueden coincidir con el mismo texto, se selecciona la coincidencia más larga. + +--- + +# X.1 Espacios en blanco y separadores + +Los siguientes caracteres se ignoran excepto cuando forman parte de literales o comentarios. + +```regex +WHITESPACE ::= [ \t]+ +EOL ::= \r\n | \n | \r +``` + +Reglas: + +- `WHITESPACE` se ignora +- `EOL` genera el token **EOL**, que actúa como terminador de sentencia +- AVAP es un lenguaje **orientado a líneas**, por lo que las sentencias no pueden dividirse en múltiples líneas. + +--- + +# X.2 Comentarios + +AVAP soporta tres tipos de comentarios. El lexer aplica longest-match, por lo que `///` debe reconocerse **antes** que `//`. + +## Comentario de documentación (mayor prioridad) + +```regex +DOC_COMMENT ::= "///"[^\r\n]* +``` + +Se utiliza para generar documentación automática o anotaciones de herramientas. + +Ejemplo: + +```avap +/// obtiene el balance del usuario +``` + +--- + +## Comentario de línea + +```regex +LINE_COMMENT ::= "//"[^\r\n]* +``` + +Ejemplo: + +```avap +// comentario +``` + +El texto se ignora hasta el final de la línea. + +--- + +## Comentario de bloque + +```regex +BLOCK_COMMENT ::= "/*" .*? "*/" +``` + +Puede abarcar múltiples líneas. + +Ejemplo: + +```avap +/* comentario + multilinea */ +``` + +--- + +# X.3 Identificadores + +Los identificadores representan nombres de variables, funciones o parámetros. + +```regex +IDENTIFIER ::= [a-zA-Z_][a-zA-Z0-9_]* +``` + +Ejemplos válidos: + +``` +x +user_id +balanceTotal +_connector +``` + +--- + +# X.4 Palabras reservadas + +Las siguientes palabras están reservadas y **no pueden utilizarse como identificadores**. + +## Control de flujo + +``` +if +else +end +startLoop +endLoop +try +exception +return +``` + +## Declaración de funciones + +``` +function +``` + +## Concurrencia + +``` +go +gather +``` + +## Modularidad + +``` +include +import +``` + +## Operadores lógicos + +``` +and +or +not +in +is +``` + +## Literales + +``` +True +False +None +``` + +--- + +# X.5 Operadores + +## Asignación + +``` += +``` + +Token: + +``` +ASSIGN +``` + +--- + +## Operadores aritméticos + +``` ++ +- +* +/ +% +** +``` + +Tokens: + +``` +PLUS +MINUS +MULT +DIV +MOD +POWER +``` + +Regla importante: + +`**` debe evaluarse antes que `*` por la regla de **máxima coincidencia**. + +--- + +## Operadores de comparación + +``` +== +!= +< +> +<= +>= +``` + +Tokens: + +``` +EQ +NEQ +LT +GT +LTE +GTE +``` + +--- + +## Operadores lógicos + +``` +and +or +not +``` + +Tokens: + +``` +AND +OR +NOT +``` + +--- + +# X.6 Delimitadores + +Los siguientes símbolos delimitan estructuras sintácticas. + +``` +( +) +[ +] +{ +} +, +. +: +``` + +Tokens: + +``` +LPAREN +RPAREN +LBRACKET +RBRACKET +LBRACE +RBRACE +COMMA +DOT +COLON +``` + +--- + +# X.7 Literales + +## Enteros + +```regex +INTEGER ::= [0-9]+ +``` + +Ejemplos: + +``` +0 +10 +999 +``` + +--- + +## Números flotantes + +```regex +FLOAT ::= [0-9]+\.[0-9]* | \.[0-9]+ +``` + +Ejemplos: + +``` +1.0 +3.14 +.5 +``` + +--- + +## Strings + +AVAP soporta cadenas con comillas simples y dobles, con soporte de secuencias de escape. + +```regex +STRING_DOUBLE ::= "\"" ( [^"\\] | ESCAPE_SEQ )* "\"" +STRING_SINGLE ::= "'" ( [^'\\] | ESCAPE_SEQ )* "'" +ESCAPE_SEQ ::= "\\" ( '"' | "'" | "\\" | "n" | "t" | "r" | "0" ) +``` + +Ejemplos: + +``` +"hola" +'texto' +"https://api.com" +``` + +Secuencias de escape soportadas: + +| Secuencia | Significado | +|-----------|-------------------| +| `\"` | Comilla doble | +| `\'` | Comilla simple | +| `\\` | Barra invertida | +| `\n` | Salto de línea | +| `\t` | Tabulación | +| `\r` | Retorno de carro | +| `\0` | Carácter nulo | + +> **Nota:** `\n` dentro de un string es un carácter de datos, no un terminador de sentencia. El EOL físico sigue siendo el único terminador. + +--- + +# X.8 Literales booleanos + +Tokens: + +``` +True +False +``` + +--- + +# X.9 Literal nulo + +Token: + +``` +None +``` + +--- + +# X.10 Operador de desreferenciación + +AVAP permite acceder al valor de una variable utilizando el prefijo `$`. + +Ejemplo: + +```avap +addVar(copia, $original) +``` + +Token: + +``` +DEREF ::= $ +``` + +--- + +# X.11 Orden de precedencia léxica + +Para evitar ambigüedades, el lexer debe aplicar el principio **longest match first**. + +Orden obligatorio: + +1. comentarios (`///` antes que `//`, luego `/* */`) +2. whitespace +3. palabras reservadas +4. identificadores +5. números flotantes +6. enteros +7. strings +8. operadores compuestos (`**`, `==`, `<=`, `>=`, `!=`) +9. operadores simples +10. delimitadores + +--- + +# X.12 Separación formal: nivel léxico vs nivel sintáctico + +``` +NIVEL LÉXICO — produce tokens: IDENTIFIER, INTEGER, FLOAT, STRING, + operadores, delimitadores, EOL, palabras reservadas. + +NIVEL SINTÁCTICO — consume tokens: construye el AST según las reglas BNF + de las Secciones I–IX. +``` + +El Apéndice X cubre el nivel léxico. Las Secciones I–IX cubren el nivel sintáctico. + +--- + +# X.13 Tokens producidos por el lexer + +El lexer produce los siguientes tokens: + +``` +IDENTIFIER +INTEGER +FLOAT +STRING + +ASSIGN +PLUS +MINUS +MULT +DIV +MOD +POWER + +EQ +NEQ +LT +GT +LTE +GTE + +AND +OR +NOT +IN +IS + +LPAREN +RPAREN +LBRACKET +RBRACKET +LBRACE +RBRACE +COMMA +DOT +COLON + +DEREF + +True +False +None + +EOL +``` + +--- + +# X.14 Elementos ignorados por el lexer + +Los siguientes elementos se descartan durante el análisis léxico: + +``` +WHITESPACE +LINE_COMMENT +DOC_COMMENT +BLOCK_COMMENT +``` + +Estos tokens no son enviados al parser. + + +# XI.1 Modelo de Memoria y Resolución de Variables + +AVAP utiliza un modelo de memoria basado en **tres tipos de ámbitos (scopes)**: + +``` +Global Scope +Main Local Scope +Function Scope +``` + +Cada tipo de ámbito tiene reglas estrictas de visibilidad. + +--- + +# XI.1.1 Global Scope + +El **Global Scope** contiene variables declaradas como globales y es accesible desde cualquier parte del programa. + +Propiedades: + +- existe durante toda la vida del proceso del intérprete +- es visible desde el flujo principal +- es visible desde todas las funciones +- es visible desde goroutines + +Las variables globales actúan como **estado compartido del programa**. + +--- + +# XI.1.2 Main Local Scope + +El **Main Local Scope** corresponde al flujo de ejecución principal del script, fuera de cualquier función. + +Ejemplo: + +``` +x = 10 +y = 20 +``` + +Estas variables son **locales del flujo principal**. + +Reglas: + +- son accesibles dentro del flujo principal +- **no son accesibles desde funciones** +- **no son accesibles desde goroutines** +- desaparecen cuando finaliza la ejecución del script + +Esto evita dependencias implícitas entre funciones y el flujo principal. + +--- + +# XI.1.3 Function Scope + +Cada vez que se invoca una función: + +``` +function nombre(parametros) +``` + +el motor crea un **Function Scope independiente**. + +Este ámbito contiene: + +- parámetros de la función +- variables creadas dentro de la función +- resultados intermedios + +Propiedades: + +- solo es visible dentro de esa función +- no es visible desde el exterior +- se destruye cuando la función termina + +--- + +# XI.1.4 Resolución de variables + +La resolución de variables sigue el siguiente orden jerárquico: + +``` +1. Function Scope +2. Global Scope +``` + +El **Main Local Scope no es visible dentro de funciones**. + +Si una variable no existe en los scopes visibles, el motor produce un **error de ejecución**. + +--- + +# XI.1.5 Aislamiento entre funciones + +Cada invocación de función crea un **scope independiente**. + +Ejemplo: + +``` +function ejemplo() +{ + x = 10 +} +``` + +La variable `x`: + +- solo existe dentro de esa ejecución de la función +- no es visible desde otras funciones +- no es visible desde el flujo principal + +--- + +# XI.1.6 Acceso desde goroutines + +Las goroutines creadas mediante: + +``` +go funcion() +``` + +siguen las mismas reglas de scope que una función normal. + +Por lo tanto: + +- pueden acceder a **Global Scope** +- pueden acceder a su propio **Function Scope** +- **no pueden acceder al Main Local Scope** \ No newline at end of file diff --git a/scripts/pipelines/samples_generator/generate_mbap.py b/scripts/pipelines/samples_generator/generate_mbap.py new file mode 100644 index 0000000..ae943ea --- /dev/null +++ b/scripts/pipelines/samples_generator/generate_mbap.py @@ -0,0 +1,329 @@ +#!/usr/bin/env python3 +""" +Use: + python generate_mbpp_avap.py + python generate_mbpp_avap.py --lrm path/to/avap.md + python generate_mbpp_avap.py --lrm avap.md --output output/mbpp_avap.json --problems 300 + +Requirements: + pip install anthropic + export ANTHROPIC_API_KEY=sk-ant-... +""" + +import argparse +import json +import os +import sys +import time +from pathlib import Path + +import anthropic + +CATEGORIES = [ + ("HTTP params / addParam / addResult / _status",10), + ("Variables y strings / addVar / replace / randomString",10), + ("Condicionales / if() Modo 1 y Modo 2 / else() / end()",10), + ("Bucles y listas / startLoop / itemFromList / getListLen",10), + ("JSON / variableFromJSON / AddVariableToJSON",10), + ("ORM / ormAccessSelect / ormAccessInsert / ormAccessUpdate",10), + ("Criptografía / encodeSHA256 / encodeMD5",10), + ("Fechas / getTimeStamp / getDateTime / stampToDatetime",10), + ("Conectores externos / avapConnector + métodos dinámicos",10), + ("Concurrencia / go + gather",10), + ("Funciones y scope / function / return()",10), + ("Manejo de errores / try() / exception()",10), + ("HTTP externo / RequestGet / RequestPost",10), + ("Modularidad / import / include + casos de uso complejos",10), +] + +TOTAL_PROBLEMS = sum(n for _, n in CATEGORIES) +PROBLEMS_PER_CALL = 10 + + +SYSTEM_PROMPT = """Eres un experto en el lenguaje AVAP. +Se te proporciona el Language Reference Manual (LRM) completo de AVAP. +Tu tarea es generar problemas de benchmark estilo MBPP para evaluar +modelos de lenguaje en su capacidad de generar código AVAP correcto. + +REGLAS ESTRICTAS para el código AVAP generado: +1. Una instrucción por línea. EOL es el terminador absoluto. +2. Sin indentación significativa (es solo decorativa). +3. Bloques de control: if()...else()...end(), startLoop()...endLoop(), try()...exception()...end() +4. Funciones: function name(args) { ... return(val) } +5. if() Modo 1: if(var_o_literal, var_o_literal, "operador") + — los argumentos NO pueden ser expresiones de acceso como dict['key']; + hay que extraer el valor a una variable propia primero. +6. if() Modo 2: if(None, None, "expresion_completa_como_string") +7. _status se asigna con: addVar(_status, 404) +8. ormAccessSelect firma: ormAccessSelect(campos, "tabla", selector, varTarget) + — selector puede ser cadena vacía. +9. Acceso a campos de dict: val = dict['campo'] (línea propia, luego se usa val). +10. Genera ÚNICAMENTE código AVAP válido según el LRM. Sin Python, sin pseudocódigo. + +MODO DE EJECUCIÓN — MUY IMPORTANTE: +- El código se ejecuta DIRECTAMENTE, línea a línea, sin servidor ni registro de endpoints. +- NUNCA uses registerEndpoint(), NUNCA uses mainHandler(), NUNCA envuelvas el código en funciones solo para ejecutarlo salvo que queramos probar la funcionalidad de funciones. +- El código correcto es simplemente las instrucciones en línea, por ejemplo: + result = "Hello World" + addResult(result) +- Si el problema requiere una función auxiliar reutilizable, defínela con function...{} y llámala directamente después: + function double(n) { + return(n * 2) + } + addParam("n", n) + result = double(n) + addResult(result) +- NUNCA termines el código con registerEndpoint ni con ninguna llamada de registro. + +FORMATO DE SALIDA: responde ÚNICAMENTE con un array JSON válido. +Sin texto adicional, sin bloques de código markdown, sin explicaciones. +Estructura exacta de cada elemento: +{ + "task_id": , + "text": "", + "code": "", + "test_list": ["", ""] +} + +FORMATO DE test_list — MUY IMPORTANTE: +Cada aserción debe ser una expresión Python con re.match() o re.search() +evaluable directamente sobre las variables del stack AVAP (disponibles como +variables Python locales). El módulo 're' está siempre disponible. +La expresión debe devolver un match object (truthy) si el test pasa. + +Reglas estrictas: +- USA ÚNICAMENTE re.match(r'', ) o re.search(r'', str()) +- Convierte a string si es necesario: re.match(r'^\\d+$', str(result)) +- Puedes encadenar con 'and': re.match(r'^[a-zA-Z0-9]{32}$', token) and re.match(r'.{32}', token) +- Las variables referenciadas deben existir en el stack tras ejecutar el código. +- NUNCA uses comparaciones directas (==, !=, >, <). +- NUNCA uses isinstance(), len(), assert, ni texto descriptivo. +- NUNCA uses nada que no sea re.match() o re.search(). + +Ejemplos correctos de test_list: + "re.match(r'^[a-f0-9]{64}$', hashed)" + "re.match(r'^[a-zA-Z0-9]{32}$', token)" + "re.match(r'^\\d{4}-\\d{2}-\\d{2}$', date_str)" + "re.search(r'Hello', result)" + "re.match(r'^-?\\d+(\\.\\d+)?$', str(result))" + "re.match(r'^(par|impar)$', result)" + "re.match(r'^40[134]$', str(_status))" + "re.match(r'^\\d+$', str(length))" +""" + + +def build_user_prompt(lrm: str, category: str, count: int, start_id: int): + return f"""# LRM AVAP — Language Reference Manual + +{lrm} + +--- + +# TAREA + +Genera exactamente {count} problemas de benchmark MBPP-AVAP para la categoría: + +**{category}** + +Requisitos: +- Los task_id deben comenzar en {start_id} y ser consecutivos. +- Cada problema debe cubrir un aspecto distinto de la categoría. +- Dificultad variada: algunos simples, algunos intermedios, alguno avanzado. +- El código debe ser realista como endpoint de microservicio HTTP en AVAP. +- Incluye 2-3 aserciones descriptivas en test_list por problema. + +Responde ÚNICAMENTE con el array JSON. Sin texto antes ni después. +""" + + +def parse_response(raw: str): + text = raw.strip() + + if text.startswith("```"): + lines = text.splitlines() + inner = lines[1:] + if inner and inner[-1].strip() == "```": + inner = inner[:-1] + text = "\n".join(inner).strip() + problems = json.loads(text) + + if not isinstance(problems, list): + raise ValueError("answer is not a JSON.") + + for p in problems: + for field in ("task_id", "text", "code", "test_list"): + if field not in p: + raise ValueError(f"field '{field}' not found in a problem.") + + return problems + + +def call_api( client: anthropic.Anthropic, lrm: str, category: str, count: int, start_id: int, retries: int = 3,): + + for attempt in range(1, retries + 1): + try: + message = client.messages.create( + model="claude-sonnet-4-20250514", + max_tokens=8000, + system=SYSTEM_PROMPT, + messages=[ + { + "role": "user", + "content": build_user_prompt(lrm, category, count, start_id), + } + ], + ) + raw = message.content[0].text + problems = parse_response(raw) + + for i, problem in enumerate(problems): + problem["task_id"] = start_id + i + + return problems + + except (json.JSONDecodeError, ValueError, KeyError) as e: + print(f"\n Attempt {attempt}/{retries} — parser error: {e}") + if attempt < retries: + time.sleep(2 ** attempt) + + except anthropic.RateLimitError: + wait = 30 * attempt + print(f"\n Rate limit. waiting {wait}s...") + time.sleep(wait) + + except anthropic.APIError as e: + print(f"\n API error at attempt {attempt}: {e}") + if attempt < retries: + time.sleep(5) + + raise RuntimeError( + f"Cant generate problems '{category}' since {retries} trys." + ) + + +def scale_categories(target: int): + base = TOTAL_PROBLEMS + scaled = [ + (cat, max(1, round(n * target / base))) + for cat, n in CATEGORIES + ] + + diff = target - sum(n for _, n in scaled) + if diff != 0: + last_cat, last_n = scaled[-1] + scaled[-1] = (last_cat, max(1, last_n + diff)) + return scaled + + +def main(): + parser = argparse.ArgumentParser( + description="Create a bunch of samples of code from an LRM." + ) + parser.add_argument( + "--lrm", + default="avap.md", + help="Path to AVAP LRM (default: avap.md)", + ) + parser.add_argument( + "--output", + default="output/mbpp_avap.json", + help="Output JSON file (default: output/mbpp_avap.json)", + ) + parser.add_argument( + "--problems", + type=int, + default=300, + help="Total problems number to generate (default: 300)", + ) + parser.add_argument( + "--api-key", + default=None, + help="Anthropic API key", + ) + args = parser.parse_args() + + api_key = args.api_key or os.environ.get("ANTHROPIC_API_KEY") + if not api_key: + sys.exit( + "ERROR: API key not found.\n" + " Export variable: export ANTHROPIC_API_KEY=sk-ant-...\n" + " Or indicate with: --api-key sk-ant-..." + ) + + lrm_path = Path(args.lrm) + if not lrm_path.exists(): + sys.exit( + f"ERROR: file '{lrm_path}' not found.\n" + f" Put avap.md in actual directory or use --lrm ." + ) + lrm = lrm_path.read_text(encoding="utf-8") + print(f" Source LRM: {lrm_path} ") + + output_path = Path(args.output) + output_path.parent.mkdir(parents=True, exist_ok=True) + + categories = scale_categories(args.problems) + total_calls = sum((n + PROBLEMS_PER_CALL - 1) // PROBLEMS_PER_CALL for _, n in categories) + + print(f" Problems : {args.problems}") + print(f" Output file : {output_path}\n") + print("────────────────────────────────────────────────────────────") + + client = anthropic.Anthropic(api_key=api_key) + all_problems: list[dict] = [] + task_id = 1 + call_count = 0 + + for cat_idx, (category, total_cat) in enumerate(categories, 1): + print(f"\n[{cat_idx:02d}/{len(categories)}] {category} ({total_cat} problems)") + + remaining = total_cat + batch_num = 0 + + while remaining > 0: + batch_size = min(PROBLEMS_PER_CALL, remaining) + batch_num += 1 + call_count += 1 + + print( + f" Batch {batch_num} | task_id {task_id}–{task_id + batch_size - 1} " + f"| API Call {call_count}/{total_calls} ... ", + end="", + flush=True, + ) + + try: + batch = call_api(client, lrm, category, batch_size, task_id) + except RuntimeError as e: + print(f"\n {e}") + if all_problems: + _save(all_problems, output_path, partial=True) + sys.exit(1) + + all_problems.extend(batch) + task_id += len(batch) + remaining -= len(batch) + print(f" {len(batch)} generated (total: {len(all_problems)})") + + if remaining > 0: + time.sleep(1.5) + + _save(all_problems, output_path, partial=False) + print(f" '- Save actual results.") + + print("\n" + "────────────────────────────────────────────────────────────") + print(f" Process completed") + print(f" Problems generated : {len(all_problems)}") + print(f" task_id range : {all_problems[0]['task_id']} – {all_problems[-1]['task_id']}") + print(f" Output file : {output_path}") + + +def _save(problems: list[dict], path: Path, partial: bool = False): + suffix = ".partial" if partial else "" + target = path.with_suffix(suffix + path.suffix) if partial else path + with open(target, "w", encoding="utf-8") as f: + json.dump(problems, f, ensure_ascii=False, indent=2) + + +if __name__ == "__main__": + main()