--- name: eda_llm_insights kind: function lang: py domain: datascience version: "1.0.0" purity: impure signature: "def eda_llm_insights(profile: dict, model: str = \"claude-haiku-4-5-20251001\") -> dict" description: "Capa LLM interpretativa del grupo eda. Toma un TableProfile YA CALCULADO (el dict de profile_table) y, con UNA sola llamada al LLM, genera el bloque 'llm': resumen de la tabla, significado de una fila, diccionario de datos, deteccion de PII (RGPD), sugerencias de limpieza y analisis sugeridos. Clave de coste/privacidad: NO envia filas crudas al LLM, solo el perfil AGREGADO (nombres, tipos, % nulos, distinct, top valores agregados de categoricas, stats de numericas, pares de correlacion fuertes). Reusa ask_llm del grupo claude-direct (API directa con token OAuth de Claude). Impura, dict-no-throw." tags: [eda, llm, claude-direct, datascience, profiling, pii, data-dictionary] params: - name: profile desc: "TableProfile ya calculado (el dict que devuelve profile_table()['profile']). Se espera {table, n_rows, columns:[{name, inferred_type, semantic_type, null_pct, distinct_count, numeric:{min,max,mean,p50,...}, categorical:{top:[{value,count,pct}], mode,...}}], correlations:{strong:[{a,b,method,value}]} | None}. Solo se le envia al LLM un resumen agregado; nunca filas crudas." - name: model desc: "id del modelo Anthropic a usar. Default 'claude-haiku-4-5-20251001' (haiku, coste bajo). Para mayor calidad interpretativa, pasar p.ej. 'claude-opus-4-8'." output: "dict dict-no-throw. En exito: {status:'ok', llm:{summary:str, row_meaning:str, dictionary:[{column,description,business_meaning,unit}], pii:[{column,kind,severity}], cleaning:[str], analyses:[str]}}. Las claves que el LLM omita se rellenan con defaults vacios. En error (sin lanzar): {status:'error', error:str}." uses_functions: [ask_llm_py_core] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [] tested: true tests: ["test_build_prompt_includes_table_and_columns", "test_build_prompt_includes_numeric_stats_and_top_values", "test_build_prompt_handles_empty_profile", "test_parse_llm_json_plain", "test_parse_llm_json_with_fences", "test_parse_llm_json_with_surrounding_text", "test_parse_llm_json_nested_braces_in_strings", "test_parse_llm_json_raises_without_object", "test_eda_llm_insights_ok_with_monkeypatched_llm", "test_eda_llm_insights_fills_missing_keys", "test_eda_llm_insights_error_on_empty_profile", "test_eda_llm_insights_error_on_empty_llm_response", "test_eda_llm_insights_error_on_unparseable_llm_response"] test_file_path: "python/functions/datascience/eda_llm_insights_test.py" file_path: "python/functions/datascience/eda_llm_insights.py" --- ## Ejemplo ```python import sys, os sys.path.insert(0, os.path.join("python", "functions")) from pipelines.profile_table import profile_table from datascience import eda_llm_insights # 1) Perfila la tabla (calculo agregado, sin LLM). r = profile_table("data/ventas.duckdb", "ventas", write_report=False) profile = r["profile"] # 2) Interpreta el perfil con UNA llamada al LLM (solo el perfil agregado viaja). out = eda_llm_insights(profile) # haiku por defecto # out = eda_llm_insights(profile, model="claude-opus-4-8") # mas calidad if out["status"] == "ok": llm = out["llm"] print(llm["summary"]) # que es la tabla, 2-3 frases print(llm["row_meaning"]) # que representa una fila for d in llm["dictionary"]: # diccionario de datos por columna print(d["column"], "->", d["description"], f"({d['unit']})") for p in llm["pii"]: # datos personales/sensibles RGPD print("PII:", p["column"], p["kind"], p["severity"]) print(llm["cleaning"]) # sugerencias de limpieza print(llm["analyses"]) # analisis sugeridos + hipotesis else: print("error:", out["error"]) ``` ## Cuando usarla Cuando necesites entender SEMANTICAMENTE una tabla ya perfilada: generar un diccionario de datos legible, detectar PII/datos sensibles RGPD, recibir sugerencias de limpieza y una lista de analisis/hipotesis a explorar. Es el paso interpretativo que sigue a `profile_table`: este calcula las metricas, y `eda_llm_insights` las traduce a lenguaje de negocio. El resultado encaja en la clave `llm` del TableProfile (la que `render_eda_markdown` renderiza en la seccion "Analisis LLM"). ## Gotchas - **Impura: hace 1 llamada de red al LLM.** No es determinista ni gratis. - **Requiere token OAuth de Claude** en `~/.claude/.credentials.json` (via `ask_llm` / grupo `claude-direct`). Sin token, devuelve `{status:'error'}`. - **NO envia filas crudas al LLM**, solo el perfil AGREGADO (nombres, tipos, % nulos, distinct, top valores ya agregados, stats numericas, correlaciones fuertes). Privacidad y coste minimos por diseno — pero requiere que el `profile` venga ya calculado por `profile_table`. - **Modelo `haiku` por defecto** para coste bajo; sube a `claude-opus-4-8` si necesitas interpretacion mas fina (mas caro y lento). - El LLM puede omitir claves: las que falten se rellenan con defaults vacios (`""` o `[]`), nunca lanza por shape incompleto. - El parseo tolera `\`\`\`json` fences y texto alrededor del objeto, pero si el modelo no devuelve ningun objeto JSON, retorna `{status:'error'}`.