← Toda la documentación
Operación en producción

Ejecución en producción

Los valores predeterminados están ajustados para un cliente SaaS típico. A continuación están los parámetros que tocarás cuando cambie el patrón del tráfico: limitación de frecuencia, política de reintentos, registro de auditoría, sondeos de disponibilidad de las dependencias, integración con IHealthCheck, idempotencia, sustitución de plantillas de prompts y la administración de BYOK.

Limitar la frecuencia de las llamadas de IA

Tanto POST /richtextbox/ai como POST /richtextbox/ai/stream comparten un token bucket por IP, para que una sola pestaña descontrolada no pueda agotar en segundos la cuota de tu proveedor. Valores predeterminados: 30 llamadas por minuto y por IP.

builder.Services.AddRichTextBox(opts =>
{
    opts.AiRateLimit       = 30;                          // calls per window
    opts.AiRateLimitWindow = TimeSpan.FromMinutes(1);     // window length
});

Establece AiRateLimit = 0 para desactivarlo, o sustitúyelo por un limitador basado en Redis:

builder.Services.AddSingleton<IRichTextBoxAiRateLimiter, RedisAiRateLimiter>();
builder.Services.AddRichTextBox();

Reintentos HTTP ante fallos transitorios del proveedor

OpenAI / Anthropic / Azure OpenAI devuelven 429 y 503 con una carga normal. Los resolvers integrados reintentan esas respuestas (además de 502 / 504 y las conexiones cortadas) con backoff exponencial y jitter completo, con un máximo de 30 segundos. Se respeta el encabezado Retry-After del proveedor.

services.AddRichTextBoxOpenAiResolver(opts =>
{
    opts.ApiKey         = builder.Configuration["OpenAI:ApiKey"];
    opts.MaxRetryAttempts = 2;                              // 0 to disable
    opts.RetryBaseDelay   = TimeSpan.FromMilliseconds(500);
});

Claves de idempotencia

Todos los POST /richtextbox/ai, /ai/stream y /upload respetan Idempotency-Key. Los reintentos duplicados reproducen tal cual el cuerpo en caché: sin cobros dobles ni archivos huérfanos. Las respuestas en streaming reproducen byte a byte todo el flujo de bytes SSE.

builder.Services.AddRichTextBox(opts =>
{
    opts.IdempotencyTtl = TimeSpan.FromHours(1);
    opts.IdempotencyPruneInterval = TimeSpan.FromMinutes(5);
});

Hosts con varias instancias: registra un IIdempotencyStore basado en Redis en lugar del predeterminado en memoria. Con un TTL nativo puedes desactivar la limpieza periódica.

Registro de auditoría de seguridad

Cada rechazo relevante para la seguridad emite un LogWarning estructurado en la categoría RichTextBox.Audit con un EventId estable:

EventIdNombreSe emite cuando
8001LicenseInvalidUn endpoint rechazó una solicitud porque el archivo .lic faltaba o no era válido.
8101UploadMagicByteMismatchLos bytes mágicos no coinciden con la extensión declarada.
8102UploadRejectedByValidatorUn IUploadValidator devolvió Reject.
8103UploadInvalidExtensionCarga rechazada por estar fuera de la lista de permitidos.
8104UploadOversizeSe superó MaxUploadBytes.
8201AiRequestOversizeEl cuerpo de la solicitud de IA superó MaxRequestBytes.
8202AiRateLimitExceededEl llamante superó el límite de frecuencia de IA por IP.
8203AiResolverExceptionEl resolver de IA lanzó una excepción; se registra con el id de correlación.
8204AiKeyVaultMissEl almacén BYOK no devolvió ninguna clave.
8205AiKeyVaultHitEl almacén BYOK devolvió una clave (se registra el KeyId; el secreto, nunca).
8206AiResponseFilterMatchedUn IRichTextBoxAiResponseFilter modificó la respuesta.

Enviar los eventos de auditoría a un flujo aparte

IRichTextBoxAuditSink permite a las cargas de trabajo sujetas a normativa (HIPAA / PCI / SOC2) enviar solo los eventos de auditoría a un destino de escritura única: Splunk HEC, S3 con object-lock, una base de datos solo de auditoría. Incluye JsonLinesAuditSink para uso sin conexión o con una sola instancia:

builder.Services.AddRichTextBox();
builder.Services.AddJsonLinesAuditSink("/var/log/myapp/rtb-audit.jsonl");
Comportamiento. Channel acotado + vaciado en segundo plano; RecordAsync nunca bloquea. Si la cola se desborda, se descartan eventos y se cuentan en la métrica DroppedCount. Se pueden registrar varios sinks sin más: combina JsonLines con un sink de Splunk en tiempo real sin coste adicional.

Sondeos de disponibilidad de las dependencias

Unas interfaces complementarias opcionales permiten que cualquier dependencia de RichTextBox informe de si su servicio subyacente está accesible. El endpoint /richtextbox/health muestra el resultado junto con el estado de la licencia:

InterfazImplementaciones integradasDestino del sondeo
IRichTextBoxAiResolverProbeOpenAI, Azure OpenAI (gratis); wrapper AnthropicResolverWithProbe (POST con pocos tokens)GET /v1/models / GET /openai/deployments / POST /v1/messages con max_tokens=1
IIdempotencyStoreProbeAlmacén Redis / SQL personalizado (lo implementas tú)PING / SELECT 1
IRichTextBoxUploadStoreProbeAlmacén S3 / Azure Blob / Cloudinary personalizadoHEAD del bucket / list-objects --max-items 1
builder.Services.AddRichTextBox(opts =>
{
    opts.AiResolverHealthProbeEnabled        = true;
    opts.AiResolverHealthProbeTimeout        = TimeSpan.FromSeconds(3);
    opts.AiResolverHealthProbeCacheTtl       = TimeSpan.FromSeconds(30);

    opts.IdempotencyStoreHealthProbeEnabled  = true;
    opts.UploadStoreHealthProbeEnabled       = true;
});

El TTL de la caché de cada sondeo protege frente al acoso del balanceador de carga: un balanceador que llama a /health cada 5 segundos produciría de lo contrario 12 sondeos por minuto, incluso cuando los sondeos cuestan dinero de verdad.

Integración estándar con IHealthCheck

En los hosts que usan Microsoft.Extensions.Diagnostics.HealthChecks, registra el adaptador y el estado de RichTextBox aparece junto a tus demás comprobaciones, sin una vía de monitorización paralela:

builder.Services.AddRichTextBox();
builder.Services.AddHealthChecks()
.AddRichTextBox(name: "richtextbox", failureStatus: HealthStatus.Unhealthy, tags: new[]  { "ready" } );

app.MapHealthChecks("/health");

Devuelve Healthy cuando la licencia y todos los sondeos activados pasan; Unhealthy, con una descripción que identifica la dependencia que falló. HealthCheckResult.Data refleja la carga JSON de /richtextbox/health.

BYOK: claves de API por inquilino

Los hosts SaaS con varios inquilinos cargan el tráfico de IA a la cuenta de proveedor de cada inquilino mediante IAiKeyVault + IAiKeyVaultAdmin. El paquete incluye una implementación de referencia en memoria; los despliegues de producción se apoyan en Azure Key Vault, Redis o un almacén SQL real.

Consulta la página dedicada al almacén de claves BYOK para ver el contrato completo, los endpoints REST de administración y las implementaciones de referencia (Redis, Azure Key Vault y FileBackedAiKeyVault cifrado en disco).

Registro de costes por llamada

IRichTextBoxAiCostSink emite un AiUsageRecord por cada llamada de IA: proveedor, modelo, modo, tokens (entrada / salida / total), latencia, estado y KeyId. Reenvíalo a tu sistema de facturación para repercutir el coste a cada inquilino:

builder.Services.AddSingleton<IRichTextBoxAiCostSink, MyBillingApiCostSink>();

Para inquilinos con mucho tráfico, envuélvelo con el sink con búfer, para que el sink interior reciba un único vaciado por lotes en cada intervalo en lugar de una llamada HTTP/SQL por cada solicitud de IA. Si tu sink interior también implementa IRichTextBoxAiCostBatchSink, el wrapper pasa por RecordBatchAsync: un INSERT en lugar de N.

builder.Services.AddBufferingAiCostSink<MyBillingApiCostSink>(
    flushInterval: TimeSpan.FromSeconds(30),
    maxBatchSize:  500);

Filtrado de la salida: ocultación de datos personales y bloqueo de contenido

IRichTextBoxAiResponseFilter se ejecuta después del resolver, pero antes de que la respuesta salga por la red. El RegexAiResponseFilter de referencia cubre ~80% de los casos prácticos de ocultación de datos personales:

var emailMask = new Regex(@"[\w.+-]+@[\w-]+\.[\w.-]+");
var ssnMask   = new Regex(@"\b\d{3}-\d{2}-\d{4}\b");

builder.Services.AddSingleton<IRichTextBoxAiResponseFilter>(
new RegexAiResponseFilter(
        (emailMask, "[email-redacted]"),
        (ssnMask,   "[ssn-redacted]")));

En las respuestas en streaming, el framework almacena en búfer los tokens en el servidor cuando hay filtros registrados (BufferStreamingForFilters = true de forma predeterminada), para que una expresión regular de tarjetas de crédito que abarca varios fragmentos coincida de verdad.

Sustituir las plantillas de prompts

IRichTextBoxPromptTemplateProvider te permite sustituir los prompts de sistema por modo que envían los resolvers integrados. Usa la clase base decoradora PromptTemplateProviderBase para sobrescribir solo los métodos que necesites:

public sealed class FormalTonePromptProvider : PromptTemplateProviderBase
{
    public override string BuildSystemPrompt(RichTextBoxAiRequest request, string? operatorSuffix)
        => base.BuildSystemPrompt(request, operatorSuffix)
         + "\n\nAlways reply in formal British English; avoid contractions.";
}

Para ajustes puntuales de un modo sin un proveedor completo, usa el diccionario:

services.AddRichTextBoxOpenAiResolver(opts =>
{
    opts.ApiKey = "sk-...";
    opts.SystemPromptOverridesByMode["proofread"] =
        "You are a hyper-pedantic copy-editor for legal documents.";
});

Filtrado de inyección de prompts

IRichTextBoxAiPolicy es el control que consultan los endpoints de IA antes de invocar el resolver. La DefaultPromptInjectionPolicy integrada rechaza las plantillas de jailbreak habituales (“ignore previous instructions”, “you are now DAN”, etc.). Añade políticas adicionales para ocultar datos personales o para listas de modos permitidos por inquilino.

builder.Services.AddSingleton<IRichTextBoxAiPolicy, DefaultPromptInjectionPolicy>();
builder.Services.AddSingleton<IRichTextBoxAiPolicy, MyTenantPolicy>();

Trazas distribuidas (OpenTelemetry)

Cada llamada de IA, exportación a DOCX y carga emite un span a través del ActivitySource RichTextBox.AspNetCore. Suscríbete en la configuración de tu tracer provider:

builder.Services.AddOpenTelemetry().WithTracing(t => t
.AddSource(RichTextBoxDiagnostics.SourceName)   // "RichTextBox.AspNetCore"
.AddAspNetCoreInstrumentation()
.AddOtlpExporter());

Validación de opciones al arrancar

RichTextBoxOptionsValidator + AiResolverOptionsValidator<T> se ejecutan en la primera resolución de IOptions.Value, antes de que el host acepte la primera solicitud. Una configuración incorrecta (una / olvidada, un límite de bytes negativo, heartbeat > timeout, falta la clave de API, falta el deployment de Azure) lanza OptionsValidationException al arrancar, para que los despliegues fallen de forma visible.

No hace falta conectar nada. Los validadores se registran automáticamente con AddRichTextBox().

Documentación relacionada

Almacén de claves BYOK · Proveedores de IA y streaming · Proveedores de carga en la nube · Accesibilidad