Skip to content

Commit 31a99f4

Browse files
committed
Added strands example
Signed-off-by: Prasad Mujumdar <prasad@okahu.ai>
1 parent f4f076d commit 31a99f4

5 files changed

Lines changed: 226 additions & 0 deletions

File tree

python/strands-nba-agent/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# AWS Strands agent
2+
Agent example to provide NBA game data implemented with AWS Strands
3+
4+
## Pre-requesits
5+
- A Sagemaker resource with the large language deployed. This example is tested with Claud Sonnet 4. If you don't have a resource the process of creating one is documented in AWS resource [deployment guide](https://docs.aws.amazon.com/sagemaker/latest/dg/how-it-works-deployment.html)
6+
- Sagemaker configuration details
7+
- AWS credentials- This value can be obtained by running AWS CLI commend aws sts get-session-token
8+
- Sagemaker API Endpoint - This value can be found in the configuration section of Sagemaker in AWS portal.
9+
- If you are not familiar with NBA or basketball checkout NBA.com :)
10+
11+
## Setup environment to run the agent
12+
- Copy env.template to .env
13+
- Set the AWS credentials and sagemaker end point values in .env
14+
- Install python dependencies
15+
- ` pip install -r requirements.txt
16+
- Run the demo app
17+
- `python nba_agent.py
18+
- When prompted for questions, ask game or team ranking related questions
19+
- `What happened in warrior's game on 26th November 2025`
20+
- `What is Piston's ranking this season?`
21+
22+
## Debug the agent with Monocle
23+
[Monocle](monocle2ai.org) is a GenAI-native community driven open source project created to simplify instrumentation of AI apps so app developers can built high impact, safe and reliable AI apps.
24+
25+
### Install Monocle
26+
`pip install monocle_apptrace`
27+
You can generate monocle telemetry by using the monocle_apptrace module in python command line (no code change) or enable monocle tracing by just calling one API in app code
28+
29+
### Run agent app with monocle with no code change
30+
`python -m monocle_apptrace nba_agent.py`
31+
32+
### Run agent app with monocle with a simple code change
33+
```python
34+
from monocle_apptrace import setup_monocle_telemetry
35+
setup_monocle_telemetry(workflow_name = 'aws_strands_nba_agent')
36+
```
37+
from datetime import datetime, timedelta
38+
39+
## Visualize the telemetry to understand the agent execution
40+
- Install extension `Okahu Trace Visualizer` from marketplace for VSCode or Cursor
41+
- This will add the extension [icon](media/okahu-favicon.png) in the list of extension (left pane for VSCode or extension dropdown in Cursor).
42+
- Click on the extension icon. It'll open a new pane on left that will list the traces for each agent turn in the descending order of execution time.
43+
- When you click on any of the trace list, it will open a new windows with the trace visualization.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
AWS_ACCESS_KEY_ID=
2+
AWS_SECRET_ACCESS_KEY=
3+
AWS_REGION=
4+
SAGEMAKER_ENDPOINT_NAME=
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
from datetime import datetime, timedelta
2+
import json
3+
import time
4+
from uuid import UUID
5+
import dotenv
6+
from nba_api.live.nba.endpoints import scoreboard
7+
from nba_api.stats.endpoints import scheduleleaguev2, leaguestandingsv3
8+
from strands import tool
9+
from strands.models.bedrock import BedrockModel
10+
from strands.session.file_session_manager import FileSessionManager
11+
import boto3
12+
from strands import Agent
13+
import pandas as pd
14+
15+
dotenv.load_dotenv()
16+
17+
@tool
18+
def get_nba_live_scores(name:str):
19+
"""Get today's NBA live scores for a given team.
20+
This tool doesn't provide past scores.
21+
Args:
22+
name (str): team name or city of the NBA team.
23+
"""
24+
# Today's Score Board
25+
games = scoreboard.ScoreBoard()
26+
games_json = json.loads(games.get_json())
27+
name = name.lower()
28+
scores = []
29+
for game in games_json['scoreboard']['games']:
30+
home_team = game['homeTeam']['teamName'].lower()
31+
away_team = game['awayTeam']['teamName'].lower()
32+
if name in [home_team, away_team]:
33+
scores.append({
34+
'home_team': game['homeTeam']['teamName'],
35+
'home_score': game['homeTeam']['score'],
36+
'away_team': game['awayTeam']['teamName'],
37+
'away_score': game['awayTeam']['score'],
38+
'quarter': game['period'],
39+
'time_remaining': game['gameClock']
40+
})
41+
break
42+
if not scores:
43+
return f"No games found for team: {name}"
44+
return scores
45+
46+
@tool
47+
def get_nba_past_scores(name:str, game_date:str):
48+
"""Get past NBA scores for a given team for a specific date (MM/DD/YYYY).
49+
Use 'today' or 'yesterday' for convenience.
50+
Args:
51+
name (str): team name or city of the NBA team.
52+
"""
53+
if game_date.lower() == "today":
54+
date = datetime.now().strftime("%m/%d/%Y")
55+
elif game_date.lower() in ["yesterday", "yday", "prev day", "y'day"]:
56+
date = (datetime.now() - timedelta(days=1)).strftime("%m/%d/%Y")
57+
else:
58+
try:
59+
date_obj = datetime.strptime(game_date, "%m/%d/%Y")
60+
date = date_obj.strftime("%m/%d/%Y")
61+
except ValueError:
62+
return "Please provide the date in 'MM/DD/YYYY' format or use 'today'/'yesterday'."
63+
schedule = scheduleleaguev2.ScheduleLeagueV2()
64+
json_schedule = json.loads(schedule.get_json())
65+
games = json_schedule['leagueSchedule']['gameDates']
66+
past_scores = []
67+
for day_games in games:
68+
if day_games['gameDate'].startswith(date):
69+
for game in day_games['games']:
70+
if name.lower() in [
71+
game['homeTeam']['teamName'].lower(),
72+
game['homeTeam']['teamCity'].lower(),
73+
game['awayTeam']['teamName'].lower(),
74+
game['awayTeam']['teamCity'].lower()
75+
]:
76+
past_scores.append({
77+
'home_team': game['homeTeam']['teamName'],
78+
'home_score': game['homeTeam']['score'],
79+
'away_team': game['awayTeam']['teamName'],
80+
'away_score': game['awayTeam']['score']
81+
})
82+
break
83+
if not past_scores:
84+
if game_date.lower() == "today":
85+
return f"Also check live scores tool. No past games found for team: {name} today."
86+
return f"No past games found for team: {name} on date: {date}"
87+
return past_scores
88+
89+
@tool
90+
def get_team_standings(name:str):
91+
"""Get standings/ranking for a given team.
92+
93+
Args:
94+
name (str): team name or city of the NBA team.
95+
"""
96+
standings = leaguestandingsv3.LeagueStandingsV3()
97+
json_standings = json.loads(standings.get_json())
98+
df = pd.DataFrame(json_standings['resultSets'][0]['rowSet'],
99+
columns=json_standings['resultSets'][0]['headers'])
100+
team_status = None
101+
for index, row in df.iterrows():
102+
if row['TeamName'].lower() == name.lower() or row['TeamCity'].lower() == name.lower():
103+
team_status = {
104+
'Rank': row['PlayoffRank'],
105+
'Record': row['Record'],
106+
'Conference': row['Conference']
107+
}
108+
break
109+
if team_status is None:
110+
return f"No standings found for team: {name}"
111+
return team_status
112+
113+
def setup_agents() -> Agent:
114+
boto_session = boto3.Session()
115+
claud_on_bedrock = BedrockModel(boto_session=boto_session, streaming=False)
116+
# Create a session manager with a unique session ID
117+
session_manager = FileSessionManager(session_id=str(UUID(int=time.time_ns())))
118+
119+
nba_score_agent = \
120+
Agent(name="Nba Score Agent", model=claud_on_bedrock,
121+
system_prompt= """You are an agent who provides NBA scores
122+
and standings for the given team. Look at the tool output and
123+
anwer the user query only from that output.
124+
Be very concise and to the point.
125+
If anything other than NBA scores or standings is asked,
126+
respond with 'I can only provide NBA scores and standings.'.
127+
When providing live scores use following format:
128+
'Team A is playing against Team B with current score X-Y.'
129+
If no games found, just say 'I didn't find any game for the team'
130+
When providing response to game status use following format:
131+
'Team A won/lost against Team B with score X-Y.'
132+
If no games found, just say 'I didn't find any game for the team'
133+
When providing team status or standings use following format:
134+
'Team A is currently ranked X in the CONFERENCE with a record of W-L.'
135+
""",
136+
tools = [get_nba_live_scores, get_nba_past_scores, get_team_standings],
137+
description="NBA scores agent", callback_handler=None,
138+
session_manager=session_manager
139+
)
140+
return nba_score_agent
141+
142+
def get_scores(message: str):
143+
travel_agent = setup_agents()
144+
response = travel_agent(message)
145+
return response.message['content'][0]['text']
146+
147+
if __name__ == "__main__":
148+
nba_agent = setup_agents()
149+
while True:
150+
try:
151+
user_request = input("\nHey, got a question on NBA scores or standings? ")
152+
except EOFError:
153+
user_request = "exit"
154+
if user_request.lower() in ["exit", "quit", ""]:
155+
print("Exiting the NBA scores agent. Goodbye!")
156+
break
157+
response = nba_agent(prompt=user_request)
158+
print(response.message['content'][0]['text'])
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
strands-agents
2+
strands-agents-tools
3+
nba_api
4+
monocle_apptrace
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
import pytest
3+
import logging
4+
from nba_agent import get_scores
5+
from monocle_test_tools import TraceAssertion
6+
7+
@pytest.mark.asyncio
8+
async def test_tool_invocation(monocle_trace_asserter:TraceAssertion):
9+
get_scores("What happened in Clippers game on 22 Nov 2025")
10+
monocle_trace_asserter.called_tool("get_nba_past_scores")\
11+
.contains_input("Clippers")\
12+
.contains_output("Clippers")\
13+
.contains_output("Hornets")\
14+
.contains_output("131-116")
15+
16+
if __name__ == "__main__":
17+
pytest.main([__file__])

0 commit comments

Comments
 (0)