Skip to content

Commit bc61834

Browse files
committed
feat(market-trends-agent): add demo UI, LangGraph checkpointer/store, multi-region support
- Add AgentCoreMemorySaver (checkpointer) and AgentCoreMemoryStore for dual-layer memory - Add React chat UI with pre-built broker profiles and markdown rendering - Add FastAPI backend proxy bridging UI to deployed AgentCore agent - Add run_demo_ui.py launcher for single-command startup - Replace hardcoded us-east-1 with os.getenv for multi-region deployability - Add IAM permissions for checkpointer and memory record operations - Fix extract_actor_id regex and add new intro patterns - Remove personal AWS_PROFILE reference from error message - Sync requirements.txt with pyproject.toml (fastapi, uvicorn, langgraph-checkpoint-aws) - Update README with UI setup, architecture diagram, and troubleshooting
1 parent 74600e2 commit bc61834

17 files changed

Lines changed: 4484 additions & 260 deletions

02-use-cases/market-trends-agent/.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@
77
optimization/state.json
88
optimization/custom_evaluator_ids.json
99

10+
# Browser screenshots captured during demo runs
11+
browser_screenshots/
12+
13+
# Test results (generated by test_auto.py)
14+
test_results.md
15+
test_auto.py
16+
17+
# PR description drafts (local only)
18+
PR_DESCRIPTION*.md
19+
1020
# Python
1121
__pycache__/
1222
*.py[cod]
@@ -47,6 +57,9 @@ venv.bak/
4757
*.swp
4858
*.swo
4959

60+
# Node
61+
node_modules/
62+
5063
# OS
5164
.DS_Store
5265
Thumbs.db

02-use-cases/market-trends-agent/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,80 @@ uv run python optimization/optimize_agent.py --cleanup --state-file optimization
599599
600600
---
601601

602+
## Demo UI
603+
604+
The agent includes a React-based chat interface for interactive conversations, backed by a FastAPI proxy that forwards requests to your deployed AgentCore agent.
605+
606+
### Install Frontend Dependencies
607+
608+
```bash
609+
cd frontend
610+
npm install
611+
cd ..
612+
```
613+
614+
### Launch the Demo UI
615+
616+
```bash
617+
uv run python run_demo_ui.py
618+
```
619+
620+
This starts both:
621+
- **Backend** (FastAPI): http://localhost:8001 — proxies requests to your deployed AgentCore agent
622+
- **Frontend** (React/Vite): http://localhost:3000 — the chat interface
623+
624+
Open http://localhost:3000 in your browser.
625+
626+
### Try It Out
627+
628+
1. Click a **Quick Demo Profile** in the sidebar (e.g., "Growth Investor") to load a sample broker card
629+
2. Press Enter to submit the profile — the agent will store it in memory
630+
3. Ask follow-up questions like:
631+
- "What's the current Apple stock price?"
632+
- "Search Bloomberg for latest AI sector news"
633+
- "What do you remember about my investment preferences?"
634+
- "Give me a personalized market briefing for today"
635+
636+
The agent remembers your profile across messages in the same session.
637+
638+
> **Note:** Press `Ctrl+C` to stop both servers when done. The AgentCore agent itself remains deployed (serverless, no idle cost).
639+
640+
### Stop the Demo UI
641+
642+
Press `Ctrl+C` in the terminal where you ran `uv run python run_demo_ui.py`. This stops both the FastAPI backend and the Vite frontend dev server.
643+
644+
### Clean Up Frontend (Optional)
645+
646+
```bash
647+
# Remove installed node modules
648+
rm -rf frontend/node_modules
649+
650+
# On Windows PowerShell
651+
Remove-Item -Recurse -Force frontend/node_modules
652+
```
653+
654+
### Project Structure (UI Components)
655+
656+
```
657+
market-trends-agent/
658+
├── api_server.py # FastAPI backend for the demo UI
659+
├── run_demo_ui.py # Launches both backend and frontend
660+
├── frontend/
661+
│ ├── src/
662+
│ │ ├── App.jsx # React chat interface
663+
│ │ ├── App.css # Dark theme styling
664+
│ │ └── main.jsx # Entry point
665+
│ ├── package.json # Frontend dependencies
666+
│ └── vite.config.js # Vite dev server config (proxies to backend)
667+
```
668+
669+
### Troubleshooting (UI)
670+
671+
1. **Port 8000 Conflict** — If port 8000 is in use (e.g., by AWSWorkDocsDriveClient on Windows), the backend runs on port 8001 instead. The frontend proxy is already configured for this.
672+
2. **Frontend Not Loading** — Make sure you ran `npm install` in the `frontend/` directory. Check that port 3000 is not in use.
673+
674+
---
675+
602676
## Architecture
603677

604678
### Component Overview
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
"""
2+
FastAPI backend for Market Trends Agent React UI.
3+
Proxies chat requests to the deployed AgentCore Runtime agent.
4+
"""
5+
6+
import json
7+
import os
8+
import logging
9+
from pathlib import Path
10+
from fastapi import FastAPI
11+
from fastapi.middleware.cors import CORSMiddleware
12+
from pydantic import BaseModel, Field
13+
import boto3
14+
from botocore.config import Config
15+
16+
logging.basicConfig(level=logging.INFO)
17+
logger = logging.getLogger(__name__)
18+
19+
app = FastAPI(title="Market Trends Agent API")
20+
21+
# Allow React dev server to connect
22+
app.add_middleware(
23+
CORSMiddleware,
24+
allow_origins=["http://localhost:3000"],
25+
allow_methods=["*"],
26+
allow_headers=["*"],
27+
)
28+
29+
# AWS region for AgentCore Runtime
30+
REGION = os.getenv("AWS_REGION", "us-east-1")
31+
32+
# Boto3 client with extended timeout for agent responses (browser + LLM calls can be slow)
33+
boto_config = Config(read_timeout=300, retries={"max_attempts": 2})
34+
agentcore_client = boto3.client("bedrock-agentcore", region_name=REGION, config=boto_config)
35+
36+
37+
def load_agent_arn() -> str | None:
38+
"""Load deployed agent ARN from the .agent_arn file created by deploy.py"""
39+
arn_file = Path(__file__).parent / ".agent_arn"
40+
if arn_file.exists():
41+
return arn_file.read_text().strip()
42+
return None
43+
44+
45+
class ChatRequest(BaseModel):
46+
message: str = Field(..., max_length=4000)
47+
session_id: str = Field(default="", max_length=100)
48+
49+
50+
@app.get("/api/health")
51+
def health():
52+
"""Health check — also reports whether the agent is deployed"""
53+
arn = load_agent_arn()
54+
return {"status": "ok", "agent_deployed": arn is not None, "region": REGION}
55+
56+
57+
@app.post("/api/chat")
58+
def chat(req: ChatRequest):
59+
"""Send a message to the deployed AgentCore agent and return the response"""
60+
arn = load_agent_arn()
61+
if not arn:
62+
return {"error": "Agent not deployed. Run 'uv run python deploy.py' first."}
63+
64+
try:
65+
# Build invocation payload matching the agent's expected format
66+
payload = json.dumps({"prompt": req.message, "session_id": req.session_id}).encode("utf-8")
67+
params: dict = {"agentRuntimeArn": arn, "payload": payload}
68+
69+
# Pass session ID for memory continuity across messages
70+
if req.session_id:
71+
params["runtimeSessionId"] = req.session_id
72+
73+
logger.info(f"Invoking agent | session={req.session_id[:24]}...")
74+
response = agentcore_client.invoke_agent_runtime(**params)
75+
76+
# Read the response body (handles both streaming and standard responses)
77+
if "response" in response:
78+
body = response["response"].read().decode("utf-8")
79+
else:
80+
body = str(response)
81+
82+
# AgentCore often returns a JSON-encoded string (e.g. "\"hello\\nworld\"")
83+
# Unwrap it so the frontend receives clean text with real newlines
84+
try:
85+
parsed = json.loads(body)
86+
if isinstance(parsed, str):
87+
body = parsed
88+
except (json.JSONDecodeError, TypeError):
89+
pass
90+
91+
# Final safety: replace any remaining literal \n sequences with real newlines
92+
body = body.replace("\\n", "\n")
93+
94+
return {"response": body, "session_id": req.session_id}
95+
96+
except Exception as e:
97+
logger.error(f"Agent invocation error: {e}")
98+
return {"error": "Agent request failed. Check server logs for details."}
99+
100+
101+
if __name__ == "__main__":
102+
import uvicorn
103+
uvicorn.run(app, host="0.0.0.0", port=8001)

02-use-cases/market-trends-agent/deploy.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,12 @@ def create_execution_role(self, role_name: str) -> str:
190190
"bedrock-agentcore:DeleteMemory",
191191
"bedrock-agentcore:GetMemory",
192192
"bedrock-agentcore:RetrieveMemoryRecords",
193+
"bedrock-agentcore:CreateMemoryRecord",
194+
"bedrock-agentcore:DeleteMemoryRecord",
195+
"bedrock-agentcore:UpdateMemoryRecord",
196+
"bedrock-agentcore:GetCheckpoint",
197+
"bedrock-agentcore:PutCheckpoint",
198+
"bedrock-agentcore:ListCheckpoints",
193199
],
194200
"Resource": [
195201
f"arn:aws:bedrock-agentcore:{self.region}:{account_id}:memory/*"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Market Trends Agent — AgentCore Demo</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/main.jsx"></script>
11+
</body>
12+
</html>

0 commit comments

Comments
 (0)