検索/アップロードした文書を読み込み、AIエージェントが検索できる仕組みを作る

アップロードした文書を読み込み、AIエージェントが検索できる仕組みを作る

PDFなどのファイルを取り込み、AIが検索できるサービスを作る時に使う。ファイルを形式に合ったパーサーへ回し、見出しと表を保ち、チャンクを決まった形で保存して、上限の中で検索し、出どころつきの根拠を返す。

このAIルールを使うには
## ブランチ名の付け方を決め、API・ワーカー・共通のコードを分ける コードを変更する前に責任範囲を可視化し、それぞれの動作をテストと保守を担えるレイヤーに置きます。 ### 追跡可能なブランチを使う - ブランチ名は `type/user/description pattern` とします。 - `type` は小文字にし、`feat`、`fix`、`refactor`、`chore`、`docs`、`test`、`perf`、`ci`、`build`、`revert` など、認められた変更分類を選びます。 - `user` には人間の作業責任者を使い、一般的なエージェント名やツール名で決して代用しません。 - `description` は短い小文字のケバブケースにします。 ### 実行時の責務を分ける - 同期API処理、非同期の文書処理、共有モデルとサービス、デプロイ関連の処理を別々のパッケージに保ちます。 - エンドポイントのオーケストレーション、ビジネスルール、永続化、解析、検索、汎用ユーティリティを責任範囲が明示されたモジュールに配置し、独立して配布されるSDKは別製品として扱います。 - 動作が複数のパッケージにまたがる場合は、最初に共有契約を定義し、それぞれの担当実装をテストします。 ## アップロードされたファイルを、形式に合ったパーサーへ回す 段階的なパイプラインを使い、すべての文書がアップロードから引用可能な検索根拠まで、見える経路をたどるようにします。 1. アップロードを受け付け、ジョブを作成し、ソースオブジェクトを永続化して、非同期処理をキューに入れます。 2. 文書を一度だけプロファイリングし、ファイル形式と構造上の根拠に基づいてパーサーへ振り分けます。 3. パーサーの結果を単一の型付き表形式契約に変換します。 4. 各行を型付きチャンクレコードに変換します。 5. 検索を始める前に、文書、セクション、チャンク、グラフの状態を反映します。 ### パーサーの振り分けを明示する - すべての形式に対して単一の型付きパーサー入口を使い、タスク固有の成果物パスを返します。 - PDF、DOCX、PPTX、スプレッドシート、Markdown、テキスト、画像、生の断片入力を形式別の戦略へ振り分け、旧式のOffice形式は最新のパーサーへ渡す前に変換します。 - PDFでは、自由な意味分類と、アトラス、スキャン文書、スライド、汎用など振り分け専用の分類を分けます。 - 大容量文書では入口で構造解析を一度だけ実行し、得られたシャード計画を後続工程へ渡します。 - 解析後は参照されていない画像を取り除き、決定論的な画像圧縮を適用します。 ## 見出しの階層を見つけ、変換の時に表と画像を残す 利用できる最も強い根拠から構造を判断し、変換中も表、画像、見出しの関係を維持します。 ### 根拠の優先順で見出しを決める 1. 埋め込まれた目次が利用できる場合は、それを正本として使います。 2. 明示的な番号付けパターンと言語固有の章マーカーを照合します。 3. 未解決の見出し候補に限ってLLM階層モデルを使います。 4. レイアウト情報が豊富なPDFでは、フォントの幾何情報を上限付きの見出し階層へクラスタリングします。 ### 形式固有の動作を保つ - 段落、表、画像、目次領域を含むDOCX本文要素を、ソース上の順序どおりに処理します。 - 新しい見出しが親レベルへ戻った時に子孫を閉じる見出しスタックを維持します。 - 正確な `rowspan` と `colspan` を使って表をHTMLへ変換し、知覚ハッシュで画像の重複を排除します。 - 標準的なPDFは抽出とMarkdown解析へ振り分け、アトラス型または大容量の文書は専用経路へ振り分けます。 - 要約、階層認識、画像説明、OCR、粗い分類には、それぞれ別のモデル設定キーを割り当てます。 ## チャンク・目次・マニフェストを決まった形で保存する 永続化されたコーパスを正本として扱い、中間のDataFrameは一時的な実装詳細とみなします。 ### コーパスと文書の状態を保存する - 取り込んだ文書ごとのディレクトリと並べて、コーパス全体のグラフメタデータと検索ヒット統計を保持します。 - 順序付きチャンク、階層ナビゲーション、解析メタデータ、完全な見出しツリー、保管したソース資産、抽出した画像と表、診断結果を永続化します。 - 中核となるチャンク型は `text`、`image`、`table` の厳密に三種類だけを使います。 - 各チャンクに決定論的な `chunk_id`、型付きコンテンツ、階層パスを持たせ、長さ、要約、ページ番号、トークン数、キーワード、ファイルパス、チャンク間参照のメタデータを付けます。 ### 参照とナビゲーションを保つ - インラインの関係は `connect_to` エントリで表し、そこに `target`、`relation`、`ref`、文字オフセットの `position` を含めます。 - インライン資産には `embeds`、インラインではない関連付けには `related` を使います。 - `doc_nav.json` を、要約、チャンク数、子要素、画像または表のリソースを持つセクションツリーとして構築します。 - `manifest.json` を構築し、ソースメタデータ、統計、入れ子の `HIERARCHY` を含めます。 - 一時的なパーサー行を型付きチャンクのペイロードへ変換してから `chunks.json` に書き込みます。 ## データベース・検索用の索引・グラフのつながりを、そろえて反映する リレーショナルレコード、検索チャネル、グラフのエッジが同じチャンク集合を表すように、単一の一貫したリビジョンを反映します。 ### リレーショナルデータの責任範囲を保つ - 文書レベルで文書の識別情報、所有者、名前空間、状態、アクティブなリビジョン、ソースファイル名を保存します。 - 親へのリンク、パス、タイトル、レベル、要約、並び順を、リビジョンを認識するセクションツリーに保存します。 - チャンクごとに、コンテンツハッシュ、チャンク型、検索可能なテキスト、ソースパス、資産パス、メタデータ、並び順を保存します。 - グラフノードの範囲を文書内に限定し、対称的な関連性を表すグラフエッジは無向にします。 ### 固定した順序で反映する 1. 決定論的なコンテンツハッシュにより、文書をまたいでチャンクの重複を排除します。 2. チャンクパスから祖先セクションをトップダウン順に構築します。 3. 各チャンクに、コンテンツ、パス、生の語句という別々の検索チャネルを生成します。 4. 文書グラフのノードとキーワード重複エッジは、共有キーワードが3個以上でスコアが0.8以上の場合にのみ作成します。 コンテンツ検索にはトークン化したコンテンツと要約、パス検索にはトークン化したファイル名とセクションパス、部分文字列検索には生のコンテンツとパスを使います。 ## 従来の順位付け検索と、上限つきのエージェント検索の両方を用意する リクエスト整形、字句検索、ハイドレーション、グラフアクセス、ヒット統計、ワークフロー実行、ナビゲーション、根拠のレンダリングを責任範囲ごとに分けます。 ### 両方の検索モードを維持する - 従来モードでは、パス、コンテンツ、語句の各チャネルを逆数順位融合で組み合わせます。 - チャネルの既定の重みは `path=1.0`、`content=2.0`、`term=1.5` とし、`score = weight / (k + rank + 1)` および `k=60` を使います。 - エージェント型モードでは、プランナーが複雑なクエリを検索ステップのDAGへ分解します。検索の内部で回答合成ステップを計画してはいけません。 - DAG全体で単一のトークン予算を強制します。予算を使い切ったら安全に停止し、それまでに収集した最良の根拠を返します。 - 発見、制約付き幅優先のセクションナビゲーション、根拠のレンダリングを別々のフェーズとして実行します。 - `evidence_text` を主要な応答として提示し、非推奨の `answer_text` は空の互換フィールドとしてのみ残します。 ### 意図をもってハイドレーションと順位付けを行う - 開いていないセクションはタイトルと要約のアウトラインとして描画し、選択した末端に限って生の `text`、`image`、`table` チャンクをハイドレーションします。 - 接続された資産をネイティブに解決し、壊れやすい文字列置換プレースホルダーを使わずインラインに埋め込みます。 - エージェントの結果は `agent_score`、次に `discovery_score` の順で順位付けし、`agent_score=0` の行はフォールバックプールへ降格します。 - 組み立て前に、除外対象の文書、除外対象のセクション、許可されたチャンク型で絞り込みます。 - `document_id`、`chunk_id`、`source_file_name`、`section_path` を含む引用情報を付けます。 ## チャンクIDを毎回同じにし、成功した結果だけをキャッシュする 最適化によって検索の意味が変わらないよう、ショートカット、識別子、変更順序、テレメトリを明示します。 ### 結果を再現可能に保つ - 画像と表への参照を除外し、テキストコンテンツだけの安定したハッシュからチャンクIDを導出して、アップロードをまたぐ同一コンテンツの重複を排除します。 - 最初にDOMの変更を計画し、その後インデックスのずれを避けるため逆順で適用します。 - 文書内の同一画像を知覚ハッシュで重複排除し、キャッシュ済みのメタデータを再利用します。 - 異なる位置に繰り返し現れる画像には、決定論的で一意なIDを付け、近くのテキストコンテキストを探す時はバイナリ識別子を読み飛ばします。 ### ショートカットとキャッシュに上限を設ける - `total_chunks <= top_k` の場合は、明示的な小規模コーパス経路を通じてすべてのチャンクを直接返します。 - ユーザー、名前空間、クエリ、`top_k`、フィルターをキーにしてキャッシュし、実行前にキャッシュのバージョンを検証し、成功後にだけ書き込みます。 - ソースコンテンツを変更せず、順位付けのブーストに使うチャンク単位と文書単位のヒットを記録します。 - エージェントのアクション、入力、応答、レイテンシー、消費トークン数について、追記専用の実行レコードとステップレコードを保持します。 ## LLMのAPIのくせに対応し、完了の報告の前に手元で確かめる 障害を診断可能な状態に保てるよう、モデルの制約とローカル検証コマンドを明示します。 ### モデル連携を堅牢にする - `json` という語を、DeepSeekの `json_object` モードを要求する時に含めます。 - ストリーミング中は、`delta.content` が `None` ではない場合にのみ連結します。 - APIトークンを各トークンのレート上限内でローテーションし、クールダウンを適用して、次に利用可能なトークンでその場の再試行を行います。 - 検索は根拠だけを返すように保ちます。後続のエージェントが根拠の十分性を判断し、検索サービスの外部で回答を合成します。 ### システムをローカルで検証する - バージョン固定済みのワークスペースからインストールし、環境テンプレートをローカルにコピーして、必要なデータベース、キャッシュ、オブジェクトストレージ、API、ワーカーの各サービスを起動します。 - パーサー、プロファイラー、階層、エージェント型処理に絞ったエンドツーエンドのデバッグコマンドを用意し、API、ワーカー、共有パッケージ全体でリント、安全な自動修正、型チェックを実行します。 - 上位パイプラインの障害を診断する前に、ローカルAPIドキュメントと基盤サービスのエンドポイントを検証します。 - 影響を受けるパーサー、反映経路、検索モード、品質チェックについて直接の結果が得られるまで、完了を報告してはいけません。