AI Assistant API
AIアシスタントのチャットエンドポイントのAPI仕様です。
showcase実装の境界
このreferenceはzudo-doc showcase repositoryのhost-ownedなlive実装を説明します。@takazudo/zudo-doc package/scaffoldは、このhandler、worker-entry.ts、AiChatDailySpendCapを出荷しません。host実装なしでlive modeを選ぶとpackage-owned routeは HTTP 501を返します。downstream projectは同等のhandlerとWorker graphを用意する必要があります。
エンドポイント
POST /api/ai-chat
Content-Type: application/json リクエストボディ
interface AiChatRequest {
message: string;
history?: ChatMessage[];
}
interface ChatMessage {
role: "user" | "assistant";
content: string;
}| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
message | string | はい | ユーザーの現在のメッセージ。空でないこと。 |
history | ChatMessage[] | いいえ | 過去の会話メッセージ。不正な形式(非配列、50件超、コンテンツが8192文字超、またはインジェクション一致)の場合はHTTP 400で拒否。 |
成功レスポンス (200)
interface AiChatResponse {
response: string;
}responseフィールドはアシスタントの返答をマークダウン文字列として含みます。
例:
// Request
{
"message": "How do I add a new page?",
"history": []
}
// Response
{
"response": "Create an MDX file in `src/content/docs/`:\n\n1. Add frontmatter with `title`\n2. Write your content in MDX\n3. The page appears in the sidebar automatically"
}エラーレスポンス (400 / 500)
interface AiChatErrorResponse {
error: string;
}| ステータス | 条件 |
|---|---|
| 400 | 無効なJSONボディ |
| 400 | messageが空でない文字列ではない |
| 400 | messageが4000文字の制限を超えている |
| 400 | 入力スクリーニング(プロンプトインジェクションガード)で拒否された |
| 400 | historyの形式が不正(フィールド説明を参照) |
| 405 | リクエストメソッドがPOSTでもOPTIONSでもない |
| 415 | Content-Typeがapplication/jsonではない |
| 429 | レート制限超過(Retry-Afterヘッダーを含む) |
| 500 | Anthropic APIコール失敗 |
このエンドポイントはPOST(チャット)とOPTIONS(CORSプリフライト)のみを受け付けます。それ以外のメソッドはすべて{ "error": "Method not allowed" }とともに405を返します。
CORS
このエンドポイントはオリジンごとの許可リストを使用します。aiChatDemoModeがfalseの場合、Access-Control-Allow-OriginはaiChatAllowedOrigins設定にリストされたリクエストオリジンに対してのみエコーされます。それ以外のオリジンにはallow-originヘッダーが返されず、ブラウザによってブロックされます。(デモモードでは後方互換性のため常に*が返されます。)これはワイルドカードCORS(*)を使用するSearch Workerよりも意図的に厳格です。各コールに実際のAnthropic APIコストが伴うAIチャットエンドポイントはオリジンでゲートしますが、検索はメーター課金のないオプトインサービスだからです。2つのエンドポイントが同じCORSポリシーを共有していると仮定しないでください。
CF環境バインディング
| バインディング | 種別 | 必須 | 説明 |
|---|---|---|---|
ANTHROPIC_API_KEY | secret | はい | Anthropic APIキー |
DOCS_SITE_URL | var | はい | デプロイ済みのドキュメントサイトURL(llms-full.txtの取得に使用) |
RATE_LIMIT | KV namespace | はい | ソフトなIPごとのカウンタとprivacy-safeな結果監査recordを保存 |
AI_CHAT_DAILY_SPEND_CAP | Durable Object namespace | aiChatGlobalDailyLimitが数値の場合 | 正確な有料呼び出し許可。クラスAiChatDailySpendCap、SQLite migration v1-ai-chat-daily-spend-cap |
RATE_LIMIT_PER_MINUTE | var | いいえ | IPあたりの1分間の最大リクエスト数(デフォルト10) |
RATE_LIMIT_PER_DAY | var | いいえ | IPあたりの1日の最大リクエスト数(デフォルト100) |
設定
以下のzfb.config.ts内のzudoDoc({...})フィールドがエンドポイントの動作を制御します(上記のCF環境変数とは 別のもので、こちらはCloudflare側のランタイム設定です)。
| 設定 | 型 | デフォルト | 説明 |
|---|---|---|---|
aiChatDemoMode | boolean | false(ショーケース: true) | 固定返答でショートサーキット。APIキー、KV、Durable Object、provider fetchにアクセスしない |
aiChatAllowedOrigins | string[] | [] | CORSオリジン許可リスト(非デモ時のみ有効)。空配列 = 全クロスオリジンリクエストをブロック |
aiChatGlobalDailyLimit | number | false | false | UTC日ごとの正確なAnthropic fetch許可上限。false = 正確な上限なし |
セキュリティ
このエンドポイントは、レガシーのスタンドアロンWorkerから移植された多層的な防御を備えています:
ハードニングされたシステムプロンプト — XMLタグでコンテキストを区切り、明示的なガードレールによってモデルが設定情報を漏洩したり、トピック外の指示に従ったりすることを防ぎます。さらに、過去の会話ターンはすべてクライアント由来の信頼できない入力として扱うようモデルに指示します(下記の チャット履歴の信頼モデル を参照)
入力スクリーニング — 一般的なプロンプトインジェクションパターンを、どちらのリミッターやClaude APIよりも先に正規表現で検査します。その後、validation rejectionを返す前にIPごとのKVガードを実行するため、拒否された入力も近似的なIPごとのquotaを消費しますが、audit writeはそのガードより後ろに保たれます
IPごとのソフトガード —
RATE_LIMITKVを使う近似的で結果整合な制限。aiChatDemoModeがfalseの場合は フェイルクローズ(KV障害時 → HTTP 429)。デモモードではフェイルオープン(デモのショートサーキットが先に実行されるため、実際にはレートリミッターには到達しない)CORSオリジン許可リスト — 非デモ時、
Access-Control-Allow-OriginはaiChatAllowedOriginsに含まれるオリジンにのみエコーされます。リスト外のオリジンからのクロスオリジンリクエストはブラウザでブロックされます。デモモードは後方互換性のため常に*を送信します正確な有料呼び出し許可 — UTC日ごとのSQLite
AiChatDailySpendCapが、ちょうど1回のAnthropic fetch直前の許可判定を直列化します。名前空間はAI_CHAT_DAILY_SPEND_CAP、Worker migration tagはv1-ai-chat-daily-spend-cap、設定はnew_sqlite_classes = ["AiChatDailySpendCap"]です。上限超過は 次のUTC日まで429、binding/RPC/storage障害は500でフェイルクローズします。provider/network障害でも 許可枠は返却せず、providerが確定した請求数ではありませんPrivacy-safeな監査record —
RATE_LIMITKVのaudit:にはtimestamp、completed/blockedoutcome、任意の限定されたblock-reason enumだけを保存します(7日のTTL、fire-and-forget)。prompt/response text、IP、IP hashは永続化しませんメッセージ長制限 — 4000文字を超えるメッセージはAPIに到達する前に拒否されます
cf-connecting-ipの注意点 — IPごとのレート制限にはこのヘッダーを使用しており、WorkerがCloudflareのネットワーク経由でデプロイされている場合にのみ信頼できます
運用ログ
Wranglerの[observability] enabled = trueにより、Workers Logsへ正確な上限のadmitted、denied、 failed_closedをclosed-schemaオブジェクトとして記録します。各イベントにはutc_day、 configured_limitと非機密な結果フィールドだけが含まれます。別のper_ip_kvイベントで、通常の 上限拒否とKV障害によるフェイルクローズを区別できます。prompt、response、生IP、IP hash、secret、 Durable Object名/ID、raw error textは記録しません。
チャット履歴の信頼モデル
history配列はクライアント由来かつステートレスです。サーバーはセッション記録を一切保持しないため、assistantロールのターンが実際に過去のモデル応答によって生成されたものかを検証できません。各エントリは依然としてハードニングされています。すなわち、user/assistantへの厳格なロールホワイトリスト、上記のエントリ件数および1エントリあたりの長さ制限、そして紛れ込んだ余分なフィールドを取り除く{ role, content }への再構築です。userロールのターンはインジェクションスクリーニングされますが、assistantロールのターンはされません(本物のアシスタント応答はインジェクションのような文面を正当に引用しうるためです)。
ロールが検証不可能であるため、呼び出し元は敵対的な指示を含むassistantターンを偽造してユーザーターンのスクリーニングを回避できます。この残存リスクは意図的に受容しています。本チャットはブラスト半径の小さいドキュメントアシスタントであり、システムプロンプトはすべての過去ターンを、自身のルールを上書きできない信頼できない入力として扱うようモデルに指示しているためです。堅牢な対策(サーバー発行の署名付き履歴)はシークレットのプロビジョニングとクライアント/サーバー間ペイロード契約の変更を必要とし、この機能には見合いません。決定の全記録はissue #2036を参照してください。
ドキュメントコンテキスト
このエンドポイントは、llms.txtインテグレーションで生成されたllms-full.txtをDOCS_SITE_URLから取得し、CF Workersのアイソレートが生きている間メモリにキャッシュします(ベストエフォート、約1時間)。取得したコンテンツはシステムプロンプトに<documentation>XMLコンテキストとして含められます。