アシスタント
ドキュメントに基づいたページ内アシスタント — 推奨質問、カスタム指示、検索サイズの調整、Vercel AI Gateway から任意の OpenAI 互換エンドポイントまでのプロバイダーアダプター、そして必要となるサーバー出力について解説します。
読者の質問にページ内チャットパネルで回答するアシスタントを追加します。ストリーミング対応のサーバーエンドポイントと AI SDK によって動作します。これはオプトイン方式であり、有効にするまで静的ドキュメントは完全に静的なままです。
ai: {
assistant: {
enabled: true,
},
}
ほかに何も設定しなければ、回答は openai/gpt-5.5 から Vercel AI Gateway を経由してストリーミングされます。別のモデルやプロバイダーを使用するには、アダプターで選択してください。
推奨質問
空の状態にいくつかの開始プロンプトを設定できます。それぞれはクリック可能な候補として表示され、クリックするとその質問が送信されます。ラベルの横にはオプションで Lucide アイコン を表示できます。
ai: {
assistant: {
enabled: true,
suggestions: [
{ label: "What is Blume?", icon: "rocket" },
{ label: "How do I write a docs page?", icon: "file-text" },
{ label: "How do I configure the theme?", icon: "settings" },
],
},
}
label は実際に送信される質問で、icon は任意です。suggestions を未設定(または空)のままにすると、パネルはシンプルな入力欄だけで開きます。
カスタム指示
instructions で独自のシステムプロンプトのテキストを追加できます。アイデンティティ、言語、トーンなど、アシスタントに意識してほしい内容を自由に指定できます。
ai: {
assistant: {
enabled: true,
instructions:
"You are Bloomy, the Acme docs assistant. Answer in the language the question was asked in, and keep answers under three paragraphs.",
},
}
指定したテキストは組み込みの指示を置き換えるのではなく、その後に追加されます。組み込み部分にはグラウンディングの取り決め(取得したページのみに基づいて回答し、それらを Markdown リンクとして引用する)が含まれており、チャットパネルの引用機能はこれに依存しているため、何を追加してもそのまま維持されます。
グラウンディング
アシスタントはドキュメントに基づいて動作します。質問ごとに最も関連性の高いページを取得し(オンページ検索を支えるものと同じ字句ベースの Orama インデックスを使用)、モデルのシステムプロンプトに挿入します。これにより、回答はモデル自身の知識ではなくコンテンツから生成されます。アシスタントには、取得したページのみに基づいて回答すること、扱われていない内容についてはその旨を伝えること、参照したページを引用することが指示されています。
読者が現在閲覧しているページが最初にコンテキストへ追加され、そのページの言語に検索範囲を絞り込むために使用されます。これにより、ドキュメント内のどこにいても関連性の高い回答が得られます。検索はビルド時に焼き込まれたスナップショットからリクエスト時に実行されるため、検索プロバイダーに関係なく動作し(search: false の場合でも)、設定は不要です。
グラウンディングは Inkeep を除くすべてのアダプターで有効です。Inkeep はダッシュボードでインデックス化したコンテンツに対して独自の検索を実行します。
検索サイズ
質問がどれだけのドキュメントを伴うかは、読者が最初の単語を目にするまでの待ち時間を左右する最大の要因です。モデルは挿入されたすべての文字を読み込んでからトークンを出力するためです。ホスト型のフロンティアモデルではこれは体感できませんが、セルフホストのバックエンドでは支配的な要素になります。retrieval でこのサイズを調整します。
ai: {
assistant: {
enabled: true,
retrieval: {
maxResults: 3, // fewer pages retrieved per question
excerptChars: 1200, // shorter excerpt from each one
contextBudget: 3000, // smaller total injection
},
},
}
| オプション | デフォルト | 説明 |
|---|---|---|
maxResults |
6 |
質問ごとに取得するドキュメント数。 |
excerptChars |
2000 |
取得した各ページから保持する文字数。 |
contextBudget |
10000 |
すべての抜粋を合わせた挿入文字数の合計。 |
この3つは互いに置き換えられるものではありません。contextBudget は挿入全体の上限を定め、excerptChars は1つの長いページのどこまで深く抜粋するかを決定し(1ページに回答全体が含まれていて抜粋が途中で切れる場合は引き上げてください)、maxResults は検索が追加するページ数の上限を定めます。読者が閲覧中のページは取得されたページに加えて挿入されるため、回答は maxResults より最大で1ページ多く引用できます。
デフォルト値はホスト型モデルに適しています。自前のハードウェアで提供していて、再現率よりも最初のトークンまでの時間が重要な場合は値を下げてください。いずれの場合も回答はドキュメントに基づいたままであり、アシスタントは扱われていない内容について推測で埋めるのではなく、その旨を伝えるよう指示されています。
外部エンドポイント
すでに AI 用の API バックエンドをお持ちですか?パネルをそこに向けることで、ドキュメントのビルドを静的なまま維持できます。
ai: {
assistant: {
enabled: true,
endpoint: "https://api.example.com/v1/docs/ask",
},
}
Blume は組み込みルートと同じ POST ボディを送信します。
{
"messages": [{ "role": "user", "content": "How do I deploy?" }],
"page": { "path": "/deployment" }
}
ボディがプレーンな UTF-8 テキストストリームである成功レスポンスを返してください。エンドポイントが別のオリジンにある場合は、CORS でドキュメントのオリジンを許可してください。OPTIONS と POST を受け付け、content-type リクエストヘッダーを許可し、プリフライトとストリーミングレスポンスの両方で CORS ヘッダーを返します。endpoint を設定すると、Blume はチャット UI のみを生成し、サーバールート、グラウンディング用スナップショット、プロバイダーの依存関係、プロバイダーシークレットの警告は生成しません。検索、認証、レート制限、モデルへのアクセス、引用はすべてバックエンド側の責任となります。併せて設定したアダプターは無視されます。
クロスオリジンの呼び出し元
生成されるエンドポイントは、同一オリジン上のページ内アシスタントに応答します。別のサイトからも呼び出せるようにするには(たとえば質問ボックスを備えたマーケティングページなど)、そのサイトのオリジンを cors に列挙します。
ai: {
assistant: {
enabled: true,
cors: ["https://www.example.com"],
},
}
これによりルートはブラウザの OPTIONS プリフライトに応答し、すべてのレスポンスで列挙されたオリジンを指定します。ストリーミングされる回答もエラーステータスも同様であるため、呼び出し元はボディが拒否されたのかプロバイダー側の障害なのかを判別できます。列挙されていないオリジンにはヘッダーが付与されず、ブラウザの同一オリジンポリシーの対象のままとなります。各エントリはそのオリジンに正規化されるため、https://www.example.com/docs/ と https://www.example.com は同じ意味になります。任意のページからルートを呼び出せるようにするには、オリジンの代わりに "*" を列挙してください。
呼び出し元は外部エンドポイントの取り決めで説明されているものと同じ POST ボディを送信し、同じテキストストリームを読み取ります。content-type: application/json ヘッダーを付けて JSON として送信してください。
const response = await fetch("https://docs.example.com/api/ask", {
body: JSON.stringify({
messages: [{ role: "user", content: "How do I deploy?" }],
}),
headers: { "content-type": "application/json" },
method: "POST",
});
コンテンツタイプは重要です。Astro のクロスサイトリクエストチェックは、コンテンツタイプのないクロスオリジンの POST、または text/plain のようなフォーム類似のコンテンツタイプを、ルートが実行される前に 403 で拒否します。このレスポンスには CORS ヘッダーが付与されないため、ブラウザはステータスではなくネットワークエラーとして報告します。プリフライトは呼び出し元が要求するリクエストヘッダーをすべて許可するため、独自のヘッダーを追加する fetch ラッパーでも追加の設定は不要です。
cors は生成されたルートにのみ影響します。外部 endpoint を使用する場合、CORS はそのバックエンドの責任となり、両方を設定することは設定エラーになります。いずれの場合もエンドポイントは認証されないままなので、レート制限に関する指針はクロスオリジンのトラフィックにも当てはまります。
サーバー出力が必要です
Blume の組み込みアシスタントバックエンドはサーバールート(POST /api/ask)であるため、静的ビルドでは動作しません。blume/deploy からホストアダプターを指定して、サーバー出力に切り替えてください。
import { vercel } from "blume/deploy";
export default defineConfig({
deployment: vercel(),
});
アシスタントが有効で外部 endpoint が設定されていない静的ビルドは、ホストアダプターを設定するよう促すメッセージとともに即座に失敗します。アダプターについては デプロイ を参照してください。
アダプター
provider は回答するバックエンドを選択します。その値はアダプターです。アダプターは blume/ai からエクスポートされる小さな関数で、そのバックエンド固有のオプションを受け取り、Blume が生成されたルートに書き込むプレーンなディスクリプターを返します。各アダプターはモデル、キーを読み取る環境変数、推論のマッピング方法、必要なプロバイダー SDK をそれぞれ独自に持つため、バックエンド間で共通のフィールドを揃える必要はありません。
import { defineConfig } from "blume";
import { openrouter } from "blume/ai";
export default defineConfig({
ai: {
assistant: {
enabled: true,
provider: openrouter({ model: "anthropic/claude-sonnet-4-5" }),
},
},
});
| アダプター | 回答に使用するもの | API キーの環境変数 | インストールする SDK |
|---|---|---|---|
gateway()(デフォルト) |
Vercel AI Gateway 経由の provider/model 形式の文字列 |
AI_GATEWAY_API_KEY |
不要 — Blume に同梱 |
openrouter() |
任意の OpenRouter モデル | OPENROUTER_API_KEY |
@openrouter/ai-sdk-provider |
llmgateway() |
任意の LLMGateway モデル | LLMGATEWAY_API_KEY |
@ai-sdk/openai-compatible |
inkeep() |
Inkeep の QA モデル | INKEEP_API_KEY |
@ai-sdk/openai-compatible |
openaiCompatible() |
エンドポイントが提供するもの | apiKeyEnv で指定 |
@ai-sdk/openai-compatible |
これらの SDK はオプションのピア依存関係なので、アダプターに必要なものをプロジェクトに追加してください(例: npm install @openrouter/ai-sdk-provider)。不足している場合、blume build は Vite の実行前に停止し、パッケージ名とそれをインストールするコマンドを示します。blume doctor でも報告されます。
アダプターが返すディスクリプターは、種類、オプション、読み取る環境変数、必要な SDK からなるプレーンなデータです。そのため、生成されたルート(およびイジェクトしたルート)はこれをリテラルとしてインライン化し、プロバイダー SDK を名前でインポートします。リクエスト時に blume.config.ts を読み取るものはなく、シークレットがルートに書き込まれることもありません。アダプターはキーを保持する環境変数の名前を受け取り、ルートは Astro の getSecret() を通じてその値を読み取るため、各デプロイアダプターがそれぞれの方法でキーを提供します。Node、Vercel、Netlify では環境変数、Cloudflare では Worker のバインディングが使用されます。
Vercel AI Gateway
デフォルトのアダプターです。model は provider/model 形式の文字列なので、これを変更するだけでモデルを切り替えられ(openai/gpt-5.5、anthropic/claude-sonnet-4-5 など)、プロバイダー SDK をインストールする必要はありません。ゲートウェイは環境から AI_GATEWAY_API_KEY を読み取り、Vercel にデプロイする際は自動的に接続されます。Vercel 上ではデプロイメントの OIDC トークンで認証することもできます。
import { defineConfig } from "blume";
import { gateway } from "blume/ai";
export default defineConfig({
ai: {
assistant: {
enabled: true,
provider: gateway({ model: "anthropic/claude-sonnet-4-5" }),
},
},
});
provider を未設定のままにすると、gateway({ model: "openai/gpt-5.5" }) と同じになります。
OpenRouter
OpenRouter 上の任意のモデルを、専用の AI SDK プロバイダーを通じて使用できます。
import { defineConfig } from "blume";
import { openrouter } from "blume/ai";
export default defineConfig({
ai: {
assistant: {
enabled: true,
provider: openrouter({
model: "anthropic/claude-sonnet-4-5",
reasoning: "none",
}),
},
},
});
LLMGateway
LLMGateway 上の任意のモデルを、その OpenAI 互換エンドポイントを通じて使用できます。
import { defineConfig } from "blume";
import { llmgateway } from "blume/ai";
export default defineConfig({
ai: {
assistant: {
enabled: true,
provider: llmgateway({ model: "openai/gpt-5.5" }),
},
},
});
LLMGateway をセルフホストしている場合は、baseUrl でプリセットのエンドポイント(https://api.llmgateway.io/v1)を上書きできます。
Inkeep
Inkeep は Inkeep ダッシュボードでインデックス化したコンテンツから回答し、独自の検索を実行するため、Blume はこれにグラウンディングを適用しません。このサイトのページのスナップショットは挿入されず、検索サイズのオプションも適用されません。また推論の制御も持たないため、このアダプターは reasoning を受け付けません。
import { defineConfig } from "blume";
import { inkeep } from "blume/ai";
export default defineConfig({
ai: {
assistant: {
enabled: true,
provider: inkeep({ model: "inkeep-qa-expert" }),
},
},
});
baseUrl でプリセットのエンドポイント(https://api.inkeep.com/v1)を上書きできます。
OpenAI 互換エンドポイント
OpenAI API に対応したエンドポイントであれば openaiCompatible() を通じて動作します。baseUrl、エンドポイントが提供する model、キーを保持する環境変数を指定してください。汎用エンドポイントにはこれらのプリセットがないため、3つとも必須です。name は AI SDK が報告するプロバイダー名で、デフォルトは openai-compatible です。
import { defineConfig } from "blume";
import { openaiCompatible } from "blume/ai";
export default defineConfig({
ai: {
assistant: {
enabled: true,
provider: openaiCompatible({
baseUrl: "https://my-gateway.example.com/v1",
apiKeyEnv: "MY_GATEWAY_API_KEY",
model: "gpt-4o",
name: "my-gateway",
}),
},
},
});
すべてのアダプターで使えるオプション
apiKeyEnv を指定すると、アダプターはデフォルトとは別の環境変数を参照します。gateway({ apiKeyEnv: "DOCS_GATEWAY_KEY" }) は AI_GATEWAY_API_KEY の代わりにその変数を読み取り、blume dev/build 時のシークレット不足の警告もその変数をチェックします。キーが設定されるまで、デプロイされたルートは変数名を示すメッセージとともに 503 を返します。ルートはリクエストボディを 64 KB までしか読み取らず、それより大きいものには 413 を返します。
headers は、すべての呼び出しで静的なリクエストヘッダーを送信します。たとえば共有バックエンドに対して呼び出し元を識別するヘッダーを付与すれば、そのバックエンド側の可観測性やレート制限がドキュメントからのトラフィックを他のトラフィックと区別できます。
provider: openaiCompatible({
baseUrl: "https://llm.internal.example.com/v1",
apiKeyEnv: "INTERNAL_LLM_API_KEY",
model: "gpt-4o",
headers: { "X-Caller-Id": "docs" },
}),
値は生成されたルートにそのまま書き込まれるため、シークレットは headers ではなく apiKeyEnv に保持してください。API キーの Authorization ヘッダーが最初に適用されるため、カスタムヘッダーがそれを置き換えることはできません。
providerOptions は、それ以外のあらゆる設定を AI SDK の providerOptions へ、SDK 自身の形式(プロバイダー、次にオプションをキーとする形式)のまま渡します。そのため、新しいモデルの制御項目のために Blume 側で専用のフィールドを用意する必要はありません。
provider: gateway({
model: "openai/gpt-5.5",
providerOptions: { openai: { textVerbosity: "low" } },
}),
Blume がマッピングするのは Blume 自身が定義するオプション(model、reasoning、apiKeyEnv、headers)のみで、providerOptions はそのまま転送します。そのため providerOptions はルートにインライン化される JSON である必要があり、基盤となるプロバイダーが想定するキー(ゲートウェイ経由の OpenAI モデルなら openai、OpenRouter なら openrouter)を使用する必要があります。アシスタントを有効にすると、ページ内アイランドのために React も有効になります。詳細はカスタマイズを参照してください。
推論
推論モデルは回答する前に思考しますが、デフォルトでどれだけ思考するかはモデルによって異なります。ドキュメントに基づいた Q&A では取得した抜粋が回答を含んでいるため、その思考の大半は読者が待たされるだけのレイテンシになります。アダプターの reasoning オプションは、モデルがどれだけ推論するかを設定します。指定できる値は "none"、"minimal"、"low"、"medium"、"high"、"xhigh" です。
provider: gateway({ model: "openai/gpt-5.5", reasoning: "none" }),
各アダプターはこのレベルをバックエンド自身の推論強度の制御として送信します。そのため、このオプションは assistant ではなくアダプターに置かれています。
| アダプター | レベルの変換先 |
|---|---|
gateway() |
AI SDK の reasoning 呼び出しオプション。ゲートウェイがこれをモデル自身の設定(たとえば OpenAI の reasoning_effort)にマッピングします。 |
openrouter() |
モデルに設定される OpenRouter の reasoning.effort。このプロバイダーは AI SDK の呼び出しオプションを無視するため、レベルは OpenRouter が読み取る場所に配置されます。 |
llmgateway() |
AI SDK の呼び出しオプションを通じて送信される、リクエスト内の reasoning_effort。 |
openaiCompatible() |
リクエスト内の reasoning_effort。そのため、エンドポイントがそのパラメーターを受け付ける必要があります。 |
inkeep() |
利用できません。Inkeep は推論の制御を持たない独自の QA パイプラインを実行するため、このアダプターには reasoning オプションがなく、設定すると設定エラーになります。 |
選択したレベルをモデルがサポートしている必要があります。OpenAI はモデルが提供していないレベルを拒否するため("none" と "xhigh" は一部のモデルにのみ存在します)、設定する前にモデルのドキュメントを確認してください。未設定のままにすると、モデルのデフォルトが維持されます。検索サイズと同様に、これは網羅性と最初のトークンまでの時間のトレードオフであり、いずれの場合も回答はドキュメントに基づいたままです。
アナリティクス
アナリティクスプロバイダーを設定している場合、アシスタントはページフィードバックウィジェットと同じ track() を通じて利用状況を報告するため、質問がページビューと並んで記録されます。
| イベント | タイミング | プロパティ |
|---|---|---|
ask |
質問が送信されたとき | path、questionChars |
ask_answer |
回答のストリーミングが完了したとき | path、questionChars、ms、chars |
ask_error |
リクエストが失敗、中断、または空のまま返されたとき | path、questionChars、ms、status |
path は読者が質問したページ(配信されるパス名なので、フィードバックウィジェットや base の下でのページビューと一致します)、questionChars は質問の長さ、ms は質問の送信から最後のチャンクまでの時間、chars は回答の長さです。status は HTTP ステータスで、レスポンスがまったく届かなかった場合(オフライン、DNS、CORS)は 0、レスポンス自体は正常だったものの回答の途中でストリームが途切れた場合(バックエンドがすでにヘッダーを送信済みのため、プロバイダーや認証情報のエラーはこの形で表面化します)や、何も配信されなかった場合は 200 になります。回答の途中で会話をクリアした場合は、いずれの結果も報告されません。
質問のテキストがプロバイダーに届くことはありません。これは自由形式の読者入力(貼り付けられたキー、エラーログ、氏名など)であり、ほとんどのプロバイダーの利用規約や値ごとのサイズ制限に抵触するためです。テキストは blume:track の DOM イベント上でのみ、detail.props の question として送信されるため、ご自身で記述したリスナーで適切と判断した先へ転送できます。blume/hooks の useAssistant を使って構築したカスタムチャット UI も同じイベントを報告します。プロバイダーを設定していない場合、組み込みのプロバイダー呼び出しは何も行いませんが、blume:track イベントは引き続き発火するため、これを購読するカスタム連携は各イベントを受け取れます。
レート制限
POST /api/ask エンドポイントは認証されていません。ページ内アシスタントから呼び出せる必要があるため、これは必然です。Blume は各リクエストを検証し(不正なボディを拒否し、メッセージ数を 1〜40 に制限し、user/assistant ロールのみを受け付けることで、呼び出し元が独自のシステムプロンプトを注入してこのルートを汎用 LLM プロキシとして転用できないようにします)、1回の呼び出しでモデルに対して消費できる量を制限しますが、同じエンドポイントが繰り返し呼び出されるのを防ぐことはできません。コストの悪用が懸念される場合は、ルートをレート制限の背後に置いてください。ホスティング事業者(Vercel など)のエッジレート制限、ミドルウェア、あるいはモデルプロバイダーのキーごとの利用上限などが利用できます。
このエンドポイントは、サイトの機械可読な情報とともにエージェント可読性マニフェストで公開されます。