OpenAI File Searchは、PDFなどのファイルをVector Storeへ登録し、Responses APIの file_search toolから意味検索とキーワード検索を行う管理型の検索機能です。JavaScriptでは、ファイル作成、Vector Store作成、ファイル追加、処理完了確認、回答、引用annotation確認の順に実装します。
File SearchはOpenAIが実行するhosted tool(ホステッドツール:検索処理をAPI提供側が管理する機能)です。自分でPDFを分割し、Embeddingを作り、ベクトルデータベースを運用する最初の手間を減らせます。一方、保持条件、検索の細かな制御、ベンダー依存、費用を要件として確認する必要があります。
この記事では、1つのPDFに1つの質問を送り、回答に file_citation が含まれるところまでを検証します。Responses APIの基本が初めてなら、先にOpenAI Responses APIの使い方を確認してください。
情報確認日:2026年7月20日(日本時間)
結論:1PDF・1質問・1引用で最小構成を検証する
ローカルPDF
↓ Files APIへupload
OpenAI File
↓ Vector Storeへ追加
処理状態をpoll
↓ completedを確認
Responses API
↓ file_search + vector_store_ids
回答テキスト
↓ annotationsを読む
ファイル名・file_idを引用として表示
最小検証の合格条件
- 対象PDFだけを入れたVector Storeを作る
- ファイル状態が
completedになってから質問する - PDF内に答えがある質問と、答えがない質問を1つずつ試す
- 回答文だけでなく
file_citationを保存する - ファイル名、file ID、Vector Store ID、実行日時を記録する
- 不要になった検証用データを削除する
File Searchと自作RAGの違い
RAG(Retrieval-Augmented Generation:検索拡張生成)は、質問に関係する資料を検索し、その内容をモデルの回答へ使う構成です。File SearchはRAGに必要な検索処理の多くをOpenAI側へ任せる選択肢です。
| 項目 | OpenAI File Search | 自作RAG |
|---|---|---|
| ファイル解析・分割 | OpenAI側で管理 | 自分で方式を設計 |
| Embedding・索引 | OpenAI側で管理 | モデルとDBを選ぶ |
| 検索 | 意味検索とキーワード検索をhosted toolで実行 | 検索、filter、rerankを設計 |
| 実装量 | 比較的少ない | 多いが自由度が高い |
| データ配置 | OpenAIのFile・Vector Storeへ登録 | 自社環境や選定サービスへ配置可能 |
| 移行性 | OpenAI固有APIへ依存 | 設計次第で部品を交換しやすい |
Embedding(埋め込み)は、文章の意味を数値の並びに変換する処理です。filter(フィルター)は部署・日付などの属性で検索対象を絞ること、rerank(リランキング)は検索候補を別の基準で並べ直すことです。これらを自分で細かく調整したい場合は自作RAGが向きます。
File Searchを選びやすい条件
- 小さく検証し、管理画面や社内検索の試作を早く作りたい
- 回答生成もResponses APIへ統一したい
- 対応形式の文書を標準的な検索方法で扱える
- OpenAIへファイルを登録することが組織の方針上許可されている
自作した方がよい条件
- データを特定の地域・自社ネットワークから出せない
- 表、図、OCR、独自区切りなど前処理を厳密に制御したい
- 複数の生成モデルや検索基盤へ同じ索引を使いたい
- 検索ログ、ランキング、権限制御を自社要件で詳細に設計したい
自作RAGの全体像はRAGとは何かで確認できます。この記事ではFile Searchを使う最小構成に絞ります。
ファイルとVector Storeを準備する
検証用PDFを決める
最初は、内容と正解を自分で確認できる1〜3ページのPDFを使います。例として、次の1文を含む社内検証用の remote-work-policy.pdf を想定します。
在宅勤務の申請期限は、原則として実施日の2営業日前17時です。
この内容に対し「在宅勤務はいつまでに申請しますか」と質問します。検索精度の検証に機密文書や実在顧客データを使う必要はありません。ダミー文書でAPIの動作を確認してから、社内承認とデータ分類に進みます。
JavaScriptプロジェクトを作る
mkdir file-search-sample
cd file-search-sample
npm init -y
npm pkg set type=module
npm install openai
export OPENAI_API_KEY="your_api_key_here"
export OPENAI_MODEL="your_available_model_id"
PDFをプロジェクト直下へ置きます。APIキーをソースコード、Git、ブラウザへ含めないでください。
File APIへPDFを登録する
import fs from "node:fs";
import OpenAI from "openai";
const openai = new OpenAI();
const model = process.env.OPENAI_MODEL;
if (!model) {
throw new Error("OPENAI_MODEL is not set");
}
const uploadedFile = await openai.files.create({
file: fs.createReadStream("remote-work-policy.pdf"),
purpose: "assistants",
});
console.log({ fileId: uploadedFile.id });
公式File Searchガイドは、File APIへの登録で purpose: "assistants" を使う例を示しています。purpose名だけを見て旧Assistants API専用と判断せず、実装時の公式ガイドとSDK型を確認します。
Vector Storeを作ってファイルを追加する
const vectorStore = await openai.vectorStores.create({
name: "remote-work-policy-test",
});
await openai.vectorStores.files.create(vectorStore.id, {
file_id: uploadedFile.id,
});
console.log({ vectorStoreId: vectorStore.id });
Vector Store(ベクトルストア)は、検索対象のファイルと索引をまとめる単位です。環境、部署、公開範囲、更新サイクルが異なる文書を何でも1つへ入れると、古い規程や権限外の資料が検索される原因になります。
処理完了を待つ
const wait = (milliseconds) =>
new Promise((resolve) => setTimeout(resolve, milliseconds));
async function waitUntilReady(vectorStoreId, fileId) {
for (let attempt = 1; attempt <= 60; attempt += 1) {
const page = await openai.vectorStores.files.list({
vector_store_id: vectorStoreId,
});
const item = page.data.find((file) => file.id === fileId);
console.log({
attempt,
status: item?.status ?? "not_found",
});
if (item?.status === "completed") return item;
if (item?.status === "failed") {
throw new Error(`Vector Store processing failed: ${fileId}`);
}
await wait(2000);
}
throw new Error("Timed out waiting for Vector Store");
}
await waitUntilReady(vectorStore.id, uploadedFile.id);
ファイル追加のAPIが成功しても、検索用の処理が完了したとは限りません。completed を確認せずすぐ質問すると、検索結果が空になり、モデルや質問の問題と誤認します。実運用では固定回数のpollingに加えて、全体のタイムアウトと失敗通知を設けます。
Responses APIからFile Searchを呼ぶ
vector_store_idsを指定する
const response = await openai.responses.create({
model,
input:
"在宅勤務はいつまでに申請しますか。" +
"資料にない場合は、推測せず「資料に記載なし」と答えてください。",
tools: [
{
type: "file_search",
vector_store_ids: [vectorStore.id],
max_num_results: 5,
},
],
include: ["file_search_call.results"],
});
console.log(response.output_text);
max_num_results は検索結果の最大数です。少なくすると遅延やトークン量を抑えられる場合がありますが、必要な断片を落とす可能性があります。固定値を正解とせず、評価質問で2、5、10などを比較します。
include: ["file_search_call.results"] を指定すると、通常は省略される検索結果もレスポンスへ含められます。開発時の原因調査には便利ですが、検索断片に機密情報が含まれるため、ログへ無制限に保存しないでください。
検索したかをoutputで確認する
for (const item of response.output) {
if (item.type === "file_search_call") {
console.log({
id: item.id,
status: item.status,
queries: item.queries,
resultCount: item.results?.length ?? null,
});
}
}
回答がそれらしくても、File Searchを使ったとは限りません。file_search_call の存在、状態、query、検索結果を確認します。PDFにしかない検証用の固有情報を質問すると、モデルの一般知識と区別しやすくなります。
引用元を画面へ表示する
file_citation annotationを読む
annotation(注釈)は、出力テキストへ付随する引用情報です。File Searchの回答では、file_citation に index、file_id、filename が含まれます。
const citations = new Map();
for (const item of response.output) {
if (item.type !== "message") continue;
for (const content of item.content) {
if (content.type !== "output_text") continue;
for (const annotation of content.annotations ?? []) {
if (annotation.type !== "file_citation") continue;
citations.set(annotation.file_id, {
fileId: annotation.file_id,
filename: annotation.filename,
index: annotation.index,
});
}
}
}
console.log([...citations.values()]);
同じファイルが複数箇所で引用されることがあるため、画面下の資料一覧では file_id で重複を除けます。回答中のどの位置に対応するか示す場合は、SDKの型とannotationのindex仕様を確認し、日本語の文字位置を自己流で計算しないようにします。
利用者向けの出典表示を作る
回答
在宅勤務は、原則として実施日の2営業日前17時までに申請します。
参照した資料
・remote-work-policy.pdf
確認用ログ
・response_id: resp_...
・vector_store_id: vs_...
・file_id: file_...
file IDを一般利用者へ見せる必要はありません。画面では資料名、版、更新日、社内文書ページへのリンクを表示し、運用ログではresponse ID、Vector Store ID、file IDを紐付けます。ファイル名だけでは同名別版を区別できないためです。
1PDF・1質問の検証ログ
| 検証項目 | 期待値 | 記録する実測値 |
|---|---|---|
| ファイル | remote-work-policy.pdf 1件 |
file ID、SHA-256、登録日時 |
| 処理 | completed |
完了までの秒数、失敗理由 |
| 正解あり質問 | 「2営業日前17時」を回答 | 回答、検索query、result数 |
| 引用 | 対象PDFの file_citation |
filename、file ID、index |
| 正解なし質問 | 「資料に記載なし」 | 推測の有無、誤った引用の有無 |
| 再現性 | 同じ版で必要条件を満たす | 3〜5回の正答・引用率 |
「回答が自然だった」だけでは合格にしません。正解語、引用元、検索実行、資料にない質問への拒否を別々に評価します。temperatureなど他の条件を変えず、検索件数や文書だけを変えると原因を追いやすくなります。
精度・保持・コストの注意点
古いファイルを混在させない
2025年版と2026年版の規程を同じVector Storeへ無条件に入れると、両方の断片が検索される可能性があります。次のいずれかで版を管理します。
- 最新版だけを検索対象へ入れ、旧版は別の保管場所へ移す
- 文書へ年度・部署・公開範囲の属性を付け、metadata filterで絞る
- 画面の質問に対象年度を含め、出典にも版と更新日を表示する
- 更新処理を「新規登録 → 検証 → 切替 → 旧版削除」の順にする
機密資料の投入可否を決める
OpenAI APIへファイルを送る前に、公式のデータ利用方針、保持条件、利用するendpoint、契約、地域要件、組織の規程を確認してください。個人情報や営業秘密が含まれる場合は、担当者の判断だけでアップロードしません。
利用者ごとに閲覧権限が異なる場合、プロンプトで「他部署の情報を出さない」と書くだけでは不十分です。検索前に認可し、アクセス可能なVector Storeやmetadataだけをサーバー側で選びます。
費用は3つに分けて測る
- モデルの入力・出力トークン
- File Search toolの利用
- Vector Storeの保存
料金や無料枠は変わるため、固定金額はOpenAIの公式Pricingで公開前・本番導入前に確認します。文書数、保存容量、質問数、1質問あたりの検索結果数を分けて見積もり、使っていない検証用Vector Storeを残さないようにします。
不要な検証データを削除する
await openai.vectorStores.delete(vectorStore.id);
await openai.files.delete(uploadedFile.id);
Vector Storeからの削除とFile APIの元ファイル削除は、別の管理単位として確認します。実行前に、そのファイルを他のVector Storeや処理が参照していないか調べてください。本番では削除要求、保持期限、監査ログを運用へ組み込みます。
よくあるエラー
| 症状 | 確認点 |
|---|---|
| 回答にPDF内容が出ない | ファイル状態、Vector Store ID、質問内の固有語、検索call |
| 引用がない | message内のannotation、資料に根拠があるか |
| 古い規程を回答する | 旧版の混在、属性、filter、切替手順 |
| 処理が終わらない | 対応形式、文字コード、ファイル状態、タイムアウト |
| 資料にない内容を断定する | 拒否指示、正解なし評価、検索結果と回答の照合 |
PDFが画像だけで構成されている、複雑な段組みや表がある、文字抽出が崩れている場合は、登録成功と検索品質を分けて確認します。必要ならOCRや文書整形を前処理し、元PDFと抽出テキストの対応を残します。
ノーコードの資料検索との使い分け
個人が資料を読み、質問して要点を確認するだけなら、アプリを開発せず資料検索サービスを使う方が早い場合があります。たとえばNotebookLMの使い方は、画面上でソースを登録して調べたい読者向けです。
File Search APIは、自社画面への組み込み、利用者認証、業務フローとの連携、ログ、独自UIが必要な場合に向きます。「APIを使えるから」ではなく、誰がどの資料へ質問し、回答後に何を行うかで選びます。
まとめ:回答と引用を別々に検証する
OpenAI File Searchの最小構成は、PDFをFiles APIへ登録し、Vector Storeへ追加し、completed を待ち、Responses APIで file_search を指定する流れです。回答文は output_text、検索実行は file_search_call、引用はmessage内の file_citation で確認します。
最初は1PDF・1質問で、正解、引用、正解なし質問、削除までを記録してください。その結果を基準にしてから、ファイル数、権限、版管理、metadata filter、費用を段階的に増やすと、原因を見失わずに実用化できます。