Prompts del agente
El prompt base del agente (su identidad, tono y reglas de comunicación) es editable en caliente por un superadmin desde el dashboard, sin redeploy. Vive en la base de datos con versionado, rollback y auditoría.
De dónde sale el texto
Tres lugares, y confundirlos cuesta una tarde:
| Qué es | Cuándo manda | |
|---|---|---|
Tabla prompt_versions | La fuente de verdad. Lo que el agente usa en cada turno | Siempre |
PROMPT_SEEDS en el código (src/memory/prompts/prompts.ts) | El texto con el que se crea la v1 | Sólo en el primer arranque de un ambiente que todavía no tiene fila activa |
Aura-reloaded/prompts/copilot/versions/*.txt | El registro de versiones, para diffear y revisar | Nunca por sí solo |
O sea: editar el seed en el código no cambia nada en un ambiente ya booteado — onModuleInit
salta el seed apenas encuentra una versión activa. Y editar un .txt tampoco: es documentación.
Activar una versión nueva es pegar el texto en /anfibios/prompts, que crea la versión, la
marca activa y la audita.
Consecuencia práctica: el seed del código puede quedar varias versiones atrás del prompt real, y de hecho hoy lo está. No es un bug, es el diseño — pero conviene saberlo antes de leer el código buscando el prompt que está corriendo.
Modelo de datos
Tabla prompt_versions (src/prompts/prompts.schema.ts), append-only: cada guardado crea una
fila nueva y exactamente una fila por prompt_key tiene is_active=true.
Ver Base de datos.
Versionado SemVer. Cada versión tiene dos números: un version entero interno
(secuencia monotónica max + 1, para orden/unicidad) y un semver que es lo que se muestra
(1.2.3). Al guardar, el superadmin elige el tipo de cambio — patch (default), minor o
major — y el semver se calcula bumpeando el de la última versión (siempre creciente, así un
rollback no genera semvers duplicados). La v1 (seed) arranca en 1.0.0.
Está indexada por prompt_key, así sumar un prompt editable es agregar una key + su semilla
en prompts.constants.ts (y una entrada en el PROMPT_CATALOG del front), sin tocar el schema.
Keys de hoy:
| Key | Para qué |
|---|---|
copilot_identity | Prompt del copiloto (audio-fork): guion de la llamada y sugerencias al operador. |
agent_identity | Prompt del bot de voz (Retell / ElevenLabs), con las reglas de prosodia para TTS. |
agent_identity_whatsapp | Prompt del bot de WhatsApp: mismo dominio clínico, registro escrito, sin prosodia, listas cortas permitidas. |
copilot.data_extraction | Lectura del nombre, documento, WhatsApp y motivo desde el transcript. |
copilot.transcript_polish | Corrección post-llamada de los errores de reconocimiento de Deepgram. |
transcription.* | Tres prompts del pipeline de /anfibios (hablantes, valor, lecciones). |
El servicio
PromptsService (src/prompts/prompts.service.ts) es la única puerta al store:
| Método | Qué hace |
|---|---|
getActiveContent(key) | Hot path. Devuelve el contenido activo. Cache-first → DB → semilla. |
saveNewVersion(key, content, user, note?) | Crea versión nueva y la activa (desactiva la anterior). |
activateVersion(key, versionId, user) | Rollback: mueve el puntero activo a una versión existente. |
listVersions(key) / getState(key) | Historial + versión activa (para la pantalla). |
- Seed: en
onModuleInit, si no hay versión activa para una key con semilla, inserta la v1 desdePROMPT_SEEDS. Así el historial arranca limpio, y una key nueva se siembra sola en el próximo boot (no necesita migración). - Cache en memoria (
Map<key, string>): evita pegarle a la DB en cada turno. Se invalida al guardar o hacer rollback (saveNewVersion/activateVersionreescriben la entrada). - Fallback: si la tabla no tuviera versión activa,
getActiveContentcae a la semilla y nunca rompe una llamada. - Cada
saveNewVersion/activateVersionescribe un evento de auditoríaprompt_updated(ver event_log).
Quién consume el prompt
El system message lo resuelve el ConversationEngine, que elige la key según el canal
(PROMPT_KEY_BY_SOURCE en src/conversation/conversation.engine.ts):
source | Key | Por qué |
|---|---|---|
audio_fork | copilot_identity | El bot no habla: escribe una línea para que la lea un humano. Nada de prosodia, y lleva el guion de la llamada. |
retell / elevenlabs | agent_identity | El texto se convierte en voz: manda la prosodia y el "máximo 2 oraciones". |
whatsapp | agent_identity_whatsapp | Nadie sintetiza nada: sobran las reglas de prosodia y sobra el tope de 80 tokens, que cortaba las respuestas. |
Separar las dos keys evita el problema que había antes: editar el prompt del copiloto cambiaba
también el del bot que atiende hablando. El log [TURN] prompt=<key> de cada turno dice cuál se
usó.
Por fuera del engine, CallDataService (src/audio-fork/call-data.service.ts) usa
copilot.data_extraction en su propia llamada al LLM.
El system message sigue siendo estable durante la llamada (solo cambia si un superadmin guarda), así que el prompt-caching de OpenAI se mantiene (ver Visión general). El contexto RAG se sigue inyectando en el último mensaje del usuario, no en el system.
El prompt del Conversation Flow de Retell (custom function consult, retell.consult.service.ts)
vive en el dashboard de Retell, no en este store. Esta pantalla solo controla el prompt del
Custom LLM y del audio-fork. Los builders buildPrompt* de prompts.ts son código muerto (no los
llama nadie) y tampoco se editan.
Edición desde el dashboard
Pantalla /anfibios/prompts (dentro del hub de administración /anfibios), solo para
superadmin global (ver Guía de pantallas). Endpoints (todos gateados
con @RequireGlobalRole('superadmin'), ver Ajolote y roles):
| Método | Ruta | Qué hace |
|---|---|---|
GET | /anfibios/prompts/:key | Versión activa + historial |
PATCH | /anfibios/prompts/:key | Guarda una versión nueva ({ content, note?, bump? }; bump = major/minor/patch) |
POST | /anfibios/prompts/:key/activate | Rollback ({ versionId }) |
El path está ofuscado (no
/admin) como leve disuasivo; la seguridad real es el guard de rol, no el nombre de la ruta.
Además del historial y el rollback, la pantalla permite comparar versiones estilo GitHub: un modal con dos selectores (base ↔ comparar) que muestra el diff línea a línea (verde = agregado, rojo = quitado) entre dos versiones cualesquiera. El diff se calcula en el cliente (el contenido de cada versión ya viene en el estado).
Un cambio se aplica a todas las llamadas nuevas de inmediato. Si se guarda durante una llamada activa, el system message de esa llamada cambia a mitad de camino y se invalida su prompt-cache (mayor costo/latencia puntual en ese call). Un prompt mal escrito degrada todas las respuestas — siempre se puede restaurar una versión anterior. La pantalla avisa esto de forma permanente.