跳转到内容

实现和配置 Story 代理

Story 代理是一个在模块 1中使用 --protocol=AG-UI 生成的 Strands 代理,因此 UI 可以通过 CopilotKit 使用 Agent-User Interaction 协议从中进行流式传输。它使用 Inventory MCP Server 来管理玩家的物品,并使用 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-dev` (`LOCAL_DEV=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("LOCAL_DEV") == "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 添加了一个 session_manager_provider,在部署时为每个 thread_id 创建一个 S3SessionManager,并在 agent-dev 下运行时(LOCAL_DEV=true)回退到 /tmp/strands-sessions 下的磁盘 FileSessionManager。在部署时,S3 存储桶与 Game API 的 queryActions 读取的存储桶相同,因此浏览器可以在重新访问时重建记录;在本地,代理将会话持久化到磁盘并与本地 MCP 服务器通信,无需部署。
  • agent.py 删除了示例 subtract 工具,并将系统提示替换为地牢主持人提示,邀请第一条用户消息说明玩家的名字和类型,并使用 Inventory MCP Server 的工具。

要构建代码:

Terminal window
pnpm build

生成的 agent-chat 目标会针对您的代理打开一个交互式 REPL。它独立运行并连接到您本地运行的代理,因此首先在一个终端中启动代理的本地服务器:

Terminal window
pnpm nx agent-dev story

然后,在第二个终端中启动聊天:

Terminal window
pnpm nx agent-chat story

您的第一条消息应该告诉代理您的英雄名字和类型(例如:My name is Alice. Start my zombie adventure.),故事将流式返回。

恭喜。您已经在本地构建并测试了您的第一个 Strands 代理!🎉🎉🎉