Execução em produção
Os padrões são ajustados para um cliente SaaS típico. Abaixo estão os ajustes que você vai mexer quando o perfil de tráfego mudar — limitação de taxa, política de nova tentativa, log de auditoria, sondas de disponibilidade de dependências, integração com IHealthCheck, idempotência, substituição de templates de prompt e a interface de administração do BYOK.
Limitação de taxa das chamadas de IA
POST /richtextbox/ai e POST /richtextbox/ai/stream compartilham um token bucket por IP, para que uma única aba descontrolada não consuma a cota do seu provedor em segundos. Padrão: 30 chamadas por minuto por IP.
builder.Services.AddRichTextBox(opts => { opts.AiRateLimit = 30; // calls per window opts.AiRateLimitWindow = TimeSpan.FromMinutes(1); // window length });
Defina AiRateLimit = 0 para desativar, ou troque por um limitador baseado em Redis:
builder.Services.AddSingleton<IRichTextBoxAiRateLimiter, RedisAiRateLimiter>(); builder.Services.AddRichTextBox();
Nova tentativa HTTP em falhas transitórias do provedor
OpenAI / Anthropic / Azure OpenAI retornam 429 e 503 sob carga normal. Os resolvers nativos repetem essas requisições (além de 502 / 504 e quedas de conexão) com backoff exponencial e full jitter, limitado a 30 segundos. O cabeçalho Retry-After do provedor é respeitado.
services.AddRichTextBoxOpenAiResolver(opts => { opts.ApiKey = builder.Configuration["OpenAI:ApiKey"]; opts.MaxRetryAttempts = 2; // 0 to disable opts.RetryBaseDelay = TimeSpan.FromMilliseconds(500); });
Chaves de idempotência
Todo POST /richtextbox/ai, /ai/stream e /upload respeita Idempotency-Key. Novas tentativas duplicadas reproduzem o corpo em cache literalmente — sem cobrança dupla, sem arquivos órfãos. Respostas em streaming reproduzem todo o fluxo de bytes SSE byte a byte.
builder.Services.AddRichTextBox(opts => { opts.IdempotencyTtl = TimeSpan.FromHours(1); opts.IdempotencyPruneInterval = TimeSpan.FromMinutes(5); });
Hosts com várias instâncias: registre um IIdempotencyStore baseado em Redis no lugar do padrão em memória. Com o TTL nativo, você pode desativar a limpeza periódica.
Log de auditoria de segurança
Toda rejeição relevante para a segurança emite um LogWarning estruturado na categoria RichTextBox.Audit com um EventId estável:
| EventId | Nome | Quando é emitido |
|---|---|---|
| 8001 | LicenseInvalid | Um endpoint recusou uma requisição porque o arquivo .lic estava ausente ou era inválido. |
| 8101 | UploadMagicByteMismatch | A assinatura (magic bytes) não corresponde à extensão declarada. |
| 8102 | UploadRejectedByValidator | Um IUploadValidator retornou Reject. |
| 8103 | UploadInvalidExtension | Upload rejeitado por estar fora da lista de permitidos. |
| 8104 | UploadOversize | Excedeu MaxUploadBytes. |
| 8201 | AiRequestOversize | O corpo da requisição de IA excedeu MaxRequestBytes. |
| 8202 | AiRateLimitExceeded | O chamador excedeu o limite de taxa de IA por IP. |
| 8203 | AiResolverException | O resolver de IA lançou uma exceção; registrado com o correlation id. |
| 8204 | AiKeyVaultMiss | O cofre BYOK não retornou nenhuma chave. |
| 8205 | AiKeyVaultHit | O cofre BYOK retornou uma chave (o KeyId é registrado; o segredo, nunca). |
| 8206 | AiResponseFilterMatched | Um IRichTextBoxAiResponseFilter alterou a resposta. |
Como encaminhar eventos de auditoria para um fluxo separado
IRichTextBoxAuditSink permite que cargas de trabalho sujeitas a regulamentação de conformidade (HIPAA / PCI / SOC2) encaminhem apenas os eventos de auditoria para um destino de gravação única — Splunk HEC, S3 com object lock, um banco de dados só de auditoria. Inclui JsonLinesAuditSink para uso offline / em instância única:
builder.Services.AddRichTextBox(); builder.Services.AddJsonLinesAuditSink("/var/log/myapp/rtb-audit.jsonl");
Channel limitado + drenador em segundo plano; RecordAsync nunca bloqueia. Quando a fila transborda, os eventos são descartados e contabilizados na métrica DroppedCount. Vários sinks se registram naturalmente — combine o JsonLines com um sink do Splunk em tempo real sem custo adicional.Sondas de disponibilidade de dependências
Interfaces complementares opcionais permitem que qualquer dependência do RichTextBox informe se o serviço por trás dela está acessível. O endpoint /richtextbox/health exibe o resultado junto com o status da licença:
| Interface | Implementações nativas | Alvo da sonda |
|---|---|---|
IRichTextBoxAiResolverProbe | OpenAI, Azure OpenAI (sem custo); wrapper AnthropicResolverWithProbe (POST com poucos tokens) | GET /v1/models / GET /openai/deployments / POST /v1/messages com max_tokens=1 |
IIdempotencyStoreProbe | Store Redis / SQL personalizado (implementado por você) | PING / SELECT 1 |
IRichTextBoxUploadStoreProbe | Store S3 / Azure Blob / Cloudinary personalizado | HEAD no 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; });
O TTL de cache em cada sonda protege contra o bombardeio do balanceador de carga — um balanceador que acessa /health a cada 5 segundos geraria, caso contrário, 12 sondas por minuto, mesmo quando as sondas custam dinheiro de verdade.
Integração padrão com IHealthCheck
Em hosts que usam Microsoft.Extensions.Diagnostics.HealthChecks, registre o adaptador e o status do RichTextBox aparece junto com suas outras verificações — sem um caminho de monitoramento paralelo:
builder.Services.AddRichTextBox(); builder.Services.AddHealthChecks() .AddRichTextBox(name: "richtextbox", failureStatus: HealthStatus.Unhealthy, tags: new[] { "ready" } ); app.MapHealthChecks("/health");
Retorna Healthy quando a licença e todas as sondas habilitadas passam; Unhealthy, com uma descrição que identifica a dependência que falhou. HealthCheckResult.Data espelha o payload JSON de /richtextbox/health.
BYOK — chaves de API por tenant
Hosts SaaS multi-tenant cobram o tráfego de IA da conta de provedor de cada tenant por meio de IAiKeyVault + IAiKeyVaultAdmin. O pacote inclui uma implementação de referência em memória; implantações em produção usam como backend o Azure Key Vault, o Redis ou um store SQL de verdade.
Veja a página dedicada BYOK key vault para o contrato completo, os endpoints REST de administração e as implementações de referência (Redis, Azure Key Vault, FileBackedAiKeyVault criptografado em disco).
Registro de custo por chamada
IRichTextBoxAiCostSink emite um AiUsageRecord por chamada de IA — provedor, modelo, modo, tokens (entrada / saída / total), latência, status, KeyId. Encaminhe para o seu sistema de cobrança para fazer o repasse de custos por tenant:
builder.Services.AddSingleton<IRichTextBoxAiCostSink, MyBillingApiCostSink>();Para tenants com muito tráfego, envolva-o no sink com buffer, para que o sink interno receba um único flush em lote por intervalo, em vez de uma chamada HTTP/SQL por requisição de IA. Se o seu sink interno também implementar IRichTextBoxAiCostBatchSink, o wrapper encaminha por RecordBatchAsync — um INSERT em vez de N.
builder.Services.AddBufferingAiCostSink<MyBillingApiCostSink>( flushInterval: TimeSpan.FromSeconds(30), maxBatchSize: 500);
Filtragem da saída — ocultação de PII, bloqueio de conteúdo
IRichTextBoxAiResponseFilter é executado depois do resolver, mas antes de a resposta ser enviada. O RegexAiResponseFilter de referência cobre ~80% dos casos práticos de ocultação de PII:
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]")));
Em respostas em streaming, o framework faz buffer dos tokens no servidor quando há filtros registrados (BufferStreamingForFilters = true por padrão), para que uma regex de cartão de crédito que se estende por vários deltas de fato encontre a correspondência.
Substituição de templates de prompt
IRichTextBoxPromptTemplateProvider permite substituir os prompts de sistema por modo que os resolvers nativos enviam. Use a classe base decoradora PromptTemplateProviderBase para sobrescrever apenas o(s) método(s) de que você precisa:
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 pontuais de um modo sem um provider completo, use o dicionário:
services.AddRichTextBoxOpenAiResolver(opts => { opts.ApiKey = "sk-..."; opts.SystemPromptOverridesByMode["proofread"] = "You are a hyper-pedantic copy-editor for legal documents."; });
Triagem de prompt injection
IRichTextBoxAiPolicy é a barreira que os endpoints de IA consultam antes de chamar o resolver. O DefaultPromptInjectionPolicy nativo rejeita templates de jailbreak prontos (“ignore previous instructions”, “you are now DAN” etc.). Adicione políticas extras para ocultação de PII ou listas de modos permitidos por tenant.
builder.Services.AddSingleton<IRichTextBoxAiPolicy, DefaultPromptInjectionPolicy>(); builder.Services.AddSingleton<IRichTextBoxAiPolicy, MyTenantPolicy>();
Rastreamento distribuído (OpenTelemetry)
Toda chamada de IA, exportação DOCX e upload emite um span por meio do ActivitySource RichTextBox.AspNetCore. Inscreva-se na configuração do seu tracer provider:
builder.Services.AddOpenTelemetry().WithTracing(t => t .AddSource(RichTextBoxDiagnostics.SourceName) // "RichTextBox.AspNetCore" .AddAspNetCoreInstrumentation() .AddOtlpExporter());
Validação das opções na inicialização
RichTextBoxOptionsValidator + AiResolverOptionsValidator<T> são executados na primeira resolução de IOptions.Value — antes de o host aceitar a primeira requisição. Uma configuração inválida (/ esquecida, limite de bytes negativo, heartbeat > timeout, chave de API ausente, deployment do Azure ausente) lança OptionsValidationException na inicialização, para que as implantações falhem de forma visível.
Nenhuma configuração necessária. Os validadores são registrados automaticamente por AddRichTextBox().
Documentação relacionada
Cofre de chaves BYOK·Provedores de IA e streaming·Provedores de upload na nuvem·Acessibilidade