Skip to content

Story エージェントの実装と設定

タスク1: ストーリーエージェントの実装

Section titled “タスク1: ストーリーエージェントの実装”

ストーリーエージェントは、モジュール1--protocol=AG-UIを指定して生成されたStrandsエージェントです。これにより、UIはCopilotKitを介してAgent-User Interactionプロトコルでストリーミングできます。インベントリMCPサーバーを使用してプレイヤーのアイテムを管理し、Strandsの組み込みS3SessionManagerを使用して、モジュール2でプロビジョニングしたセッションバケットに会話履歴を永続化します。

packages/story/dungeon_adventure_story/agent配下のファイルを更新します:

import logging
import os
import uuid
from functools import cache
from typing import Any, cast
from ag_ui.core import RunAgentInput
from ag_ui_strands import StrandsAgent, StrandsAgentConfig, create_strands_app
from aws_lambda_powertools.utilities import parameters
from dungeon_adventure_agent_connection import session_id_context
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
from strands.session import FileSessionManager, S3SessionManager, SessionManager
from .agent import get_agent
logging.basicConfig(level=logging.INFO)
SESSION_ID_HEADER = "x-amzn-bedrock-agentcore-runtime-session-id"
@cache
def _resolve_sessions_bucket() -> str:
"""Read the conversation-history bucket name from runtime config.
Resolved lazily (and memoised) so `fastapi dev` can import this module
before ``RUNTIME_CONFIG_APP_ID`` is in the environment.
"""
application = os.environ.get("RUNTIME_CONFIG_APP_ID")
if not application:
raise RuntimeError("RUNTIME_CONFIG_APP_ID is not set — cannot resolve the StorySessions bucket.")
provider = parameters.AppConfigProvider(environment="default", application=application)
buckets = cast(dict[str, Any], provider.get("buckets", transform="json"))
return buckets["StorySessions"]["bucketName"]
def _session_manager_provider(input_data: RunAgentInput) -> SessionManager:
"""Create a session manager keyed by the AG-UI thread_id.
- In AgentCore (and `agent-serve`), persist to the shared ``StorySessions``
S3 bucket — the same bucket the Game API reads from to rebuild
conversation history on revisit.
- In `agent-serve-local` (`SERVE_LOCAL=true`), persist to a temp
directory so the agent can run fully offline against the local MCP
server without any AWS calls.
"""
session_id = input_data.thread_id or "default"
if os.environ.get("SERVE_LOCAL") == "true":
return FileSessionManager(session_id=session_id, storage_dir="/tmp/strands-sessions")
return S3SessionManager(session_id=session_id, bucket=_resolve_sessions_bucket())
# The template Agent is cloned per thread_id by ``StrandsAgent`` — we plug in
# a ``session_manager_provider`` so each thread gets its own session manager
# and conversation history is replayed on subsequent turns and survives agent
# restarts.
_agent_ctx = get_agent()
_agent = _agent_ctx.__enter__()
agui_agent = StrandsAgent(
agent=_agent,
name="StoryAgent",
description="A Strands Agent exposed via the AG-UI protocol.",
config=StrandsAgentConfig(session_manager_provider=_session_manager_provider),
)
class _SessionIdMiddleware(BaseHTTPMiddleware):
"""Bind the session ID for this request so downstream MCP / A2A clients forward it on outbound calls."""
async def dispatch(self, request: Request, call_next):
session_id = request.headers.get(SESSION_ID_HEADER) or str(uuid.uuid4())
with session_id_context(session_id):
return await call_next(request)
app = create_strands_app(agui_agent, path="/invocations")
app.add_middleware(_SessionIdMiddleware)

変更内容は以下の通りです:

  • main.pyは、デプロイ時にthread_idごとにS3SessionManagerを作成し、agent-serve-localSERVE_LOCAL=true)で実行時には/tmp/strands-sessions配下のディスク上のFileSessionManagerにフォールバックするsession_manager_providerを追加します。デプロイ時、S3バケットはGame APIのqueryActionsが読み取るものと同じであるため、ブラウザは再訪問時にトランスクリプトを再構築できます。ローカルでは、エージェントはAWS呼び出しなしでローカルMCPサーバーに対して完全にオフラインで実行されます。
  • agent.pyは、サンプルのsubtractツールを削除し、システムプロンプトをダンジョンマスター用のものに置き換えます。このプロンプトは、最初のユーザーメッセージでプレイヤーの名前とジャンルを述べるよう促し、インベントリMCPサーバーのツールを使用します。

コードをビルドするには:

Terminal window
pnpm build

アプリケーションをデプロイするには、以下のコマンドを実行します:

Terminal window
pnpm nx deploy infra "dungeon-adventure-infra-sandbox/*"

デプロイは約2分で完了します。

デプロイが完了すると、以下のような出力が表示されます(一部の値は編集済み):

Terminal window
dungeon-adventure-infra-sandbox-Application
dungeon-adventure-infra-sandbox-Application: deploying... [2/2]
dungeon-adventure-infra-sandbox-Application
Deployment time: 354s
Outputs:
dungeon-adventure-infra-sandbox-Application.ElectroDbTableTableNameXXX = dungeon-adventure-infra-sandbox-Application-ElectroDbTableXXX-YYY
dungeon-adventure-infra-sandbox-Application.GameApiEndpointXXX = https://xxx.execute-api.region.amazonaws.com/prod/
dungeon-adventure-infra-sandbox-Application.GameUIDistributionDomainNameXXX = xxx.cloudfront.net
dungeon-adventure-infra-sandbox-Application.InventoryMcpArn = arn:aws:bedrock-agentcore:region:xxxxxxx:runtime/dungeonadventureventoryMcpServerXXXX-YYYY
dungeon-adventure-infra-sandbox-Application.RuntimeConfigApplicationId = xxxx
dungeon-adventure-infra-sandbox-Application.StoryAgentArn = arn:aws:bedrock-agentcore:region:xxxxxxx:runtime/dungeonadventurecationStoryAgentXXXX-YYYY
dungeon-adventure-infra-sandbox-Application.UserIdentityUserIdentityIdentityPoolIdXXX = region:xxx
dungeon-adventure-infra-sandbox-Application.UserIdentityUserIdentityUserPoolIdXXX = region_xxx

エージェントのテスト方法は2通りあります:

  • エージェントサーバーをローカルで起動し、生成されたagent-chatターゲットを介してチャットする、または
  • デプロイ済みAPIをJWTトークン付きのcurlで呼び出す。

生成されたagent-chatターゲットを使用して、ローカルで提供されるAG-UIエージェントのコピーに対する対話型REPLを開きます:

Terminal window
pnpm nx agent-chat story

agent-chatにはdependsOn: ['agent-serve-local']が設定されているため、エージェントサーバーを別途起動する必要はありません。Nxがagent-serve-localを起動し(これによりInventory MCPサーバーもローカルで起動されます)、その後agent-chat-cliを使用してhttp://localhost:8081/invocationsに対するAG-UI REPLを起動します。最初のメッセージでは、エージェントにヒーローの名前とジャンルを伝えてください(例:My name is Alice. Start my zombie adventure.)。ストーリーがストリーミングで返されます。

コマンドが正常に実行されると、AG-UIイベントストリーム(Server-Sent Events)としてストーリーがストリーミングされ始めます。RUN_STARTED / TEXT_MESSAGE_START / TEXT_MESSAGE_CONTENTチャンクが実際のストーリートークンをラップします:

data: {"type":"RUN_STARTED","threadId":"Alice-zombie00000000000000000000000",...}
data: {"type":"TEXT_MESSAGE_START","messageId":"...","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"...","delta":"Greetings, Alice. "}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"...","delta":"The moans grow louder beyond the barricaded door..."}

おめでとうございます。Bedrock AgentCore Runtime上に初めてのStrandsエージェントを構築・デプロイできました! 🎉🎉🎉