Volver a la wiki

Modelo AIPrompt: prompt del AI Brain por organización

Descripción

AIPrompt es un modelo Django que almacena prompts del AI Brain (el motor de IA que analiza imágenes para Auto-Plan).

Antes de s105: un único prompt global en fichero (prompts/blueprint_analyst.md) compartido por TODOS los tenants (violación de RLS + interferencia cross-tenant).

Desde s105: prompts por organización en BD. El fichero queda como semilla read-only.


Campos

class AIPrompt(models.Model):
    organization = ForeignKey(Organization)
    name = CharField(max_length=255)  # Nombre del prompt ("__brain_active__" o nombre custom)
    content = TextField()              # Contenido del prompt (markdown/instrucciones)
    is_default = BooleanField(default=False)
    created_at = DateTimeField(auto_now_add=True)
    updated_at = DateTimeField(auto_now=True)

    class Meta:
        unique_together = [[organization-name]]

Invariantes:


Roles del prompt

1. Prompt activo (Brain AI del Auto-Plan)

Nombre: __brain_active__ (constante blueprints.api.prompt.ACTIVE_PROMPT_NAME)

El prompt que usa Auto-Plan cuando analiza una imagen:

# blueprints/api/autoplan.py
prompt = get_active_prompt_text(org)
ai_data = AutoPlanService.analyze_blueprint_image(api_key, file_path, prompt, provider=provider)

Endpoints:

2. Saved prompts (custom, del usuario)

Nombres: cualquiera excepto __brain_active__.

Prompts que el usuario puede guardar, etiquetar y reutilizar en sesiones del editor.

Endpoints:


Funciones helper

_seed_prompt_text() -> str | None

Lee la semilla read-only del fichero prompts/blueprint_analyst.md (default/fallback).

def _seed_prompt_text() -> str | None:
    """Default del AI Brain si la org no ha personalizado."""
    prompt_file = os.path.join(settings.BASE_DIR, "prompts", "blueprint_analyst.md")
    try:
        with open(prompt_file, encoding="utf-8") as f:
            return f.read()
    except FileNotFoundError:
        return None

get_active_prompt_text(org) -> str | None

Obtiene el prompt activo de una org:

  1. Intenta leer el registro AIPrompt(organization=org, name="__brain_active__").
  2. Si no existe, retorna la semilla read-only.
def get_active_prompt_text(org) -> str | None:
    if org is not None:
        p = AIPrompt.objects.filter(organization=org, name=ACTIVE_PROMPT_NAME).first()
        if p:
            return p.content
    return _seed_prompt_text()

Usado por:


Cambios en s105

AntesAhora
Único fichero global prompts/blueprint_analyst.mdBD: uno por org (con fallback a semilla)
/prompt/save escribía fichero (pisa a otros tenants)Actualiza AIPrompt.__brain_active__ de la org
/prompt/reset restauraba desde _default.mdBorra el registro, vuelve a _seed_prompt_text()
No había aislamiento de tenantsAIPrompt.unique_together = (org, name)

Ejemplo de uso

from blueprints.models import AIPrompt
from core.models import Organization

# Organización A personaliza su prompt
org_a = Organization.objects.get(id=1)
AIPrompt.objects.update_or_create(
    organization=org_a,
    name="__brain_active__",
    defaults={"content": "Eres un experto en racks de servidores...", "is_default": False}
)

# Organización B sigue con la semilla (no tiene registro activo)
org_b = Organization.objects.get(id=2)
# get_active_prompt_text(org_b) retorna _seed_prompt_text()

# Ambas pueden guardar prompts custom
AIPrompt.objects.create(
    organization=org_a,
    name="Análisis detallado v1",
    content="Prompt custom 1...",
    is_default=False
)

Impacto para devs


Véase también

Subir