Especificación del Agente
Spec Driven Development — HealthQuery v0.3
Descripción General
Agente de consulta a un dataset de salud mediante lenguaje natural. El usuario hace una pregunta en portugués, español o inglés — el agente interpreta, genera SQL SELECT, ejecuta en la base de datos conectada y devuelve la respuesta en texto con el razonamiento expuesto.
Opera sobre 5 tablas: pacientes, consultas, diagnosticos, medicamentos, exames. Límite de 30 consultas por sesión. Ningún dato es modificado.
Capacidades
Qué hace el agente
- Acepta preguntas en lenguaje natural en PT, ES e EN
- Clasifica la intención mediante el modelo Haiku antes de cualquier otra llamada
- Genera SQL SELECT compatible con la base de datos conectada (SQLite en preparación, Teradata en el evento)
- Ejecuta la consulta y procesa el resultado — filas devueltas, resultado vacío, error de SQL
- Devuelve respuesta en 3 partes: respuesta directa + SQL generado (siempre expuesto) + razonamiento
- Soporta preguntas multi-paso: descompone en sub-consultas, informa cuántas usará antes de ejecutar
- Gestiona contador de consultas: avisa cuando restan 5, finaliza cuando llega a 30
- Registra cada interacción con intención clasificada, SQL, tiempo de ejecución, tokens
- Memoria conversacional con ventana deslizante de 30 mensajes — soporta refinamientos anafóricos
- Sugerencias de siguiente pregunta post-respuesta, en el idioma del usuario
- Modo reporte — sintetiza historial de la sesión sin incrementar el contador
Qué no hace el agente
- Sin escritura — sin INSERT, UPDATE, DELETE, DROP, CREATE, ALTER (bloqueo programático)
- No responde preguntas fuera del dominio del dataset
- No formula diagnósticos médicos ni da consejos clínicos
- No ejecuta SQL enviado directamente por el usuario
- No referencia tablas fuera de las 5 definidas en el schema (bloqueo programático)
- No continúa después del límite de 30 consultas
- No inventa datos — si no encontró en la base, lo dice
7 Herramientas
| Herramienta | Función |
|---|---|
execute_sql |
NL→SQL principal con guard rails multicapa |
detect_anomalies |
Compara resultado_valor con referencia_min/max, calcula desvío %, clasifica alterado/crítico |
compare_periods |
2 consultas de agregación, diff absoluto + variación porcentual por columna |
rank_patients |
CTE con 4 factores ponderados, top N con puntaje y justificación |
patient_profile |
4 consultas internas, síntesis en prosa clínica por Sonnet |
trend_analysis |
Divide registros en 2 mitades, compara promedios, interpreta relativo al rango de referencia |
calculate |
Evaluador AST seguro para crecimiento compuesto, exponencial, fórmulas multi-paso |
Guard Rails
Los guard rails son validaciones programáticas en el código, no en el prompt. El modelo no decide qué está permitido — el código lo decide antes.
| Guard Rail | Tipo | Trigger | Acción |
|---|---|---|---|
| Escritura bloqueada | Programático | Haiku clasifica como write_attempt |
Bloquea, responde sin llamar a la BD |
| Límite de consultas | Programático | Contador ≥ 30 | Cierra sesión |
| Schema enforcement | Programático | SQL referencia tabla fuera de las 5 definidas | Rechaza antes de ejecutar |
| PII + diagnóstico sensible | Programático | Query devolvería nombre + condición sin agrupación | Fuerza agregación o rechaza |
| check_pii_result | Programático | Resultado real contiene columna nombre + columnas clínicas | Bloquea — captura SELECT * y split-queries |
Comportamiento por Situación
Pregunta clara, consulta directa
Clasifica como data_query, genera SELECT, ejecuta, devuelve resultado con SQL y razonamiento.
Pregunta ambigua
Hace 1 pregunta de aclaración por turno. Si sigue ambigua, elige la interpretación más común e informa cuál eligió.
Pregunta fuera del dominio
Clasifica como out_of_scope e informa que opera exclusivamente sobre el dataset de salud.
Intento de escritura o modificación
Haiku clasifica como write_attempt → bloqueo programático antes de llegar al modelo principal o a la BD.
Consulta devuelve vacío
No inventa datos, no especula. Informa que no se encontraron resultados para la condición consultada.
Error de SQL
Captura el error, intenta auto-corregir una vez (reenvía error + SQL al modelo). Si falla de nuevo, informa al usuario. El intento cuenta en el límite de 30.
Pregunta multi-paso
Descompone explícitamente antes de ejecutar: informa cuántas consultas usará y qué resuelve cada una.
Acercándose al límite
Avisa al llegar a 5 restantes. Cierra con mensaje claro al llegar a 30. Orienta a iniciar nueva sesión.
PII + condición sensible
Bloquea retorno de identificadores combinados con diagnóstico específico. Ofrece conteo o agregación como alternativa. Estándar production-safe incluso con datos ficticios.
Input no es una pregunta (saludo)
Clasifica como greeting, responde brevemente. No decrementa el contador de consultas.
Schema — 5 tablas
pacientes
| Columna | Tipo | Descripción |
|---|---|---|
| id | INTEGER PK | Identificador |
| nome | TEXT | Nombre completo |
| data_nascimento | DATE | Para calcular edad |
| sexo | TEXT | 'M' o 'F' |
| cidade | TEXT | Ciudad de residencia |
| estado | TEXT | UF (estado) |
| plano_saude | TEXT | Ej: "Unimed", "SUS", "Bradesco" |
consultas
| Columna | Tipo | Descripción |
|---|---|---|
| id | INTEGER PK | Identificador |
| paciente_id | INTEGER FK | → pacientes |
| data_consulta | DATE | Fecha de la consulta |
| medico | TEXT | Nombre del médico |
| especialidade | TEXT | Ej: "Cardiología" |
| tipo | TEXT | 'electiva', 'emergencia', 'retorno' |
| duracao_min | INTEGER | Duración en minutos |
diagnosticos
| Columna | Tipo | Descripción |
|---|---|---|
| id | INTEGER PK | Identificador |
| paciente_id | INTEGER FK | → pacientes |
| consulta_id | INTEGER FK | → consultas |
| cid | TEXT | Código CIE (ej: E11.9, I10) |
| descricao | TEXT | Ej: "Diabetes tipo 2" |
| data_diagnostico | DATE | Fecha del diagnóstico |
| status | TEXT | 'activo', 'crónico', 'resuelto' |
medicamentos
| Columna | Tipo | Descripción |
|---|---|---|
| id | INTEGER PK | Identificador |
| paciente_id | INTEGER FK | → pacientes |
| consulta_id | INTEGER FK | → consultas (nullable) |
| nome_comercial | TEXT | Ej: "Losartán" |
| principio_ativo | TEXT | Principio activo |
| dosagem | TEXT | Ej: "500mg" |
| frequencia | TEXT | Ej: "2 veces al día" |
| data_inicio | DATE | Inicio del tratamiento |
| data_fim | DATE | Fin (NULL = en uso) |
| status | TEXT | 'activo', 'suspendido', 'concluido' |
exames
| Columna | Tipo | Descripción |
|---|---|---|
| id | INTEGER PK | Identificador |
| paciente_id | INTEGER FK | → pacientes |
| consulta_id | INTEGER FK | → consultas (nullable) |
| tipo_exame | TEXT | 'laboratorial', 'imagen', 'funcional' |
| nome_exame | TEXT | Ej: "Glucemia en ayunas" |
| resultado_valor | REAL | Para resultados numéricos (nullable) |
| resultado_texto | TEXT | Para resultados textuales |
| unidade | TEXT | Ej: "mg/dL" |
| referencia_min | REAL | Valor mínimo normal |
| referencia_max | REAL | Valor máximo normal |
| data_coleta | DATE | Fecha de recolección |
| status | TEXT | 'normal', 'alterado', 'crítico' |
Decisiones de Diseño
| Decisión | Elección | Razón |
|---|---|---|
| Clasificador de intención | Haiku (modelo) | Más robusto que regex para edge cases; costo despreciable |
| Guard rails | Programático, no via prompt | El prompt puede ser evadido; el código no |
| Auto-corrección de SQL | Máximo 1 retry | Los loops son costosos; 2 fallos señalan ambigüedad en el input |
| Conteo de consultas | Incluye intentos con error | Se alinea con el límite de 30 del hackathon |
| SQL siempre expuesto | Sí | Observabilidad y credibilidad |
| SELECT * | Nunca permitido | Agujero de PII via wildcard |
| EXISTS para múltiples condiciones | EXISTS separado, no AND en la misma fila | Un registro de diagnóstico solo tiene un CID; AND devolvería vacío |
| tool_choice="none" en final_response | Forzado | Evita BadRequestError 400 cuando el modelo intenta generar una segunda herramienta |
| Idioma | PT, ES y EN | Patrick trabaja en PT; el hackathon es global |
Observabilidad — Estructura del Log
Cada interacción es registrada en logs/session_<id>.jsonl. Un registro por línea.
{
"session_id": "uuid",
"query_number": 3,
"timestamp": "ISO8601",
"user_input": "...",
"intent_classified": "data_query",
"sql_generated": "SELECT ...",
"execution_time_ms": 120,
"rows_returned": 47,
"error": null,
"model_used": "claude-sonnet-4-6",
"input_tokens": 340,
"output_tokens": 85
}
Changelog
- 7 herramientas: +detect_anomalies, +compare_periods, +rank_patients, +patient_profile, +trend_analysis, +calculate
- 6 intents: +greeting, +report_request
- Memoria conversacional con ventana deslizante de 30 mensajes
- Sugerencias de siguiente pregunta post-respuesta
- Modo reporte sin incrementar query_count
- Multilingüe PT/ES/EN — clasificador detecta idioma
- 12 reglas en SYSTEM_PROMPT (vs. 5 originales) — 7 nuevas surgidas de bugs en pruebas
- check_pii_result: analiza columnas del resultado real, no solo el SQL
- Soporte a múltiples bloques tool_use en paralelo
- tool_choice="none" en final_response — corrige BadRequestError 400
- Calibración del clasificador Haiku: ejemplos de clarification_needed vs. data_query
- Spec inicial: descripción general, capacidades, 4 guard rails, schema 5 tablas
- 10 situaciones de comportamiento documentadas
- Estructura del log JSONL definida
HealthQuery — AI Agent Hackathon Pilot — Teradata Global Services — Junio 2026