Aller au contenu

Implémenter et configurer l'agent Story

Le Story Agent est un agent Strands généré avec --protocol=AG-UI dans le Module 1, afin que l’interface utilisateur puisse diffuser depuis celui-ci via le protocole Agent-User Interaction via CopilotKit. Il utilise l’Inventory MCP Server pour gérer les objets du joueur, et le S3SessionManager intégré de Strands pour persister l’historique des conversations dans le bucket de sessions que nous avons provisionné dans le Module 2.

Mettez à jour les fichiers suivants dans 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)

Les modifications sont :

  • main.py ajoute un session_manager_provider qui crée un S3SessionManager par thread_id lors du déploiement, et se replie sur un FileSessionManager sur disque sous /tmp/strands-sessions lors de l’exécution sous agent-serve-local (SERVE_LOCAL=true). En déploiement, le bucket S3 est le même que celui depuis lequel le queryActions de l’API Game lit, de sorte que le navigateur peut reconstruire les transcriptions lors d’une nouvelle visite ; localement, l’agent s’exécute entièrement hors ligne contre le serveur MCP local sans aucun appel AWS.
  • agent.py supprime l’outil d’exemple subtract et remplace le prompt système par un prompt de maître de donjon qui invite le premier message utilisateur à indiquer le nom du joueur et le genre, et utilise les outils de l’Inventory MCP Server.

Pour compiler le code :

Terminal window
pnpm build

Pour déployer votre application, exécutez la commande suivante :

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

Ce déploiement prendra environ 2 minutes.

Une fois le déploiement terminé, vous verrez des sorties similaires à ceci (certaines valeurs ont été masquées) :

Fenêtre de terminal
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

Vous pouvez tester votre Agent via :

  • Le démarrage d’une instance locale du serveur Agent et le dialogue avec celui-ci via la cible générée agent-chat, ou
  • L’appel de l’API déployée en utilisant curl avec un token JWT.

Ouvrez un REPL interactif contre une copie servie localement de l’agent AG-UI en utilisant la cible générée agent-chat :

Terminal window
pnpm nx agent-chat story

agent-chat a dependsOn: ['agent-serve-local'], il n’est donc pas nécessaire de démarrer le serveur agent séparément — Nx lancera agent-serve-local (qui à son tour démarre le serveur MCP Inventory localement), puis lancera le REPL AG-UI agent-chat-cli contre http://localhost:8081/invocations. Votre premier message devrait indiquer à l’agent le nom de votre héros et le genre (par exemple : My name is Alice. Start my zombie adventure.) et l’histoire sera diffusée en retour.

Si la commande s’exécute avec succès, vous devriez commencer à voir l’histoire diffusée en retour sous forme de flux d’événements AG-UI (Server-Sent Events) — les blocs RUN_STARTED / TEXT_MESSAGE_START / TEXT_MESSAGE_CONTENT encapsulent les tokens réels de l’histoire :

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..."}

Félicitations. Vous avez construit et déployé votre premier agent Strands sur Bedrock AgentCore Runtime ! 🎉🎉🎉