文字起こしとシーン検索
質問の前に用意するデータと、質問後に実行する検索を順番に追います。具体的な質問の例から知りたい場合は、AIが回答を作るまでから読んでください。
1. 時刻付きの文章を取得する
| 入力元 | 現在の処理 | 進められない場合 |
|---|---|---|
| アップロード動画 | 保存ファイルを取得し、FFmpegでモノラル音声を抽出。選択されたWhisperで文字起こしし、区間をSRTへ変換 | ファイル・音声がない、文字起こしが失敗する、結果が空 |
| YouTube | SearchAPIで手動字幕、次に自動字幕を取得し、SRTへ変換 | SearchAPIの設定がない、利用できる字幕がない |
YouTubeの経路には、字幕がないときに動画をダウンロードして音声認識するフォールバックはありません。アップロードの経路は、WHISPER_BACKEND に応じてOpenAI Whisperかローカルの互換エンドポイントを使います。音声が24 MiBを超える場合は600秒単位に分割して処理し、結果の時刻に各区間の開始位置を加算します。これらは実装上の設定であり、提供元の最新の利用制限を示す値ではありません。
開発時に ENABLE_HEAVY_PIPELINE を無効にすると、1秒の仮の文字起こしを返します。状態遷移の確認には使えますが、音声認識や検索の精度は確認できません。このパイプラインでは、映像フレームの解析やスライドのOCRは行いません。
2. 字幕を場面にまとめる
SRTの字幕区間には開始時刻・終了時刻・文章があります。WhisperやYouTubeが細かな区間を返した場合、VideoQは動画内の位置を保ちながら、検索用の文章のまとまりにします。
分割には多次元の大津法の基準を使います。埋め込みの空間で、前後のグループをよく分ける境界を探す方法です。長い区間を再帰的に分け、既定の512トークン以内に収めます。短い区間は途中で話題が変わっても一つの場面に残ることがあります。LLMがすべての話題を判定する処理ではなく、文章の長さに上限を設ける分割処理です。
一つの字幕区間だけで上限を超える場合はトークンを分け、元の時間幅をトークン数の比率で配分します。このとき途中の時刻は推定値です。分割中に例外が起きた場合は元のSRTをそのまま使うため、その経路では512トークン以内になる保証はありません。
3. 検索用データを保存する
索引作成ジョブが場面のSRTを読み、各場面の文章を埋め込みに変換して scene_embeddings へ保存します。埋め込みのリクエストは64件ずつまとめます。各行には文章・ベクトルと、所有者、動画ID・タイトル、場面番号、開始・終了時刻が入ります。
索引の作成時には、その動画の既存ベクトルを置き換えます。APIの検索とworkerの索引作成で、埋め込みモデルと次元を一致させる必要があります。現行DBの列は1536次元です。モデルの設定を変えただけではDBの列や保存済みベクトルは変わりません。埋め込みモデルを変えた場合は、そのモデルで検索する資料の索引を作り直します。
索引作成が終わると、Q&Aが場面を検索できます。PLOGは後続の別ジョブで生成するため、検索の準備完了と学習モードの準備完了は別です。動画の状態も参照してください。
4. モデルが根拠を要求したら検索する
search_scenes は自然言語の検索文と、任意でアクセス確認済みの講座内動画IDを受け取ります。APIは次の順で処理します。
- 許可された範囲外の動画IDが指定されていれば拒否します。
- 設定された埋め込みプロバイダーで検索文をベクトルにします。
- 所有者と動画IDで範囲を絞り、PGVectorで類似検索します。
- 既定で
RETRIEVER_K = 20、最大20場面の文章・動画タイトルとID・時刻を返します。
例えば「直交するベクトル」という検索文で、質問と同じ単語がなくても「角度が90度」の説明を探せる可能性があります。見つかるかどうかは、文字起こしと埋め込みの内容に依存します。
現在のアプリには、最低類似度による足切りや、別のモデルによる再ランキングはありません。検索結果の数値スコアは、ツールの結果を作るときに取り除きます。「20場面返った」は候補が取得できたという意味で、20場面すべてが質問の根拠になるという意味ではありません。索引にデータがあれば、関連が弱い場面でも0件にならず返ることがあります。
モデルは検索文を変え、1回答につき最大3回まで検索できます。プログラムは動画IDと開始・終了時刻で重複を除き、その回答内で安定した [1]、[2] などの番号を付けます。この番号は全動画共通の場面IDでも、回答の信頼度でもありません。
5. 根拠を回答モデルへ渡す
シーン検索の結果には、引用番号・動画タイトル・時刻・字幕の文章が入ります。講座の登録情報は別のツールで取得します。回答モデルは今回のツール実行で得た結果を読み、設定された指示に従って回答を作ります。
この経路ではWeb検索を行いません。使えるツールは講座の登録情報と索引済みの場面を取得するものです。長い動画の要約も、取得した場面に制約されます。全場面を自動で巡回して網羅性を確認する処理はありません。
回答の問題をどの段階で調べるか
| 症状 | 最初に見るもの |
|---|---|
| スライドの数式に触れない | 音声や字幕で数式が説明されているか |
| 人名や専門用語が違う | 回答用プロンプトを変える前に、保存された文字起こし |
| 引用の再生時刻がずれる | 元字幕の時刻、長い字幕区間を比率で分割していないか |
| 別の授業の話になる | 講座の動画構成、モデルが指定した動画ID、検索文 |
| 検索した文章の関連が弱い | 字幕に必要な説明があるか、埋め込みが一致しているか。現行処理には最低スコアでの除外がない |
動画が completed でも学習できない | 検索索引とは別に、PLOGの状態とグラフの利用可能性 |
実装の参照先
| ファイル | 役割 |
|---|---|
| transcription.py | 音声・字幕の取得と開発用の仮データ |
| scene_otsu/ | 分割、トークン数の計算、元SRTへのフォールバック |
| vector_index.py | 場面の埋め込みとメタデータの保存 |
| vector-repository.ts | 検索範囲と取得件数 |
| rag.ts | ツール実行、場面の重複除去、引用番号 |
次に読む: Q&Aのプロンプトと回答評価。