AI活用

OpenAI File Search APIの使い方|PDFを検索して回答する最小構成

OpenAI File SearchをJavaScriptで使い、PDFの登録、Vector Storeの処理完了待ち、Responses APIでの検索、file_citation表示、更新・削除・精度確認まで解説します。

この記事の目次
  1. 結論:1PDF・1質問・1引用で最小構成を検証する
  2. File Searchと自作RAGの違い
  3. File Searchを選びやすい条件
  4. 自作した方がよい条件
  5. ファイルとVector Storeを準備する
  6. 検証用PDFを決める
  7. JavaScriptプロジェクトを作る
  8. File APIへPDFを登録する
  9. Vector Storeを作ってファイルを追加する
  10. 処理完了を待つ
  11. Responses APIからFile Searchを呼ぶ
  12. vector_store_idsを指定する
  13. 検索したかをoutputで確認する
  14. 引用元を画面へ表示する
  15. file_citation annotationを読む
  16. 利用者向けの出典表示を作る
  17. 1PDF・1質問の検証ログ
  18. 精度・保持・コストの注意点
  19. 古いファイルを混在させない
  20. 機密資料の投入可否を決める
  21. 費用は3つに分けて測る
  22. 不要な検証データを削除する
  23. よくあるエラー
  24. ノーコードの資料検索との使い分け
  25. まとめ:回答と引用を別々に検証する

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_citationindexfile_idfilename が含まれます。

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つに分けて測る

  1. モデルの入力・出力トークン
  2. File Search toolの利用
  3. 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、費用を段階的に増やすと、原因を見失わずに実用化できます。

スポンサーリンク