Amplify AI KitのLLMにツールを使わせる「Tools」機能を試してみた
はじめに
こんにちは。コンサルティング部の神野です。
皆さんはAWS Amplify AI Kitを使っていますか?
Amplify AI KitはAmplify上でAIアプリケーションを簡単に構築できる面白いツールキットです。
Amplify AI Kit のアップデートは下記ブログで紹介されているので、ぜひご参照ください!
直近AWS SummitでもAmplify AI Kitが取り上げられていて、特に気になった機能が「Tools」です。
私は使ったことがなかったので気になり試してみました!
Tools機能とは何か?
Toolsは、LLMが現在の関連情報でレスポンスするために情報をクエリできるようにする機能です。ユーザーのメッセージとツールの説明に基づいて、LLMが要求した場合にのみ呼び出されます。
LLMが会話の文脈から「この情報が必要だな」と判断したときに、自動的にデータベースやAPIを呼び出して、最新の情報を取得できるようになるわけです。これによって、単なるチャットボットではなく、実際のデータと連携したAIアプリケーションが作れるようになります。
Toolsの3つのタイプ
Amplify AI Kitでは、3つの異なる方法でLLMツールを定義できます。
- Model tools - データモデルへのアクセス
- Query tools - カスタムクエリの実行
- Lambda tools - Lambda関数内でのツール実行
それぞれに特徴があるので、順番に見ていきたいと思います。
準備
まずは動かせる環境を作成していきましょう。
前提条件の確認
私が使用した環境は以下の通りです。
- Node.js v20.16.0
- npm 10.8.1
Bedrock有効化
Amazon Bedrockの有効化は、AWSコンソールから行います。
今回はClaude 3.5 Sonnet
を使用するので有効化していきます。
-
コンソール画面で Amazon Bedrock > Bedrock configurations > モデルアクセス の順に進みます
-
ベースモデル一覧の中から使用したいモデルの「リクエスト可能」をクリックしてモデルアクセスをリクエストします。
-
数分後アクセスが付与されていることを確認して
アクセスが付与されました
と表示されていればOKです!
Amplifyプロジェクトの作成
下記コマンドを実行して、Amplify プロジェクトを作成します。
npm create amplify@latest
このコマンドでバックエンドが作成されるので、今度はフロント側を作成していきます。
Vite + React + TypeScriptプロジェクトの作成
新しいReactプロジェクトをViteで作成します。
# Viteを使ってReact + TypeScriptプロジェクトを作成
npm create vite@latest amplify-ai-tools-demo -- --template react-ts
# プロジェクトディレクトリに移動
cd amplify-ai-tools-demo
# 基本パッケージのインストール
npm install
Tailwind CSSのセットアップ
UI作成にTailwind CSSを使用するため、セットアップを行います。
# Tailwind CSS関連パッケージをインストール
npm install -D tailwindcss postcss autoprefixer
# Tailwind設定ファイルを作成
npx tailwindcss init -p
tailwind.config.js
を編集してcontentパスを設定
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
src/index.css
を編集してTailwindディレクティブを追加します。
@tailwind base;
@tailwind components;
@tailwind utilities;
Amplify関連パッケージのインストール
AI機能と認証機能を使用するために、必要なパッケージをインストールします。
# Amplify関連パッケージをインストール
npm install aws-amplify @aws-amplify/ui-react @aws-amplify/ui-react-ai @aws-amplify/backend-ai
プロジェクト構造の確認
作成されたプロジェクトの構造は以下のようになります。
amplify-ai-tools-demo/
├── amplify/ # Amplifyバックエンド設定(npm create amplify@latestで作成)
│ ├── auth/
│ ├── data/
│ └── backend.ts
├── src/ # Vite + Reactフロントエンド
│ ├── App.tsx
│ ├── main.tsx
│ ├── index.css # Tailwindディレクティブを含む
│ └── ...
├── tailwind.config.js # Tailwind CSS設定
├── postcss.config.js # PostCSS設定
├── vite.config.ts # Vite設定
├── amplify_outputs.json # バックエンド設定ファイル(サンドボックス起動時に自動生成)
└── package.json
Amplifyとフロントエンドの連携設定
フロントエンドでAmplifyを使用するため、src/main.tsx
を編集してAmplifyの設定を追記します。
// src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Amplify } from 'aws-amplify'
import App from './App.tsx'
import './index.css'
import outputs from '../amplify_outputs.json'
Amplify.configure(outputs)
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
サンドボックス環境の起動
開発用のサンドボックス環境を起動します。
# サンドボックスを起動
npx ampx sandbox
[Sandbox] Watching for file changes...
File written: amplify_outputs.json
File written: amplify_outputs.json
と出ればOKです!
フロント側は下記コマンドで実行して、問題なくテンプレートが表示されていればOKです。
npm run dev
基本的にサンドボックス環境およびフロント側は立ち上げっぱなしとします。
ソースコード
本記事で紹介する3つのToolsタイプをすべて含んだデモアプリケーションのソースコードは、以下のGitHubリポジトリで公開しています!
リポジトリには以下が含まれています。
- すべてのToolsタイプを含むAmplifyバックエンド
- 3つのタブを持つフロントエンドUI
手軽に試したい方は、リポジトリをクローンして以下のコマンドで起動できます!
git clone https://github.com/yuu551/amplify-ai-tools-demo.git
cd amplify-ai-tools-demo
# 依存関係のインストール(Tailwind CSS設定済み)
npm install
# バックエンドの起動
npx ampx sandbox
# 別ターミナルでフロントエンドの起動
npm run dev
Model toolsを使ってみる
Model toolsは、データスキーマ内のデータモデルとカスタムクエリに対してa.ai.dataTool()
を使用する最も簡単な方法です。
実際にProductReview(商品レビュー)モデルをLLMに検索させる例を見てみましょう。ECサイトで商品の評判をAIに聞けるようにします。
データスキーマの定義
まずは、amplify/data/resource.ts
ファイルを編集して、ProductReviewモデルとAIチャット機能を定義します。
import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
const schema = a.schema({
ProductReview: a.model({
productName: a.string().required(),
rating: a.integer().required(),
comment: a.string(),
purchaseVerified: a.boolean().default(false),
})
.authorization((allow) => [allow.owner()]),
chat: a.conversation({
aiModel: a.ai.model('Claude 3.5 Sonnet'),
systemPrompt: 'あなたはECサイトのショッピングアシスタントです。商品レビューに基づいてアドバイスを提供します。',
tools: [
a.ai.dataTool({
// LLMに参照される際のツール名
name: 'ProductReviewSearch',
// LLMに提供されるツールの説明
// LLMがいつツールを使うべきか理解するのに役立ちます
description: '商品レビューを検索して、評価やコメントを取得します',
// ツールが使用する `a.model()` への参照
model: a.ref('ProductReview'),
// モデルに対して実行する操作
modelOperation: 'list',
}),
],
})
.authorization((allow) => allow.owner()),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: "userPool",
},
});
このコードでは、ProductReviewモデルとAIチャット機能を定義しています。a.ai.dataTool
を使用することで、LLMがProductReviewモデルのデータを検索できるようになります。また、owner認証により、各ユーザーが自分のデータのみにアクセスできるような設定になっています。
フロントエンドの実装
src/App.tsx
を編集して、AI機能を使用するアプリケーションを実装します。
// src/App.tsx
import { Authenticator } from '@aws-amplify/ui-react';
import { AIConversation } from '@aws-amplify/ui-react-ai';
import { generateClient } from 'aws-amplify/data';
import { createAIHooks } from '@aws-amplify/ui-react-ai';
import type { Schema } from '../amplify/data/resource';
import { useEffect } from 'react';
import '@aws-amplify/ui-react/styles.css';
const client = generateClient<Schema>();
const { useAIConversation } = createAIHooks(client);
function Chat() {
const [
{
data: { messages },
isLoading,
},
sendMessage,
] = useAIConversation('chat');
// サンプルレビューデータを作成(重複チェック付き)
useEffect(() => {
const createSampleReviews = async () => {
try {
// 既存データをチェック
const existingReviews = await client.models.ProductReview.list();
// データが既に存在する場合は作成をスキップ
if (existingReviews.data.length > 0) {
console.log('サンプルレビューは既に存在します');
return;
}
await client.models.ProductReview.create({
productName: 'Amplify AI Kit スターターガイド',
rating: 5,
comment: 'とてもわかりやすくて実装が簡単でした!',
purchaseVerified: true,
});
await client.models.ProductReview.create({
productName: 'AWSクラウドプラクティショナー問題集',
rating: 4,
comment: '試験対策に最適です。説明が丁寧。',
purchaseVerified: true,
});
await client.models.ProductReview.create({
productName: 'AWSクラウドプラクティショナー問題集',
rating: 3,
comment: '内容はいいが、もう少し実践的な例が欲しい',
purchaseVerified: false,
});
console.log('サンプルレビューを作成しました');
} catch (error) {
console.error('レビュー作成エラー:', error);
}
};
createSampleReviews();
}, []);
return (
<AIConversation
messages={messages}
isLoading={isLoading}
handleSendMessage={sendMessage}
/>
);
}
function App() {
return (
<Authenticator>
{({ signOut }) => (
<div style={{ padding: '20px' }}>
<h1>AIショッピングアシスタント</h1>
<p>商品レビューの検索機能を体験できます</p>
<button onClick={signOut}>サインアウト</button>
<Chat />
</div>
)}
</Authenticator>
);
}
export default App;
このフロントエンドコードでは、AmplifyのAIConversationコンポーネントを使用してチャットUIを実装しています。useEffectでサンプルデータを作成していますが、重複を避けるために既存データをチェックしてから作成するロジックを入れています。
アプリケーションの起動と動作確認
プロジェクトのセットアップが完了したら、ローカル開発環境を起動します。
# サンドボックスを起動(amplify_outputs.jsonが自動生成されます)
npx ampx sandbox
# 別のターミナルでReactアプリを起動
npm run dev
実際に使ってみる
アプリケーションにアクセスするには、ブラウザでhttp://localhost:5173
を開きます。
初回アクセス時は新規ユーザー登録またはログインが必要です。
認証が完了すると、下記のような画面が表示されます。
AWSクラウドプラクティショナー問題集の評判はどう?
といった質問を入力してみましょう
しっかりと事前に作成したダミーデータを参照して回答していますね!!
数行のコードでユーザー作成のデータを認識およびクエリしてその結果を踏まえて回答してくれるのは良いですね。
権限について
今回の例では、owner()
を使用しています。この設定により、ユーザーは自分が作成したデータにのみアクセスできるようになります。
- ユーザーが作成したProductReviewは、そのユーザーのみがアクセス可能
- ただレビューなんかは皆が作成したデータを活用したいので、実際に使われる際は
owner
ではなく認証されたユーザー皆が参照できるauthenticated
がいいかと思いました。
- ただレビューなんかは皆が作成したデータを活用したいので、実際に使われる際は
- 各ユーザーのチャット履歴は独立して管理される
Query toolsで外部APIと連携する
Query toolsは、カスタムクエリを定義して外部APIやシステムと連携する場合に便利です。ECサイトで商品の在庫確認をする例を見てみましょう。
Lambda関数の定義
次に、在庫確認をするLambda関数を定義します。
// amplify/data/checkInventory.ts
import { env } from "$amplify/env/checkInventory";
import type { Schema } from "./resource";
export const handler: Schema["checkInventory"]["functionHandler"] = async (event) => {
const { productId } = event.arguments;
if (!productId) {
throw new Error('Product ID is required');
}
// デモ用にモックデータを返す
// 実際の実装では、コメントアウトして下のコードを使用
console.log(`Checking inventory for product ID: ${productId}`);
const mockInventoryData = {
'PRD001': {
stock: 15,
nextDelivery: '2024-01-15',
productName: 'Amplify AI Kit スターターガイド'
},
'PRD002': {
stock: 0,
nextDelivery: '2024-01-20',
productName: 'AWSクラウドプラクティショナー問題集'
},
'PRD003': {
stock: 8,
nextDelivery: null,
productName: 'React パフォーマンス最適化ガイド'
},
};
const inventoryInfo = mockInventoryData[productId] || {
stock: 0,
nextDelivery: null,
productName: '未知の商品'
};
return inventoryInfo;
// 実際の在庫管理システムアクセスコード
// const url = `${env.INVENTORY_API_ENDPOINT}/products/${productId}/stock`;
// const request = new Request(url, {
// headers: {
// Authorization: `Bearer ${env.INVENTORY_API_KEY}`
// }
// });
// const response = await fetch(request);
// const inventoryInfo = await response.json();
// return inventoryInfo;
}
このLambda関数では、商品IDを受け取って在庫情報を返します。デモ用にモックデータを使用していますが、実際の実装では外部APIと連携することが可能です。コメントアウトされた部分は実際にアプリケーションと連携した場合のイメージを記載しました。
スキーマでカスタムクエリとツールの定義
import { type ClientSchema, a, defineData, defineFunction } from "@aws-amplify/backend";
export const checkInventory = defineFunction({
name: 'checkInventory',
entry: './checkInventory.ts',
});
const schema = a.schema({
// 在庫確認クエリ
checkInventory: a.query()
.arguments({ productId: a.string().required() })
.returns(a.customType({
stock: a.integer(),
nextDelivery: a.string(),
productName: a.string()
}))
.handler(a.handler.function(checkInventory))
.authorization((allow) => [allow.owner()]),
inventoryChat: a.conversation({
aiModel: a.ai.model('Claude 3.5 Sonnet'),
systemPrompt: 'あなたはECサイトの在庫管理アシスタントです。商品の在庫状況と入荷予定を確認できます。',
tools: [
a.ai.dataTool({
name: 'check_inventory',
description: '指定された商品IDの在庫数と入荷予定を確認します',
query: a.ref('checkInventory'),
}),
],
})
.authorization((allow) => allow.owner()),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: "userPool",
},
});
このスキーマ定義では、checkInventoryクエリをAIツールとして公開しています。a.ai.dataTool
を使用してクエリをツールとして定義することで、LLMがLambda関数を呼び出して在庫情報を取得できるようになります。
backend.tsの更新
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data, checkInventory } from './data/resource';
const backend = defineBackend({
auth,
data,
checkInventory
});
backend.tsファイルでは、定義したLambda関数をAmplifyバックエンドに追加しています。これにより、Lambda関数がデプロイされ、AIツールから呼び出せるようになります。
フロントエンドの修正
先ほど作成したApp.tsx
に在庫確認クエリをテストできるようにします。基本的には同じチャットインターフェースを使用します。
// src/App.tsx(Query Tools用に修正)
import { Authenticator } from '@aws-amplify/ui-react';
import { AIConversation } from '@aws-amplify/ui-react-ai';
import { generateClient } from 'aws-amplify/data';
import { createAIHooks } from '@aws-amplify/ui-react-ai';
import type { Schema } from '../amplify/data/resource';
import '@aws-amplify/ui-react/styles.css';
const client = generateClient<Schema>();
const { useAIConversation } = createAIHooks(client);
function InventoryChat() {
const [
{
data: { messages },
isLoading,
},
sendMessage,
] = useAIConversation('inventoryChat');
return (
<AIConversation
messages={messages}
isLoading={isLoading}
handleSendMessage={sendMessage}
/>
);
}
function App() {
return (
<Authenticator>
{({ signOut }) => (
<div style={{ padding: '20px' }}>
<h1>在庫確認アシスタント</h1>
<p>商品の在庫状況を確認できます</p>
<button onClick={signOut}>サインアウト</button>
<InventoryChat />
</div>
)}
</Authenticator>
);
}
export default App;
このコードでは、在庫確認専用のチャットコンポーネントを作成しています。Query toolsで定義したcheckInventoryクエリを、AIが必要に応じて自動的に呼び出してくれます。
実際に使ってみる
チャット画面で在庫情報を問い合わせてみましょう。
ダミーで作ったクエリから問い合わせしてくれていますね。
こちらもいい感じに問い合わせを可能にしていて良きですね。
Query toolsの便利な点
今回はデモ用にモックデータを返していますが、実際のプロダクションでは外部APIやデータベースとの連携も可能です。クエリした結果をもとに回答させたい場合は便利だなと感じました。
また、Lambda関数内でより高度な処理を実装することもできます。
実際の在庫管理システムや外部APIを使用する場合は、コメントアウトされたコードのようなイメージで、問い合わせしてその結果を返却して活用します。
3つのToolsタイプを統合したデモアプリ
Model tools、Query tools、Lambda toolsのすべてを体験できるように、タブ形式のUIで統合デモアプリを実装してみましょう。
3つのツールタイプすべてを体験できる統合アプリを実装します。src/App.tsx
を以下のように修正して、タブで切り替えられるようにします。
import { Authenticator } from '@aws-amplify/ui-react';
import { AIConversation } from '@aws-amplify/ui-react-ai';
import { generateClient } from 'aws-amplify/data';
import { createAIHooks } from '@aws-amplify/ui-react-ai';
import type { Schema } from '../amplify/data/resource';
import { useEffect, useState } from 'react';
import '@aws-amplify/ui-react/styles.css';
const client = generateClient<Schema>();
const { useAIConversation } = createAIHooks(client);
// Model tools用のチャット(商品レビュー検索)
function ReviewChat() {
const [{ data: { messages }, isLoading }, sendMessage] = useAIConversation('chat');
// サンプルレビューデータ作成処理は省略...
return <AIConversation messages={messages} isLoading={isLoading} handleSendMessage={sendMessage} />;
}
// Query tools用のチャット(在庫確認)
function InventoryChat() {
const [{ data: { messages }, isLoading }, sendMessage] = useAIConversation('inventoryChat');
return <AIConversation messages={messages} isLoading={isLoading} handleSendMessage={sendMessage} />;
}
// Lambda tools用のチャット(テキスト要約)
function SummaryChat() {
const [{ data: { messages }, isLoading }, sendMessage] = useAIConversation('summaryChat');
return <AIConversation messages={messages} isLoading={isLoading} handleSendMessage={sendMessage} />;
}
function App() {
const [activeTab, setActiveTab] = useState<'model' | 'query' | 'lambda'>('model');
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
<Authenticator>
{({ signOut }) => (
<div className="container mx-auto px-4 py-8 max-w-4xl">
<div className="bg-white rounded-lg shadow-lg">
{/* 3つのタブナビゲーション */}
<div className="flex border-b border-gray-200">
<button onClick={() => setActiveTab('model')} className={`flex-1 px-6 py-4 text-center font-medium transition-colors ${
activeTab === 'model' ? '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>Model Tools</span>
</div>
<p className="text-sm text-gray-500 mt-1">商品レビュー検索</p>
</button>
<button onClick={() => setActiveTab('query')} className={`flex-1 px-6 py-4 text-center font-medium transition-colors ${
activeTab === 'query' ? '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>Query Tools</span>
</div>
<p className="text-sm text-gray-500 mt-1">在庫確認</p>
</button>
<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 Tools</span>
</div>
<p className="text-sm text-gray-500 mt-1">テキスト要約</p>
</button>
</div>
{/* タブコンテンツ */}
<div className="p-6">
{activeTab === 'model' && <ReviewChat />}
{activeTab === 'query' && <InventoryChat />}
{activeTab === 'lambda' && <SummaryChat />}
</div>
</div>
</div>
)}
</Authenticator>
</div>
);
}
export default App;
この実装では、3つの異なるツールタイプを一つのアプリで体験できます。各タブは独立したチャットセッションを持ち、異なるツールセットとシステムプロンプトで動作します。
先にフロント側を実装してしまいましたが、もちろんバックエンド側も実装する必要があります。
Lambda toolsで複雑な処理を実装
Lambda toolsは、会話ハンドラーAWS Lambda関数内で実行されるツールを定義できます。これは、データスキーマに関連しないツールや、Lambda関数ランタイム内で簡単なタスクを実行するツールを定義したい場合に便利です。
カスタム会話ハンドラー関数の定義
// amplify/data/resource.ts(統合版:3つのツールタイプすべてを含む)
import { type ClientSchema, a, defineData, defineFunction } from "@aws-amplify/backend";
import { defineConversationHandlerFunction } from '@aws-amplify/backend-ai/conversation';
// 在庫確認Lambda関数の定義
export const checkInventory = defineFunction({
name: 'checkInventory',
entry: './checkInventory.ts',
});
// Lambda tools用の会話ハンドラー
export const chatHandler = defineConversationHandlerFunction({
entry: './chatHandler.ts',
name: 'customChatHandler',
models: [
{ modelId: a.ai.model("Claude 3.5 Sonnet") }
]
});
const schema = a.schema({
ProductReview: a.model({
productName: a.string().required(),
rating: a.integer().required(),
comment: a.string(),
purchaseVerified: a.boolean().default(false),
})
.authorization((allow) => [allow.owner()]),
// 在庫確認クエリ
checkInventory: a.query()
.arguments({ productId: a.string().required() })
.returns(a.customType({
stock: a.integer(),
nextDelivery: a.string(),
productName: a.string()
}))
.handler(a.handler.function(checkInventory))
.authorization((allow) => [allow.authenticated()]),
// Model tools用のチャット(商品レビュー検索)
chat: a.conversation({
aiModel: a.ai.model('Claude 3.5 Sonnet'),
systemPrompt: 'あなたはECサイトのショッピングアシスタントです。商品レビューに基づいてアドバイスを提供します。',
tools: [
a.ai.dataTool({
name: 'ProductReviewSearch',
description: '商品レビューを検索して、評価やコメントを取得します',
model: a.ref('ProductReview'),
modelOperation: 'list',
}),
],
})
.authorization((allow) => allow.owner()),
// Query tools用のチャット(在庫確認)
inventoryChat: a.conversation({
aiModel: a.ai.model('Claude 3.5 Sonnet'),
systemPrompt: 'あなたはECサイトの在庫管理アシスタントです。商品の在庫状況と入荷予定を確認できます。',
tools: [
a.ai.dataTool({
name: 'check_inventory',
description: '指定された商品IDの在庫数と入荷予定を確認します',
query: a.ref('checkInventory'),
}),
],
})
.authorization((allow) => allow.owner()),
// Lambda tools用のチャット(テキスト要約)
summaryChat: a.conversation({
aiModel: a.ai.model('Claude 3.5 Sonnet'),
systemPrompt: 'あなたはテキスト要約が得意なアシスタントです。長い文章を簡潔にまとめます。',
handler: chatHandler,
})
.authorization((allow) => allow.owner()),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: "userPool",
},
});
この統合版のresource.tsでは、3つのToolsタイプをすべて1つのファイルで定義しています。
handler: chatHandler
でLambda関数のハンドラーを渡すだけなので、一番シンプルですね。
実行可能なツールとハンドラーの定義
新しいファイルamplify/data/chatHandler.ts
を作成します。
// amplify/data/chatHandler.ts
import {
ConversationTurnEvent,
createExecutableTool,
handleConversationTurnEvent
} from '@aws-amplify/backend-ai/conversation/runtime';
const jsonSchema = {
json: {
type: 'object',
properties: {
'text': {
'type': 'string',
'description': '要約したいテキスト'
},
'maxLength': {
'type': 'number',
'description': '要約の最大文字数(デフォルト: 200)',
'minimum': 50,
'maximum': 1000
}
},
required: ['text']
}
} as const; // constアサーションで型推論を有効にする
const textSummarizer = createExecutableTool(
'textSummarizer',
'長いテキストを指定した文字数以内で要約します',
jsonSchema,
(input) => {
const { text, maxLength = 200 } = input;
if (!text || text.trim().length === 0) {
throw new Error('要約するテキストが空です');
}
// シンプルな要約ロジック(実際のプロダクションではより高度なAIを使用)
const sentences = text.split(/[.。!?!?]/).filter(s => s.trim().length > 0);
if (text.length <= maxLength) {
return Promise.resolve({ text: `元テキストがすでに${maxLength}文字以内です。\n\n${text}` });
}
// 最初の数文を取って簡易要約を作成
let summary = '';
for (const sentence of sentences) {
if ((summary + sentence).length > maxLength - 20) break;
summary += sentence.trim() + '。';
}
if (summary.length === 0) {
summary = text.substring(0, maxLength - 3) + '...';
}
return Promise.resolve({
text: `【要約】(${summary.length}文字)\n${summary}`
});
},
);
export const handler = async (event: ConversationTurnEvent) => {
await handleConversationTurnEvent(event, {
tools: [textSummarizer],
});
};
このchatHandler.tsは、Lambda tools用の核となるコードです。createExecutableTool
を使ってテキスト要約機能を実装しています。JSONスキーマでLLMにツールの使い方を説明し、実際の要約処理を行っています。
インプットが空であるとエラー、200文字以内なら元テキストが・・・
とエラーメッセージを返却するようにしています。
backend.tsの更新
amplify/backend.ts
ファイルを更新して、新しい会話ハンドラーを追加します。
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data, checkInventory, chatHandler } from './data/resource';
const backend = defineBackend({
auth,
data,
checkInventory,
chatHandler
});
作成した会話ハンドラーを追加します。これで3つのツールタイプがすべて使えるようになりました。
実際にテキスト要約ツールを使ってみる
チャット画面でテキストの要約をリクエストしてみましょう。
返ってきた要約は微妙でしたが、78文字に短縮しましたね。
後は空のテキストを要約して
と送ると、text
パラメータを指定する必要があると怒られました。
意図通りの反応ですが、関数名とパラメータまで返されるのはちょっと嫌ですね・・・
全く関係ないエンドユーザーから内部の仕様が見えるのは好ましくないですね。
200文字以内だと処理通り要約しないですね。実装通りでいいですね!
このextSummarizerツールの例で入力が無効な場合(空のテキストなど)にエラーをスローしています。このエラーは会話ハンドラー関数によってLLMに通知されます。エラーメッセージに応じて、LLMは異なる入力でツールを再度使用しようとしたり、ユーザー向けのテキストでレスポンスを完了させています。
Lambda toolsを使うことで、データスキーマに依存しないロジックを実装でき、AIアプリケーションをカスタマイズ可能にしていますね!
AWSサービスとの連携
カスタムクエリを定義し、関数ハンドラーでそのサービスを呼び出すことで、任意のAWSサービスに接続できます。Lambda関数に適切な権限を付与する必要があります。例えばS3と連携するケースを考えてみます。
import { defineBackend } from "@aws-amplify/backend";
import { auth } from "./auth/resource";
import { data } from "./data/resource";
import { storage } from "./storage/resource";
import { getWeather } from "./functions/getWeather/resource";
import { PolicyStatement } from "aws-cdk-lib/aws-iam";
const backend = defineBackend({
auth,
data,
storage,
getWeather
});
backend.getWeather.resources.lambda.addToRolePolicy(
new PolicyStatement({
resources: ["arn:aws:s3:::my-bucket/*"],
actions: ["s3:GetObject"],
}),
)
これで、S3バケットからデータを取得するような処理も、LLMのツールとして実装できるようになります。
Amplifyがバックグラウンドで何をしているのか
会話ルートのツールを定義すると、Amplifyは重い処理を引き受けます。
-
LLMへのツールの説明: 各ツール定義は、スキーマで定義されたAmplifyモデルクエリまたはカスタムクエリです。Amplifyはそのツールに必要な入力パラメータを知っており、それらをLLMに説明します
-
適切なパラメータでツールを呼び出す: LLMが必要な入力パラメータでツールの使用を要求した後、会話ハンドラーLambda関数がツールを呼び出し、結果をLLMに返し、会話を続けます
-
呼び出し元のアイデンティティと認証の維持: ツールを通じて、LLMはアプリケーションユーザーがアクセスできるデータにのみアクセスできます
これらの処理が自動的に行われるため、開発者は本質的なビジネスロジックの実装に集中できるわけです。
セキュリティに関する考慮事項
実装時に気になった点として、LLMがツールを呼び出す際、ツール名や引数がユーザーにも見えてしまうことがあります。本格的な本番運用時には、この点についても考慮が必要かもしれません。特に内部システムの構造や機密情報が推測されうる場合は、ツールの命名や応答内容に注意を払う必要があります。
隠す方法ないのかなと思って調べたのですが見つからず、特にそういったプロパティはなかったので、プロンプトを工夫したら何とかなるかなぁと疑問に思いました。
もしいい方法をご存知の方いたら教えてください!!私も思いついたら検証してみます!!
合わせて公式ドキュメントのベストプラクティスを見ると下記記載がありました。
入力の検証とサニタイズ: LLMからの入力は、アプリケーションで使用する前に検証およびサニタイズしてください。例えば、データベースクエリで直接使用したり、
eval()
で実行したりしないでください適切なエラーハンドリング: エラーを適切に処理し、意味のあるエラーメッセージを提供してください
ログとモニタリング: ツールの使用状況をログに記録し、監視して、潜在的な誤用や問題を検出してください
1に記載があるようにユーザーからの入力には前もって検証する必要があるなと感じました。
3つのToolsタイプのまとめ
- Model tools: 既存のAmplifyデータモデルをそのままLLMに使わせることができ、既存プロジェクトへのAI機能追加が非常に簡単
- Query tools: カスタムクエリで外部APIやシステムとの連携が可能で、実用的なデータ取得に最適
- Lambda tools: データスキーマに依存しないロジックを実装でき、AIアプリケーションのカスタマイズが可能
今回作成した統合デモアプリでは、1つのアプリケーション内で3つのツールタイプすべてを体験でき、それぞれの特徴と使い分けを理解できるようになっています。
おわりに
Query toolsやLambda toolsを組み合わせることで、外部APIとの連携や複雑な処理も実装できるのでは魅力的だなと感じました!
今回のサンプルコードが気になったらぜひお試しください!
今後も、Amplify AI Kitについてドシドシ発信していきたいと思います!
最後までご覧いただきありがとうございましたー!