Files
fn_registry/python/functions/datascience/suggest_aggregations_llm.md
T
egutierrez 96da9e3015 feat(eda): funciones de agregación/OLAP para AutomaticEDA (groupby/pivot push-down + selección LLM)
Cuatro funciones nuevas del grupo eda que nutren el capítulo AGREGACION:
- select_groupby_keys (pure): elige categóricas agrupables + numéricas medida desde el TableProfile.
- groupby_stats_duckdb (impure): GROUP BY push-down en DuckDB (count/mean/median/std/min/max por grupo).
- pivot_table_duckdb (impure): pivot A×B push-down, limitado a top filas/cols para no cortar.
- suggest_aggregations_llm (impure): el LLM elige las agregaciones interesantes con fallback determinista.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:33:55 +02:00

6.6 KiB

name, kind, lang, domain, version, purity, signature, description, tags, params, output, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path
name kind lang domain version purity signature description tags params output uses_functions uses_types returns returns_optional error_type imports tested tests test_file_path file_path
suggest_aggregations_llm function py datascience 1.0.0 impure def suggest_aggregations_llm(profile: dict, candidates: dict, max_aggs: int = 4, model: str = "claude-haiku-4-5-20251001") -> dict MUST-11.1 del capitulo AGREGACION del AutomaticEDA (grupo eda). Dado el TableProfile de una tabla y los candidatos cuantitativos de select_groupby_keys ({group_keys:[{col,cardinality,score}], measures:[str], pivots:[{index,columns,value}]}), con UNA sola llamada al LLM elige y ordena las K agregaciones (GROUP BY categorica x medidas numericas) y los pivots MAS INFORMATIVOS para un analisis de grupos, con una razon corta cada uno, evitando la explosion combinatoria (no todo contra todo). Privacidad/coste: NO envia filas crudas, solo el resumen AGREGADO de los candidatos (tabla, columnas categoricas con cardinalidad/score, medidas, pivots). Reusa ask_llm del grupo claude-direct (API directa con token OAuth de Claude). Impura, dict-no-throw: NUNCA lanza y SIEMPRE devuelve algo usable; si el LLM falla, el JSON no parsea o no hay seleccion valida, cae a un fallback determinista construido desde los candidatos (source='fallback'). Toda columna que el LLM invente se descarta.
eda
claude-direct
llm
aggregation
groupby
pivot
datascience
automatic-eda
name desc
profile TableProfile del grupo eda. Solo se usa profile['table'] para nombrar la tabla en el prompt; puede ir vacio o sin esa clave (se usa '(tabla sin nombre)').
name desc
candidates Salida de select_groupby_keys: {group_keys:[{col, cardinality, score}], measures:[str], pivots:[{index, columns, value}]}. group_keys = columnas categoricas candidatas para GROUP BY; measures = columnas numericas a agregar (sum/avg); pivots = cruces index x columns -> value sugeridos. Cualquier columna que el LLM elija debe existir aqui o se descarta. None o no-dict se trata como vacio.
name desc
max_aggs Tope de agregaciones a devolver. Default 4. Valores <1 o no-int se normalizan a 4. Limita tanto la seleccion del LLM como el fallback determinista, para evitar la explosion combinatoria.
name desc
model id del modelo Anthropic a usar en la unica llamada. Default 'claude-haiku-4-5-20251001' (haiku, coste bajo, ~2-3s). Para razones mas finas, pasar p.ej. 'claude-opus-4-8'.
dict dict-no-throw: {status:'ok', source:'llm'|'fallback', aggregations:[{group_by:str, measures:[str], why:str}], pivots:[{index:str, columns:str, value:str|None, why:str}], note:str}. source=='llm' si el LLM produjo al menos una agregacion valida (columnas existentes en candidates); en cualquier otro caso (LLM caido, JSON invalido, seleccion vacia, sin candidatos) source=='fallback' y aggregations/pivots se derivan de candidates con why='selección cuantitativa (sin LLM)'. NUNCA lanza.
ask_llm_py_core
select_groupby_keys_py_datascience
false error_go_core
true
test_extract_json_object
test_extract_json_wrapped_in_fences_and_junk
test_extract_json_non_json_returns_none
test_validate_aggregations_drops_invalid_columns
test_llm_path_uses_selection
test_llm_path_respects_max_aggs
test_llm_invented_column_is_discarded
test_fallback_on_empty_llm_response
test_fallback_on_unparseable_response
test_fallback_respects_max_aggs
test_fallback_when_llm_raises
test_no_candidates_returns_empty_fallback
test_non_dict_candidates_does_not_raise
python/functions/datascience/suggest_aggregations_llm_test.py python/functions/datascience/suggest_aggregations_llm.py

Ejemplo

import sys, os
sys.path.insert(0, os.path.join("python", "functions"))

from datascience.suggest_aggregations_llm import suggest_aggregations_llm

profile = {"table": "ventas"}

# candidates = salida de select_groupby_keys (aqui literal de ejemplo).
candidates = {
    "group_keys": [
        {"col": "categoria", "cardinality": 8, "score": 0.91},
        {"col": "region", "cardinality": 5, "score": 0.74},
        {"col": "canal", "cardinality": 3, "score": 0.60},
    ],
    "measures": ["importe", "unidades"],
    "pivots": [
        {"index": "categoria", "columns": "region", "value": "importe"},
    ],
}

out = suggest_aggregations_llm(profile, candidates, max_aggs=4)   # haiku por defecto

print("fuente:", out["source"])        # "llm" o "fallback" si no hay red
for agg in out["aggregations"]:
    print(f"GROUP BY {agg['group_by']} -> {agg['measures']}  ({agg['why']})")
for piv in out["pivots"]:
    print(f"pivot {piv['index']} x {piv['columns']} = {piv['value']}  ({piv['why']})")

Cuando usarla

Justo despues de select_groupby_keys en el capitulo AGREGACION del AutomaticEDA: cuando ya tienes los candidatos cuantitativos (columnas categoricas con cardinalidad, medidas numericas y pivots posibles) y quieres que un LLM se quede con las K agregaciones y pivots MAS INFORMATIVOS en vez de generar "todo contra todo". Usala para priorizar el plan de analisis de grupos antes de materializar las tablas con aggregate_by_group / pivots, manteniendo el coste y el ruido bajos. Si no hay red o credenciales, sigue funcionando con un fallback determinista, asi que es seguro ponerla en un pipeline.

Gotchas

  • Impura: hace 1 llamada de red al LLM. No es determinista ni gratis. Latencia tipica ~2-3s con haiku. Una sola llamada cubre toda la seleccion.
  • Requiere token OAuth de Claude en ~/.claude/.credentials.json (via ask_llm / grupo claude-direct). Sin token / sin red NO lanza: cae al fallback determinista (source="fallback") construido desde candidates (group_keys x measures hasta max_aggs, pivots tal cual) con why="selección cuantitativa (sin LLM)". Comprueba out["source"] para saber si la seleccion vino del LLM o del fallback.
  • NO envia filas crudas al LLM, solo el resumen AGREGADO de los candidatos. Esto exige que candidates venga ya calculado por select_groupby_keys (cardinalidades, scores, medidas, pivots).
  • Valida columnas inventadas: si el LLM propone un group_by/measure/index/ columns que no esta en candidates, esa entrada se descarta (las medidas se recortan a las validas). Si tras validar no queda ninguna agregacion, cae al fallback completo.
  • max_aggs acota la explosion combinatoria tanto en el camino LLM como en el fallback. Subirlo demasiado reintroduce el ruido que esta funcion evita.
  • Modelo haiku por defecto para coste bajo; sube a claude-opus-4-8 si necesitas razones (why) mas finas (mas caro y lento).