Conversor intermedio (REQUIERE DE LLM) para traducir preguntas en lenguaje natural a una tabla en JSON
Python
0
100 commits
updated Jun 10, 2026
Conversor intermedio (requiere LLM) para traducir preguntas en lenguaje natural a SQL y devolver resultados en JSON.
.env.example definen defaults operativos de 15 minutos para timeouts.Ver guía: docs/operacion/timeouts-y-politica-sql.md.
Para casos de extracción documental con LLM, ver también: docs/operacion/ejemplos-documentos-llm.md.
La API usa autenticación por Bearer JWT para proteger POST /nl2sql/query y endpoints administrativos (ejemplo: DELETE /session/{session_id}).
AUTH_REQUIRED (default true): activa/desactiva verificación JWT.AUTH_JWT_SECRET (requerida cuando AUTH_REQUIRED=true): secreto HMAC para validar JWT.AUTH_JWT_ALGORITHM (default HS256).AUTH_JWT_AUDIENCE (opcional).AUTH_JWT_ISSUER (opcional).Si AUTH_REQUIRED=true, la aplicación falla al arrancar cuando:
AUTH_JWT_SECRET.AUTH_JWT_SECRET tiene valores inseguros (changeme, default, secret, etc.).AUTH_JWT_SECRET tiene menos de 32 caracteres.POST /nl2sql/query requiere scope query:execute.audit:admin, oadmin.sub: identificador del sujeto (requerido).scopes (lista) o scope (string separado por espacios).roles (lista) o role (string).El servicio soporta dos backends de sesión configurables por variables de entorno:
SESSION_MANAGER_BACKEND=memory (default): historial en memoria del proceso.SESSION_MANAGER_BACKEND=redis: historial persistido en Redis por session_id y agente (refiner / sql_agent).Variables para Redis:
REDIS_URL (default redis://localhost:6379/0)SESSION_TTL_SECONDS (default 3600)SESSION_KEY_PREFIX (default nl2sql:session)SESSION_TTL_POLICY (default absolute):
absolute: el TTL solo se aplica en escrituras (set_history).sliding: el TTL se renueva en cada lectura válida (get_history).Cada historial se serializa en JSON y se almacena con TTL para expiración automática. También se puede borrar explícitamente con DELETE /session/{session_id}.
export SESSION_MANAGER_BACKEND=memory
export SESSION_MANAGER_BACKEND=redis
export REDIS_URL=redis://redis.internal:6379/0
export SESSION_TTL_SECONDS=3600
export SESSION_TTL_POLICY=absolute
export SESSION_KEY_PREFIX=nl2sql:session:staging
SESSION_MANAGER_BACKEND=memory (simple y sin dependencias externas)SESSION_KEY_PREFIX=nl2sql:session:dev y SESSION_TTL_SECONDS=900SESSION_MANAGER_BACKEND=redisSESSION_KEY_PREFIX=nl2sql:session:stagingSESSION_TTL_SECONDS=1800 o 3600SESSION_MANAGER_BACKEND=redisSESSION_KEY_PREFIX=nl2sql:session:prodSESSION_TTL_SECONDS=3600 o mayor según retención requeridaSESSION_TTL_POLICY=absolute para comportamiento más predecible, o sliding si se busca mantener vivas sesiones activasFormato de clave:
{SESSION_KEY_PREFIX}:{session_id}:{agent}
Donde:
session_id: identificador de conversación.agent: refiner o sql_agent.Ejemplos:
nl2sql:session:prod:abc123:refinernl2sql:session:prod:abc123:sql_agentConsideraciones operativas:
dev/staging/prod) evita colisiones.SESSION_TTL_POLICY=sliding, las lecturas renuevan expiración.DELETE /session/{session_id} elimina las claves de ambos agentes.Todos los ejemplos usan
POST /nl2sql/querycon Bearer JWT (query:execute).
const payload = {
host: "localhost",
usuario: "user",
contraseña: "secret",
puerto: 5432,
nombre_bd: "testdb",
motor_bd: "postgres",
consulta_nl: "Muestra los 10 usuarios más recientes",
session_id: "demo-session-1"
};
const resp = await fetch("http://localhost:8000/nl2sql/query", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer TU_JWT"
},
body: JSON.stringify(payload)
});
console.log(await resp.json());
import requests
payload = {
"host": "localhost",
"usuario": "user",
"contraseña": "secret",
"puerto": 5432,
"nombre_bd": "testdb",
"motor_bd": "postgres",
"consulta_nl": "Muestra los 10 usuarios más recientes",
"session_id": "demo-session-1",
}
resp = requests.post(
"http://localhost:8000/nl2sql/query",
json=payload,
headers={"Authorization": "Bearer TU_JWT"},
timeout=30,
)
print(resp.status_code, resp.json())
<?php
$payload = [
"host" => "localhost",
"usuario" => "user",
"contraseña" => "secret",
"puerto" => 5432,
"nombre_bd" => "testdb",
"motor_bd" => "postgres",
"consulta_nl" => "Muestra los 10 usuarios más recientes",
"session_id" => "demo-session-1"
];
$ch = curl_init("http://localhost:8000/nl2sql/query");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"Authorization: Bearer TU_JWT"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
echo $result;
curl_close($ch);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
String json = """
{
"host":"localhost",
"usuario":"user",
"contraseña":"secret",
"puerto":5432,
"nombre_bd":"testdb",
"motor_bd":"postgres",
"consulta_nl":"Muestra los 10 usuarios más recientes",
"session_id":"demo-session-1"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8000/nl2sql/query"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer TU_JWT")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
using System.Net.Http.Headers;
using System.Text;
var payload = """
{
"host":"localhost",
"usuario":"user",
"contraseña":"secret",
"puerto":5432,
"nombre_bd":"testdb",
"motor_bd":"postgres",
"consulta_nl":"Muestra los 10 usuarios más recientes",
"session_id":"demo-session-1"
}
""";
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:8000/nl2sql/query");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "TU_JWT");
request.Content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());
#include <curl/curl.h>
#include <string>
int main() {
CURL* curl = curl_easy_init();
if (!curl) return 1;
std::string body = R"({
"host":"localhost",
"usuario":"user",
"contraseña":"secret",
"puerto":5432,
"nombre_bd":"testdb",
"motor_bd":"postgres",
"consulta_nl":"Muestra los 10 usuarios más recientes",
"session_id":"demo-session-1"
})";
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "Authorization: Bearer TU_JWT");
curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:8000/nl2sql/query");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
curl_easy_perform(curl);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return 0;
}
La API registra cada request de POST /nl2sql/query en formato JSON estructurado con:
session_idengine (motor_bd)status_codedurations_ms por etapa (schema, llm, sql) y totalerror_type / error_message (si aplica)Backends de auditoría configurables:
AUDIT_LOG_BACKEND=stream (default): emite JSON por stdout.AUDIT_LOG_BACKEND=file: persiste a AUDIT_LOG_FILE_PATH (default logs/audit.log).AUDIT_LOG_BACKEND=sqlite: persiste a AUDIT_LOG_DB_PATH y tabla AUDIT_LOG_DB_TABLE.Métricas base compatibles con Prometheus disponibles en GET /metrics:
nl2sql_requests_totalnl2sql_request_latency_ms_count / nl2sql_request_latency_ms_sumnl2sql_errors_total{error_type="..."}Para asegurar ejecución en un entorno con dependencias instaladas:
make test
El target test instala requirements.txt + requirements-dev.txt y luego ejecuta pytest tests/test_app.py.
Se agregó un demo en demo/ con comando especial de Make:
make install-main-config # define proveedor/key/modelo oficiales en .env del programa principal
make demo-install # configura endpoint/credenciales runtime del demo
make demo-up # build + up del contenedor demo (valida demo/.env)
make demo-smoke # smoke test: build + valida que sakila.film tenga filas
Detalles en demo/README.md.
Parámetros dinámicos en la vista: URL/API bearer, host/puerto/db/usuario/password/engine, TTL y overrides LLM por request; al cambiar contexto DB/LLM/API se rota sesión automáticamente.
Las definiciones oficiales de compañía/proveedor, API key y modelo ahora pertenecen a la instalación principal:
make install-main-config
Este asistente escribe .env con:
LLM_PROVIDEROPENAI_API_KEYOPENAI_MODELOPENAI_BASE_URLAdemás, POST /nl2sql/query permite sobreescribir por request (útil para consumidores que usan otra compañía):
llm_providerllm_modelllm_api_keyllm_base_urlSe agregó workflow de CI demo-smoke para validar build del demo y carga de sakila.
Tip demo CI: INSTALL_YII_ON_BOOT=false por defecto para evitar timeouts en arranque del smoke test.
Conversor intermedio (REQUIERE DE LLM) para traducir preguntas en lenguaje natural a una tabla en JSON
Python
0
100 commits
updated Jun 10, 2026
Conversor intermedio (requiere LLM) para traducir preguntas en lenguaje natural a SQL y devolver resultados en JSON.
.env.example definen defaults operativos de 15 minutos para timeouts.Ver guía: docs/operacion/timeouts-y-politica-sql.md.
Para casos de extracción documental con LLM, ver también: docs/operacion/ejemplos-documentos-llm.md.
La API usa autenticación por Bearer JWT para proteger POST /nl2sql/query y endpoints administrativos (ejemplo: DELETE /session/{session_id}).
AUTH_REQUIRED (default true): activa/desactiva verificación JWT.AUTH_JWT_SECRET (requerida cuando AUTH_REQUIRED=true): secreto HMAC para validar JWT.AUTH_JWT_ALGORITHM (default HS256).AUTH_JWT_AUDIENCE (opcional).AUTH_JWT_ISSUER (opcional).Si AUTH_REQUIRED=true, la aplicación falla al arrancar cuando:
AUTH_JWT_SECRET.AUTH_JWT_SECRET tiene valores inseguros (changeme, default, secret, etc.).AUTH_JWT_SECRET tiene menos de 32 caracteres.POST /nl2sql/query requiere scope query:execute.audit:admin, oadmin.sub: identificador del sujeto (requerido).scopes (lista) o scope (string separado por espacios).roles (lista) o role (string).El servicio soporta dos backends de sesión configurables por variables de entorno:
SESSION_MANAGER_BACKEND=memory (default): historial en memoria del proceso.SESSION_MANAGER_BACKEND=redis: historial persistido en Redis por session_id y agente (refiner / sql_agent).Variables para Redis:
REDIS_URL (default redis://localhost:6379/0)SESSION_TTL_SECONDS (default 3600)SESSION_KEY_PREFIX (default nl2sql:session)SESSION_TTL_POLICY (default absolute):
absolute: el TTL solo se aplica en escrituras (set_history).sliding: el TTL se renueva en cada lectura válida (get_history).Cada historial se serializa en JSON y se almacena con TTL para expiración automática. También se puede borrar explícitamente con DELETE /session/{session_id}.
export SESSION_MANAGER_BACKEND=memory
export SESSION_MANAGER_BACKEND=redis
export REDIS_URL=redis://redis.internal:6379/0
export SESSION_TTL_SECONDS=3600
export SESSION_TTL_POLICY=absolute
export SESSION_KEY_PREFIX=nl2sql:session:staging
SESSION_MANAGER_BACKEND=memory (simple y sin dependencias externas)SESSION_KEY_PREFIX=nl2sql:session:dev y SESSION_TTL_SECONDS=900SESSION_MANAGER_BACKEND=redisSESSION_KEY_PREFIX=nl2sql:session:stagingSESSION_TTL_SECONDS=1800 o 3600SESSION_MANAGER_BACKEND=redisSESSION_KEY_PREFIX=nl2sql:session:prodSESSION_TTL_SECONDS=3600 o mayor según retención requeridaSESSION_TTL_POLICY=absolute para comportamiento más predecible, o sliding si se busca mantener vivas sesiones activasFormato de clave:
{SESSION_KEY_PREFIX}:{session_id}:{agent}
Donde:
session_id: identificador de conversación.agent: refiner o sql_agent.Ejemplos:
nl2sql:session:prod:abc123:refinernl2sql:session:prod:abc123:sql_agentConsideraciones operativas:
dev/staging/prod) evita colisiones.SESSION_TTL_POLICY=sliding, las lecturas renuevan expiración.DELETE /session/{session_id} elimina las claves de ambos agentes.Todos los ejemplos usan
POST /nl2sql/querycon Bearer JWT (query:execute).
const payload = {
host: "localhost",
usuario: "user",
contraseña: "secret",
puerto: 5432,
nombre_bd: "testdb",
motor_bd: "postgres",
consulta_nl: "Muestra los 10 usuarios más recientes",
session_id: "demo-session-1"
};
const resp = await fetch("http://localhost:8000/nl2sql/query", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer TU_JWT"
},
body: JSON.stringify(payload)
});
console.log(await resp.json());
import requests
payload = {
"host": "localhost",
"usuario": "user",
"contraseña": "secret",
"puerto": 5432,
"nombre_bd": "testdb",
"motor_bd": "postgres",
"consulta_nl": "Muestra los 10 usuarios más recientes",
"session_id": "demo-session-1",
}
resp = requests.post(
"http://localhost:8000/nl2sql/query",
json=payload,
headers={"Authorization": "Bearer TU_JWT"},
timeout=30,
)
print(resp.status_code, resp.json())
<?php
$payload = [
"host" => "localhost",
"usuario" => "user",
"contraseña" => "secret",
"puerto" => 5432,
"nombre_bd" => "testdb",
"motor_bd" => "postgres",
"consulta_nl" => "Muestra los 10 usuarios más recientes",
"session_id" => "demo-session-1"
];
$ch = curl_init("http://localhost:8000/nl2sql/query");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"Authorization: Bearer TU_JWT"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
echo $result;
curl_close($ch);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
String json = """
{
"host":"localhost",
"usuario":"user",
"contraseña":"secret",
"puerto":5432,
"nombre_bd":"testdb",
"motor_bd":"postgres",
"consulta_nl":"Muestra los 10 usuarios más recientes",
"session_id":"demo-session-1"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8000/nl2sql/query"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer TU_JWT")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
using System.Net.Http.Headers;
using System.Text;
var payload = """
{
"host":"localhost",
"usuario":"user",
"contraseña":"secret",
"puerto":5432,
"nombre_bd":"testdb",
"motor_bd":"postgres",
"consulta_nl":"Muestra los 10 usuarios más recientes",
"session_id":"demo-session-1"
}
""";
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:8000/nl2sql/query");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "TU_JWT");
request.Content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());
#include <curl/curl.h>
#include <string>
int main() {
CURL* curl = curl_easy_init();
if (!curl) return 1;
std::string body = R"({
"host":"localhost",
"usuario":"user",
"contraseña":"secret",
"puerto":5432,
"nombre_bd":"testdb",
"motor_bd":"postgres",
"consulta_nl":"Muestra los 10 usuarios más recientes",
"session_id":"demo-session-1"
})";
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "Authorization: Bearer TU_JWT");
curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:8000/nl2sql/query");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
curl_easy_perform(curl);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return 0;
}
La API registra cada request de POST /nl2sql/query en formato JSON estructurado con:
session_idengine (motor_bd)status_codedurations_ms por etapa (schema, llm, sql) y totalerror_type / error_message (si aplica)Backends de auditoría configurables:
AUDIT_LOG_BACKEND=stream (default): emite JSON por stdout.AUDIT_LOG_BACKEND=file: persiste a AUDIT_LOG_FILE_PATH (default logs/audit.log).AUDIT_LOG_BACKEND=sqlite: persiste a AUDIT_LOG_DB_PATH y tabla AUDIT_LOG_DB_TABLE.Métricas base compatibles con Prometheus disponibles en GET /metrics:
nl2sql_requests_totalnl2sql_request_latency_ms_count / nl2sql_request_latency_ms_sumnl2sql_errors_total{error_type="..."}Para asegurar ejecución en un entorno con dependencias instaladas:
make test
El target test instala requirements.txt + requirements-dev.txt y luego ejecuta pytest tests/test_app.py.
Se agregó un demo en demo/ con comando especial de Make:
make install-main-config # define proveedor/key/modelo oficiales en .env del programa principal
make demo-install # configura endpoint/credenciales runtime del demo
make demo-up # build + up del contenedor demo (valida demo/.env)
make demo-smoke # smoke test: build + valida que sakila.film tenga filas
Detalles en demo/README.md.
Parámetros dinámicos en la vista: URL/API bearer, host/puerto/db/usuario/password/engine, TTL y overrides LLM por request; al cambiar contexto DB/LLM/API se rota sesión automáticamente.
Las definiciones oficiales de compañía/proveedor, API key y modelo ahora pertenecen a la instalación principal:
make install-main-config
Este asistente escribe .env con:
LLM_PROVIDEROPENAI_API_KEYOPENAI_MODELOPENAI_BASE_URLAdemás, POST /nl2sql/query permite sobreescribir por request (útil para consumidores que usan otra compañía):
llm_providerllm_modelllm_api_keyllm_base_urlSe agregó workflow de CI demo-smoke para validar build del demo y carga de sakila.
Tip demo CI: INSTALL_YII_ON_BOOT=false por defecto para evitar timeouts en arranque del smoke test.