2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Amazon Bedrock Agents で「検索して終わり」を卒業する ── Knowledge Bases連携+チケット管理+レポート生成の業務AIエージェント構築

2
Last updated at Posted at 2026-03-23

はじめに

「社内の問い合わせ対応を生成AIで効率化したい」── RAG(検索拡張生成)で社内ドキュメントを検索・回答するシステムは広く普及し始めています。しかし、実際に運用してみると、RAGだけでは解決できない課題に直面します。

RAGの限界:

  1. 「申請方法を教えて」→ 手順は出るが、実際の申請操作はユーザーが手動で実行
  2. 「チケットを作成して」→ ナレッジ検索の範囲外で対応不可
  3. 「先月の問い合わせ件数を教えて」→ データ集計は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の処理フローの違いを明確にします。

image.png

判断基準:「ユーザーが回答を受け取った後に手作業が必要か?」

  • Noなら → RAGで十分(例:「有給休暇の日数は何日?」→ 事実を回答するだけ)
  • Yesなら → Agentが有効(例:「有給休暇を申請して」→ 申請操作まで自動化)

構築するAgentの全体設計

ユースケース:社内ヘルプデスクの業務支援エージェント

社内のIT/総務ヘルプデスクを想定し、以下の3つの機能を持つAgentを構築します。

機能 実装方法 ユーザーの指示例
ナレッジ検索 Knowledge Bases連携 「リモートワークのポリシーを教えて」
チケット管理 Action Group(Lambda → DynamoDB) 「PCの不具合のチケットを作成して」「チケットID-001の状況は?」
レポート生成 Action Group(Lambda → S3) 「今月のオープンチケット一覧を出力して」

アーキテクチャ

image.png


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 を作成します。

  1. Amazon Bedrockコンソール → 「Agents」→「Create Agent」
  2. Agent名(例:helpdesk-agent)と説明を入力
  3. 基盤モデルの選択:Anthropic Claude 3.5 Sonnet v1 を選択(日本語の理解力とツール選択精度が高い)
  4. Agent Instruction:上記の内容を入力
  5. IAMサービスロールは「Create and use a new service role」を選択

article02_03.png

Step 3:Knowledge Basesの関連付け

Bedrock Knowledge Basesで構築済みのナレッジベースを、Agentに関連付けます。

💡 Knowledge Baseの構築方法は Amazon Bedrock Knowledge Bases × S3 Vectors で社内ナレッジ検索RAGを構築する を参照してください。

  1. Agent設定画面の「Knowledge bases」セクションで「Add」を選択
  2. 構築済みのKnowledge Base(例:company-kb-fixed512)を選択
  3. Knowledge Baseの説明(Agentがいつこのナレッジを参照すべきか)を入力
社内規程、手順書、FAQ、製品マニュアルに関する質問を受けた場合に、
このKnowledge Baseを検索して回答を生成する。

article02_04.png

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がユーザーの発話からパラメータを抽出する際の手がかりになります

article02_05-1.png
article02_05-2.png

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関数は、特定のレスポンス形式に従う必要があります。messageVersionresponseオブジェクト内のactionGroupapiPathhttpMethodhttpStatusCoderesponseBodyは必須フィールドです。

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を「準備」してテストします。

  1. Agent設定画面で「準備」ボタンを押す(ドラフトバージョンが準備される)
  2. テスト画面で以下の指示を入力して動作確認

テスト1:ナレッジ検索

リモートワークのセキュリティポリシーを教えて

→ Knowledge Basesから関連ドキュメントを検索し、セキュリティポリシーの要点を回答

テスト2:チケット一覧取得

現在オープンなチケットの一覧を教えて

→ Action Group(チケット管理)を呼び出し、DynamoDBからオープンチケットを取得して一覧表示

テスト3:チケット作成

PCの画面が映らなくなったのでチケットを作成して

→ Agentがタイトルと説明を確認してからチケットを作成(ユーザー確認ステップあり)

テスト4:レポート生成

今月のオープンチケット一覧をレポート出力して

→ Action Group(レポート生成)を呼び出し、CSVをS3に出力してURLを返却

article02_06.png


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の画面が映らない / ステータス: オープン

article02_07.png

トレースの読み解き方

ステップ 名称 内容
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の作成時にrequireConfirmationENABLEDに設定することで、Agentが操作を実行する前にユーザーに確認を求めるようになります。


まとめ

本記事では、Amazon Bedrock Agentsを使って、ナレッジ検索・チケット管理・レポート生成を自律的に実行する業務AIエージェントを構築しました。

RAGとAgentの価値比較:

観点 RAG Agent
できること 情報を検索して回答 情報検索 + 判断 + アクション実行
ユーザー体験 「手順を教えてもらい、自分で操作する」 「指示するだけで操作まで完了する」
業務効率化 検索時間の削減 検索 + 操作の自動化

得られた知見:

  1. Agent Instructionの設計が品質の鍵:役割・判断基準・制約条件を明確に記述する
  2. OpenAPIスキーマのdescriptionが精度を左右する:「いつ使うか」「何を返すか」を具体的に記述
  3. ReActの思考過程をトレースで確認:問題が起きた際の原因特定に不可欠
  4. 確認ステップの挿入:重要な操作の前にユーザー確認を挟み、誤操作を防止

本記事が、Bedrock Agentsを活用した業務AIエージェントの構築に役立てば幸いです。


参考リンク

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?