Skip to main content

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é esCuándo manda
Tabla prompt_versionsLa fuente de verdad. Lo que el agente usa en cada turnoSiempre
PROMPT_SEEDS en el código (src/memory/prompts/prompts.ts)El texto con el que se crea la v1Sólo en el primer arranque de un ambiente que todavía no tiene fila activa
Aura-reloaded/prompts/copilot/versions/*.txtEl registro de versiones, para diffear y revisarNunca por sí solo

O sea: editar el seed en el código no cambia nada en un ambiente ya booteadoonModuleInit 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 cambiopatch (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:

KeyPara qué
copilot_identityPrompt del copiloto (audio-fork): guion de la llamada y sugerencias al operador.
agent_identityPrompt del bot de voz (Retell / ElevenLabs), con las reglas de prosodia para TTS.
agent_identity_whatsappPrompt del bot de WhatsApp: mismo dominio clínico, registro escrito, sin prosodia, listas cortas permitidas.
copilot.data_extractionLectura del nombre, documento, WhatsApp y motivo desde el transcript.
copilot.transcript_polishCorrecció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étodoQué 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 desde PROMPT_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 / activateVersion reescriben la entrada).
  • Fallback: si la tabla no tuviera versión activa, getActiveContent cae a la semilla y nunca rompe una llamada.
  • Cada saveNewVersion / activateVersion escribe un evento de auditoría prompt_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):

sourceKeyPor qué
audio_forkcopilot_identityEl bot no habla: escribe una línea para que la lea un humano. Nada de prosodia, y lleva el guion de la llamada.
retell / elevenlabsagent_identityEl texto se convierte en voz: manda la prosodia y el "máximo 2 oraciones".
whatsappagent_identity_whatsappNadie 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.

Compatibilidad con prompt caching

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 path del "Conductor" NO se edita acá

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étodoRutaQué hace
GET/anfibios/prompts/:keyVersión activa + historial
PATCH/anfibios/prompts/:keyGuarda una versión nueva ({ content, note?, bump? }; bump = major/minor/patch)
POST/anfibios/prompts/:key/activateRollback ({ 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).

Riesgo: guardar en caliente

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.