Amplify AI KitでAmazon Bedrock Knowledge Baseと連携してみた
はじめに
こんにちは。コンサルティング部の神野です。
前回の記事でAmplify AI KitとAmazon Bedrockの連携を検証していました。
その際にAI Kitの公式ドキュメントをパラパラとみていると、Amazon Bedrock 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を連携したアプリケーションを作成してみます。
作成するシステムのイメージ
今回実装するシステムのイメージは以下の通りです。
前提条件
実装を始める前に、以下の環境を準備しておきましょう。
- 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リポジトリにまとめておきました。
Knowledge BaseのIDはこの後の設定で必要になるので、デプロイ後にメモしておいてください。このIDを、この後のコード例でYOUR_KNOWLEDGE_BASE_ID
と書いてある箇所に置き換えて使ってください。
また、Amplifyのアプリケーションコードは別レポジトリに格納しています。
こちらも必要に応じてご参照ください。
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を組み込んでいきます。
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
を編集
@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関数を実行するような形ですね。
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を立ち上げて、質問を回答するようなシンプルな作りとしています。
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
はデプロイ時にメモした値を使用しましょう!
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を使用します。
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データソース版のクエリを追加します。
// 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
はデプロイ時に取得した値を使用します。
// 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を呼び出します。
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を使用します。
Knowledge Baseの同期
S3にアップロードしたドキュメントをKnowledge Baseに同期します。AWSコンソールから「同期」ボタンをクリックするだけで、自動的にベクトル化されてOpenSearchに保存されます。
チャットでの質問
早速チャットで質問してみましょう!「Amazon Verified Permissionsのアップデートについて教えて」と資料に記載がある情報を質問してみました。
AppSync直接連携
しっかりとドキュメントを参照して回答してくれていますね!!
Lambda関数での連携
こちらもしっかりとドキュメントを参照して回答してくれています!
引用元のドキュメントを教えてというとしっかり参照先を教えてくれますね。
回答と同タイミングで返信するようにするにはプロンプトや実装を一工夫する必要があるのかなと感じました。この辺りも今後取り上げていきたいですね。
2つの方法でそれぞれ、Knowledge Baseが関連するドキュメントを検索し、その内容を基にLLMが回答を生成してくれました!見た目は自分で整えたり工夫する必要があると思いますが、楽にRAGアプリケーションが作成できるのはいいですね。
2つの実装方法の比較
実際に両方試してみた結果を踏まえて個人的に感じたメリット・デメリットをまとめてみました。
Lambda関数方式
- メリット:
- 独自の処理を実装可能
- レスポンスの構造化が柔軟
- デメリット:
- Lambda関数の管理が必要
- 若干のレイテンシー増加
AppSync直接連携方式
- メリット:
- Lambda関数が不要でシンプル
- インフラ管理が少ない
- デメリット:
- JavaScriptリゾルバーの記述が必要
- デバッグが難しい
- レスポンスの加工が限定的
個人的な感想としては、特定の要件を満たす処理が必要であったり、複雑な処理が必要ならLambda関数、シンプルな検索だけならAppSync直接連携という使い分けかなという印象を受けました。
おわりに
Knowledge Baseを使ったRAGの実装は思っていたより簡単でした。特に、Amplify AI Kitのツール機能と組み合わせることで、LLMが必要に応じて自動的にドキュメント検索してくれるのは便利ですね。
今後、Knowledge BaseもAI Kitで作成してシームレスに連携できるともっとありがたいなぁと思っています!
本記事が少しでも参考になりましたら幸いです!最後までご覧いただきありがとうございましたー!!