🕸️

AWS のマルチエージェント基盤をフル活用して業務アプリに AI を組み込んでみた

に公開

はじめに

AI エージェントを単体ツールとして使うのではなく、業務アプリケーションの一部として組み込む構成に関心があります。

その実験として、AI がインシデント対応を自動で行うデモシステム Agora を作りました。AWS FIS で障害を注入すると、CloudWatch アラームを起点に複数のエージェントが診断・解決・知識化まで実行します。コードは mahitotsu/moirai で公開しており、CDK 1 コマンドでデプロイできます。

このようなシステムを作ろうとすると、次のような課題が見えてきます。

  • エージェント同士をどう連携させるか
  • 既存システムとどう接続するか
  • プロンプトやスキルをどう管理するか
  • 運用時にどう観測するか

エージェントも既存システムと同じクラウド上で実行・運用したいと考えました。今回は AWS を選び、AgentCore を中心にサービスを積極的に活用する構成を組みました。

この記事では、その実装を通じて見えてきた「AI エージェントを業務システムに組み込むときに何が必要になるか」を整理します。

処理の全体像と利用サービス

まず構成要素とその関係を示します。各サービスを選んだ理由は後のセクションで説明します。

  1. FIS が AWS API に RequestLimitExceeded を注入すると、監視対象の Lambda がエラーを返し始める
  2. CloudWatch がエラーレート上昇を検知してアラームを発火。EventBridge → SQS → Bridge Lambda と伝播し、Ticket Service にインシデントチケットが自動起票される
  3. DynamoDB Streams が INSERT を検知し、ticket-dispatcher Lambda が Pipeline Orchestrator を起動する
  4. Orchestrator が Triage → Diagnosis → Resolution の順で InvokeAgentRuntime API を使って各エージェントを呼び出す
  5. Triage Agent(Claude Haiku)がインシデントの重要度とカテゴリを分類する
  6. Diagnosis Agent が Registry でランブックをセマンティック検索し、GraphBuilder で Diagnosis Worker を並列起動。各 Worker が MCP ツール(CloudWatch / Lambda 状態 / Stack Overflow / GitHub Issues / 過去チケット)を呼び出して診断する
  7. Resolution Agent が診断結果を受け取り、Gateway 経由で Ticket Service に解決手順と lesson_learned を書き込む(status=resolved
  8. DynamoDB Streams が MODIFY を検知し、knowledge-consumer Lambda が lesson_learned をベクトル化して S3 Vectors に格納する

また、第2部から第4部にかけてAgentCoreを中心とした今回利用したAWSのサービスについて紹介していきます。

※ Prompt Management は正確には Amazon Bedrock AgentCoreではなく、Amazon Bedrock のサービスです。

第 1 部:Strands Agents — エージェントを書く

Strands Agents は AWS が OSS として公開している Python 製エージェントフレームワークです。Bedrock の Converse API をネイティブにサポートしており、AgentCore との統合が公式パッケージとして提供されています。

@tool でツールを定義するだけ

Strands のエージェントは @tool デコレータでツールを定義し、あとは LLM に委ねます。

from strands import Agent, tool

@tool
def invoke_triage(incident_description: str) -> str:
    """Triage Agent を呼び出してインシデントを分類する"""
    # AgentCore Runtime の別コンテナを呼び出す
    ...

agent = Agent(
    model="us.anthropic.claude-sonnet-4-6",
    tools=[invoke_triage, invoke_diagnosis, invoke_resolution],
    system_prompt=load_prompt(),
)

ツール呼び出しの順序・回数・終了タイミングはすべて LLM が決定します。フロー制御をコードで書く必要がなく、モデルが賢くなるほどエージェントの品質も上がります。

GraphBuilder による動的な並列実行

Diagnosis Agent は「選択されたランブック数に応じて診断ノードを動的に生成し、並列実行する」という構成です。GraphBuilder を使うと、このような動的なエージェントグラフが数行で書けます。

from strands.multiagent import GraphBuilder

# Judgment Agent がランブックを選択
selected_runbooks = judgment_agent.run(incident)  # ["api-error-runbook", "severity-classification"]

# 選択数に応じてノードを生成し、並列実行
graph = GraphBuilder()
for runbook in selected_runbooks:
    graph.add_node(DiagnosisWorker(runbook=runbook))

results = await graph.run_parallel(context)  # 2 ノードが同時に MCP ツールを呼び出す

今回の実行では 2 ノードが T+227s〜T+393s の 166 秒間、CloudWatch・Lambda 状態・Stack Overflow・GitHub Issues など複数の MCP ツールを並列で呼び出し、診断結果を生成しました。

AgentCore との統合

Strands は AgentCore Runtime へのデプロイと Registry スキルの注入を、それぞれ専用の仕組みで簡潔に扱えます。第 2 部で説明するマネージドサービスとの連携も、この2点が起点になっています。

Runtime へのデプロイ — BedrockAgentCoreApp

AgentCore Runtime はコンテナに /invocations(POST)と /ping(GET)の実装を要求します。bedrock-agentcore SDK の BedrockAgentCoreApp がこのサービスコントラクトを担うため、エージェントのロジック以外に HTTP サーバーの実装は不要です。

from bedrock_agentcore.runtime import BedrockAgentCoreApp

app = BedrockAgentCoreApp()

@app.entrypoint
def invoke(payload: dict, context: Any) -> dict:
    result = agent(payload["message"])
    return {"response": str(result)}

if __name__ == "__main__":
    app.run()

Registry スキルの注入 — AgentSkills

Registry から取得したスキルは、Strands 公式の AgentSkills プラグインでエージェントに注入します。

from strands.vended_plugins.skills.agent_skills import AgentSkills

_SKILLS = registry.discover_skills(["incident-severity-classification"])

agent = Agent(
    model=BedrockModel(...),
    system_prompt=_SYSTEM_PROMPT,
    plugins=[AgentSkills(skills=_SKILLS)],
)

第 2 部:AgentCore + Bedrock — エージェントを業務システムに統合する

複数のエージェントが協調するシステムを ECS などで自前構築する場合、エージェントのロジックとは別に実装が必要なものが出てきます。エージェント間呼び出しのプロトコルスタック、MCP ツール呼び出し、認証、サービスディスカバリなどです。

AgentCore はこれらを3つのサービスで分担します。

サービス 役割
Runtime エージェントと MCP サーバーのサーバーレスホスティング基盤。セッション管理・認証・プロトコルスタックを担当する
Gateway エージェントから見た MCP の統一アクセスポイント。REST API・Lambda・外部 MCP を同一プロトコルで公開し、認証・認可を担当する
Registry エージェント・スキル・MCP サーバーの承認済みカタログ。起動時の動的発見とセマンティック検索によるスキル選択を提供する

AgentCore Runtime — エージェントと MCP サーバーをホストする

AgentCore Runtime はエージェントと MCP サーバーをコンテナとしてサーバーレスでホストします。セッション管理・IAM 認証・プロトコルスタックを担うため、呼び出し側はコンテナの URL やポートを意識せず、Registry から取得した ARN を渡すだけで済みます。

Agora では各エージェントをエージェントコンテナとして登録し、InvokeAgentRuntime API でオーケストレーターからサブエージェントを呼び出しています。

# Orchestrator から Triage を呼び出す
result = invoke_agent_runtime(agent_arn=triage_arn, payload=incident_description)

AgentCore Gateway — MCP アクセスの統一エンドポイントを提供する

Gateway はエージェントから見た MCP の統一アクセスポイントです。背後に登録できるターゲットは1種類ではありません。

ターゲットタイプ 今回の例
AgentCore Runtime 上の MCP サーバー Stack Overflow MCP / GitHub Issues MCP / Infra Inspector MCP
Lambda 関数ラッパー CloudWatch MCP(awslabs の stdio MCP を Lambda で HTTP 化)
既存 REST API(OpenAPI から MCP 自動変換) Ticket Service
外部マネージドエンドポイント AWS Docs MCP(awslabs 提供)

エージェントはターゲットの種類を意識せず、Gateway に向けて同じ MCP プロトコルでツールを呼び出せます。Ticket Service を Gateway で公開するための CDK コードは以下だけです。

gateway = AgentCoreGateway(
    name="agora-ticket-service",
    openapi_spec=ticket_service_openapi_spec,  # 既存の /openapi.json
)

AgentCore Registry — エージェントとスキルを動的に発見する

Registry はエージェントと MCP サーバーを capability タグ付きで登録するサービスカタログです。エージェントはエンドポイントをハードコードせず、起動時に Registry から動的に解決します。

# ログ(実際のトレースより)
T+171s: "Discovered runtime ARN for 'agora-diagnosis-agent' from Registry"
T+438s: "Discovered MCP Gateway URL from Registry"

さらに強力なのが セマンティック検索によるランブック動的選択です。Judgment Agent は search_registry_records でインシデントの症状に合致するランブックを LLM が判断して選びます。

今回の実行では 3 件の候補から以下の選択が行われました。

ランブック 結果
api-error-diagnosis-runbook ✅ 選択(ThrottlingException に合致)
incident-severity-classification ✅ 選択(重要度再評価に使用)
resolution-documentation-standard ❌ 除外(「診断フェーズには不要」と LLM が自律判断)

このロジックはコードに一行も書いていません。ランブックや MCP サーバーを追加・削除しても、エージェントの再デプロイは不要です。

Bedrock Prompt Management — プロンプトをアプリケーションから分離する

システムプロンプトをコードの外に出すことで、エージェントの振る舞いをコード変更なしに調整できます。Bedrock Prompt Management にプロンプトを保存し、エージェント起動時に動的取得します。

# エージェント起動時にプロンプトを取得
prompt = bedrock.get_prompt(prompt_identifier="HJAJSIT21O", prompt_version="3")
agent = Agent(model=..., system_prompt=prompt.content)

開発者以外でもコンソールから編集・更新でき、コードの変更・再デプロイなしに反映されます。 バージョン管理も Prompt Management 側で行えるため、実験的な変更とロールバックもコードを触らずに完結します。

AWS サービスとのイベント駆動連携

AWS ネイティブのサービスが自然につながります。

CloudWatch Alarm → EventBridge → SQS → Lambda(Bridge)
    → Ticket Service(DynamoDB)
    → DynamoDB Streams → ticket-dispatcher Lambda
    → AgentCore Runtime(Pipeline Orchestrator)

EventBridge のルール設定だけで「アラームが ALARM 状態になったらチケットを起票する」が実現できます。SQS を挟むことで複数アラームの同時発火にも耐性を持てます。DynamoDB Streams はチケットの INSERT を検知して診断パイプラインを起動し、MODIFY(resolved)を検知して知識化を行います。このイベント駆動の連鎖は Ticket Service のコードと完全に独立しており、既存サービスを一切触らずにエージェントを後付けできます。

第 3 部:付属サービス — 運用コストを下げる

Bedrock Prompt Caching — キャッシュポイントを手動設計せずに済む

Amazon Bedrock にはプロンプトキャッシングの仕組みがあり、同じコンテキストを繰り返し送信するときの入力トークンを削減できます。通常はリクエスト内のどこにキャッシュポイントを置くかを手動で設計する必要がありますが、Strands の BedrockModelCacheConfig(strategy="auto") を渡すと、ツール定義の後・最後のユーザーメッセージの後に自動でキャッシュポイントが挿入されます。

BedrockModel(
    model_id=_settings.model_id,
    cache_config=CacheConfig(strategy="auto"),
)

マルチターンのエージェントループほど効果が大きく、今回の Diagnosis Workers では以下の変化が起きました。

ターン 新規 input tokens cache_read tokens
Turn 1 6,451 0
Turn 2 1,175 26,413
Turn 3–5 3 44,594

Turn 3–5 の新規 input が 3 tokens まで激減しているのは、長大なシステムプロンプト+ランブック本文+ツール結果がすべてキャッシュから提供されているためです。Sonnet 4.6 全体のキャッシュ再利用率は 91%(cache_read 74,795 tokens / 総 input 82,429 tokens)でした。

S3 Vectors — クラスタ管理なしのベクトル検索

S3 Vectors は S3 ネイティブのベクトルストレージで、インデックス(put_vectors / query_vectors)だけで類似検索が完結します。Bedrock Embeddings(Titan Embed)と組み合わせれば、OpenSearch や別途のベクトル DB クラスタが不要です。

# 解決済みチケットを格納(knowledge-consumer Lambda)
embedding = bedrock.invoke_model(model_id="amazon.titan-embed-text-v2:0", body=lesson_learned)
s3vectors.put_vectors(vector_bucket="agora-incident-vectors", vectors=[{"key": ticket_id, "data": {"float32": embedding}}])

# 新規インシデント診断時に類似検索(Diagnosis Agent)
results = s3vectors.query_vectors(query_vector=query_embedding, top_k=5)

今回の実行では過去チケット cc847877(distance=0.328)と ce50638d(distance=0.286)が取得され、解決手順に活用されました。

第 4 部:Observability — トレースで実行の内側を見る

AgentCore Observability — エージェントの実行を可視化する

CDK の環境変数に 2 行追加するだけで、AgentCore Runtime のコンテナで ADOT(AWS Distro for OpenTelemetry)が自動起動します。

# CDK
environment={
    "AGENT_OBSERVABILITY_ENABLED": "true",
    "OTEL_SERVICE_NAME": "agora-orchestrator",
}

ADOT は Strands・Bedrock API 呼び出し・ツール実行を自動でインストゥルメントし、OpenTelemetry スパンとして X-Ray 経由で CloudWatch に送信します。CloudWatch Application Signals(APM)の GenAI Observability ダッシュボードで以下が確認できます。

  • LLM 呼び出し: 入出力トークン数・キャッシュヒット数・レイテンシ
  • ツール実行: Strands ツールとして登録された MCP ツール呼び出しを含む各ツールの実行履歴
  • エージェント間の連鎖: 複数エージェントにまたがるトレース全体が単一ビューに集約

第 3 部で示した Prompt Caching のキャッシュヒット率(91%)もこのトレースデータから取得しています。

CloudWatch Application Signals(APM)のトレースで確認できたこと

以下は CloudWatch Application Signals のトレース(TraceId: 6a1ed8ea7226456ef18bb6b57c931ee7)のスパン境界から取得したタイムラインです。

マイルストーン 経過時間
FIS 注入 → CloudWatch ALARM T+155s
ALARM → チケット自動起票 +3s(T+158s)
起票 → Orchestrator 起動 +1s(T+159s)
Triage 完了(Haiku / 1 LLM call) +2.7s(T+167s)
Diagnosis 完了(Sonnet / GraphBuilder 2並列) +221s(T+393s)
Resolution 完了・チケット resolved +43s(T+462s)
Knowledge / S3 Vectors 格納 +1s(T+463s)
起票 → resolved(パイプライン全体) 304 秒

もちろん、以下のようなトレースマップも取得できます。

トレースにはエージェントの出力内容も記録されています。Orchestrator の完了サマリーには、根本原因(FIS による RequestLimitExceeded 注入)・対応アクション(botocore adaptive リトライへの変更など)・過去チケットへの参照が含まれていました。knowledge-consumer Lambda が lesson_learned として蓄積したのが以下の内容です。

AWS FIS カオステスト実施前に必ず CloudWatch アラームのサプレス設定を行い、Lambda の AWS API 呼び出しには botocore adaptive リトライ+Circuit Breaker を実装してスロットリング耐性を確保すること。

まとめ

今回の実装を通じて、業務システムへの AI エージェント統合における各課題を AWS のサービスがどう解決するか、具体的に見えてきました。

課題 自前実装の場合 AWS サービスを使った場合
エージェント間呼び出し 呼び出しプロトコル・セッション管理を自前実装 AgentCore Runtime に委譲
既存 API の MCP 化 MCP サーバーをスクラッチ実装 Gateway に OpenAPI spec を登録するだけ
ツール・エンドポイント管理 コードに URL をハードコード Registry から動的発見・セマンティック選択
プロンプト管理 コードに埋め込み or 独自管理システム Prompt Management でコード外に分離
エージェント間トレース OTEL boilerplate をエージェントごとに実装 環境変数 2 行で自動計装・Application Signals で可視化
Prompt Caching キャッシュポイントを手動設計 Strands の CacheConfig(strategy="auto") で自動(今回 91% 再利用)
ベクトル検索 OpenSearch 等のクラスタを管理 S3 Vectors で S3 だけで完結
動的並列エージェント グラフ制御ロジックを自前実装 Strands GraphBuilder で数行

AgentCore を触ってみて感じたのは、エージェントそのものよりも「エージェントを含むシステム全体」を支える基盤の重要性でした。

今回はインシデント対応を題材にしましたが、問い合わせ対応や申請処理など、他の業務フローにも同じ考え方は適用できるはずです。

AI エージェントを業務システムの一部として扱うなら、必要になるのは「エージェント専用の何か」ではなく、従来のサービスと同様の実行基盤・接続層・可観測性です。AgentCore を通じて見えてきたのは、エージェントを既存インフラから切り離さず、同じ権限管理・監視・イベント駆動の仕組みの上で動かせるという点の価値でした。

注: AgentCore の各機能は 2026年5月時点で GA / プレビューが混在しています。本番利用前に AWS 公式ドキュメントで GA 状況を確認してください。

参考リンク

Discussion