Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions 02-use-cases/market-trends-agent/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
optimization/state.json
optimization/custom_evaluator_ids.json

# Browser screenshots captured during demo runs
browser_screenshots/

# Test results (generated by test_auto.py)
test_results.md
test_auto.py

# PR description drafts (local only)
PR_DESCRIPTION*.md

# Python
__pycache__/
*.py[cod]
Expand Down Expand Up @@ -47,6 +57,9 @@ venv.bak/
*.swp
*.swo

# Node
node_modules/

# OS
.DS_Store
Thumbs.db
Expand Down
74 changes: 74 additions & 0 deletions 02-use-cases/market-trends-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,80 @@ uv run python optimization/optimize_agent.py --cleanup --state-file optimization

---

## Demo UI

The agent includes a React-based chat interface for interactive conversations, backed by a FastAPI proxy that forwards requests to your deployed AgentCore agent.

### Install Frontend Dependencies

```bash
cd frontend
npm install
cd ..
```

### Launch the Demo UI

```bash
uv run python run_demo_ui.py
```

This starts both:
- **Backend** (FastAPI): http://localhost:8001 — proxies requests to your deployed AgentCore agent
- **Frontend** (React/Vite): http://localhost:3000 — the chat interface

Open http://localhost:3000 in your browser.

### Try It Out

1. Click a **Quick Demo Profile** in the sidebar (e.g., "Growth Investor") to load a sample broker card
2. Press Enter to submit the profile — the agent will store it in memory
3. Ask follow-up questions like:
- "What's the current Apple stock price?"
- "Search Bloomberg for latest AI sector news"
- "What do you remember about my investment preferences?"
- "Give me a personalized market briefing for today"

The agent remembers your profile across messages in the same session.

> **Note:** Press `Ctrl+C` to stop both servers when done. The AgentCore agent itself remains deployed (serverless, no idle cost).

### Stop the Demo UI

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.

### Clean Up Frontend (Optional)

```bash
# Remove installed node modules
rm -rf frontend/node_modules

# On Windows PowerShell
Remove-Item -Recurse -Force frontend/node_modules
```

### Project Structure (UI Components)

```
market-trends-agent/
├── api_server.py # FastAPI backend for the demo UI
├── run_demo_ui.py # Launches both backend and frontend
├── frontend/
│ ├── src/
│ │ ├── App.jsx # React chat interface
│ │ ├── App.css # Dark theme styling
│ │ └── main.jsx # Entry point
│ ├── package.json # Frontend dependencies
│ └── vite.config.js # Vite dev server config (proxies to backend)
```

### Troubleshooting (UI)

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.
2. **Frontend Not Loading** — Make sure you ran `npm install` in the `frontend/` directory. Check that port 3000 is not in use.

---

## Architecture

### Component Overview
Expand Down
103 changes: 103 additions & 0 deletions 02-use-cases/market-trends-agent/api_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""
FastAPI backend for Market Trends Agent React UI.
Proxies chat requests to the deployed AgentCore Runtime agent.
"""

import json
import os
import logging
from pathlib import Path
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
import boto3
from botocore.config import Config

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = FastAPI(title="Market Trends Agent API")

# Allow React dev server to connect
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_methods=["*"],
allow_headers=["*"],
)

# AWS region for AgentCore Runtime
REGION = os.getenv("AWS_REGION", "us-east-1")

# Boto3 client with extended timeout for agent responses (browser + LLM calls can be slow)
boto_config = Config(read_timeout=300, retries={"max_attempts": 2})
agentcore_client = boto3.client("bedrock-agentcore", region_name=REGION, config=boto_config)


def load_agent_arn() -> str | None:
"""Load deployed agent ARN from the .agent_arn file created by deploy.py"""
arn_file = Path(__file__).parent / ".agent_arn"
if arn_file.exists():
return arn_file.read_text().strip()
return None


class ChatRequest(BaseModel):
message: str = Field(..., max_length=4000)
session_id: str = Field(default="", max_length=100)


@app.get("/api/health")
def health():
"""Health check — also reports whether the agent is deployed"""
arn = load_agent_arn()
return {"status": "ok", "agent_deployed": arn is not None, "region": REGION}


@app.post("/api/chat")
def chat(req: ChatRequest):
"""Send a message to the deployed AgentCore agent and return the response"""
arn = load_agent_arn()
if not arn:
return {"error": "Agent not deployed. Run 'uv run python deploy.py' first."}

try:
# Build invocation payload matching the agent's expected format
payload = json.dumps({"prompt": req.message, "session_id": req.session_id}).encode("utf-8")
params: dict = {"agentRuntimeArn": arn, "payload": payload}

# Pass session ID for memory continuity across messages
if req.session_id:
params["runtimeSessionId"] = req.session_id

logger.info(f"Invoking agent | session={req.session_id[:24]}...")
response = agentcore_client.invoke_agent_runtime(**params)

# Read the response body (handles both streaming and standard responses)
if "response" in response:
body = response["response"].read().decode("utf-8")
else:
body = str(response)

# AgentCore often returns a JSON-encoded string (e.g. "\"hello\\nworld\"")
# Unwrap it so the frontend receives clean text with real newlines
try:
parsed = json.loads(body)
if isinstance(parsed, str):
body = parsed
except (json.JSONDecodeError, TypeError):
pass

# Final safety: replace any remaining literal \n sequences with real newlines
body = body.replace("\\n", "\n")

return {"response": body, "session_id": req.session_id}

except Exception as e:
logger.error(f"Agent invocation error: {e}")
return {"error": "Agent request failed. Check server logs for details."}


if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8001)
6 changes: 6 additions & 0 deletions 02-use-cases/market-trends-agent/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,12 @@ def create_execution_role(self, role_name: str) -> str:
"bedrock-agentcore:DeleteMemory",
"bedrock-agentcore:GetMemory",
"bedrock-agentcore:RetrieveMemoryRecords",
"bedrock-agentcore:CreateMemoryRecord",
"bedrock-agentcore:DeleteMemoryRecord",
"bedrock-agentcore:UpdateMemoryRecord",
"bedrock-agentcore:GetCheckpoint",
"bedrock-agentcore:PutCheckpoint",
"bedrock-agentcore:ListCheckpoints",
],
"Resource": [
f"arn:aws:bedrock-agentcore:{self.region}:{account_id}:memory/*"
Expand Down
12 changes: 12 additions & 0 deletions 02-use-cases/market-trends-agent/frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Market Trends Agent — AgentCore Demo</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Loading
Loading