---
title: アシスタント
description: >-
  ドキュメントに基づいたページ内アシスタント — 推奨質問、カスタム指示、検索サイズの調整、Vercel AI Gateway から任意の OpenAI 互換エンドポイントまでのプロバイダーアダプター、そして必要となるサーバー出力について解説します。
---

読者の質問にページ内チャットパネルで回答するアシスタントを追加します。ストリーミング対応のサーバーエンドポイントと [AI SDK](https://ai-sdk.dev) によって動作します。これはオプトイン方式であり、有効にするまで静的ドキュメントは完全に静的なままです。

```ts blume.config.ts lineNumbers
ai: {
  assistant: {
    enabled: true,
  },
}
```

ほかに何も設定しなければ、回答は `openai/gpt-5.5` から [Vercel AI Gateway](#adapters) を経由してストリーミングされます。別のモデルやプロバイダーを使用するには、[アダプター](#adapters)で選択してください。

## 推奨質問 [#suggested-questions]

空の状態にいくつかの開始プロンプトを設定できます。それぞれはクリック可能な候補として表示され、クリックするとその質問が送信されます。ラベルの横にはオプションで [Lucide アイコン](/docs/content/components#icon) を表示できます。

```ts blume.config.ts lineNumbers
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` を未設定（または空）のままにすると、パネルはシンプルな入力欄だけで開きます。

## カスタム指示 [#custom-instructions]

`instructions` で独自のシステムプロンプトのテキストを追加できます。アイデンティティ、言語、トーンなど、アシスタントに意識してほしい内容を自由に指定できます。

```ts blume.config.ts lineNumbers
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.",
  },
}
```

指定したテキストは組み込みの指示を置き換えるのではなく、**その後に追加**されます。組み込み部分には[グラウンディング](#grounding)の取り決め（取得したページのみに基づいて回答し、それらを Markdown リンクとして引用する）が含まれており、チャットパネルの引用機能はこれに依存しているため、何を追加してもそのまま維持されます。

## グラウンディング [#grounding]

アシスタントは**ドキュメントに基づいて**動作します。質問ごとに最も関連性の高いページを取得し（オンページ検索を支えるものと同じ字句ベースの [Orama](/docs/configuration/search) インデックスを使用）、モデルのシステムプロンプトに挿入します。これにより、回答はモデル自身の知識ではなくコンテンツから生成されます。アシスタントには、取得したページのみに基づいて回答すること、扱われていない内容についてはその旨を伝えること、参照したページを引用することが指示されています。

読者が現在閲覧しているページが最初にコンテキストへ追加され、そのページの言語に検索範囲を絞り込むために使用されます。これにより、ドキュメント内のどこにいても関連性の高い回答が得られます。検索はビルド時に焼き込まれたスナップショットからリクエスト時に実行されるため、[検索](/docs/configuration/search)プロバイダーに関係なく動作し（`search: false` の場合でも）、設定は不要です。

グラウンディングは **[Inkeep](#inkeep)** を除くすべてのアダプターで有効です。Inkeep はダッシュボードでインデックス化したコンテンツに対して独自の検索を実行します。

## 検索サイズ [#retrieval-size]

質問がどれだけのドキュメントを伴うかは、読者が最初の単語を目にするまでの待ち時間を左右する最大の要因です。モデルは挿入されたすべての文字を読み込んでからトークンを出力するためです。ホスト型のフロンティアモデルではこれは体感できませんが、セルフホストのバックエンドでは支配的な要素になります。`retrieval` でこのサイズを調整します。

```ts blume.config.ts lineNumbers
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ページ多く引用できます。

デフォルト値はホスト型モデルに適しています。自前のハードウェアで提供していて、再現率よりも最初のトークンまでの時間が重要な場合は値を下げてください。いずれの場合も回答はドキュメントに基づいたままであり、アシスタントは扱われていない内容について推測で埋めるのではなく、その旨を伝えるよう指示されています。

## 外部エンドポイント [#external-endpoint]

すでに AI 用の API バックエンドをお持ちですか？パネルをそこに向けることで、ドキュメントのビルドを静的なまま維持できます。

```ts blume.config.ts lineNumbers
ai: {
  assistant: {
    enabled: true,
    endpoint: "https://api.example.com/v1/docs/ask",
  },
}
```

Blume は組み込みルートと同じ `POST` ボディを送信します。

```json
{
  "messages": [{ "role": "user", "content": "How do I deploy?" }],
  "page": { "path": "/deployment" }
}
```

ボディがプレーンな UTF-8 テキストストリームである成功レスポンスを返してください。エンドポイントが別のオリジンにある場合は、CORS でドキュメントのオリジンを許可してください。`OPTIONS` と `POST` を受け付け、`content-type` リクエストヘッダーを許可し、プリフライトとストリーミングレスポンスの両方で CORS ヘッダーを返します。`endpoint` を設定すると、Blume はチャット UI のみを生成し、サーバールート、グラウンディング用スナップショット、プロバイダーの依存関係、プロバイダーシークレットの警告は生成しません。検索、認証、レート制限、モデルへのアクセス、引用はすべてバックエンド側の責任となります。併せて設定したアダプターは無視されます。

## クロスオリジンの呼び出し元 [#cross-origin-callers]

生成されるエンドポイントは、同一オリジン上のページ内アシスタントに応答します。別のサイトからも呼び出せるようにするには（たとえば質問ボックスを備えたマーケティングページなど）、そのサイトのオリジンを `cors` に列挙します。

```ts blume.config.ts lineNumbers
ai: {
  assistant: {
    enabled: true,
    cors: ["https://www.example.com"],
  },
}
```

これによりルートはブラウザの `OPTIONS` プリフライトに応答し、すべてのレスポンスで列挙されたオリジンを指定します。ストリーミングされる回答もエラーステータスも同様であるため、呼び出し元はボディが拒否されたのかプロバイダー側の障害なのかを判別できます。列挙されていないオリジンにはヘッダーが付与されず、ブラウザの同一オリジンポリシーの対象のままとなります。各エントリはそのオリジンに正規化されるため、`https://www.example.com/docs/` と `https://www.example.com` は同じ意味になります。任意のページからルートを呼び出せるようにするには、オリジンの代わりに `"*"` を列挙してください。

呼び出し元は[外部エンドポイント](#external-endpoint)の取り決めで説明されているものと同じ `POST` ボディを送信し、同じテキストストリームを読み取ります。`content-type: application/json` ヘッダーを付けて JSON として送信してください。

```ts
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 はそのバックエンドの責任となり、両方を設定することは設定エラーになります。いずれの場合もエンドポイントは認証されないままなので、[レート制限](#rate-limiting)に関する指針はクロスオリジンのトラフィックにも当てはまります。

## サーバー出力が必要です [#server-output-required]

Blume の組み込みアシスタントバックエンドはサーバールート（`POST /api/ask`）であるため、静的ビルドでは動作しません。`blume/deploy` からホストアダプターを指定して、サーバー出力に切り替えてください。

```ts blume.config.ts lineNumbers
import { vercel } from "blume/deploy";

export default defineConfig({
  deployment: vercel(),
});
```

アシスタントが有効で外部 `endpoint` が設定されていない静的ビルドは、ホストアダプターを設定するよう促すメッセージとともに即座に失敗します。アダプターについては [デプロイ](/docs/deployment) を参照してください。

## アダプター [#adapters]

`provider` は回答するバックエンドを選択します。その値は**アダプター**です。アダプターは `blume/ai` からエクスポートされる小さな関数で、そのバックエンド固有のオプションを受け取り、Blume が生成されたルートに書き込むプレーンなディスクリプターを返します。各アダプターはモデル、キーを読み取る環境変数、[推論](#reasoning)のマッピング方法、必要なプロバイダー SDK をそれぞれ独自に持つため、バックエンド間で共通のフィールドを揃える必要はありません。

```ts blume.config.ts lineNumbers
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)（デフォルト） | Vercel AI Gateway 経由の `provider/model` 形式の文字列 | `AI_GATEWAY_API_KEY` | 不要 — Blume に同梱 |
| [`openrouter()`](#openrouter) | 任意の [OpenRouter](https://openrouter.ai) モデル | `OPENROUTER_API_KEY` | `@openrouter/ai-sdk-provider` |
| [`llmgateway()`](#llmgateway) | 任意の [LLMGateway](https://llmgateway.io) モデル | `LLMGATEWAY_API_KEY` | `@ai-sdk/openai-compatible` |
| [`inkeep()`](#inkeep) | [Inkeep](https://inkeep.com) の QA モデル | `INKEEP_API_KEY` | `@ai-sdk/openai-compatible` |
| [`openaiCompatible()`](#openai-compatible-endpoints) | エンドポイントが提供するもの | `apiKeyEnv` で指定 | `@ai-sdk/openai-compatible` |

これらの SDK はオプションのピア依存関係なので、アダプターに必要なものをプロジェクトに追加してください（例: `npm install @openrouter/ai-sdk-provider`）。不足している場合、`blume build` は Vite の実行前に停止し、パッケージ名とそれをインストールするコマンドを示します。[`blume doctor`](/docs/cli/doctor) でも報告されます。

アダプターが返すディスクリプターは、種類、オプション、読み取る環境変数、必要な SDK からなるプレーンなデータです。そのため、生成されたルート（および[イジェクト](/docs/configuration/customization#eject)したルート）はこれをリテラルとしてインライン化し、プロバイダー SDK を名前でインポートします。リクエスト時に `blume.config.ts` を読み取るものはなく、シークレットがルートに書き込まれることもありません。アダプターはキーを保持する環境変数の**名前**を受け取り、ルートは Astro の [`getSecret()`](https://docs.astro.build/en/guides/environment-variables/#retrieving-secrets-programmatically) を通じてその値を読み取るため、各デプロイアダプターがそれぞれの方法でキーを提供します。Node、Vercel、Netlify では環境変数、Cloudflare では Worker の[バインディング](https://docs.astro.build/en/guides/integrations-guide/cloudflare/#environment-variables-and-secrets)が使用されます。

### Vercel AI Gateway [#vercel-ai-gateway]

デフォルトのアダプターです。`model` は `provider/model` 形式の文字列なので、これを変更するだけでモデルを切り替えられ（`openai/gpt-5.5`、`anthropic/claude-sonnet-4-5` など）、プロバイダー SDK をインストールする必要はありません。ゲートウェイは環境から `AI_GATEWAY_API_KEY` を読み取り、Vercel にデプロイする際は自動的に接続されます。Vercel 上ではデプロイメントの OIDC トークンで認証することもできます。

```ts blume.config.ts lineNumbers
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]

[OpenRouter](https://openrouter.ai) 上の任意のモデルを、専用の AI SDK プロバイダーを通じて使用できます。

```ts blume.config.ts lineNumbers
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]

[LLMGateway](https://llmgateway.io) 上の任意のモデルを、その OpenAI 互換エンドポイントを通じて使用できます。

```ts blume.config.ts lineNumbers
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](https://inkeep.com) は Inkeep ダッシュボードでインデックス化したコンテンツから回答し、独自の検索を実行するため、Blume はこれに**グラウンディングを適用しません**。このサイトのページのスナップショットは挿入されず、[検索サイズ](#retrieval-size)のオプションも適用されません。また推論の制御も持たないため、このアダプターは `reasoning` を受け付けません。

```ts blume.config.ts lineNumbers
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-compatible-endpoints]

OpenAI API に対応したエンドポイントであれば `openaiCompatible()` を通じて動作します。`baseUrl`、エンドポイントが提供する `model`、キーを保持する環境変数を指定してください。汎用エンドポイントにはこれらのプリセットがないため、3つとも必須です。`name` は AI SDK が報告するプロバイダー名で、デフォルトは `openai-compatible` です。

```ts blume.config.ts lineNumbers
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",
      }),
    },
  },
});
```

### すべてのアダプターで使えるオプション [#options-every-adapter-takes]

**`apiKeyEnv`** を指定すると、アダプターはデフォルトとは別の環境変数を参照します。`gateway({ apiKeyEnv: "DOCS_GATEWAY_KEY" })` は `AI_GATEWAY_API_KEY` の代わりにその変数を読み取り、`blume dev`/`build` 時のシークレット不足の警告もその変数をチェックします。キーが設定されるまで、デプロイされたルートは変数名を示すメッセージとともに `503` を返します。ルートはリクエストボディを 64 KB までしか読み取らず、それより大きいものには `413` を返します。

**`headers`** は、すべての呼び出しで静的なリクエストヘッダーを送信します。たとえば共有バックエンドに対して呼び出し元を識別するヘッダーを付与すれば、そのバックエンド側の可観測性やレート制限がドキュメントからのトラフィックを他のトラフィックと区別できます。

```ts blume.config.ts lineNumbers
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`](https://ai-sdk.dev/docs/foundations/prompts#provider-options) へ、SDK 自身の形式（プロバイダー、次にオプションをキーとする形式）のまま渡します。そのため、新しいモデルの制御項目のために Blume 側で専用のフィールドを用意する必要はありません。

```ts blume.config.ts lineNumbers
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 も有効になります。詳細は[カスタマイズ](/docs/configuration/customization#interactive-islands)を参照してください。

## 推論 [#reasoning]

推論モデルは回答する前に思考しますが、デフォルトでどれだけ思考するかはモデルによって異なります。ドキュメントに基づいた Q&A では取得した抜粋が回答を含んでいるため、その思考の大半は読者が待たされるだけのレイテンシになります。アダプターの `reasoning` オプションは、モデルがどれだけ推論するかを設定します。指定できる値は `"none"`、`"minimal"`、`"low"`、`"medium"`、`"high"`、`"xhigh"` です。

```ts blume.config.ts lineNumbers
provider: gateway({ model: "openai/gpt-5.5", reasoning: "none" }),
```

各アダプターはこのレベルをバックエンド自身の推論強度の制御として送信します。そのため、このオプションは `assistant` ではなくアダプターに置かれています。

| アダプター | レベルの変換先 |
| --- | --- |
| `gateway()` | AI SDK の [`reasoning`](https://ai-sdk.dev/docs/ai-sdk-core/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"` は一部のモデルにのみ存在します）、設定する前にモデルのドキュメントを確認してください。未設定のままにすると、モデルのデフォルトが維持されます。[検索サイズ](#retrieval-size)と同様に、これは網羅性と最初のトークンまでの時間のトレードオフであり、いずれの場合も回答はドキュメントに基づいたままです。

## アナリティクス [#analytics]

[アナリティクスプロバイダー](/docs/configuration/analytics)を設定している場合、アシスタントはページフィードバックウィジェットと同じ `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` イベントは引き続き発火するため、これを購読するカスタム連携は各イベントを受け取れます。

## レート制限 [#rate-limiting]

`POST /api/ask` エンドポイントは**認証されていません**。ページ内アシスタントから呼び出せる必要があるため、これは必然です。Blume は各リクエストを検証し（不正なボディを拒否し、メッセージ数を 1〜40 に制限し、`user`/`assistant` ロールのみを受け付けることで、呼び出し元が独自のシステムプロンプトを注入してこのルートを汎用 LLM プロキシとして転用できないようにします）、1回の呼び出しでモデルに対して消費できる量を制限しますが、同じエンドポイントが繰り返し呼び出されるのを防ぐことはできません。コストの悪用が懸念される場合は、ルートをレート制限の背後に置いてください。ホスティング事業者（Vercel など）のエッジレート制限、ミドルウェア、あるいはモデルプロバイダーのキーごとの利用上限などが利用できます。

このエンドポイントは、サイトの機械可読な情報とともに[エージェント可読性マニフェスト](/docs/discoverability/agent-discovery#agent-readability)で公開されます。
