Amazon Web Services ブログ

Generative AI Use Cases JP をカスタマイズする方法

このブログでは、さまざまな 生成 AI を活用したビジネスユースケースをデモンストレーションしている Generative AI Use Cases JP をカスタマイズする方法についてご紹介します。Amazon Bedrock、Amazon Kendra などを利用して、Generative AI Use Cases JP はさまざまなビジネスユースケースを公開しています。
その Generative AI Use Cases JP の Web UI を利用することで簡単に新しいユースケースを追加することが可能です。
このデモンストレーションでは、SQL を記述する工数を削減したいデータアナリストをターゲットに、Amazon Bedrock の 基盤モデル を用いてSQL を生成するユースケースを作成してみます。
今回作成するユースケースで用いる 基盤モデル は Claude v2 を利用します。

ユースケースを作成し始める前に、基盤モデル で SQL を生成可能かチャットで確認する

ユースケースを作成する前に、そもそも 基盤モデル を利用して SQL 生成が可能かどうかをチェックします。
具体的には、チャットで意図した SQL が生成できるかを検証してみます。
ここでは、人間が記述するのが面倒な、長い Case 文を自然言語で簡単に出力できるか確認してみます。

SQL 生成のために 基盤モデル に入力すべき情報

有用な SQL を出力するために入力すべき必要な情報を整理してみます。

  1. テーブル定義
    基盤モデルは社内データベースのスキーマ情報を知りません。
    基盤モデルにテーブル定義を渡すと各カラムの type や range、コメント などを参照して正確な SQL の出力を期待できます。
  2. 出力したい SQL を説明する自然言語
    普段使用している自然言語で出力させたい SQL を説明します。

チャットに入力

チャットに入力すべき情報 1, 2 を Claude v2 に入力していきます。
まずは基盤モデルに役割とタスクを与えるため、システムコンテキストに以下を入力します。システムコンテキストとは、Claude に質問したりタスクを与える前に特定の目標や役割を指定するなど、Claude にコンテキストと指示を提供する方法です。詳細については Anthoropic 社の システムプロンプトの使用方法 を参照してください。

システムコンテキスト

  • Claude2 に役割とタスクを与える
    • 役割: ユーザーの指示をよく理解する熟練のデータベーススペシャリスト
    • タスク: を守って、可読性の高い SQL だけを出力してください
  • Claude2として利用する Claude が理解しやすい XML タグを用いて記述する。XML タグは自由に定義してよい。
    • RDB のスキーマ情報: <schemas></schemas>
    • Claude v2 の出力例: <examples></examples>
以下はユーザーと AI のやりとりです。
ユーザーは AI に <schemas></schemas> の xml タグで囲って RDB のスキーマ情報を渡します。
さらに、<input></input> の xml タグで囲って AI に記述して欲しい SQL の説明を渡します。
AI は、ユーザーの指示をよく理解する熟練のデータベーススペシャリストなので、以下の <rules></rules> を守って、可読性の高い SQL だけを出力してください。
<rules>
* <schemas> と <input> の情報を頼りに、ユーザーが求める SQL を ANSI SQL に準拠して出力してください。
* join する場合はテーブル名に別名をつけた上で、列名は必ず `別名.列名` と表記してください。
* GROUP BY や ORDER BY 句には、必ず `列名` あるいは `別名.列名`を使用することを遵守してください。列番号の使用は修正が難しくなるので禁止です。
</rules>
出力は 
<output>```sql
{SQL}
```</output>
の形式を遵守してください。
SQL のコード以外を出力してはいけません。解説なども出力してはいけません。
出力例を <examples></examples> で与えます。

<examples>
<output>```sql
SELECT * FROM v_schedule;
```</output>
<output>```sql
SELECT 
  e.id AS employee_id, 
  e.name AS employee_name, 
  c.id AS company_id, 
  c.name AS company_name
FROM
  employees e
JOIN
  companies c ON c.id = e.company_id
;
```</output>
<output>```sql
SELECT
  id,
  COUNT(price),
  SUM(price),
  MIN(price),
  MAX(price)
FROM
  transaction t
GROUP BY
  t.id
;
```</output>
</examples>

上記の通り、ユーザーは <schemas><input> を与えれば Claude v2が SQL を出力してくれるはずです。ここでは Amazon Athena を前提とした DDL と、作成したい SQL の内容をチャットに入力します。

<schemas>
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name VARCHAR(50) NOT NULL,
  gender VARCHAR(10) NOT NULL,
  age SMALLINT NOT NULL,
  CONSTRAINT users_pk PRIMARY KEY (id)
);
COMMENT ON COLUMN users.id IS 'ユーザーID';
COMMENT ON COLUMN users.name IS 'フルネーム'; 
COMMENT ON COLUMN users.gender IS 'Male, Female, Other のいずれかが格納';
COMMENT ON COLUMN users.age IS '年齢';

CREATE TABLE user_logs (
  request_id BIGSERIAL PRIMARY KEY,
  request_time TIMESTAMP NOT NULL, 
  url TEXT NOT NULL,
  id SERIAL NOT NULL REFERENCES users(id) 
);
COMMENT ON COLUMN user_logs.request_id IS 'ログレコードのユニークID';
COMMENT ON COLUMN user_logs.request_time IS 'リクエストした時刻';
COMMENT ON COLUMN user_logs.url IS 'リクエストしたURL';
COMMENT ON COLUMN user_logs.user_id IS 'ユーザーID'; 
</schemas>

<input>
年齢と性別で層分けした属性情報で、年月毎のリクエスト回数の合計を集計してください。
年齢は20歳未満、20-29、30-39、…、70-79, 80以上で分けてください。
性別は、Male, Female, Other で分けてください。
</input>

チャット UI 上ではこのように表示されます。

このプロンプトを利用してSQL 生成ユースケースを作成します。

SQL 生成ユースケースの開発

ブランチを作成する

アプリ開発をする上でバージョン管理は大切です。
SQL 生成のユースケースを作成するためにブランチを作成します。
以下のコマンドを使用してください。
以下のコマンドは feat/generate-sql ブランチを作成し、このブログ執筆時の最新のコミットを指定しています(Commit ID: 37b29d2)。

※注意) ブログ執筆時は上記コードが動きましたがメンテナンスされませんのでご注意ください。この成果物は後述の通り main ブランチを追従しない、かつ main ブランチにもマージされません。お試しや開発のヒントとしてお使いください。

git checkout -b feat/generate-sql
git reset --soft `37b29d2`

ローカル開発環境を立ち上げる

フロントエンドの開発をするにあたり、開発環境を整えます。
Generative AI Use Cases JP をデプロイしていない場合、こちらのリンクを参考にしてまずはデプロイしてください。
今回は開発体験も考慮して、ローカル開発用サーバーを立て、ローカル環境でSQL生成ユースケースを確認できるようにします。
Unix 系コマンドが使えるPC であれば (Linux や MacOS 等)、以下のコマンドでローカル環境が立ち上げられます。

npm run web:devw

http://localhost:5173 にアクセスすればローカルで開発したフロントエンドをすぐに確認することができます。
コマンドの設定については /package.json を参照してください。

メニューに追加

まずは、専用のユースケースのリンクを作成します。
packages/web/src/App.tsx でユースケースリンク一覧を作成しているので、追記することで SQL 生成のリンクを追加することができます。

カスタマイズ方法として、本ブログの Diff と書かれた内容の中で、行頭に+があればその行を追加し、行頭に-があればその行を削除して下さい。

packages/web/src/App.tsx

…(前略)…
    PiX,
+   PiTerminal,
  } from 'react-icons/pi';

…(中略)…

    {
      label: '画像生成',
      to: '/image',
      icon: <PiImages />,
      display: 'usecase' as const,
    },
+   {
+     label: 'SQL 生成',
+     to: '/generate-sql',
+     icon: <PiTerminal />,
+     display: 'usecase' as const,
+   },
    {
      label: '音声認識',
      to: '/transcribe',
      icon: <PiSpeakerHighBold />,
      display: 'tool' as const,
    },
…(後略)…

リンク先を用意する

作成したリンクは当然存在しないので 404 が表示されます。

まずはリンク先を用意しましょう。

packages/web/src/main.tsx でリンク先を設定します。
packages/web/src/main.tsx

…前略…
  import GenerateImagePage from './pages/GenerateImagePage';
+ import GenerateSqlPage from './pages/GenerateSqlPage';
  import TranscribePage from './pages/TranscribePage';
…中略…
      path: '/image',
      element: <GenerateImagePage />,
    },
+   {
+     path: '/generate-sql',
+     element: <GenerateSqlPage />,
+   },
    {
      path: '/transcribe',
      element: <TranscribePage />,
…後略…

以上で、 「/generate-sql をクリックしたときは GenerateSqlPage.tsx を参照する」という設定が出来上がりました。
ただし、 GenerateSqlPage.tsx が無いため、画面側で以下のようなエラーが発生しているはずです。

[plugin:vite:import-analysis] Failed to resolve import "./pages/GenerateSqlPage" from "src/main.tsx". Does the file exist?
…後略…

ページを作成する

ここから SQL 生成ユースケースのページを作成していきます。
SQL 生成の場合は、「テーブルを定義する DDL」を入力するフォームと、「そのテーブルからクエリを生成するための指示」を入力するフォームの二つが必要となります。すでに文書生成ユースケースで同様の UI が作成されているため、こちらのページの UI をそのまま利用して作成していきます。


以下コマンドで、文書生成のページを複製します。

# プロジェクトのルートディレクトリで実行
cp packages/web/src/pages/GenerateTextPage.tsx packages/web/src/pages/GenerateSqlPage.tsx

複製した、packages/web/src/pages/GenerateSqlPage.tsx を編集していきます。
packages/web/src/pages/GenerateSqlPage.tsx

…前略…
  import { create } from 'zustand';
+ import { generateSqlPrompt } from '../prompts';
+ import { GenerateSqlPageLocationState } from '../@types/navigate';
- import { generateTextPrompt } from '../prompts';
- import { GenerateTextPageLocationState } from '../@types/navigate';
  import { SelectField } from '@aws-amplify/ui-react';
…中略…
  clear; () => void;
  };

+ const useGenerateSqlPageState = create<StateType>((set) => {
- const useGenerateTextPageState = create<StateType>((set) => {
   const INIT_STATE = {
…中略…
    };
  }); 
+ const GenerateSqlPage: React.FC = () => {
- const GenerateTextPage: React.FC = () => {
    const {
      modelId,
…中略…
      setText,
      clear,
+   } = useGenerateSqlPageState();
-   } = useGenerateTextPageState();
    const { state, pathname } =
+     useLocation() as Location<GenerateSqlPageLocationState>;
-     useLocation() as Location<GenerateTextPageLocationState>;
    const { loading, messages, postChat, clear: clearChat } = useChat(pathname);
    const { setTypingTextInput, typingTextOutput } = useTyping(loading);
…中略…
    useEffect(() => {
      if (state !== null) {
+       setInformation(state.schemas);
+       setContext(state.instruction);
-       setInformation(state.information);
-       setContext(state.context);
      }
    }, [state, setInformation, setContext]);
…中略…
      }
    }, [modelId, availableModels, setModelId]);
+   const getGeneratedSql = (
-   const getGeneratedText = (
      modelId: string,
+     schemas: string,
+     instruction: string
-     information: string,
-     context: string
    ) => {
      postChat(
+       generateSqlPrompt.generatePrompt({
+         schemas,
+         instruction,
-       generateTextPrompt.generatePrompt({
-         information,
-         context,
        }),
        true,
        textModels.find((m) => m.modelId === modelId)
…中略…
    const onClickExec = useCallback(() => {
      if (loading) return;
+     getGeneratedSql(modelId, information, context);
-     getGeneratedText(modelId, information, context);
    }, [modelId, information, context, loading]);
…中略…
      <div className="grid grid-cols-12">
        <div className="invisible col-span-12 my-0 flex h-0 items-center justify-center text-xl font-semibold print:visible print:my-5 print:h-min lg:visible lg:my-5 lg:h-min">
+        SQL 生成
-        文章生成
        </div>
        <div className="col-span-12 col-start-1 mx-2 lg:col-span-10 lg:col-start-2 xl:col-span-10 xl:col-start-2">
…中略…
            <Textarea
+             placeholder="SQL 生成に必要なテーブル情報 (create table DDL) を入力してください"
-             placeholder="入力してください"
              value={information}
…中略…
            <Textarea
+             placeholder="生成する SQL の説明を入力してください。"
-             placeholder="文章の形式を指示してください。(マークダウン、ブログ、ビジネスメールなど)"
              value={context}
…中略…
+ export default GenerateSqlPage;
- export default GenerateTextPage;

ここでは以下 2 点の修正を主にしています。

  1. 画面で表示されている文書生成用の説明文字列を SQL 生成用に修正しています。
  2. 内部で利用している変数名を文書生成から SQL 生成用に修正しています。この変更は、実際に運用していくにあたり可読性や保守性を上げると言う観点で修正しています。

ただし、変数名を変更することによって、他に変数を定義・利用している部分で TypeScript のエラーが以下のように発生しています。

[{
    "resource": "generative-ai-use-cases-jp/packages/web/src/pages/GenerateSqlPage.tsx",
    "owner": "typescript",
    "code": "2305",
    "severity": 8,
    "message": "モジュール '\"../prompts\"' にエクスポートされたメンバー 'generateSqlPrompt' がありません。",
    "source": "ts",
    "startLineNumber": 11,
    "startColumn": 10,
    "endLineNumber": 11,
    "endColumn": 27
},{
    "resource": "generative-ai-use-cases-jp/packages/web/src/pages/GenerateSqlPage.tsx",
    "owner": "typescript",
    "code": "2724",
    "severity": 8,
    "message": "'GenerateSqlPageLocationState' という名前のエクスポートされたメンバーが '\"../@types/navigate\"' に含まれていません。候補: 'GenerateTextPageLocationState'",
    "source": "ts",
    "startLineNumber": 12,
    "startColumn": 10,
    "endLineNumber": 12,
    "endColumn": 38
}]

変数定義の修正

packages/web/src/pages/GenerateSqlPage.tsx で使用している、schemas 変数と instruction 変数の定義を行います。
これは、より型安全にするために ReactRouter の useLocation に型定義を行っています。
packages/web/src/@types/navigate.d.ts

    context: string;
  };
+ export type GenerateSqlPageLocationState = {
+   schemas: string;
+   instruction: string;
+ };
  export type RagPageLocationState = {
    content: string;

システムコンテキストとユーザープロンプトの埋め込み

最後に用意したプロンプトを SQL 生成ユースケースのページにも適用させます。
prompt は、packages/web/src/prompts/index.ts で制御しています。
このファイルは各ページのコンテキストを定義しています。このファイルに先ほど作成したシステムコンテキストを定義し、ユーザの入力を受け取り、Claude v2 に入力するためのプロンプトを作成するための処理を追加します。
packages/web/src/prompts/index.ts

…前略…
    '/generate': 'あなたは指示に従って文章を作成するライターです。',
+   '/generate-sql': `以下はユーザーと AI のやりとりです。
+ ユーザーは AI に <schemas></schemas> の xml タグで囲って RDB のスキーマ情報を渡します。
+ さらに、<input></input> の xml タグで囲って AI に記述して欲しい SQL の説明を渡します。
+ AI は、ユーザーの指示をよく理解する熟練のデータベーススペシャリストなので、以下の <rules></rules> を守って、SQL だけを出力してください。
+ <rules>
+ * <schemas> と <input> の情報を頼りに、ユーザーが求める SQL を ANSI SQL に準拠して出力してください。
+ * join する場合はテーブル名に別名をつけた上で、列名は必ず \`別名.列名\` と表記してください。
+ * GROUP BY や ORDER BY 句には、必ず \`列名\` あるいは \`別名.列名\`を使用することを遵守してください。列番号の使用は修正が難しくなるので禁止です。
+ </rules>
+ 出力は 
+ <output>```sql
+ {SQL}
+ ```</output>
+ の形式を遵守してください。
+ SQL のコード以外を出力してはいけません。解説なども出力してはいけません。
+ 出力例を <examples></examples> で与えます。
+ 
+ <examples>
+ <output>```sql
+ SELECT * FROM v_schedule;
+ ```</output>
+ <output>```sql
+ SELECT 
+   e.id AS employee_id, 
+   e.name AS employee_name, 
+   c.id AS company_id, 
+   c.name AS company_name
+ FROM
+   employees e
+ JOIN
+   companies c ON c.id = e.company_id
+ ;
+ ```</output>
+ <output>```sql
+ SELECT
+   id,
+   COUNT(price),
+   SUM(price),
+   MIN(price),
+   MAX(price)
+ FROM
+   transaction
+ GROUP BY
+   id
+ ;
+ ```</output>
+ </examples>
+ `,
'/translate': 'あなたは文章の意図を汲み取り適切な翻訳を行う翻訳者です。',
…中略…
+ export type GenerateSqlParams = {
+   schemas: string;
+   instruction: string;
+ };
+ export const generateSqlPrompt = {
+   generatePrompt: (params: GenerateSqlParams) => {
+     return `<schemas>
+ ${params.schemas}
+ </schemas>
+ <input>
+ ${params.instruction}
+ </input>`;
+   },
+ }; 
<EOF> 

このコードで行っていることは以下の 2 つです。

  • 先程作成した prompt を変数に格納
  • 入力されたテキストを受け取って、<schemas><input> タグに埋め込む

動作確認

最後に、動作確認をします。
本ブログ上部で入力した DDL (<schemas> タグの内側のテキスト )と、定義した テーブルで生成したい SQL (<input> タグの内側のテキスト) を入力します。

実行ボタンをクリックすると、次のような SQL が生成されます。


このように既存の API を利用して簡単に ユースケース を追加することができました。
API をいじるようなケースはまた別途開発が必要ですが、既存の UI を流用して ユースケースを追加するだけであればこのように簡単に作成することができます。

まとめ

本記事では、Generative AI Use Cases JP の既存の UI 、API を利用して、ユースケースを追加する方法についてご紹介しました。文書生成ページを流用して、SQL 生成ページを作成しました。ページ追加の方法や、システムコンテキストの変更方法などを併せて紹介しました。SQL 生成ユースケースとして、実際にはテーブル定義を毎回書くのは大変です。そのため、例えば前回書いたテーブル定義をlocalStorage 等に保存できるようにし、次回利用時には自動で入力された状態の方が便利かもしれません。さらに発展し、DB やテーブル一覧を API で取得しテーブル定義を自動でインポートできるようになると、利便性が格段に向上します。このように、今回の SQL 生成ユースケース一つをとっても、まだまだ拡張の余地があります。
今回のカスタマイズの方法をもとにして、独自のユースケースを作成してみましょう。

この記事のコードサンプルについては、GitHub リポジトリをご覧ください。

著者プロフィール

呉 和仁 (Go Kazuhito) は AWS Japan の機械学習ソリューションアーキテクト。 IoT の DWH 開発、データサイエンティスト兼業務コンサルタントを経て現職。プログラマの三大美徳である怠惰だけを極めてしまい、モデル構築を怠けられる AWS の AI サービスをこよなく愛す。

 

 

鈴木 大樹 (Daiki Suzuki) はAWS Japan のソリューションアーキテクト。データベース領域を得意としており、機械学習領域と絡めたソリューションの作成を行なっています。