"""eCommerce supervisor agent (sample) for Amazon Bedrock AgentCore Runtime. Versions / setup: - Python 3.12+ - strands-agents >= 1.x - bedrock-agentcore Runtime HTTP contract (BedrockAgentCoreApp) - Model id below is a pin for docs; swap for your region/account This file routes intents to specialist Runtime ARNs (or local stubs). It does NOT call refund/cancel/inventory write tools directly — those stay behind Gateway Policy on the specialists. Demo only: fake routing; replace invoke_specialist with real Runtime invoke. """ from __future__ import annotations import json import os from typing import Any from bedrock_agentcore.runtime import BedrockAgentCoreApp from strands import Agent, tool from strands.models.bedrock import BedrockModel MODEL_ID = os.environ.get( "BEDROCK_MODEL_ID", "global.anthropic.claude-sonnet-4-5-20250929-v1:0", ) # Map logical specialist → AgentCore Runtime ARN (set in deploy env) SPECIALIST_ARNS: dict[str, str] = { "sales": os.environ.get("SALES_AGENT_ARN", "arn:aws:bedrock-agentcore:us-east-1:000000000000:runtime/sales-demo"), "orders": os.environ.get("ORDERS_AGENT_ARN", "arn:aws:bedrock-agentcore:us-east-1:000000000000:runtime/orders-demo"), "support": os.environ.get("SUPPORT_AGENT_ARN", "arn:aws:bedrock-agentcore:us-east-1:000000000000:runtime/support-demo"), "inventory": os.environ.get("INVENTORY_AGENT_ARN", "arn:aws:bedrock-agentcore:us-east-1:000000000000:runtime/inventory-demo"), } MAX_SPECIALIST_HOPS = 2 app = BedrockAgentCoreApp() def invoke_specialist(name: str, user_message: str, session_ctx: dict[str, Any]) -> str: """Replace with bedrock-agentcore Runtime InvokeAgentRuntime (SigV4 or JWT). Dry-run returns a structured stub so local py_compile / unit tests work without AWS credentials. """ arn = SPECIALIST_ARNS[name] payload = { "specialist": name, "runtime_arn": arn, "message": user_message, "session": session_ctx, "mode": "dry-run", } return json.dumps(payload) @tool def route_to_sales(user_message: str) -> str: """Route product discovery, cart, and availability questions to the sales agent.""" return invoke_specialist("sales", user_message, {"hop": 1}) @tool def route_to_orders(user_message: str) -> str: """Route order status, cancel, modify, and shipment tracking to the orders agent.""" return invoke_specialist("orders", user_message, {"hop": 1}) @tool def route_to_support(user_message: str) -> str: """Route returns, refunds, policy FAQs, and escalations to the support agent.""" return invoke_specialist("support", user_message, {"hop": 1}) @tool def route_to_inventory(user_message: str) -> str: """Route warehouse stock and merchandising ops to the inventory agent (associates).""" return invoke_specialist("inventory", user_message, {"hop": 1}) @tool def escalate_to_human(reason: str, user_message: str) -> str: """Hand off to a human associate when risk is high or specialists are ambiguous.""" return json.dumps( { "status": "escalated", "reason": reason, "user_message": user_message, "queue": "commerce-tier2", } ) SYSTEM_PROMPT = f"""You are the eCommerce supervisor for a storefront assistant. Classify the shopper or associate request and call EXACTLY ONE route_* tool (or escalate_to_human). Do not invent order IDs or prices. Never call cancel/refund/inventory write APIs yourself — specialists + Gateway Policy own writes. If the request is ambiguous, ask one clarifying question instead of multi-routing. Max specialist hops per turn: {MAX_SPECIALIST_HOPS}. """ agent = Agent( model=BedrockModel(model_id=MODEL_ID), tools=[ route_to_sales, route_to_orders, route_to_support, route_to_inventory, escalate_to_human, ], system_prompt=SYSTEM_PROMPT, ) @app.entrypoint def invoke(payload: dict[str, Any]) -> dict[str, Any]: prompt = payload.get("prompt") or payload.get("inputText") or "" result = agent(prompt) text = str(result) return {"output": text, "max_specialist_hops": MAX_SPECIALIST_HOPS} if __name__ == "__main__": # Local smoke: dry-run tool path without Runtime print(route_to_orders("Where is order ORD-1001?"))