はじめに
3大クラウドAIエージェント勉強会に参加して、「マルチAIエージェントを作るハードルは、自分が思っているよりもはるかに低いのでは?」と感じて、実際に作ってみました。下記記事の続きです。
プロンプトを作る
まずは、エージェントに期待する振る舞いを定義するプロンプトから作成します。
# ルートエージェント
ROOT_AGENT_INSTR="""
あなたはエンジニア向けに書籍を提案することを本質とする、自律性を持つ書籍コンシェルジュです。
あなたの役割は、読者が興味を持つ書籍を推薦することです。
提案のプロセスは、以下のように進めてください。原則、全て日本語で応答してください。
過去にどのような本を読んだかは質問に含めないでください。
1. 読者がすでに読んだ書籍を再読したいのか、新しい書籍を探しているのかを推論し、判断がつかない場合は確認する
- すでに読んだ書籍を再読したい場合、以降の処理はサブエージェント`re_read_agent`に委任する
2. 読者の興味やニーズを理解するための質問を行う。
- 「過去に読んだ本リスト」から、読者の興味やニーズを推論して質問する
- 直接過去に読んだ本を読者に尋ねるのではなく、読者の興味やニーズを引き出す質問を行う
- 例: 「最近興味を持っている技術やトピックはありますか?」や「どのような技術書を探していますか?」など
3. ここまでの応答のコンテキストから、ジャンルを以下の中からどれを望んでいるか推論する。判断がつかない場合は質問する
- プログラミング言語
- ソフトウェア開発
- システム設計
- データベース
- ネットワーク
- セキュリティ
- クラウドコンピューティング
- 機械学習
- アジャイル開発
- DevOps
- プロジェクト管理
- UI/UXデザイン
- 組織論
- その他の技術書
4. 読者の興味に基づいて、書籍を3冊推薦する。ただし、「過去に読んだ本リスト」に含まれないものに限る
5. 推薦する書籍の情報を以下の形式で提供する
書籍情報の形式:
```
1. No
- 1から順に連番を振ってください
2. 書籍のタイトル
- 書籍のタイトルを記載してください
3. 著者名
- 書籍の著者名を記載してください
4. 出版社
- 書籍の出版社名を記載してください
5. ジャンル
- 書籍のジャンルを記載してください
- ジャンルは、上記のリストから選択してください
- 複数のジャンルがある場合は、カンマ区切りで記載してください
- 例: プログラミング言語, ソフトウェア開発
- ジャンルが不明な場合は「不明」と記載してください
6. 書籍の概要
- 書籍の内容や特徴を簡潔に100文字くらいで説明してください
7. 推薦理由
- なぜこの書籍を推薦するのか、その理由を記載してください
- 読者の興味やニーズにどのように合致しているかを説明してください
8. 価格(任意)
- 書籍の価格を記載してください
- 価格が不明な場合はこの項目は記載しないでください
```
6. 選定した書籍に対し、読者へ興味がある本の有無を尋ねる
- 読者が興味を示す本がなかった場合は、再度4.から繰り返す
7. 読者が興味を示す本があった場合、書籍の購入意思を確認する。
8. 購入意思を示した場合は、サブエージェント`purchase_agent`に委任する
"""
# 過去の書籍からの再読を希望する場合のプロンプト
RE_READ_INSTR="""
あなたは読者が過去に読んだ書籍の中から、再読したい書籍を提案する役割を持つ書籍コンシェルジュです。
読者が再読を希望する書籍を特定するために
提案のプロセスは、以下のように進めてください。原則、全て日本語で応答してください。
1. 前回読んでからどのくらい時間を空けているかを尋ねます。
2. 質問の文脈から、読みたいジャンルのコンテキストを推論できない場合
-「過去に読んだ本リスト」を要約してジャンルの一覧を生成してください。
- 生成したジャンルの中から、どのジャンルを再読したいかを尋ねてください。
3. 推薦する書籍の情報を以下の形式で提供してください。
書籍情報の形式:
```
1. 書籍のタイトル
2. 著者名
3. 出版社
4. ジャンル
5. 書籍の概要
- 書籍の内容や特徴を簡潔に100文字くらいで説明
6. 推薦理由
```
"""
# 購買エージェント
PURCHASE_AGENT_INSTR = """
あなたは書籍の購入をサポートするエージェントです。
読者が推薦された書籍を購入するための情報を提供します。
提案のプロセスは、以下のように進めてください。原則、全て日本語で応答してください。
1. 読者が推薦された書籍の中から購入したい書籍を選択するように促します。
2. 選択された書籍を取り扱っているオンライン書店の、該当する商品ページのURLを提供します。
"""
今回はエージェントを3つ使用するため、それぞれに役割を割り当てます。ここが実装で最も時間のかかるポイントです。
プログラムで読書履歴を扱う
生成AIだけで完結してしまうとやや味気ないため、今回はBigQueryに読書履歴のデータを持たせます。
まず、テーブルとデータ投入を行います。
CREATE TABLE book_recommendations.read_books (
user_id STRING NOT NULL,
book_title STRING NOT NULL,
author STRING,
read_date DATE
);
INSERT INTO `book_recommendations.read_books` (user_id, book_title, author, read_date) VALUES
('1','SQLアンチパターン 第一版','Bill Karwin','2023-12-06'),
('1','エリック・エヴァンスのドメイン駆動設計','Eric Evans','2024-06-17'),
('1','プリンシプル オブ プログラミング 3年目までに身につけたい 一生役立つ101の原理原則','上田勲','2024-06-24'),
('1','手を動かしてわかるクリーンアーキテクチャ ヘキサゴナルアーキテクチャによるクリーンなアプリケーション開発','Tom Hombergs','2024-09-30'),
('1','Clean Architecture 達人に学ぶソフトウェアの構造と設計','Robert C.Martin','2024-09-30'),
('1','セキュア・バイ・デザイン 安全なソフトウェア設計','Dan Bergh Johnsson,Daniel Deogun,Daniel Sawano','2024-11-30'),
('1','エクストリームプログラミング','Kent Beck,Cynthia Andres','2024-12-04'),
('1','Tidy First? ―個人で実践する経験主義的ソフトウェア設計','Kent Beck','2025-02-07'),
('1','テスト駆動開発','Kent Beck','2025-03-02')
;
次に、このデータを検索するプログラムと、プロンプトへ検索結果を反映させる処理を書きます。
# 読書履歴検索サービス
import json
from google.cloud import bigquery
from book_concierge.prompt import RE_READ_INSTR, ROOT_AGENT_INSTR
class BookConciergeService:
"""
BigQueryを使用して、読者の過去の書籍履歴を検索するサービスです。
このサービスは、読者が過去に読んだ書籍の情報をBigQueryから取得し、
生成AIがその情報を元に書籍を推薦するためのデータを提供します。
"""
def __init__(self):
"""
コンストラクタ
BigQueryのクライアントを初期化します。
"""
self.client = bigquery.Client()
def get_read_books(self, user_id: str = "1") -> str:
"""
指定されたユーザーIDに基づいて、過去に読んだ書籍の情報をBigQueryから取得し,
生成AIが読み取りやすいようにJSON形式で返します。
Args:
user_id (str): ユーザーの一意の識別子
Returns:
str: 過去に読んだ書籍の情報のリスト
"""
query = """
SELECT book_title, author, read_date
FROM `book_recommendations.read_books`
WHERE user_id = @user_id
"""
job_config = bigquery.QueryJobConfig(
query_parameters=[
bigquery.ScalarQueryParameter("user_id", "STRING", user_id)
]
)
query_job = self.client.query(query, job_config=job_config)
results = query_job.result()
return json.dumps([{
"book_title": row.book_title,
"author": row.author,
"read_date": row.read_date.isoformat()
} for row in results], ensure_ascii=False)
def build_root_prompt(self) -> str:
sql_result_json = self.get_read_books()
return ROOT_AGENT_INSTR + f"\n過去に読んだ本のリスト: {sql_result_json}\n"
def build_re_read_prompt(self) -> str:
sql_result_json = self.get_read_books()
return RE_READ_INSTR + f"\n過去に読んだ本のリスト: {sql_result_json}\n"
エージェント実装
最後に、エージェントを実装します。
from google.adk.agents import Agent
from book_concierge.prompt import PURCHASE_AGENT_INSTR
from book_concierge.service import BookConciergeService
service = BookConciergeService()
root_agent = Agent(
name="book_concierge_agent",
description="書籍提案エージェント",
model="gemini-2.0-flash",
instruction=service.build_root_prompt(),
sub_agents=[
Agent(
name="re_read_agent",
description="過去の書籍から再読を希望する場合のエージェント",
model="gemini-2.0-flash",
instruction=service.build_re_read_prompt(),
),
Agent(
name="purchase_agent",
description="書籍の購入をサポートするエージェント",
model="gemini-2.0-flash",
instruction=PURCHASE_AGENT_INSTR,
),
]
)
ポイントは sub_agents
の設定値です。ここで各々の処理に特化したエージェントを設定します。
また、メインプロンプト側ではどのような時にどのサブエージェントを使用するか、ROOT_AGENT_INSTR
内で以下のように記述しています。
ROOT_AGENT_INSTR="""
# (前略)
1. 読者がすでに読んだ書籍を再読したいのか、新しい書籍を探しているのかを推論し、判断がつかない場合は確認する
- すでに読んだ書籍を再読したい場合、以降の処理はサブエージェント`re_read_agent`に委任する
# (中略)
7. 読者が興味を示す本があった場合、書籍の購入意思を確認する。
8. 購入意思を示した場合は、サブエージェント`purchase_agent`に委任する
"""
# (後略)
設定ファイル
動作確認に向けて、GoogleのVertexAIとBigQueryを使えるよう、以下のファイルを設定します。
GOOGLE_GENAI_USE_VERTEXAI="FALSE"
GOOGLE_API_KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
GOOGLE_APPLICATION_CREDENTIALS="xxx/service-account.json" # BigQuery用認証情報のパス
{
"type": "service_account",
"project_id": "project-id",
"private_key_id": "project-key",
"private_key": "-----BEGIN PRIVATE KEY-----\nXXXX\nYYYYY\n-----END PRIVATE KEY-----\n",
"client_email": "xxxxx-compute@developer.gserviceaccount.com",
"client_id": "999999999",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/xxxxx-compute%40developer.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
service-account.json
は、Google Cloudの「I AM」(Identity and Access Management)→「サービスアカウント」から鍵を作成し、ダウンロードしたファイルをそのまま格納しましょう。
動作確認
adk web
コマンドを実行して、実際の動作を確認します。
未読の本を読みたいとAIに伝えてみる
「未読の本(BigQueryに登録されていない本)を読みたい」方針でチャットしてみます。
無事AIと対話を通して、本を推薦してもらえました。
ポイントはチャット内の transfer_to_agent
の部分で、このタイミングでサブエージェントへ以降のやり取りを委任するよう切り替わっています。
ただ、残念なことに最後のURLだけはハルシネーションが起こっていて、有効なURLではありませんでした。ここの精度を高めるにはもう少し工夫が必要ですが、基本的にはプログラミングよりプロンプティングや最適なAIツールあるいはモデルを選択すると良いでしょう。
過去に読んだ本を再読したいとAIに伝えてみる
今度は「既読の本(BigQueryに登録されている本)を読みたい」方針でチャットしてみます。
こちらもきちんと transfer_to_agent
が行われ、BigQueryから取得した書籍を提案してくれました。
ただ、ちょっと残念なのは事前に「ソフトウェア設計の復習」と伝えているのに re_read_agent
がそれを汲み取ってくれない点です。もう少し工夫すると、もっとスムーズな会話で済ませられるかもしれません。
最後に
マルチAIエージェントを実際に作って動かしましたが、実装のハードルは決して高くないように感じました。特に、プログラミングはほとんどいらず、いかにプロンプティングするかが鍵となることを理解できました。
生成AIをシステムへ組み込むプロジェクトも既に珍しくはないと思いますが、今後のエンジニアには、プロンプティング能力も重要なスキルの一つであると強く感じました。
宣伝
弊社ブレインパッドでは、生成AIの力によって、これまでの検索体験を大きく変えるRtoaster GenAIを提供しています。
最近、私もこのプロダクト開発に関与し始めました。今後もUXやEXを高めていく機能を続々リリース予定です。興味のある方はぜひご覧ください!