はじめに
「社内の問い合わせ対応を生成AIで効率化したい」── RAG(検索拡張生成)で社内ドキュメントを検索・回答するシステムは広く普及し始めています。しかし、実際に運用してみると、RAGだけでは解決できない課題に直面します。
RAGの限界:
- 「申請方法を教えて」→ 手順は出るが、実際の申請操作はユーザーが手動で実行
- 「チケットを作成して」→ ナレッジ検索の範囲外で対応不可
- 「先月の問い合わせ件数を教えて」→ データ集計はRAGの機能にない
つまり、RAGは 「情報を検索して回答する」 ことに特化しており、「判断してアクションを実行する」 ことはできません。
この課題を解決するのが Amazon Bedrock Agents です。Bedrock Agentsは、ユーザーの意図を理解し、適切なツール(Knowledge Base検索、API呼び出し、データ操作など)を自律的に選択して実行するAIエージェントを構築できます。
本記事では、Bedrock Agentsを用いた業務AIエージェントを構築します。具体的には、以下の機能を実装します。
- ナレッジ検索(Bedrock Knowledge Basesによるドキュメント検索)
- チケット管理(DynamoDBへのCRUD操作)
- レポート生成(データ集計・S3出力)
対象読者:生成AIを活用した業務自動化に取り組みたいエンジニア
前提条件:本記事ではナレッジ検索機能にBedrock Knowledge Basesを使用します。Knowledge Baseが未構築の場合は、以下の記事を参考に事前に構築してください。
💡 Amazon Bedrock Knowledge Bases × S3 Vectors で社内ナレッジ検索RAGを構築する
RAGとAgentの違い:どこで境界を引くか
まず、RAGとAgentの処理フローの違いを明確にします。
判断基準:「ユーザーが回答を受け取った後に手作業が必要か?」
- Noなら → RAGで十分(例:「有給休暇の日数は何日?」→ 事実を回答するだけ)
- Yesなら → Agentが有効(例:「有給休暇を申請して」→ 申請操作まで自動化)
構築するAgentの全体設計
ユースケース:社内ヘルプデスクの業務支援エージェント
社内のIT/総務ヘルプデスクを想定し、以下の3つの機能を持つAgentを構築します。
| 機能 | 実装方法 | ユーザーの指示例 |
|---|---|---|
| ナレッジ検索 | Knowledge Bases連携 | 「リモートワークのポリシーを教えて」 |
| チケット管理 | Action Group(Lambda → DynamoDB) | 「PCの不具合のチケットを作成して」「チケットID-001の状況は?」 |
| レポート生成 | Action Group(Lambda → S3) | 「今月のオープンチケット一覧を出力して」 |
アーキテクチャ
Bedrock Agentの構築
Step 1:Agent Instructionの設計
Agent Instructionは、エージェントの行動指針です。LLMがユーザーの意図を解釈し、どのツールを使うかを判断する際の基準になります。
本記事のAgent Instruction(日本語):
あなたは社内ヘルプデスクの業務支援エージェントです。以下の役割を担います。
【役割】
1. 社内ナレッジの検索と回答(社内規程、マニュアル、FAQに関する質問に対応)
2. サポートチケットの管理(チケットの作成、一覧取得、ステータス確認・更新)
3. チケットレポートの生成(オープンチケットの一覧をCSVで出力)
【ツール選択の判断基準】
- 社内規程、手順、ポリシーに関する質問 → Knowledge Basesを検索して回答する
- チケットの作成・確認・更新の依頼 → チケット管理Action Groupを使用する
- レポートの出力依頼 → レポート生成Action Groupを使用する
【制約事項】
- 社内情報以外の質問には回答しない。「社内ナレッジに該当する情報が見つかりません」と返答する
- チケット作成時は、タイトルと説明を必ずユーザーに確認してから作成する
- 不確実な情報は推測で回答せず、「確認が必要です」と伝える
Agent Instruction設計のポイント:
- 役割を明確に列挙:エージェントが何をできるかを具体的に定義
- ツール選択の判断基準を明記:どの状況でどのツールを使うかをLLMに指示
- やってはいけないことを明記:制約条件を設けることで、意図しない動作を防止
Step 2:Agentの作成
Bedrockコンソールから Agent を作成します。
- Amazon Bedrockコンソール → 「Agents」→「Create Agent」
- Agent名(例:
helpdesk-agent)と説明を入力 - 基盤モデルの選択:Anthropic Claude 3.5 Sonnet v1 を選択(日本語の理解力とツール選択精度が高い)
- Agent Instruction:上記の内容を入力
- IAMサービスロールは「Create and use a new service role」を選択
Step 3:Knowledge Basesの関連付け
Bedrock Knowledge Basesで構築済みのナレッジベースを、Agentに関連付けます。
💡 Knowledge Baseの構築方法は Amazon Bedrock Knowledge Bases × S3 Vectors で社内ナレッジ検索RAGを構築する を参照してください。
- Agent設定画面の「Knowledge bases」セクションで「Add」を選択
- 構築済みのKnowledge Base(例:
company-kb-fixed512)を選択 - Knowledge Baseの説明(Agentがいつこのナレッジを参照すべきか)を入力
社内規程、手順書、FAQ、製品マニュアルに関する質問を受けた場合に、
このKnowledge Baseを検索して回答を生成する。
Step 4:Action Group①(チケット管理)の実装
Action Groupは、Agentが外部システムと連携するためのインターフェースです。OpenAPIスキーマでAPIの定義を記述し、Lambda関数で実際のビジネスロジックを実装します。
OpenAPIスキーマ(チケット管理)
openapi: 3.0.0
info:
title: Ticket Management API
version: 1.0.0
description: 社内ヘルプデスクのチケット管理API
paths:
/tickets:
get:
summary: オープンチケットの一覧を取得する
description: ステータスが「open」または「in_progress」のチケットをすべて取得する。ユーザーがチケットの一覧や状況を確認したい場合に使用する。
operationId: listOpenTickets
responses:
"200":
description: チケット一覧の取得に成功
content:
application/json:
schema:
type: array
items:
type: object
properties:
ticketId:
type: string
title:
type: string
status:
type: string
createdAt:
type: string
post:
summary: 新しいチケットを作成する
description: ユーザーからの問い合わせやトラブル報告に基づいて新しいサポートチケットを作成する。タイトルと説明は必須。
operationId: createTicket
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- title
- description
properties:
title:
type: string
description: チケットのタイトル(問題の要約)
description:
type: string
description: 問題の詳細説明
priority:
type: string
enum: [low, medium, high, critical]
description: 優先度(デフォルトはmedium)
responses:
"201":
description: チケットの作成に成功
content:
application/json:
schema:
type: object
properties:
ticketId:
type: string
message:
type: string
/tickets/{ticketId}:
get:
summary: 特定のチケットの詳細を取得する
description: チケットIDを指定して、そのチケットの詳細情報(タイトル、説明、ステータス、優先度、作成日時)を取得する。
operationId: getTicketDetail
parameters:
- name: ticketId
in: path
required: true
description: 取得対象のチケットID
schema:
type: string
responses:
"200":
description: チケット詳細の取得に成功
content:
application/json:
schema:
type: object
properties:
ticketId:
type: string
title:
type: string
description:
type: string
status:
type: string
priority:
type: string
createdAt:
type: string
put:
summary: チケットのステータスを更新する
description: チケットIDを指定してステータスを更新する。対応開始時にin_progressへ、解決時にresolvedへ、クローズ時にclosedへ更新する。
operationId: updateTicketStatus
parameters:
- name: ticketId
in: path
required: true
description: 更新対象のチケットID
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- status
properties:
status:
type: string
enum: [open, in_progress, resolved, closed]
description: 更新後のステータス
responses:
"200":
description: ステータスの更新に成功
OpenAPIスキーマ設計のポイント:
-
descriptionフィールドが最重要:AgentのLLMがこの説明文を読んで「どのAPIを呼ぶか」を判断します。各操作とパラメータに対して、具体的かつ明確な説明を記述してください -
operationIdは一意かつ明確に:LLMがAPI操作を区別するための識別子です -
パラメータには
descriptionを必ず付与:LLMがユーザーの発話からパラメータを抽出する際の手がかりになります
Lambda関数(チケット管理)
import boto3
import json
import uuid
from datetime import datetime
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('helpdesk-tickets')
def lambda_handler(event, context):
api_path = event.get('apiPath', '')
http_method = event.get('httpMethod', '')
parameters = {p['name']: p['value'] for p in event.get('parameters', [])}
request_body = event.get('requestBody', {})
# リクエストボディの解析
body_content = {}
if request_body and 'content' in request_body:
body_props = request_body['content'].get('application/json', {}).get('properties', [])
for prop in body_props:
body_content[prop['name']] = prop['value']
# APIパスとHTTPメソッドに基づいてルーティング
if api_path == '/tickets' and http_method == 'GET':
result = list_open_tickets()
elif api_path == '/tickets' and http_method == 'POST':
result = create_ticket(body_content)
elif '/tickets/' in api_path and http_method == 'GET':
ticket_id = parameters.get('ticketId', '')
result = get_ticket_detail(ticket_id)
elif '/tickets/' in api_path and http_method == 'PUT':
ticket_id = parameters.get('ticketId', '')
result = update_ticket_status(ticket_id, body_content)
else:
result = {'error': 'Unknown API path'}
return {
'messageVersion': '1.0',
'response': {
'actionGroup': event.get('actionGroup', ''),
'apiPath': api_path,
'httpMethod': http_method,
'httpStatusCode': 200,
'responseBody': {
'application/json': {
'body': json.dumps(result, ensure_ascii=False, default=str)
}
}
}
}
def list_open_tickets():
response = table.scan(
FilterExpression='#s IN (:open, :in_progress)',
ExpressionAttributeNames={'#s': 'status'},
ExpressionAttributeValues={
':open': 'open',
':in_progress': 'in_progress'
}
)
return {'tickets': response.get('Items', []), 'count': len(response.get('Items', []))}
def create_ticket(body):
ticket_id = f"TKT-{uuid.uuid4().hex[:8].upper()}"
item = {
'ticketId': ticket_id,
'title': body.get('title', ''),
'description': body.get('description', ''),
'status': 'open',
'priority': body.get('priority', 'medium'),
'createdAt': datetime.now().isoformat()
}
table.put_item(Item=item)
return {'ticketId': ticket_id, 'message': f'チケット {ticket_id} を作成しました', 'details': item}
def get_ticket_detail(ticket_id):
response = table.get_item(Key={'ticketId': ticket_id})
return response.get('Item', {'error': f'チケット {ticket_id} が見つかりません'})
def update_ticket_status(ticket_id, body):
new_status = body.get('status', '')
table.update_item(
Key={'ticketId': ticket_id},
UpdateExpression='SET #s = :status, updatedAt = :updated',
ExpressionAttributeNames={'#s': 'status'},
ExpressionAttributeValues={
':status': new_status,
':updated': datetime.now().isoformat()
}
)
return {'ticketId': ticket_id, 'message': f'ステータスを {new_status} に更新しました'}
Lambda関数のレスポンス形式について:
Bedrock Agentsが呼び出すLambda関数は、特定のレスポンス形式に従う必要があります。messageVersion、responseオブジェクト内のactionGroup、apiPath、httpMethod、httpStatusCode、responseBodyは必須フィールドです。
Step 5:Action Group②(レポート生成)の実装
同様に、レポート生成用のAction GroupをOpenAPIスキーマとLambda関数で実装します。
OpenAPIスキーマ(レポート生成)
openapi: 3.0.0
info:
title: Report Generation API
version: 1.0.0
description: チケットレポートを生成するAPI
paths:
/reports/open-tickets:
post:
summary: オープンチケットのCSVレポートを生成する
description: 現在オープン状態のすべてのチケットをCSV形式でS3に出力する。月次レポートの作成や管理者への報告に使用する。
operationId: generateOpenTicketsReport
responses:
"200":
description: レポートの生成に成功
content:
application/json:
schema:
type: object
properties:
reportUrl:
type: string
description: 生成されたレポートのS3 URL
ticketCount:
type: integer
description: レポートに含まれるチケット数
Lambda関数(レポート生成)
import boto3
import csv
import io
import json
from datetime import datetime
dynamodb = boto3.resource('dynamodb')
s3 = boto3.client('s3')
table = dynamodb.Table('helpdesk-tickets')
REPORT_BUCKET = 'helpdesk-reports-<your-account-id>' # ← 自分のバケット名に置き換え
def lambda_handler(event, context):
api_path = event.get('apiPath', '')
if api_path == '/reports/open-tickets':
result = generate_open_tickets_report()
else:
result = {'error': 'Unknown API path'}
return {
'messageVersion': '1.0',
'response': {
'actionGroup': event.get('actionGroup', ''),
'apiPath': api_path,
'httpMethod': event.get('httpMethod', 'POST'),
'httpStatusCode': 200,
'responseBody': {
'application/json': {
'body': json.dumps(result, ensure_ascii=False, default=str)
}
}
}
}
def generate_open_tickets_report():
# オープンチケットを取得
response = table.scan(
FilterExpression='#s IN (:open, :in_progress)',
ExpressionAttributeNames={'#s': 'status'},
ExpressionAttributeValues={
':open': 'open',
':in_progress': 'in_progress'
}
)
items = response.get('Items', [])
# CSV生成
output = io.StringIO()
writer = csv.writer(output)
writer.writerow(['チケットID', 'タイトル', 'ステータス', '優先度', '作成日時'])
for item in items:
writer.writerow([
item.get('ticketId', ''),
item.get('title', ''),
item.get('status', ''),
item.get('priority', ''),
item.get('createdAt', '')
])
# S3にアップロード
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
key = f'reports/open_tickets_{timestamp}.csv'
s3.put_object(
Bucket=REPORT_BUCKET,
Key=key,
Body=output.getvalue().encode('utf-8-sig'),
ContentType='text/csv'
)
s3_url = f's3://{REPORT_BUCKET}/{key}'
return {
'reportUrl': s3_url,
'ticketCount': len(items),
'message': f'{len(items)}件のオープンチケットをCSVレポートとして出力しました。'
}
Step 6:Agentの準備と動作確認
Action GroupsとKnowledge Baseの設定が完了したら、Agentを「準備」してテストします。
- Agent設定画面で「準備」ボタンを押す(ドラフトバージョンが準備される)
- テスト画面で以下の指示を入力して動作確認
テスト1:ナレッジ検索
リモートワークのセキュリティポリシーを教えて
→ Knowledge Basesから関連ドキュメントを検索し、セキュリティポリシーの要点を回答
テスト2:チケット一覧取得
現在オープンなチケットの一覧を教えて
→ Action Group(チケット管理)を呼び出し、DynamoDBからオープンチケットを取得して一覧表示
テスト3:チケット作成
PCの画面が映らなくなったのでチケットを作成して
→ Agentがタイトルと説明を確認してからチケットを作成(ユーザー確認ステップあり)
テスト4:レポート生成
今月のオープンチケット一覧をレポート出力して
→ Action Group(レポート生成)を呼び出し、CSVをS3に出力してURLを返却
Agentの思考過程(ReAct)を読み解く
Bedrock AgentsはReAct(Reasoning + Acting)パターンで動作します。これは、LLMが「思考(Reasoning)」と「行動(Acting)」を交互に繰り返しながら、最終的な回答を導き出すアプローチです。
トレースログで思考過程を可視化
Bedrockコンソールのテスト画面では、トレース機能を有効にすることで、Agentの内部的な思考過程を確認できます。
例:「はい、その内容で作成してください」と承認した後のトレース
トレースステップ1では、Agentの思考過程(Thought)とAPI呼び出し(Action)が記録されます:
Thought: ユーザーが提案した内容でチケットを作成することに同意しました。
POST__ticket-management__createTicket 関数を使用してチケットを作成します。
Action: ticket-management / POST /tickets
title: "PCの画面が映らない"
description: "PCの電源は入っているが、画面に何も表示されない..."
Observation: {"ticketId": "TKT-51DBFA50", "message": "チケット TKT-51DBFA50 を作成しました"}
トレースステップ2では、Observationの結果を受けてAgentが最終回答を生成します:
Thought: チケットが正常に作成されました。チケットの詳細情報をユーザーに提供します。
Final Response: チケットが正常に作成されました。
チケットID: TKT-51DBFA50 / タイトル: PCの画面が映らない / ステータス: オープン
トレースの読み解き方
| ステップ | 名称 | 内容 |
|---|---|---|
| PreProcessing | 前処理 | ユーザー入力の検証。Instructionに基づいて有効なリクエストかを判定 |
| Orchestration - Thought | 思考 | LLMがユーザーの意図を解釈し、次に取るべきアクションを推論 |
| Orchestration - Action | 行動 | Knowledge Base検索、Action Group呼び出し、またはユーザーへの質問を実行 |
| Orchestration - Observation | 観察 | アクションの実行結果を受け取り、評価 |
| PostProcessing | 後処理 | 最終回答の生成とフォーマッティング |
ReActの「Thought → Action → Observation」のサイクルは、必要に応じて複数回繰り返されます。たとえば「全チケットの一覧を取得して、その中で優先度highのものだけレポート出力して」という指示の場合、まずチケット一覧を取得し(1回目のAction + Observation)、その結果を踏まえてレポート生成を実行する(2回目のAction + Observation)というマルチステップ処理が自動的に行われます。
Agentの精度を上げる実践Tips
Tip 1:OpenAPIスキーマのdescriptionが精度を左右する
Agentが正しいAPIを選択できるかどうかは、OpenAPIスキーマのdescriptionフィールドの品質に大きく依存します。
悪い例(曖昧なdescription):
description: チケットを取得する
良い例(具体的なdescription):
description: ステータスが「open」または「in_progress」のチケットをすべて取得する。ユーザーがチケットの一覧や状況を確認したい場合に使用する。
descriptionに「いつ使うか」「何を返すか」を明記することで、LLMのツール選択精度が向上します。
Tip 2:Agent Instructionのイテレーション
Agent Instructionは一度書いて終わりではなく、テスト結果を見ながら継続的に改善します。
- ツール選択を間違えたケースがあれば、Instructionの判断基準を具体化
- ユーザーへの確認が不十分なケースがあれば、制約条件を追加
- 回答が冗長なケースがあれば、回答形式の指示を追加
Tip 3:Knowledge BasesとAction Groupの優先度設計
Knowledge BaseとAction Groupの両方を持つAgentでは、どちらを先に参照するかを明確にInstructionで指示します。
【ツール選択の優先順位】
1. まずユーザーの質問が知識の検索で回答可能かを判断する
2. 知識検索で回答可能な場合 → Knowledge Basesを検索する
3. 操作の実行が必要な場合 → 適切なAction Groupを使用する
4. 判断に迷う場合 → ユーザーに確認を求める
Tip 4:ユーザー確認(Confirmation)の活用
重要な操作(チケット作成、ステータス更新など)の前にユーザー確認を挟むことで、誤操作を防止できます。Action Groupの作成時にrequireConfirmationをENABLEDに設定することで、Agentが操作を実行する前にユーザーに確認を求めるようになります。
まとめ
本記事では、Amazon Bedrock Agentsを使って、ナレッジ検索・チケット管理・レポート生成を自律的に実行する業務AIエージェントを構築しました。
RAGとAgentの価値比較:
| 観点 | RAG | Agent |
|---|---|---|
| できること | 情報を検索して回答 | 情報検索 + 判断 + アクション実行 |
| ユーザー体験 | 「手順を教えてもらい、自分で操作する」 | 「指示するだけで操作まで完了する」 |
| 業務効率化 | 検索時間の削減 | 検索 + 操作の自動化 |
得られた知見:
- Agent Instructionの設計が品質の鍵:役割・判断基準・制約条件を明確に記述する
- OpenAPIスキーマのdescriptionが精度を左右する:「いつ使うか」「何を返すか」を具体的に記述
- ReActの思考過程をトレースで確認:問題が起きた際の原因特定に不可欠
- 確認ステップの挿入:重要な操作の前にユーザー確認を挟み、誤操作を防止
本記事が、Bedrock Agentsを活用した業務AIエージェントの構築に役立てば幸いです。







