← 全部文档
生产环境运维

在生产环境中运行

默认设置针对典型的 SaaS 客户进行了调优。下面是流量形态变化时需要调整的参数——速率限制、重试策略、审计日志、依赖可达性探针、IHealthCheck 集成、幂等性、提示词模板覆盖,以及 BYOK 管理接口。

AI 调用的速率限制

POST /richtextbox/ai 和 POST /richtextbox/ai/stream 共用一个按 IP 计算的令牌桶,这样单个失控的标签页就无法在几秒钟内耗尽您的服务商配额。默认值:每个 IP 每分钟 30 次调用。

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

设置 AiRateLimit = 0 可禁用限制,也可以换成基于 Redis 的限流器:

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

服务商临时故障时的 HTTP 重试

OpenAI / Anthropic / Azure OpenAI 在正常负载下也会返回 429 和 503。内置的解析器会对这些状态码(以及 502 / 504 和连接中断)进行重试,采用带完全抖动的指数退避,上限为 30 秒。会遵循服务商的 Retry-After 标头。

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

幂等键

每个 POST /richtextbox/ai、/ai/stream 和 /upload 都会遵循 Idempotency-Key。重复的重试会原样重放缓存的响应体——不会重复计费,也不会产生孤立文件。流式响应会逐字节重放整个 SSE 字节流。

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

多实例主机:请注册一个基于 Redis 的 IIdempotencyStore,代替默认的内存存储。借助原生 TTL,您可以禁用定期清理程序。

安全审计日志

每一次与安全相关的拒绝,都会在 RichTextBox.Audit 类别下输出一条结构化的 LogWarning,并带有稳定的 EventId:

EventId名称触发时机
8001LicenseInvalid端点因 .lic 文件缺失或无效而拒绝了请求。
8101UploadMagicByteMismatch文件签名与声明的扩展名不符。
8102UploadRejectedByValidator某个 IUploadValidator 返回了 Reject。
8103UploadInvalidExtension上传因不在允许列表中而被拒绝。
8104UploadOversize超过了 MaxUploadBytes。
8201AiRequestOversizeAI 请求体超过了 MaxRequestBytes。
8202AiRateLimitExceeded调用方超过了按 IP 计算的 AI 速率限制。
8203AiResolverExceptionAI 解析器抛出了异常;记录时附带关联 id。
8204AiKeyVaultMissBYOK 密钥库没有返回密钥。
8205AiKeyVaultHitBYOK 密钥库返回了密钥(记录 KeyId,绝不记录密钥本身)。
8206AiResponseFilterMatched某个 IRichTextBoxAiResponseFilter 修改了响应。

把审计事件路由到单独的流

IRichTextBoxAuditSink 让受合规监管的工作负载(HIPAA / PCI / SOC2)只把审计事件路由到一次写入的目标——Splunk HEC、启用了对象锁定的 S3、专用于审计的数据库。附带的 JsonLinesAuditSink 适用于离线/单实例场景:

builder.Services.AddRichTextBox();
builder.Services.AddJsonLinesAuditSink("/var/log/myapp/rtb-audit.jsonl");
行为。有界的 Channel 加后台排空程序;RecordAsync 从不阻塞。队列溢出时会丢弃事件,并记录 DroppedCount 指标。可以自然地注册多个接收器——把 JsonLines 与实时的 Splunk 接收器搭配使用,无需额外成本。

依赖可达性探针

可选的配套接口让任何 RichTextBox 依赖都能报告其后端服务是否可达。/richtextbox/health 端点会把结果与授权状态一起显示:

接口内置实现探测目标
IRichTextBoxAiResolverProbeOpenAI、Azure OpenAI(免费);AnthropicResolverWithProbe 包装器(消耗少量令牌的 POST)GET /v1/models / GET /openai/deployments / 带 max_tokens=1 的 POST /v1/messages
IIdempotencyStoreProbe自定义的 Redis / SQL 存储(由您实现)PING / SELECT 1
IRichTextBoxUploadStoreProbe自定义的 S3 / Azure Blob / Cloudinary 存储对存储桶执行 HEAD / 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;
});

每个探针上的缓存 TTL 可以防止负载均衡器的频繁探测——否则每 5 秒请求一次 /health 的负载均衡器每分钟会产生 12 次探测,即使探测会产生真实费用也是如此。

标准的 IHealthCheck 集成

对于使用 Microsoft.Extensions.Diagnostics.HealthChecks 的主机,注册适配器之后,RichTextBox 的状态就会与您的其他检查一起显示——无需另建一套监控路径:

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

app.MapHealthChecks("/health");

授权和所有受控探针都通过时返回 Healthy;否则返回 Unhealthy,并附带指明失败依赖的描述。HealthCheckResult.Data 与 /richtextbox/health 的 JSON 载荷相同。

BYOK——按租户分配的 API 密钥

多租户 SaaS 主机可以通过 IAiKeyVault + IAiKeyVaultAdmin,把 AI 流量计入每个租户自己的服务商账户。软件包附带一个内存中的参考实现;生产部署则以 Azure Key Vault、Redis 或真正的 SQL 存储作为后端。

完整的约定、管理 REST 端点和参考实现(Redis、Azure Key Vault、磁盘加密的 FileBackedAiKeyVault),请参阅专门的 BYOK 密钥库页面。

按调用的成本台账

IRichTextBoxAiCostSink 为每次 AI 调用输出一条 AiUsageRecord——服务商、模型、模式、令牌数(输入/输出/总计)、延迟、状态、KeyId。可以转发到您的计费系统,用于向租户分摊费用:

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

对于高流量租户,请用缓冲接收器进行包装,这样内部接收器在每个时间间隔内只看到一次批量刷新,而不是每个 AI 请求一次 HTTP/SQL 调用。如果您的内部接收器还实现了 IRichTextBoxAiCostBatchSink,包装器会通过 RecordBatchAsync 路由——执行一次 INSERT,而不是 N 次。

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

输出过滤——PII 脱敏、内容拦截

IRichTextBoxAiResponseFilter 在解析器之后、响应发出之前运行。参考实现 RegexAiResponseFilter 可以覆盖约 80% 的实际 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]")));

对于流式响应,注册了过滤器时,框架会在服务器端缓冲令牌(默认 BufferStreamingForFilters = true),这样跨越多个增量片段的信用卡号正则表达式也能真正匹配。

提示词模板覆盖

IRichTextBoxPromptTemplateProvider 允许您覆盖内置解析器按模式发送的系统提示词。使用装饰器基类 PromptTemplateProviderBase,只覆盖您需要的方法即可:

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.";
}

如果只是临时调整某个模式而不想实现完整的提供程序,可以使用字典:

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

提示词注入筛查

IRichTextBoxAiPolicy 是 AI 端点在调用解析器之前要经过的关卡。内置的 DefaultPromptInjectionPolicy 会拒绝现成的越狱模板("ignore previous instructions"、"you are now DAN" 等)。可以叠加更多策略,用于 PII 脱敏或按租户限定的模式允许列表。

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

分布式跟踪(OpenTelemetry)

每次 AI 调用、DOCX 导出和上传,都会通过 RichTextBox.AspNetCore ActivitySource 输出一个 span。请在您的 tracer provider 配置中订阅它:

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

启动时的选项验证

RichTextBoxOptionsValidator + AiResolverOptionsValidator<T> 会在第一次解析 IOptions.Value 时运行——在主机接受第一个请求之前。错误的配置(遗漏了 /、字节上限为负数、心跳间隔 > 超时时间、缺少 API 密钥、缺少 Azure 部署)会在启动时抛出 OptionsValidationException,让部署明确地失败。

无需任何连接代码。验证器会通过 AddRichTextBox() 自动注册。

配套文档

BYOK 密钥库·AI 服务商与流式输出·云上传服务商·无障碍