Amplify AI KitでAmazon Bedrock Knowledge Baseと連携してみた

Amplify AI KitでAmazon Bedrock Knowledge Baseと連携してみた

Clock Icon2025.07.22

はじめに

こんにちは。コンサルティング部の神野です。

前回の記事でAmplify AI KitとAmazon Bedrockの連携を検証していました。
その際にAI Kitの公式ドキュメントをパラパラとみていると、Amazon Bedrock Knowledge Baseとの連携について記載があって気になりました。

https://docs.amplify.aws/react/ai/conversation/knowledge-base/

既存のKnowledge Baseに対して、Amplify AI Kitで簡単に連携してチャットボット作れるみたいですね。今回はこの機能を実際にやってみたいと思います!

Amazon Bedrock Knowledge Baseとは?

Amazon Bedrock Knowledge Baseは、RAGパターンを簡単に実装できるマネージドサービスです。PDFやテキストファイルなどのドキュメントをS3にアップロードすると、自動的にベクトル化してOpenSearch Serverlessなどのベクトルデータベースに保存してくれます。

ユーザーが質問すると、その質問に関連する情報をベクトル検索で取得し、LLMがその情報を基に回答を生成します。これにより、LLMが学習していない最新の情報や社内ドキュメントなどを基に回答できるようになります。

今回はこのAmazon Bedrock Knowledge BaseとAmplify AI Kitを連携したアプリケーションを作成してみます。

作成するシステムのイメージ

今回実装するシステムのイメージは以下の通りです。

CleanShot 2025-07-22 at 18.43.05@2x

前提条件

実装を始める前に、以下の環境を準備しておきましょう。

  • Node.js v20.16.0以上
  • npm 10.8.1以上
  • AWSアカウント(Bedrockが利用可能なリージョン)
    • 使用するモデルは事前に有効化する必要があります(Claude 3.5 Sonnetを使用)
  • AWS CDK v2がインストール済み
  • リージョン: 今回はus-east-1を使用します(他のリージョンを使用する場合は、コード内のリージョン指定を適宜変更してください)

やってみる

CDKでKnowledge Baseを作成

最初に、CDKでKnowledge Baseを作成していきます。CDKのコードは結構長くなってしまうので、GitHubリポジトリにまとめておきました。

https://github.com/yuu551/cdk-knowledgebase

Knowledge BaseのIDはこの後の設定で必要になるので、デプロイ後にメモしておいてください。このIDを、この後のコード例でYOUR_KNOWLEDGE_BASE_IDと書いてある箇所に置き換えて使ってください。

また、Amplifyのアプリケーションコードは別レポジトリに格納しています。
こちらも必要に応じてご参照ください。

https://github.com/yuu551/amplify-ai-knowledgebase

Amplifyプロジェクトのセットアップ

早速Amplifyプロジェクトを作成していきましょう!

# Amplifyプロジェクトの作成
npm create amplify@latest

Vite + React + TypeScriptプロジェクトの作成

フロントエンドの準備も進めていきましょう。

# Viteを使ってReact + TypeScriptプロジェクトを作成
npm create vite@latest amplify-ai-knowledgebase -- --template react-ts

# プロジェクトディレクトリに移動
cd amplify-ai-knowledgebase

# 基本パッケージのインストール
npm install

Tailwind CSSのセットアップ

今回はUIを素早く作りたいので、Tailwind CSSを導入していきます。サクッと導入できるのでいいですよね。

# Tailwind CSS関連パッケージをインストール
npm install tailwindcss @tailwindcss/vite

vite.config.tsを編集してTailwind CSSを組み込んでいきます。

vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite' // Tailwind CSS プラグインをインポート

// ViteにTailwind CSSを組み込む設定です
export default defineConfig({
  plugins: [
    react(), // React用のプラグイン
    tailwindcss() // Tailwind CSSを有効化
  ],
})

src/index.cssを編集

src/index.css
@import "tailwindcss";

Amplify関連パッケージのインストール

必要なライブラリを諸々とインストールしておきます。

npm install aws-amplify @aws-amplify/ui-react @aws-amplify/ui-react-ai @aws-amplify/backend-ai @aws-sdk/client-bedrock-agent-runtime

実装方法1: Lambda関数を使う方法

Lambda関数を使ってKnowledge Baseと連携する方法を実装します。

スキーマの定義

amplify/data/resource.tsでKnowledge Baseとの連携を定義していきます。
スキーマを作成してリクエストからLLMと連携して回答するような設定を入れていきます。
Lambda関数を実行するような形ですね。

amplify/data/resource.ts
import { type ClientSchema, a, defineData, defineFunction } from "@aws-amplify/backend";

// Lambda関数の定義
export const knowledgeBaseLambda = defineFunction({
  name: 'knowledgeBaseLambda',
  entry: './knowledgeBaseLambda.ts',
  timeoutSeconds: 30, // Bedrockのレスポンス時間を考慮
  memoryMB: 256,
});

const schema = a.schema({
  // Knowledge Base検索クエリ
  queryKnowledgeBase: a.query()
    .arguments({ query: a.string().required() })
    .returns(a.customType({
      response: a.string(),
      sourceDocuments: a.string().array(),
      error: a.string()
    }))
    .authorization((allow) => [allow.authenticated()])
    .handler(a.handler.function(knowledgeBaseLambda)),

  // AIチャット機能 (Lambda版)
  chat: a.conversation({
    aiModel: a.ai.model('Claude 3.5 Sonnet'),
    systemPrompt: 'You are a helpful AI assistant with access to a knowledge base. Use the search tool to find relevant information to answer user questions.',
    tools: [
      a.ai.dataTool({
        name: 'searchKnowledgeBase',
        description: 'Search the knowledge base for relevant information to answer user questions',
        query: a.ref('queryKnowledgeBase'),
      }),
    ],
  })
  .authorization((allow) => allow.owner()),
});

export type Schema = ClientSchema<typeof schema>;

export const data = defineData({
  schema,
  authorizationModes: {
    defaultAuthorizationMode: "userPool",
  },
});

Lambda関数の実装

ここからが実際のKnowledge Base連携のロジックです。amplify/data/knowledgeBaseLambda.tsを作成していきます。
YOUR_KNOWLEDGE_BASE_IDはデプロイ時にメモした値を使用します。

ロジック自体はシンプルにAgentを立ち上げて、質問を回答するようなシンプルな作りとしています。

amplify/data/knowledgeBaseLambda.ts
import { BedrockAgentRuntimeClient, RetrieveAndGenerateCommand } from '@aws-sdk/client-bedrock-agent-runtime';
import type { Schema } from './resource';

export const handler: Schema["queryKnowledgeBase"]["functionHandler"] = async (event) => {

  const { query } = event.arguments;

  if (!query) {
    console.log('Query parameter is missing');
    return {
      response: null,
      sourceDocuments: [],
      error: 'Query is required'
    };
  }

  const client = new BedrockAgentRuntimeClient({ 
    region: 'us-east-1'
  });

  try {
    const command = new RetrieveAndGenerateCommand({
      input: {
        text: query
      },
      retrieveAndGenerateConfiguration: {
        type: 'KNOWLEDGE_BASE',
        knowledgeBaseConfiguration: {
          knowledgeBaseId: 'YOUR_KNOWLEDGE_BASE_ID',
          modelArn: 'arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0'
        }
      }
    });

    const response = await client.send(command);

    // ソース文書の情報を抽出
    const sourceDocuments: string[] = [];
    if (response.citations) {
      for (const citation of response.citations) {
        if (citation.retrievedReferences) {
          for (const ref of citation.retrievedReferences) {
            if (ref.location?.s3Location?.uri) {
              sourceDocuments.push(ref.location.s3Location.uri);
            } else if (ref.content?.text) {
              sourceDocuments.push(ref.content.text.substring(0, 50) + '...');
            }
          }
        }
      }
    }

    return {
      response: response.output?.text || 'No response generated',
      sourceDocuments: sourceDocuments,
      error: null
    };

  } catch (error) {
    console.error('Bedrock error details:', error);

    return {
      response: null,
      sourceDocuments: [],
      error: error instanceof Error ? `${error.name}: ${error.message}` : 'Unknown error occurred'
    };
  }
};

バックエンドの設定

ここでIAM権限を設定します。この設定を忘れるとLambdaがエラーになるので注意です!
YOUR_KNOWLEDGE_BASE_IDはデプロイ時にメモした値を使用しましょう!

amplify/backend.ts
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data, knowledgeBaseLambda } from './data/resource';
import { PolicyStatement, Effect } from 'aws-cdk-lib/aws-iam';

const backend = defineBackend({
  auth,
  data,
  knowledgeBaseLambda,
});

// Bedrock Knowledge BaseへのIAM権限を追加
backend.knowledgeBaseLambda.resources.lambda.addToRolePolicy(
  new PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      'bedrock:RetrieveAndGenerate',
      'bedrock:Retrieve',
      'bedrock:InvokeModel'
    ],
    resources: [
      'arn:aws:bedrock:us-east-1:*:knowledge-base/YOUR_KNOWLEDGE_BASE_ID',
      'arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0'
    ]
  })
);

実装方法2: AppSync直接連携を使う方法

AppSyncのHTTPデータソースを使って直接Bedrock APIに接続する方法も実装してみました。

AppSyncリゾルバーの作成

こちらはLambdaを使わずにAppSyncから直接Bedrockにアクセスする方法です。

amplify/data/resolvers/kbResolver.jsを作成します。
処理の中身としては、直接Knowledge Baseに問い合わせするような形となります。
YOUR_KNOWLEDGE_BASE_IDはデプロイ時に取得したIDを使用します。

amplify/data/resolvers/kbResolver.js
export function request(ctx) {
  const { input } = ctx.args;

  return {
    resourcePath: "/knowledgebases/YOUR_KNOWLEDGE_BASE_ID/retrieve",
    method: "POST",
    params: {
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        retrievalQuery: {
          text: input
        }
      }),
    },
  };
}

export function response(ctx) {
  return JSON.stringify(ctx.result.body);
}

スキーマの更新

先ほどのスキーマにHTTPデータソース版のクエリを追加します。

amplify/data/resource.ts
// HTTPデータソース版のKnowledge Base Query
queryKnowledgeBaseHttp: a.query()
  .arguments({ input: a.string().required() })
  .returns(a.string())
  .authorization((allow) => [allow.authenticated()])
  .handler(a.handler.custom({
    dataSource: "KnowledgeBaseDataSource",
    entry: "./resolvers/kbResolver.js"
  })),

chatHttp: a.conversation({
  aiModel: a.ai.model('Claude 3.5 Sonnet'),
  systemPrompt: 'You are a helpful AI assistant with access to a knowledge base. Use the search tool to find relevant information to answer user questions.',
  tools: [
    a.ai.dataTool({
      name: 'searchDocumentation',
      description: 'Performs a similarity search over the documentation',
      query: a.ref('queryKnowledgeBaseHttp'),
    }),
  ],
})
.authorization((allow) => allow.owner()),

HTTPデータソースの設定

amplify/backend.tsにHTTPデータソースとしてBedrockの連携を追加します。
YOUR_KNOWLEDGE_BASE_IDはデプロイ時に取得した値を使用します。

amplify/backend.ts
// HTTP DataSource for Knowledge Base
const KnowledgeBaseDataSource = backend.data.resources.graphqlApi.addHttpDataSource(
  "KnowledgeBaseDataSource",
  `https://bedrock-agent-runtime.us-east-1.amazonaws.com`,
  {
    authorizationConfig: {
      signingRegion: "us-east-1",
      signingServiceName: "bedrock",
    },
  }
);

// Grant permissions to the HTTP DataSource
KnowledgeBaseDataSource.grantPrincipal.addToPrincipalPolicy(
  new PolicyStatement({
    effect: Effect.ALLOW,
    actions: [
      "bedrock:Retrieve",
    ],
    resources: [
      "arn:aws:bedrock:us-east-1:*:knowledge-base/YOUR_KNOWLEDGE_BASE_ID", // 実際のKnowledge Base IDに置き換え
    ],
  })
);

フロントエンドの実装

両方の実装方法を比較できるように、タブ切り替えできるUIを作成しました!
バックエンドで定義したスキーマでLLMを呼び出します。

src/App.tsx
import { Authenticator } from '@aws-amplify/ui-react';
import { AIConversation, createAIHooks } from '@aws-amplify/ui-react-ai';
import { generateClient } from 'aws-amplify/data';
import type { Schema } from '../amplify/data/resource';
import { useState } from 'react';
import '@aws-amplify/ui-react/styles.css';

const client = generateClient<Schema>();
const { useAIConversation } = createAIHooks(client);

// Lambda版のKnowledge Base Chatコンポーネント
function KnowledgeBaseChatLambda() {
  const [
    {
      data: { messages },
      isLoading,
    },
    sendMessage,
  ] = useAIConversation('chat');

  return (
    <div style={{ height: '600px', overflow: 'hidden' }}>
      <AIConversation
        messages={messages}
        isLoading={isLoading}
        handleSendMessage={sendMessage}
      />
    </div>
  );
}

// AppSync直接連携版のKnowledge Base Chatコンポーネント
function KnowledgeBaseChatHttp() {
  const [
    {
      data: { messages },
      isLoading,
    },
    sendMessage,
  ] = useAIConversation('chatHttp');

  return (
    <div style={{ height: '600px', overflow: 'hidden' }}>
      <AIConversation
        messages={messages}
        isLoading={isLoading}
        handleSendMessage={sendMessage}
      />
    </div>
  );
}

function App() {
  const [activeTab, setActiveTab] = useState<'lambda' | 'appsync'>('lambda');

  return (
    <Authenticator>
      {({ signOut, user }) => (
        <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
          {/* Header with sign out */}
          <div className="bg-white shadow-sm border-b">
            <div className="container mx-auto px-4 py-3 max-w-6xl">
              <div className="flex justify-between items-center">
                <h1 className="text-xl font-semibold text-gray-800">
                  Knowledge Base AI Assistant
                </h1>
                <div className="flex items-center gap-4">
                  <span className="text-sm text-gray-600">
                    {user?.signInDetails?.loginId}
                  </span>
                  <button
                    onClick={signOut}
                    className="text-sm text-gray-500 hover:text-gray-700 transition-colors"
                  >
                    Sign out
                  </button>
                </div>
              </div>
            </div>
          </div>

          {/* Main Content */}
          <div className="container mx-auto px-4 py-8 max-w-6xl">
            <div className="bg-white rounded-lg shadow-lg">
              {/* Tab Navigation */}
              <div className="flex border-b border-gray-200">
                <button
                  onClick={() => setActiveTab('lambda')}
                  className={`flex-1 px-6 py-4 text-center font-medium transition-colors ${
                    activeTab === 'lambda'
                      ? 'text-blue-600 border-b-2 border-blue-600 bg-blue-50'
                      : 'text-gray-600 hover:text-gray-800 hover:bg-gray-50'
                  }`}
                >
                  <div className="flex items-center justify-center gap-2">
                    <span></span>
                    <span>Lambda関数版</span>
                  </div>
                  <p className="text-sm text-gray-500 mt-1">Lambda + Bedrock API</p>
                </button>

                <button
                  onClick={() => setActiveTab('appsync')}
                  className={`flex-1 px-6 py-4 text-center font-medium transition-colors ${
                    activeTab === 'appsync'
                      ? 'text-blue-600 border-b-2 border-blue-600 bg-blue-50'
                      : 'text-gray-600 hover:text-gray-800 hover:bg-gray-50'
                  }`}
                >
                  <div className="flex items-center justify-center gap-2">
                    <span></span>
                    <span>AppSync連携版</span>
                  </div>
                  <p className="text-sm text-gray-500 mt-1">AppSync連携</p>
                </button>
              </div>

              {/* Tab Content */}
              <div className="p-6">
                {activeTab === 'lambda' && (
                  <div>
                    <div className="mb-6 text-center">
                      <h2 className="text-2xl font-bold text-gray-800 mb-2">
                        Lambda関数版 - Knowledge Base Chat
                      </h2>
                      <p className="text-gray-600">
                        Lambda関数を使用してBedrock Knowledge Baseに接続します。
                      </p>
                    </div>
                    <KnowledgeBaseChatLambda />
                  </div>
                )}

                {activeTab === 'appsync' && (
                  <div>
                    <div className="mb-6 text-center">
                      <h2 className="text-2xl font-bold text-gray-800 mb-2">
                        AppSync連携版 - Knowledge Base Chat
                      </h2>
                      <p className="text-gray-600">
                        AppSyncから直接Bedrock APIに接続します。
                      </p>
                    </div>
                    <KnowledgeBaseChatHttp />
                  </div>
                )}
              </div>
            </div>
          </div>
        </div>
      )}
    </Authenticator>
  );
}

export default App;

実際に使ってみた結果

ドキュメントの準備

まず、CDKで作成したS3バケットにテスト用のドキュメントをアップロードします。今回は、先日の登壇で使用したPDFを使用します。

https://speakerdeck.com/yuu551/da-ban-kai-cui-amazon-inspectortoamazon-verified-permissionsnoatuhuteto-and-xian-di-qing-bao-noshao-jie

Knowledge Baseの同期

S3にアップロードしたドキュメントをKnowledge Baseに同期します。AWSコンソールから「同期」ボタンをクリックするだけで、自動的にベクトル化されてOpenSearchに保存されます。

CleanShot 2025-07-16 at 07.58.56@2x 1

チャットでの質問

早速チャットで質問してみましょう!「Amazon Verified Permissionsのアップデートについて教えて」と資料に記載がある情報を質問してみました。

AppSync直接連携

CleanShot 2025-07-22 at 19.46.39@2x

しっかりとドキュメントを参照して回答してくれていますね!!

Lambda関数での連携

CleanShot 2025-07-22 at 19.43.49@2x

こちらもしっかりとドキュメントを参照して回答してくれています!

引用元のドキュメントを教えてというとしっかり参照先を教えてくれますね。
回答と同タイミングで返信するようにするにはプロンプトや実装を一工夫する必要があるのかなと感じました。この辺りも今後取り上げていきたいですね。

CleanShot 2025-07-22 at 19.44.47@2x

2つの方法でそれぞれ、Knowledge Baseが関連するドキュメントを検索し、その内容を基にLLMが回答を生成してくれました!見た目は自分で整えたり工夫する必要があると思いますが、楽にRAGアプリケーションが作成できるのはいいですね。

2つの実装方法の比較

実際に両方試してみた結果を踏まえて個人的に感じたメリット・デメリットをまとめてみました。

Lambda関数方式

  • メリット:
    • 独自の処理を実装可能
    • レスポンスの構造化が柔軟
  • デメリット:
    • Lambda関数の管理が必要
    • 若干のレイテンシー増加

AppSync直接連携方式

  • メリット:
    • Lambda関数が不要でシンプル
    • インフラ管理が少ない
  • デメリット:
    • JavaScriptリゾルバーの記述が必要
    • デバッグが難しい
    • レスポンスの加工が限定的

個人的な感想としては、特定の要件を満たす処理が必要であったり、複雑な処理が必要ならLambda関数、シンプルな検索だけならAppSync直接連携という使い分けかなという印象を受けました。

おわりに

Knowledge Baseを使ったRAGの実装は思っていたより簡単でした。特に、Amplify AI Kitのツール機能と組み合わせることで、LLMが必要に応じて自動的にドキュメント検索してくれるのは便利ですね。
今後、Knowledge BaseもAI Kitで作成してシームレスに連携できるともっとありがたいなぁと思っています!

本記事が少しでも参考になりましたら幸いです!最後までご覧いただきありがとうございましたー!!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.