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:
| EventId | Nombre | Se emite cuando |
|---|---|---|
| 8001 | LicenseInvalid | Un endpoint rechazó una solicitud porque el archivo .lic faltaba o no era válido. |
| 8101 | UploadMagicByteMismatch | Los bytes mágicos no coinciden con la extensión declarada. |
| 8102 | UploadRejectedByValidator | Un IUploadValidator devolvió Reject. |
| 8103 | UploadInvalidExtension | Carga rechazada por estar fuera de la lista de permitidos. |
| 8104 | UploadOversize | Se superó MaxUploadBytes. |
| 8201 | AiRequestOversize | El cuerpo de la solicitud de IA superó MaxRequestBytes. |
| 8202 | AiRateLimitExceeded | El llamante superó el límite de frecuencia de IA por IP. |
| 8203 | AiResolverException | El resolver de IA lanzó una excepción; se registra con el id de correlación. |
| 8204 | AiKeyVaultMiss | El almacén BYOK no devolvió ninguna clave. |
| 8205 | AiKeyVaultHit | El almacén BYOK devolvió una clave (se registra el KeyId; el secreto, nunca). |
| 8206 | AiResponseFilterMatched | Un 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");
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:
| Interfaz | Implementaciones integradas | Destino del sondeo |
|---|---|---|
IRichTextBoxAiResolverProbe | OpenAI, Azure OpenAI (gratis); wrapper AnthropicResolverWithProbe (POST con pocos tokens) | GET /v1/models / GET /openai/deployments / POST /v1/messages con max_tokens=1 |
IIdempotencyStoreProbe | Almacén Redis / SQL personalizado (lo implementas tú) | PING / SELECT 1 |
IRichTextBoxUploadStoreProbe | Almacén S3 / Azure Blob / Cloudinary personalizado | HEAD 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