xiaohongshu
xiaohongshu
Gestión del esquema PostgreSQL, Auto-Healing y arquitectura RAG híbrida.
Full skill instructions
Platform AI Solutions usa Self-Healing: el esquema se auto-repara en cada startup.
# orchestrator_service/main.py
from app.models import Base
from app.db import engine
@app.on_event("startup")
async def startup_event():
async with engine.begin() as conn:
# Crea SOLO las tablas que no existen
await conn.run_sync(Base.metadata.create_all)
# Ejecutar migraciones de esquema
await run_migration_steps()
create_all solo crea lo que falta)# app/models/business_asset.py
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import String, Integer, JSONB, ForeignKey
from datetime import datetime
from .base import Base
class BusinessAsset(Base):
__tablename__ = "business_assets"
id: Mapped[str] = mapped_column(String, primary_key=True) # UUID
tenant_id: Mapped[str] = mapped_column(String(50), nullable=False)
asset_type: Mapped[str] = mapped_column(String(50)) # branding, scripts, roi
content: Mapped[dict] = mapped_column(JSONB, nullable=False)
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
is_active: Mapped[bool] = mapped_column(default=True)
# main.py
from app.models import (
Base,
Tenant,
Agent,
BusinessAsset, # ← Agregar aquí
Credential
)
# Esto asegura que SQLAlchemy conozca todos los modelos
Al reiniciar el servicio, create_all ejecutará y la tabla se creará automáticamente.
create_all NO altera tablas existentes.
Para agregar columnas, cambiar tipos, o renombrar: Migration Script
# orchestrator_service/scripts/migration_steps.py
from sqlalchemy import text
async def run_migration_steps(engine):
"""Migraciones idempotentes"""
async with engine.begin() as conn:
# Migración 1: Agregar columna
await conn.execute(text("""
ALTER TABLE agents
ADD COLUMN IF NOT EXISTS template_type VARCHAR(50) DEFAULT 'custom'
"""))
# Migración 2: Crear índice
await conn.execute(text("""
CREATE INDEX IF NOT EXISTS idx_agents_tenant_active
ON agents(tenant_id, is_active)
"""))
# Migración 3: Agregar columna JSONB
await conn.execute(text("""
ALTER TABLE tenants
ADD COLUMN IF NOT EXISTS tool_config JSONB DEFAULT '{}'::jsonb
"""))
# main.py
from scripts.migration_steps import run_migration_steps
@app.on_event("startup")
async def startup_event():
# 1. Crear tablas nuevas
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
# 2. Ejecutar migraciones
await run_migration_steps(engine)
| Almacenamiento | Responsabilidad | Tecnología |
|---|---|---|
| PostgreSQL Local | Metadata (filename, collection, file_path) | rag_documents table |
| Supabase Remote | Vectores (embeddings) | pgvector extension |
# app/models/rag_document.py
class RAGDocument(Base):
__tablename__ = "rag_documents"
id: Mapped[str] = mapped_column(String, primary_key=True) # UUID
tenant_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("tenants.id"),
index=True
)
filename: Mapped[str] = mapped_column(String(255))
collection: Mapped[str] = mapped_column(String(100)) # General, ADN Personal, Shadow RAG
file_type: Mapped[str] = mapped_column(String(50)) # pdf, txt, docx
file_path: Mapped[str] = mapped_column(String(500), nullable=True)
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
# 1. Guardar metadata en PostgreSQL
doc = RAGDocument(
id=str(uuid.uuid4()),
tenant_id=tenant_id,
filename=filename,
collection="General",
file_type=file_extension,
file_path=storage_path
)
session.add(doc)
await session.flush() # Obtener ID sin commit final
# 2. Procesar documento
chunks = process_document(file_content)
# 3. Vectorizar y guardar en Supabase
from app.services.rag.vector_store import SupabaseVectorStore
vector_store = SupabaseVectorStore(tenant_id)
await vector_store.add_documents(
chunks,
metadata={
"tenant_id": tenant_id,
"source_id": doc.id,
"collection": collection
}
)
# 4. Commit
await session.commit()
CRÍTICO: Mantener coherencia entre PostgreSQL y Supabase
# Paso 1: Surgical Strike (Remoto) - Eliminar vectores de Supabase
await supabase.from_("documents").delete().eq(
"metadata->>source_id", doc_id
).execute()
# Paso 2: Metadata Cleanup (Local) - Eliminar de PostgreSQL
stmt = delete(RAGDocument).where(
RAGDocument.id == doc_id,
RAGDocument.tenant_id == tenant_id
)
await session.execute(stmt)
# Paso 3: Physical Sweep (Disco) - Best effort
try:
os.remove(file_path)
except FileNotFoundError:
logger.warning(f"File {file_path} not found, skipping physical delete")
await session.commit()
CREATE TABLE IF NOT EXISTS credentials (
id_uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id INTEGER REFERENCES tenants(id),
name TEXT NOT NULL,
value TEXT NOT NULL, -- Encrypted AES-256
category TEXT DEFAULT 'general', -- openai, google, smtp
scope TEXT DEFAULT 'global', -- global o tenant
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(name, tenant_id) -- Unicidad multi-tenant
);
CREATE TABLE IF NOT EXISTS agents (
id SERIAL PRIMARY KEY,
tenant_id INTEGER REFERENCES tenants(id),
name TEXT NOT NULL,
role TEXT DEFAULT 'sales',
model_provider TEXT DEFAULT 'openai',
model_version TEXT DEFAULT 'gpt-5-mini',
temperature FLOAT DEFAULT 0.7,
system_prompt_template TEXT NOT NULL,
enabled_tools JSONB DEFAULT '[]',
channels JSONB DEFAULT '["whatsapp", "instagram", "facebook", "web"]',
config JSONB DEFAULT '{}',
template_type VARCHAR(50) DEFAULT 'custom',
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS chat_conversations (
id UUID PRIMARY KEY,
tenant_id INTEGER REFERENCES tenants(id),
channel VARCHAR(32) NOT NULL, -- whatsapp, instagram, facebook, web
channel_source VARCHAR(32) DEFAULT 'whatsapp',
display_name VARCHAR(255),
meta JSONB DEFAULT '{}',
last_message_preview TEXT,
last_message_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS tools (
id SERIAL PRIMARY KEY,
tenant_id INTEGER REFERENCES tenants(id), -- NULL para Global
name VARCHAR(255) NOT NULL,
type VARCHAR(32) NOT NULL, -- http, internal
description TEXT,
prompt_injection TEXT,
response_guide TEXT,
config JSONB DEFAULT '{}',
service_url TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, name)
);
from sqlalchemy import String, Integer, Boolean, DateTime, Text, JSONB, ARRAY
class ExampleModel(Base):
__tablename__ = "example"
# IDs
id_uuid: Mapped[str] = mapped_column(String, primary_key=True)
id_int: Mapped[int] = mapped_column(Integer, primary_key=True)
# Texto
short_text: Mapped[str] = mapped_column(String(255))
long_text: Mapped[str] = mapped_column(Text)
# JSON
metadata: Mapped[dict] = mapped_column(JSONB, default={})
# Arrays
tags: Mapped[list] = mapped_column(JSONB, default=[])
# Booleanos
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
id es INTEGERid es UUID, tenant_id es INTEGERtenant_id es INTEGER (foreign key)# NUNCA asumir que current_user.tenant_id es UUID
# SIEMPRE resolver desde tabla users
user_row = await db.pool.fetchrow(
"SELECT tenant_id FROM users WHERE id = $1",
current_user.id # UUID
)
real_tenant_int = user_row['tenant_id'] # INTEGER
# Usar en queries
stmt = delete(Agent).where(Agent.tenant_id == real_tenant_int)
from sqlalchemy import Index
class Message(Base):
__tablename__ = "messages"
# ... columnas ...
__table_args__ = (
Index('idx_messages_conversation', 'conversation_id', 'created_at'),
Index('idx_messages_tenant', 'tenant_id'),
)
from sqlalchemy.orm import selectinload
# ❌ MAL - N+1 queries
conversations = await session.execute(select(Conversation))
for conv in conversations:
messages = conv.messages # Query adicional
# ✅ BIEN - Single query
stmt = select(Conversation).options(
selectinload(Conversation.messages)
)
conversations = await session.execute(stmt)
# Al subir .txt a "ADN Personal", se activa parser
if collection == "ADN Personal" and file_ext == ".txt":
from app.services.parsers import WhatsAppParser
parser = WhatsAppParser(hero_name=hero_name)
parsed = parser.parse(file_content)
# Genera pares context/response para stored patterns
-- Limpiar plataforma completa
TRUNCATE TABLE
users, tenants, credentials, agents, tools,
business_assets, chat_conversations, chat_messages,
rag_documents
RESTART IDENTITY CASCADE;
main.py?Mapped[type])?tenant_id está indexado?IF NOT EXISTS?xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience