xiaohongshu
xiaohongshu
Experto en FastAPI y gestión segura de credenciales multi-tenant para Platform AI Solutions.
Full skill instructions
NUNCA usar os.getenv("OPENAI_API_KEY") para lógica de agentes.
SIEMPRE usar el sistema de credenciales soberanas:
from app.core.credentials import get_tenant_credential
# Correcto - Credenciales por tenant
api_key = await get_tenant_credential(
tenant_id=tenant_id,
category="openai", # openai, google, smtp, tiendanube, whatsapp_cloud
name="API_KEY"
)
###Categories:
openai: GPT-5.2, gpt-5-minigoogle: Gemini 3 Pro, Gemini 3 Flashsmtp: Email delivery (Modo Agente)tiendanube: E-commerce tokenswhatsapp_cloud: Meta Business APIchatwoot: Chatwoot API (v6.1 uses both CHATWOOT_API_TOKEN and CHATWOOT_BOT_TOKEN)Al procesar mensajes de Chatwoot, SIEMPRE persistir external_chatwoot_id e external_account_id en la tabla chat_conversations. Esto es crítico para que unified_message_delivery pueda responder correctamente.
NO llamar directamente a meta_service o ycloud para envíos desde el Orquestador.
SIEMPRE delegar al whatsapp_service usando el endpoint de Relay:
# Protocolo v6.2.9
relay_payload = {
"to": phone,
"text": text,
"provider": "meta_direct" | "chatwoot" | "ycloud",
"channel_source": "instagram" | "facebook" | "whatsapp",
"tenant_id": tenant_id
}
await client.post("/messages/relay", json=relay_payload)
Beneficio: Manejo automático de Spacing (4s) y Buffer (16s).
user.id es UUIDtenant_id es INTEGER###Solución:
# ❌ MAL - No confiar directamente en current_user.tenant_id
stmt = delete(Agent).where(Agent.tenant_id == current_user.tenant_id)
# ✅ BIEN - Resolver desde tabla users
user_row = await db.pool.fetchrow(
"SELECT tenant_id FROM users WHERE id = $1",
current_user.id
)
real_tenant_int = user_row['tenant_id']
stmt = delete(Agent).where(Agent.tenant_id == real_tenant_int)
TODA query debe filtrar por tenant_id:
# SQLAlchemy 2.0 Async
from sqlalchemy import select
stmt = select(Agent).where(
Agent.id == agent_id,
Agent.tenant_id == tenant_id # CRÍTICO
)
result = await session.execute(stmt)
agent = result.scalar_one_or_none()
new_agent = Agent(
name="Sales Agent",
role="sales",
model_provider="openai",
model_version="gpt-5-mini",
tenant_id=tenant_id, # SIEMPRE incluir
enabled_tools=["search_products", "rag_search"],
channels=["whatsapp", "instagram"]
)
session.add(new_agent)
await session.commit()
rag_documents table)# 1. Metadata en PostgreSQL
doc = RAGDocument(
tenant_id=tenant_id,
filename=filename,
collection="General", # General, ADN Personal, Shadow RAG
file_path=storage_path
)
session.add(doc)
await session.flush() # Obtener ID
# 2. Vectorizar y almacenar en Supabase
chunks = process_document(file_content)
await supabase_vector_store.add_documents(
chunks,
metadata={"tenant_id": tenant_id, "source_id": str(doc.id)}
)
await session.commit()
# 1. Eliminar vectores de Supabase
await supabase.from_("documents").delete().eq(
"metadata->>source_id", str(doc_id)
).execute()
# 2. Eliminar metadata de PostgreSQL
stmt = delete(RAGDocument).where(
RAGDocument.id == doc_id,
RAGDocument.tenant_id == tenant_id
)
await session.execute(stmt)
await session.commit()
from fastapi import APIRouter, Depends, HTTPException
from app.core.deps import verify_admin_token
router = APIRouter()
@router.post("/agents", status_code=201)
async def create_agent(
payload: AgentCreate,
admin_user = Depends(verify_admin_token)
):
# Resolver tenant
tenant_id = await resolve_tenant(admin_user.id)
# Validar credenciales existen
has_creds = await check_credentials(tenant_id, "openai")
if not has_creds:
raise HTTPException(
status_code=400,
detail="OpenAI credentials not configured"
)
# Crear agente
agent = Agent(**payload.dict(), tenant_id=tenant_id)
# ...
return agent
# app/services/tools_registry.py
from langchain.tools import tool
@tool
def search_products(query: str, tenant_id: int) -> dict:
"""Busca productos en Tienda Nube del tenant."""
# Obtener credenciales de Tienda Nube
tn_token = await get_tenant_credential(
tenant_id=tenant_id,
category="tiendanube"
)
# Llamar API
response = requests.get(
f"https://api.tiendanube.com/v1/products/search",
headers={"Authorization": f"Bearer {tn_token}"},
params={"q": query}
)
return response.json()
@tool
async def report_assistance(type: str, score: float, reasoning: str):
"""Registra métricas de ayuda (sales/support) en la DB."""
# Implementado en admin_routes.py (/tools/report_assistance)
# y reflejado en el Dashboard v7.6
pass
tool_entry = Tool(
tenant_id=None, # Global tool
name="search_products",
type="http",
description="Busca productos en catálogo",
prompt_injection="Usa esta tool cuando el usuario pregunte por productos",
config={"timeout": 10}
)
DEFAULT_AGENT_TONE = """
Sos una asesora experta en danza clásica y ballet.
Usá voseo argentino. Sé cálida y profesional.
"""
SYNONYM_DICTIONARY = {
"mallas": "leotardos",
"can can": "medias",
"zapatillas de punta": "puntas"
}
# Orquestador proxy a meta_service
response = await httpx.post(
"http://meta_service:8000/connect",
json={
"code": auth_code,
"redirect_uri": redirect_uri,
"tenant_id": tenant_id
},
headers={"X-Internal-Secret": INTERNAL_SECRET_KEY}
)
# Meta service devuelve Long-Lived Token (60 días)
# Y persiste en credentials automáticamente
# ❌ MAL
raise HTTPException(status_code=400, detail="Error")
# ✅ BIEN
raise HTTPException(
status_code=404,
detail=f"Agent {agent_id} not found or access denied"
)
import logging
logger = logging.getLogger(__name__)
try:
# Operación
pass
except Exception as e:
logger.error(f"Failed to create agent: {str(e)}", exc_info=True)
raise HTTPException(status_code=500, detail="Internal error")
tenant_id?get_tenant_credential para API keys?tenant_id desde tabla users?tools_registry.py?main.py?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