5
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?

2025AWSre:Inventのre:capエージェント作ってみる

Last updated at Posted at 2025-12-23

閲覧ありがとうございます。
本記事は セゾンテクノロジー Advent Calendar 2025 23日目の記事です。

今回はAmazon Bedrock AgentcoreとStrands Agentを使って、2025AWSre:Inventのセッションについて調査するAIエージェントを作成しました。

AIエージェントを作成するにあたって詰まったポイントなどを共有します。

目次

  1. はじめに
  2. 実装内容
  3. 詰まりポイント1
  4. 詰まりポイント2
  5. さいごに

1. はじめに

構成図

今回作成した構成図は以下の通りです。
image.png

slackからのリクエストをAWS Lambda経由でAmazon Bedrock Agentcoreに渡します。

Amazon Bedrock AgentcoreにはStrands Agentフレームワークで作成したAIエージェントをデプロイしており、各エージェントにはRAG検索/Web検索のツールを与えています。

Amazon Bedrock Agentcoreとは

Amazon Bedrock Agentcoreは、AWS上でAIエージェントを展開・運用するためのマネージドサービスです。
AIエージェントを運用するための様々な便利機能を提供してくれます。

詳細は以下記事を参照ください。

Strands Agentとは

Strands Agentsは、AIエージェントを構築・実行するオープンソースSDKです。
AIエージェントを数行のコードで書けるようになるだけでなく、マルチエージェントのための便利なフレームワークなどを用意してくれています。

詳細は以下記事を参照ください。

2. 実装内容

Agentcore Runtimeのホスト方法やS3ベクトルデータベース+ナレッジベースの作り方などはここでは省略します。
以下の記事を参照ください。

使用したデータ

データソースには以下のスプレッドシートを利用させていただいています。
https://docs.google.com/spreadsheets/d/1T33y-w0R7VSgNHPKdwLlTp79Z69kkUNac73B06M98EQ/edit?gid=959215466#gid=959215466

このファイルはYoutubeに掲載されているre:Inventのセッション動画について、日本語で文字起こし記事作成&要約したものを一覧化して公開いただいているファイルになります。

データの構造化

上記のスプレッドシートをCSVファイルとしてダウンロードし、S3のベクターデータベースとするために構造化します。

元々のCSVの構造は以下の通りです。

カテゴリ,オリジナルYouTube動画,日本語全文書き起こし記事,セッション概要

ここから、実際にRAGで検索したい内容のファイルとメタデータファイルに分けます。

session_2025_001.md
# re:Invent 2025: Keynote with CEO Matt Garman

**日本語タイトル**: re:Invent 2025: Keynote with CEO Matt Garman

## セッション概要

この動画では、AWS CEO Matt Garmanが re:Invent 2025で、AWSの成長(年間1,320億ドル、前年比20%増)と、AIエージェント時代における包括的なイノベーションを発表しています。「発明の自由を提供する」というミッションのもと、P6e-GB300やAWS AI Factories、Trainium3 UltraServers、次世代Trainium4といった最先端AIインフラを披露。Amazon Nova 2ファミリー(Pro、Lite、Sonic、Omni)に加え、事前学習段階で顧客データを統合できる革新的なAmazon Nova Forgeを紹介しました。Amazon Bedrock AgentCoreには、エージェントの動作を確定的に制御するPolicyと、品質を継続評価するEvaluationsを追加。さらに、Kiro autonomous agent、AWS Security Agent、AWS DevOps Agentという3つのフロンティアエージェントにより、開発者の生産性を桁違いに向上させ、数十億のエージェントが存在する未来への道筋を示しました。Sony、Adobe、Writerなどの顧客事例を通じて、実際のビジネス価値が具体的に語られ、最後には25の新サービスが10分間で発表されるという圧巻の展開となりました。

<メタデータを付与したいファイル名>.metadata.jsonファイルを作成することで、カスタムメタデータを付与することができます。

session_2025_001.md.metadata.json
{
  "metadataAttributes": {
    "year": "2025",
    "category": "*Keynote",
    "youtube_url": "https://www.youtube.com/watch?v=q3Sb9PemsSo",
    "transcript_url": "https://zenn.dev/kiiwami/articles/d515ff501cec6884"
  }
}

ナレッジベースデータにサポートされているドキュメント形式と制限については以下ドキュメントをご確認ください。
https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/knowledge-base-ds.html
プレーンテキスト (ASCII のみ)という記載があるので今回はmdとしていますが、txtファイルでもデータの同期自体は可能でした。

AIエージェントの定義

今回AIエージェントを作るにあたっては、Strands AgentのSwarmを使用しました。
Swarmを使うと、共有コンテキストとワーキングメモリを備えたエージェント間の自律的な連携が可能になります。

詳細については以下記事を参照ください。


今回作成したエージェントは以下三つです。

RAG検索を専門とするエージェント
rag_agent = Agent(
    name="rag_agent",
    description="AWS re:Inventセッション情報を検索する専門家",
    model="us.anthropic.claude-sonnet-4-5-20250929-v1:0",
    tools=[search_knowledge_base,search_web],
    system_prompt="""あなたはドキュメント検索の専門家です。

【あなたの役割】
ユーザーの質問に対して、AWS re:Inventセッションのナレッジベースから検索を実行してください。

【作業手順】
1. 質問内容を分析
2. 不明な製品・技術がある場合はsearch_webで1回のみ調査(競合、技術分類など)
3. 元々のユーザーからの質問に対して、異なる表現や関連する視点から3個の検索クエリを生成する
4. 生成した各検索クエリでsearch_knowledge_baseツールを1回のみ呼び出す
5. 検索結果を確認し、web_search_agentに引き継ぐ

検索クエリを生成する際は:
- 重要なトピック、概念、技術用語を含める
- 同義語や関連用語も考慮する
- 年度や固有名詞がある場合は含める

注意点:
- メタデータに含まれる「transcript_url」「youtube_url」を必ず記載してください
- これらのURLは省略せず、完全な形で表示すること
- 現在は2025年12月です

【重要】
- 作業完了後は**必ず**web_search_agentに引き継いでください
- search_knowledge_baseの結果のみを引継ぎ、search_webの結果は引き継がないこと
"""
)
Web検索を専門とするエージェント
web_search_agent = Agent(
    name="web_search_agent",
    description="インターネット上の最新情報を検索する専門家",
    model="us.anthropic.claude-sonnet-4-5-20250929-v1:0",
    tools=[search_web],
    system_prompt="""あなたはWeb検索の専門家です。

【あなたの役割】
前のエージェントから受け取ったセッション情報の詳細を調べるため、Web検索を実行してください。

【作業手順】
1. 前のエージェントから受け取った情報を確認
2. 渡されたre:Inventの情報について、transcript_urlをsearch_webツールで検索
3. 検索結果を確認
4. summary_agentに引き継ぐ

【重要】
- search_webツールは**1回だけ**呼び出してください
- 検索完了後はsummary_agentに引き継いでください
- 追加の検索や分析は不要です
- 現在は2025年12月です
"""
)
最終回答を作成するサマリーエージェント
summary_agent = Agent(
    name="summary_agent",
    description="全ての情報を統合してユーザーへの最終回答を作成する専門家",
    model="us.anthropic.claude-sonnet-4-5-20250929-v1:0",
    system_prompt="""あなたはユーザーがAWS re:Invent2025をrecapするのをアシスタントするAIエージェントです。

【あなたの役割】
他のエージェントから受け取った情報を統合し、re:Inventに関する情報の詳細を、ユーザーへの最終回答を生成してください。

【回答フォーマット】
- 具体的な数字や事例を記載する際は、Slackのリンク記法を使用: **XXX**<URL|[番号]>
- urlには「transcript_url」を使用
- 回答の最後に「参考ソース」セクションを設ける:
  [1] セッション名またはタイトル (re:Invent)
  完全なURL
  [2] 記事タイトル (Web検索)
  完全なURL

【注意点】
- 必ず回答フォーマットを守ること
- 回答は具体的な情報を提供し、考察は不要
- 調査した内容からのみ回答し、調査内容以外のことは答えないこと
- re:Inventに関係しない調査内容は回答しないこと
- URLは省略せず完全な形で表示する
- 現在は2025年12月です

"""
)

ツールとしては、以下二つのツールを定義しています。

search_knowledge_base
@tool
def search_knowledge_base(query: str) -> str:
    """
    2023年、2024年、2025年のAWSre:Inventで行われたセッション概要から、質問に関連するセッションを検索します。
    
    Args:
        query: 検索したいキーワード
    """
    if not KNOWLEDGE_BASE_ID:
        return "エラー: KNOWLEDGE_BASE_ID 環境変数が設定されていません。"

    agent_client = boto3.client('bedrock-agent-runtime')

    search_response = agent_client.retrieve(
        knowledgeBaseId=KNOWLEDGE_BASE_ID,
        retrievalQuery={'text': query},
        retrievalConfiguration={
            'vectorSearchConfiguration': {'numberOfResults': 5}
        }
    )

    if 'retrievalResults' not in search_response or not search_response['retrievalResults']:
        return "関連情報が見つかりませんでした。"

    results = []
    for r in search_response['retrievalResults']:
        text = r.get('content', {}).get('text', '')
        score = r.get('score', 0)

        location = r.get('location', {})
        s3_location = location.get('s3Location', {})
        uri = s3_location.get('uri', '')

        metadata = r.get('metadata', {})

        result_text = f"**スコア: {score:.3f}**\n\n{text}"

        if uri:
            result_text += f"\n\n**ソース:** {uri}"

        if metadata:
            metadata_str = ", ".join([f"{k}: {v}" for k, v in metadata.items()])
            result_text += f"\n**メタデータ:** {metadata_str}"

        results.append(result_text)

    return "\n\n---\n\n".join(results)
search_web
@tool
def search_web(query: str) -> str:
    """
    Tavilyを使ってWebから最新情報を検索します。
    
    Args:
        query: 検索したいキーワードや質問
    """
    api_key = os.environ.get("TAVILY_API_KEY")
    if not api_key:
        return "エラー: Tavily APIキーが設定されていません。"

    tavily = TavilyClient(api_key=api_key)
    search_result = tavily.search(query=query, max_results=5)

    if not search_result.get('results'):
        return "Web検索結果が見つかりませんでした。"

    results = []
    for idx, result in enumerate(search_result['results'], 1):
        title = result.get('title', '')
        url = result.get('url', '')
        content = result.get('content', '')

        result_text = f"**結果 {idx}**\n"
        result_text += f"**タイトル:** {title}\n"
        result_text += f"**URL:** {url}\n"
        result_text += f"**内容:** {content}"

        results.append(result_text)

    return "\n\n---\n\n".join(results)

実装結果

slackで「HULFT Squareの競合製品についての最新の動向を教えて」と言うと、HULFT Squareの競合について調べ、クエリ拡張したうえでre:Inventのセッションから検索してくれてます。

image.png

3. 詰まりポイント1

S3ベクターデータベースとAmazon Bedrock Knowledge Basesでのデータ同期

今回事前にデータを構造化したファイル構成としていたので、Amazon Bedrock Knowledge Basesへのデータソース追加時のチャンキング戦略として、「チャンキングなし」を選択しようとしました。
理由としては、セッション概要をファイルごとに分けていたため、「チャンキングなし」戦略を選ぶことで文脈の欠落なくチャンク分割できるからです。

image.png

しかし実際に実行しようとすると、一部のファイルが同期に失敗しました。
エラー内容は以下の通りです。

Filterable metadata must have at most 2048 bytes

エラーの原因は、フィルター可能なメタデータの最大サイズを超えてしまっていることです。
Amazon S3 Vectorsには、様々な制限があります。

特に引っかかりやすいのがフィルター可能なメタデータの最大サイズです。
Amazon S3 Vectorsのメタデータには、デフォルトでついているものとカスタムでつけられるものがあります。
両方とも特に何もしないとフィルタリング可能なメタデータとなります。


これを防ぐには、ベクトルインデックスを作成する際の追加設定でキーを追加します。

image.png

自分の場合は、カスタムで付与していた「youtube_url」と「transcript_url」を登録しました。

しかしながら、一向にエラーは解消しませんでした。。。
むしろ、データファイルの中身を少なくした時の方が、データソースの同期時に失敗するファイルの数が大幅に減りました。(エラーの内容は変わらないままです)

それ以上削れないところまで削っても失敗したので、結局諦めて「デフォルトチャンク戦略」を選択したところ、問題なく同期が完了しました。

このエラーについては課題として残っている部分になります。

4. 詰まりポイント2

Strands AgentのSwarmプロンプトについて

詰まったポイント2つ目はSwarmのプロンプトについてです。
Swarmでは引き継ぎ(handoff)という概念があり、各エージェントが次のエージェントにコンテキストを渡します。
このコンテキストの渡し方や渡す内容は、プロンプトによって指示をします。

自分の場合、以下の三つのエージェントを定義していました。

  • RAG検索を専門とするエージェント
  • Web検索を専門とするエージェント
  • 最終回答を作成するサマリーエージェント

それぞれのエージェントに適切に引継ぎをさせるために、「処理が終わったら~か~に○○○の内容を引き継いでください。」
と指示していました。

そして最終回答を作成するサマリーエージェントには無駄なループが発生するのを防ぐために「処理が終わったらどこにも引き継がないでください」
と指示しました。

しかしそうすると、エラーは出ないもののサマリーエージェントが実行されず、その前のエージェント実行で終わってしまうようなログが出ました。


実はSwarm実行の終了については、実行後のコードで定義します。
if "summary_agent" in result.results:
        sa = result.results["summary_agent"]
        final_text = (
            getattr(sa, "result", None)
            or getattr(sa, "final_output", None)
            or getattr(sa, "content", None)
            or getattr(sa, "output", None)
        )
        print(final_text if final_text else "(出力なし)")

最後に実行させたいエージェントが実行されて出力をする際には、「<エージェント名>:」のような形で値が返ってきます。
今回の場合はsummary_agentが最後のエージェントなので、そのエージェントの名前が返ってくると自動的に終了するようになっています。

つまりプロンプトで終了させるような指示をすると、結果が返ってこずいつまでも終わらなくなってしまうわけでした。

5. さいごに

Amazon Bedrock AgentcoreとStrands Agent思ったよりも簡単で面白いので、ぜひ触ってみてください!
本記事がどなたかの役に立てれば幸いです。

5
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
5
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?