← すべてのドキュメント
本番運用

本番環境での運用

既定値は、一般的な SaaS の顧客向けに調整されています。以下は、トラフィックの傾向が変わったときに調整する項目です。レート制限、再試行のポリシー、監査ログ、依存先への到達性のプローブ、IHealthCheck との統合、冪等性、プロンプトテンプレートの上書き、BYOK の管理機能です。

AI の呼び出しのレート制限

POST /richtextbox/ai と POST /richtextbox/ai/stream は IP ごとのトークンバケットを共有しているので、暴走した 1 つのタブがプロバイダーの利用枠を数秒で使い切ることはありません。既定値は、IP ごとに 1 分あたり 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 を登録してください。Redis 自体の TTL を使えるので、定期的な削除処理は無効にできます。

セキュリティの監査ログ

セキュリティに関係するすべての拒否は、RichTextBox.Audit カテゴリで、変わることのない EventId を持つ構造化された LogWarning を出力します。

EventId名前発生するタイミング
8001LicenseInvalid.lic ファイルがないか無効なため、エンドポイントがリクエストを拒否したとき。
8101UploadMagicByteMismatchマジックバイトが、申告された拡張子と一致しないとき。
8102UploadRejectedByValidatorIUploadValidator が Reject を返したとき。
8103UploadInvalidExtension許可リストにないため、アップロードが拒否されたとき。
8104UploadOversizeMaxUploadBytes を超えたとき。
8201AiRequestOversizeAI の本文が MaxRequestBytes を超えたとき。
8202AiRateLimitExceeded呼び出し元が、IP ごとの AI のレート制限を超えたとき。
8203AiResolverExceptionAI のリゾルバーが例外をスローしたとき。相関 ID とともに記録されます。
8204AiKeyVaultMissBYOK の保管庫がキーを返さなかったとき。
8205AiKeyVaultHitBYOK の保管庫がキーを返したとき(KeyId は記録されますが、シークレットは決して記録されません)。
8206AiResponseFilterMatchedIRichTextBoxAiResponseFilter がレスポンスを変更したとき。

監査イベントを別のストリームに送る

IRichTextBoxAuditSink を使うと、コンプライアンスの規制を受けるワークロード(HIPAA / PCI / SOC2)で、監査イベントだけを書き込み専用の送信先(Splunk HEC、オブジェクトロック付きの S3、監査専用の DB)に送れます。オフラインや単一インスタンスでの利用向けに、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 にアクセスするバランサーによって、プローブに実際の費用がかかる場合でも 1 分あたり 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 の呼び出しごとに 1 つの AiUsageRecord(プロバイダー、モデル、モード、トークン数(入力/出力/合計)、レイテンシー、ステータス、KeyId)を出力します。テナントへの費用の請求のために、課金システムに転送してください。

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

トラフィックの多いテナントでは、バッファリングするシンクで包むと、内側のシンクは AI のリクエストごとに 1 回の HTTP/SQL の呼び出しを受けるのではなく、一定間隔ごとにまとめて 1 回のフラッシュを受けるようになります。内側のシンクが IRichTextBoxAiCostBatchSink も実装している場合、ラッパーは RecordBatchAsync を経由するので、N 回ではなく 1 回の INSERT で済みます。

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

出力のフィルタリング — 個人情報のマスキング、コンテンツのブロック

IRichTextBoxAiResponseFilter は、リゾルバーの後、レスポンスが送信される前に実行されます。参照実装の RegexAiResponseFilter で、実用上の個人情報のマスキングの約 80% をカバーできます。

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」など)を拒否します。個人情報のマスキングや、テナント単位のモードの許可リストのために、追加のポリシーを重ねて使えます。

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

分散トレーシング(OpenTelemetry)

すべての AI の呼び出し、DOCX のエクスポート、アップロードは、RichTextBox.AspNetCore の ActivitySource を通じてスパンを出力します。トレーサープロバイダーの設定で購読してください。

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

起動時のオプションの検証

RichTextBoxOptionsValidator と AiResolverOptionsValidator<T> は、最初の IOptions.Value の解決時、つまりホストが最初のリクエストを受け付ける前に実行されます。誤った設定(/ の付け忘れ、負のバイト数の上限、タイムアウトより長いハートビート、API キーの欠落、Azure のデプロイメントの欠落)があると、起動時に OptionsValidationException がスローされるので、デプロイははっきりと失敗します。

設定は不要です。バリデーターは AddRichTextBox() によって自動的に登録されます。

関連ドキュメント

BYOK キー保管庫·AI プロバイダーとストリーミング·クラウドアップロードプロバイダー·アクセシビリティ