Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

AWS Bedrock Knowledge Baseを本番投入して見えたこと:ポリシー監査システムの構築記

By Cloud Intelligence™Jun 8, 202611 min read

このページはEnglishDeutschEspañolFrançaisItalianoPortuguêsでもご覧いただけます。

By Dima Kramskoy — Senior Cloud Architect at DoiT International


この記事を書いた理由

Bedrock Knowledge Baseのチュートリアルは、たいてい同じ筋書きです。PDFをS3に置く。Knowledge Baseを作る。質問を投げる。はい完成、ブログ記事1本。

デモなら、それで構いません。本番では、まったく通用しません。

先日、ラテンアメリカのフィンテック企業向けにポリシー監査システムを構築しました。社内ポリシーに照らして経費をAIがリアルタイムで判定する仕組みです。2言語・100本超のポリシードキュメントを対象に、1日およそ500クエリを処理しています。本番稼働中で、従業員の経費精算はこのシステムの判定で承認・却下されます。

本稿では、そこで実際に得た知見をまとめます。アーキテクチャ上の判断、チャンク分割でやらかしたこと、コスト面での誤算、そしてAWSのドキュメントが教えてくれない落とし穴です。


はじめてのBedrock Knowledge Base(5分で構築)

本題に入る前に、最低限の土台を揃えておきましょう。すでにKBを作ったことがある方は次のセクションへどうぞ。まだなら、「とりあえず動く」までの最短ルートを示します。

ステップ1:ドキュメント格納用のS3バケットを作成

Terminal window
aws s3 mb s3://my-kb-source-docs
aws s3 cp ./policies/ s3://my-kb-source-docs/ --recursive

ステップ2:Knowledge Baseを作成(コンソール)

  • Amazon Bedrock → Knowledge Bases → Create を開く
  • 名前を付け、埋め込みモデルを選ぶ(デフォルトのTitan Embeddings v2で問題ありません)
  • 先ほどのS3バケットを指定
  • ベクトルストアはS3 Vectors(サーバーレス、設定不要)を選ぶか、自動作成に任せる
  • Createをクリックし、同期完了まで2〜3分待つ

ステップ3:クエリを投げる

import boto3
client = boto3.client('bedrock-agent-runtime')
response = client.retrieve_and_generate(
input={'text': 'What is our policy on travel expenses?'},
retrieveAndGenerateConfiguration={
'type': 'KNOWLEDGE_BASE',
'knowledgeBaseConfiguration': {
'knowledgeBaseId': 'YOUR_KB_ID',
'modelArn': 'arn:aws:bedrock:us-east-1::foundation-model/amazon.nova-pro-v1:0'
}
}
)
print(response['output']['text'])

これで完了。動くRAGシステムが手に入りました。手元のドキュメントを根拠に回答してくれます。

**ただし、ここからが本題です。**ここまでで到達できるのはゴールの6割。残り4割、本番品質を支える領域こそが本稿のテーマです。チャンク分割の戦略、コスト管理、レイテンシ、変換パイプライン、そしてスケール後に牙を剥く落とし穴を順に見ていきます。


ユースケース

クライアントが抱えていたのは、成長企業が遅かれ早かれぶつかる課題でした。複雑な社内ポリシーが誰にも読まれず、複数の国と言語にまたがって運用がバラついている、という状況です。

具体的には、英語とスペイン語で100本超の社内ポリシーを抱える企業が、従業員の経費をそれらのポリシーに照らしてリアルタイムにAIで判定したいというニーズでした。「ポリシーを検索する」のではありません。経費が規定に準拠しているかを判定し、根拠となるルールを引用し、理由を説明する——そこまで求められたのです。

要件は明確でした。

  • 判定レイテンシは3秒未満
  • 判定はすべて、出典となるポリシー段落までトレース可能であること
  • ポリシー変更には人による承認ゲートを設けること(法的責任のため)
  • 多言語対応(英語・スペイン語)
  • スケールしても費用対効果が成立すること(1日約500クエリ、増加傾向)

アーキテクチャの全体像

大まかなフローは次のとおりです。

トランザクション評価パス:

API Gateway → SQS → Step Functions → Bedrock Nova Pro(レシート検証)
→ Bedrock AgentCore(KBによるポリシー判定)→ Aurora MySQL(判定の永続化)

ポリシー取り込みパス:

S3アップロード → Step Functions(タスクトークンによる人手承認)
→ LLM変換(ポリシーの再構造化、経費項目の抽出、新ビューへの整形)
→ 変換済みチャンクをS3に保存(中間状態)
→ APIがレビュー用にチャンクを公開 → 人手で承認
→ Bedrock Knowledge Baseへ取り込み

用途の異なる2本のパスです。一方はリアルタイム判定、もう一方はポリシーのライフサイクル管理。両方をStep Functionsでオーケストレーションしていますが、これは偶然ではありません。法令遵守がかかった場面で必要になる可視性とリトライ制御を、ステートマシンはちょうど過不足なく提供してくれます。


なぜOpenSearchではなくS3 Vectorsなのか

この節で、数週間分の検討時間を節約します。

このプロジェクトを始めた当時、Bedrock KBのデフォルトのベクトルストアはOpenSearch Serverlessでした。動きます。実績もあります。ですが、1万件未満のドキュメントセットには明らかにオーバースペックです。

S3 Vectorsはよりシンプルな代替としてローンチされ、今回のユースケースでは選択は明らかでした。

項目 OpenSearch Serverless S3 Vectors
月額基本コスト 約$700〜(最低2 OCU) クエリ従量課金
運用負荷 インデックス管理、スケーリング ゼロ
セットアップの複雑さ 中程度 最小限
クエリレイテンシ(p50) 約200ms 約350ms
得意領域 1万件以上、複雑なクエリ 1万件未満、シンプルな検索

**判断軸はシンプルです。**ドキュメントが1万件未満で、複雑なフィルタリングやハイブリッド検索が要らないなら、S3 Vectorsでコストも運用負荷も下がります。200ms未満のレイテンシが必要だったり、複雑なメタデータクエリを伴う数万件規模のドキュメントを扱うなら、OpenSearchを選ぶべきです。

今回は約100本のポリシードキュメント。S3 Vectors一択でした。月額最低$700の代わりに、1日数セントで済んでいます。レイテンシのトレードオフ(プラス約150ms)も、どのみちLLM推論を挟むワークフローでは誤差です。


本当に効くチャンク分割戦略

我々の選択を紹介する前に、まずは選択肢の全体像を整理しておきます。多くの解説記事は1〜2種類しか触れていないからです。

戦略 仕組み 向いている用途 注意点
固定サイズ(デフォルト) N文字/トークンごとに分割 とりあえず始めたいとき、汎用ドキュメント 文やルールの途中で切れ、ハルシネーションの温床になる
文単位 文境界で分割 シンプルな文書、FAQ 論理的なまとまりを無視。1つのルールが5文以上にまたがることも
セマンティック/セクション単位 文書構造(見出し、セクション)で分割 階層が明確な構造化文書 構造の解析が必要。チャンクサイズが不揃いになる
階層型(親子) 親チャンク(セクション全体)+子チャンク(段落) 検索品質最優先——子でマッチし、親で文脈を返す 実装が複雑、ストレージ増、インデックスが遅い
LLM支援 チャンク分割の前にLLMが文書を再構造化 誤検索が誤判定に直結する重要文書 取り込み時にコスト・レイテンシが増えるが、精度が問われる場面では十分元が取れる

**我々の結論:LLM支援。**誤検索が誤った経費判定に直結するポリシードキュメントでは、LLM変換の先行投資はすぐに回収できます。詳細は後述のパイプライン節で。

その前に、我々をこの結論に導いた失敗パターンを共有させてください。

初期に犯した、もっとも高くついたミスです。

失敗例:デフォルトのチャンク分割

Bedrock KBのデフォルトは、文字数ベースでオーバーラップ付きの分割です。汎用文書なら十分。しかしポリシードキュメントでは致命的です。

例えば「75ドルを超える食事はマネージャー承認が必要。ただしクライアント対応の出張時は上限150ドル」というルールがあったとします。デフォルトのチャンク分割は、これを文の途中で切ってしまうことがあります。結果、検索で返ってくるのは「75ドルを超える食事はマネージャー承認が必要」だけ。例外条項はどこかへ消えます。エージェントは正当な100ドルのクライアントディナーを却下し、利用者は初日でシステムへの信頼を失います。

成功例:セマンティック境界でのチャンク分割

取り込み前に文書を前処理し、ポリシーのセクション単位でチャンク化します。ルールやサブルールを、文脈を保ったまま独立したチャンクにするわけです。

import re
from dataclasses import dataclass
@dataclass
class PolicyChunk:
content: str
metadata: dict
def chunk_policy_document(text: str, doc_id: str, language: str) -> list[PolicyChunk]:
"""Chunk policy documents by semantic boundaries (section headers)."""
# Split on policy section patterns (numbered rules, headers)
section_pattern = r'\n(?=\d+\.\s|\#{1,3}\s|Article\s+\d+|Artículo\s+\d+)'
sections = re.split(section_pattern, text)
chunks = []
for i, section in enumerate(sections):
section = section.strip()
if len(section) < 50: # Skip trivial sections
continue
# Keep chunks between 200-1500 chars for optimal retrieval
if len(section) > 1500:
# Sub-chunk by paragraph, preserving section header
header = section.split('\n')[0]
paragraphs = section.split('\n\n')
for j, para in enumerate(paragraphs[1:], 1):
chunks.append(PolicyChunk(
content=f"{header}\n\n{para}",
metadata={
"doc_id": doc_id,
"section_index": i,
"sub_index": j,
"language": language,
"chunk_type": "policy_rule"
}
))
else:
chunks.append(PolicyChunk(
content=section,
metadata={
"doc_id": doc_id,
"section_index": i,
"sub_index": 0,
"language": language,
"chunk_type": "policy_rule"
}
))
return chunks

実践的なポイント

  • **チャンクサイズの目安:**ポリシードキュメントなら200〜1500文字。小さくすれば精度が上がり、大きくすれば文脈が残ります。バランスを探ってください。
  • **オーバーラップ:**どうしても文字数ベースで切るなら、最低20%のオーバーラップを。とはいえ正直、セマンティック境界で切るべきです。
  • **メタデータは検索そのもの:**すべてのチャンクに languagepolicy_typeeffective_datedepartment をタグ付けします。後でこれらでフィルタすることになります——任意ではなく必須です。

取り込みパイプライン

ポリシードキュメントはブログ記事ではありません。ベクトルストアに放り込んで「うまくいけ」と祈るわけにはいきません。誤ったポリシー解釈には法的な帰結が伴います。

パイプラインは次のとおりです。

LLM変換+ヒューマン・イン・ザ・ループのパターン

設計上のキモは、承認の前に変換すること。フローはこうなります。

S3アップロード(生のポリシーPDF)
LLM変換(再構造化、経費ルールの抽出、目的のビューへの整形)
変換済みチャンクをS3に保存(中間状態——再利用のためキャッシュ)
APIが構造化済みチャンクをレビュー用に公開
レビュアーが承認/却下(ワンクリック)
承認されたチャンクをBedrock Knowledge Baseに取り込み

これは論理的な承認ゲートであり、重量級のオーケストレーションではありません。重労働は先にLLMがこなします。複数ページのPDFを解析し、個別の経費ルールを抽出し、一貫したフォーマットに整える。レビュアーが目にする頃には、生のドキュメントではなく、整理された構造化チャンクが並んでいる状態です。

本番運用でこれが効く理由:

  • 再取り込みが速い——変換結果はS3にキャッシュ済み。ポリシー更新のたびにゼロから処理し直す必要はありません。
  • レビュアーが見るのは質の高い出力——PDFの羅列ではなく、構造化されたルールを承認できます。
  • 検索時のエラーが減る——LLMが事前に構造化しているため、KBは常に一貫した形式のチャンクを受け取れます。

ヒューマン・イン・ザ・ループが必要な理由

「うちのポリシーチームは信頼できるから」と承認ゲートを省くチームを何度も見てきました。すると誰かがドラフト文書をアップロードし、それが埋め込まれ、AIがドラフトのルールを強制し始めます。一度それをやれば、組織からのシステムへの信頼は失われます。

**アンチパターン:**S3アップロードで自動取り込み。コンプライアンスに関わる文書では絶対にやってはいけません。

**正しいパターン:**アップロード → LLMがポリシーを変換・構造化 → 変換済みチャンクをS3にキャッシュ → APIがレビュー用にチャンクを公開 → 人手で承認/却下 → そのうえでKBへ取り込み。これなら再取り込みも高速(変換はキャッシュ済み)で、承認者は生PDFではなく、整理された出力を確認できます。


検索レイテンシとコスト

本番環境(1日500クエリのワークロード)の実測値です。

レイテンシ(S3 Vectors)

p50 = 中央値(典型的な体感速度)。p99 = 99パーセンタイル(極端な外れ値を除く最悪ケース)。

  • **p50:**340ms(検索のみ、LLM推論を除く)
  • **p99:**890ms
  • **エンドツーエンドの判定(AgentCoreを含む):**p50 約2.1秒、p99 約4.8秒

月額コストの内訳

コンポーネント 月額コスト
S3 Vectors(ストレージ+クエリ) 約$12
Bedrock KB API呼び出し 約$8
Titan Embeddings(取り込み) 約$3
Nova Pro(レシート検証) 約$45
AgentCore(ポリシー判定) 約$120
Step Functions 約$5
Aurora MySQL(永続化) 約$65
合計 約$258/月

OpenSearch Serverless単体で最低$700/月かかることを思い出してください。アーキテクチャの選択は積み上がります。


誰も教えてくれない落とし穴

本番運用3か月で見えてきた、私のリストはこちらです。

  1. アップロード後の同期遅延。StartIngestionJobを呼んでも、KBがすぐに新しい内容で検索できるわけではありません。小さな更新でも30〜90秒は見ておきましょう。UXに織り込むこと——「ポリシー更新を処理中」といった状態表示を必ず用意してください。

  2. **メタデータフィルタリングは完全一致のみ(S3 Vectors)。**範囲クエリも部分一致もできません。等価フィルタを前提にスキーマを設計してください。「2025年1月以降に更新されたポリシーすべて」のような検索が必要なら、別のアプローチが要ります。

  3. **埋め込みモデルの選択は不可逆。**Titan Embeddings v2でKBを作成したあとからCohereに切り替えるには、Knowledge Baseを丸ごと作り直すしかありません。最初に慎重に選んでください(我々はTitan v2を採用——多言語コンテンツでコストと品質のバランスが良好でした)。

  4. **多言語検索は魔法ではない。**スペイン語のクエリならスペイン語チャンクはしっかり拾えますが、言語横断の検索(スペイン語クエリ → 英語ポリシー)は信頼できません。我々は両言語で並行するチャンクを維持し、クエリの検出言語でフィルタすることで解決しました。

  5. **再インデックス時のコスト急増。**増分更新ではなくKB全体を頻繁に同期すると、埋め込みコストが跳ね上がります。100ドキュメントのフル再インデックスで約$2。これを誤って毎時実行すれば、埋め込みだけで月$1,400が消えます。

  6. **KBクォータは意外と低い。*同時取り込みジョブのデフォルトは1、ドキュメントサイズのデフォルトは50MB。本番スケールに達する前に*引き上げを申請してください。

  7. **「自信満々の誤答」問題。*エージェントは近いが正しくない*チャンクを引いてくると、平然と誤ったルールを適用します。類似度スコアの閾値(我々は0.7)を設定し、信頼度の低い検索結果は人手レビューに回すことで緩和できます。


始め方

初めての本番Bedrock KB実装に向けた5ステップです。

  1. **100件ではなく10件から始める。**少数の文書でチャンク分割戦略を固めましょう。スケールする前に、検索品質を手作業で検証することが先決です。

  2. **特に理由がなければS3 Vectorsを選ぶ。**1万ドキュメント未満のKnowledge Baseユースケースの大半では、安く、シンプルです。本当に必要になってからOpenSearchへ移行すれば十分。

  3. **何より先にチャンク分割に投資する。**デフォルトのチャンク分割は、構造化文書にとって罠です。1週間は腰を据えてチャンク戦略に取り組みましょう——もっともレバレッジが効く作業です。

  4. **承認パイプラインは初日から組み込む。**今はヒューマン・イン・ザ・ループが不要でも、リスクが上がれば必ず必要になります。Step Functionsのタスクトークン・パターンなら後付けも容易ですが、後から差し込むとコストがかさみます。

  5. **とにかく計測する。**検索スコア、チャンクID、判定の信頼度をログに残しましょう。測れないものは改善できません。検索品質が劣化したとき(コーパスが育てば必ず起きます)、原因を突き止めるにはデータが要ります。


まとめ

Bedrock Knowledge Baseは、本番RAGシステムのインフラとして本当に優れています。ただし「デモ」と「本番」の間に広がる隔たりにこそ、面白いエンジニアリング判断が詰まっています——チャンク分割戦略、取り込みパイプライン、コスト最適化、エビデンスチェーン、障害モード。

S3 Vectorsのおかげで、OpenSearchではオーバースペックになる規模でも、このプロジェクトは経済的に成立しました。Step Functionsは、コンプライアンスが要求するオーケストレーション保証を与えてくれました。そしてAgentCoreは、検索を構造化された監査可能な判定へと変えてくれました。

スタックは機能します。難しいのはAWSのサービスそのものではなく、その周囲のデータエンジニアリングでした。

最初から正しく作りましょう。未来の自分(そしてクライアントの法務チーム)が感謝してくれるはずです。


Dima KramskoyはDoiT Internationalのシニアクラウドアーキテクト。ソフトウェアエンジニアリング歴20年以上、AWS認定資格10種を保有し、AWS Community Builder(2026)。AWS上での本番AI/MLシステム構築を支援している。