--- license: apache-2.0 language: - es tags: - agent-trace - conversational - education - spanish - build-small-hackathon pretty_name: Sofía — Agent Trace size_categories: - n<1K --- # Open Trace — Sofía (mérito 5, "Sharing is Caring") **TL;DR (EN):** 12 end-to-end turns of the real Sofía pipeline (intent router → curated content from `content/` → safety guard → small fine-tuned LLM), captured as JSONL with the exact prompt sent to the model at each step. Shows the LLM is a small, replaceable "conversational glue" component, not the source of facts — including a turn where the model refuses to invent a story and offers curated content instead, and a turn blocked entirely before reaching the LLM. Part of the [`sofia-educational-companion`](https://huggingface.co/spaces/build-small-hackathon/sofia-educational-companion) project (Build Small Hackathon, track *Backyard AI*). `agent_trace.jsonl` son 12 turnos representativos corridos contra el **pipeline real** (`safety.guard` + `llm.engine.LumiEngine` + `content.loader.ContentLibrary`), generados con `export_trace.py`. Cada línea es un turno completo, de punta a punta. Objetivo: que se vea, con datos reales, lo que dice el "Principio inviolable" de [`CLAUDE.md`](../CLAUDE.md): el LLM es **solo el pegamento conversacional** dentro de un pipeline mayormente determinista (router de intención → contenido curado de `content/` → guard de seguridad). El modelo nunca es la fuente de hechos, cuentos ni números. ## Formato (JSONL, un turno por línea) | campo | qué es | |---|---| | `turn` | número de turno (1-12) | | `child_age` | edad simulada (3) | | `user_message` | lo que "dice" la niña | | `safety_input_blocked` | `true` si `safety.guard.blocks_input` frenó el mensaje antes de llegar al motor | | `intent` | intención detectada por el router (`llm/engine.py:_intent`); `null` si se bloqueó en la entrada | | `prompt_messages` | los `messages` **exactos** que recibe el LLM: `system` (persona de Sofía) + historial + bloques ``/``/`` armados por `LumiEngine.respond`; `null` si se bloqueó en la entrada | | `activity` | la actividad curada elegida de `content/` (si aplica), con su `id`, `title` y `script` | | `reply` | la respuesta final que recibiría la niña | | `safety_output_blocked` | `true` si `safety.guard.blocks_output` reemplazó la respuesta por el fallback | ## Turnos destacados - **Turno 10** (`"¿Qué es una pistola?"`): `safety_input_blocked: true`, `prompt_messages: null`. El mensaje **ni llega al LLM**: lo frena `safety.guard.blocks_input` y responde `gentle_redirect()`. - **Turno 11** (`"Contame un cuento de un dragón que come autos"`): no existe ese cuento en `content/stories.json`. El modelo **no inventa** — `LumiEngine` elige otro cuento curado existente ("La tortuguita paciente") y el LLM solo lo presenta, con una `` que le prohíbe inventar contenido. - **Turnos 3-9**: cada `` es texto literal de `content/` (conteo, formas, colores, animales, canción, emoción, cambio de color); el LLM reformula/celebra pero los datos vienen del JSON curado. ## Cómo se generó ```bash LUMI_LLM_BACKEND=ollama python trace/export_trace.py ``` Corre contra Ollama (`qwen2.5:7b`) en local, usando el mismo `SYSTEM_PROMPT` y los mismos bloques ``/``/`` que arma `llm/engine.py` en producción (Qwen2.5-7B-Instruct fine-tuneado vía ZeroGPU). Usa un SQLite temporal para el historial conversacional — no toca `parental/lumi.db`.