Skip to content

Commit 19a623b

Browse files
authored
Merge #20 Incorporate AgentCore deploy into TF
Roll our previously-manual container image build & deploy steps into Terraform, now that AgentCore is GA and Terraform supports it.
2 parents f605673 + 5d8b962 commit 19a623b

File tree

17 files changed

+837
-683
lines changed

17 files changed

+837
-683
lines changed

README.md

Lines changed: 241 additions & 54 deletions
Large diffs are not rendered by default.

agentcore_runtime_deployment.ipynb

Lines changed: 0 additions & 528 deletions
This file was deleted.

chat_to_agentcore.ipynb

Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "71db7e7e",
6+
"metadata": {},
7+
"source": [
8+
"# Test your agent from a Python Notebook\n",
9+
"\n",
10+
"This interactive notebook provides an alternative way to test your deployed agent, besides the Streamlit UI app."
11+
]
12+
},
13+
{
14+
"cell_type": "markdown",
15+
"id": "cbced266",
16+
"metadata": {},
17+
"source": [
18+
"## Kernel selection and prerequisites\n",
19+
"\n",
20+
"You can use the `cx-agent-backend/.venv` as a kernel. If this is not set up already, run the following from your terminal:\n",
21+
"\n",
22+
"```bash\n",
23+
"cd cx-agent-backend\n",
24+
"uv venv\n",
25+
"uv sync --all-extras --frozen\n",
26+
"```\n",
27+
"\n",
28+
"This notebook assumes:\n",
29+
"1. You've already deployed the main solution as described in [README.md](./README.md)\n",
30+
"2. Your Python kernel is already configured with AWS credentials and target AWS Region (for example via environment variables, potentially set via a `.env` file as documented [here for VSCode](https://code.visualstudio.com/docs/python/environments#_environment-variables)).\n",
31+
" - Note that the AWS SDK for Python, `boto3`, [expects](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#using-environment-variables) an `AWS_DEFAULT_REGION` environment variable rather than `AWS_REGION`."
32+
]
33+
},
34+
{
35+
"cell_type": "markdown",
36+
"id": "b51a5349",
37+
"metadata": {},
38+
"source": [
39+
"## Dependencies and setup\n",
40+
"\n",
41+
"First we'll import the necessary libraries, and initialize clients for AWS Services, and define some utility functions that'll be used later:"
42+
]
43+
},
44+
{
45+
"cell_type": "code",
46+
"execution_count": null,
47+
"id": "93b97307",
48+
"metadata": {},
49+
"outputs": [],
50+
"source": [
51+
"# Python Built-Ins:\n",
52+
"import base64\n",
53+
"import json\n",
54+
"import getpass\n",
55+
"import hashlib\n",
56+
"import hmac\n",
57+
"import os\n",
58+
"import uuid\n",
59+
"import secrets\n",
60+
"import string\n",
61+
"import urllib.parse\n",
62+
"\n",
63+
"# External Libraries:\n",
64+
"import boto3 # AWS SDK for Python\n",
65+
"import requests # For making raw HTTP(S) API calls\n",
66+
"\n",
67+
"# AWS Service Clients:\n",
68+
"botosess = boto3.Session() # You could set `region_name` here explicitly if wanted\n",
69+
"cognito_client = botosess.client(\"cognito-idp\") # Cognito (Identity Provider)\n",
70+
"\n",
71+
"\n",
72+
"def _set_if_undefined(var: str, name: str | None = None) -> str:\n",
73+
" \"\"\"Utility to prompt user once for a value, and cache it in environment variable\"\"\"\n",
74+
" if not os.environ.get(var):\n",
75+
" os.environ[var] = getpass.getpass(f\"Please provide your {name or var}:\")\n",
76+
" return os.environ[var]\n",
77+
"\n",
78+
"\n",
79+
"def calculate_secret_hash(username, client_id, client_secret):\n",
80+
" \"\"\"Utility to hash a username + client ID + client secret for Cognito login\"\"\"\n",
81+
" message = username + client_id\n",
82+
" return base64.b64encode(\n",
83+
" hmac.new(\n",
84+
" client_secret.encode(\"utf-8\"),\n",
85+
" message.encode(\"utf-8\"),\n",
86+
" hashlib.sha256\n",
87+
" ).digest()\n",
88+
" ).decode(\"utf-8\")\n",
89+
"\n",
90+
"\n",
91+
"def invoke_agent(\n",
92+
" message,\n",
93+
" agent_arn,\n",
94+
" auth_token,\n",
95+
" session_id,\n",
96+
" qualifier=\"DEFAULT\",\n",
97+
" region=botosess.region_name,\n",
98+
"):\n",
99+
" \"\"\"Invoke Bedrock AgentCore runtime with a message.\"\"\"\n",
100+
" escaped_agent_arn = urllib.parse.quote(agent_arn, safe='')\n",
101+
" response = requests.post(\n",
102+
" f\"https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{escaped_agent_arn}/invocations?qualifier={qualifier}\",\n",
103+
" headers={\n",
104+
" \"Authorization\": f\"Bearer {auth_token}\",\n",
105+
" \"Content-Type\": \"application/json\",\n",
106+
" \"X-Amzn-Bedrock-AgentCore-Runtime-Session-Id\": session_id\n",
107+
" },\n",
108+
" data=json.dumps({\"input\": {\"prompt\": message, \"conversation_id\": session_id}}),\n",
109+
" timeout=61,\n",
110+
" )\n",
111+
" \n",
112+
" print(f\"Status Code: {response.status_code}\")\n",
113+
" \n",
114+
" if response.status_code == 200:\n",
115+
" return response.json()\n",
116+
" else:\n",
117+
" raise ValueError(f\"HTTP {response.status_code}: {response.text}\")"
118+
]
119+
},
120+
{
121+
"cell_type": "markdown",
122+
"id": "b1f027bf",
123+
"metadata": {},
124+
"source": [
125+
"## Fetch access token from Amazon Cognito\n",
126+
"\n",
127+
"To talk to the AgentCore-deployed agent, we'll need to log in to Amazon Cognito to fetch a session token.\n",
128+
"\n",
129+
"You'll need to fetch your Cognito user_pool_id and client_id in the cell below, which you can view by running the `terraform output` command in your terminal:"
130+
]
131+
},
132+
{
133+
"cell_type": "code",
134+
"execution_count": null,
135+
"id": "09e8aa2d",
136+
"metadata": {},
137+
"outputs": [],
138+
"source": [
139+
"user_pool_id = TODO # E.g. run `terraform output -raw user_pool_id`\n",
140+
"client_id = TODO # E.g. run `terraform output -raw client_id`\n",
141+
"\n",
142+
"# From these we should be able to look up the client secret automatically:\n",
143+
"client_secret = cognito_client.describe_user_pool_client(\n",
144+
" UserPoolId=user_pool_id,\n",
145+
" ClientId=client_id\n",
146+
")[\"UserPoolClient\"][\"ClientSecret\"]"
147+
]
148+
},
149+
{
150+
"cell_type": "markdown",
151+
"id": "66238615",
152+
"metadata": {},
153+
"source": [
154+
"The next cell will prompt you for your Cognito username (email address) and password, or re-use the existing one if you run the cell again without restarting the notebook:"
155+
]
156+
},
157+
{
158+
"cell_type": "code",
159+
"execution_count": null,
160+
"id": "00938dba",
161+
"metadata": {},
162+
"outputs": [],
163+
"source": [
164+
"username = _set_if_undefined(\"COGNITO_USERNAME\", \"Cognito user name (email address)\")\n",
165+
"password = _set_if_undefined(\"COGNITO_PASSWORD\", \"Cognito password\")"
166+
]
167+
},
168+
{
169+
"cell_type": "markdown",
170+
"id": "e00e6221",
171+
"metadata": {},
172+
"source": [
173+
"The deployment steps in [README.md](./README.md) guide you through setting up your Cognito user from the AWS CLI, but you could instead un-comment and run the below to achieve the same effect from Python:"
174+
]
175+
},
176+
{
177+
"cell_type": "code",
178+
"execution_count": null,
179+
"id": "fde3f16b",
180+
"metadata": {},
181+
"outputs": [],
182+
"source": [
183+
"## Create a user (with temporary password)\n",
184+
"# create_user_resp = cognito_client.admin_create_user(\n",
185+
"# UserPoolId=user_pool_id,\n",
186+
"# Username=username,\n",
187+
"# # Temp password is randomized here because we'll never use it:\n",
188+
"# TemporaryPassword=\"\".join((\n",
189+
"# secrets.choice(\n",
190+
"# string.ascii_uppercase + string.ascii_lowercase + string.digits +\n",
191+
"# \"^$*.[]{}()?-'\\\"!@#%&/\\\\,><':;|_~`+=\"\n",
192+
"# ) for i in range(20)\n",
193+
"# )),\n",
194+
"# MessageAction=\"SUPPRESS\"\n",
195+
"# )\n",
196+
"# print(f\"User created: {create_user_resp['User']['Username']}\")\n",
197+
"\n",
198+
"## Override the password to the given value (permanently)\n",
199+
"# set_password_resp = cognito_client.admin_set_user_password(\n",
200+
"# UserPoolId=user_pool_id,\n",
201+
"# Username=username,\n",
202+
"# Password=password,\n",
203+
"# Permanent=True,\n",
204+
"# )\n",
205+
"# print(\"Password set successfully\")"
206+
]
207+
},
208+
{
209+
"cell_type": "markdown",
210+
"id": "ac6d3aff",
211+
"metadata": {},
212+
"source": [
213+
"With the configuration set up, we're ready to request an access token from Cognito:"
214+
]
215+
},
216+
{
217+
"cell_type": "code",
218+
"execution_count": null,
219+
"id": "33858093",
220+
"metadata": {},
221+
"outputs": [],
222+
"source": [
223+
"auth_resp = cognito_client.initiate_auth(\n",
224+
" ClientId=client_id,\n",
225+
" AuthFlow=\"USER_PASSWORD_AUTH\",\n",
226+
" AuthParameters={\n",
227+
" \"USERNAME\": username,\n",
228+
" \"PASSWORD\": password,\n",
229+
" \"SECRET_HASH\": calculate_secret_hash(username, client_id, client_secret),\n",
230+
" }\n",
231+
")\n",
232+
"\n",
233+
"access_token = auth_resp[\"AuthenticationResult\"][\"AccessToken\"]\n",
234+
"print(\"Access token fetched\")"
235+
]
236+
},
237+
{
238+
"cell_type": "markdown",
239+
"id": "05e0c2e6",
240+
"metadata": {},
241+
"source": [
242+
"## Invoke the agent\n",
243+
"\n",
244+
"With the access token ready, we're almost ready to invoke our AgentCore Agent. First though, you'll need to:\n",
245+
"1. Look up the deployed AgentRuntime ARN from the terraform, and\n",
246+
"2. Choose a session ID (we'll randomize this automatically)"
247+
]
248+
},
249+
{
250+
"cell_type": "code",
251+
"execution_count": null,
252+
"id": "6754ec78",
253+
"metadata": {},
254+
"outputs": [],
255+
"source": [
256+
"agent_arn = TODO # E.g. run `terraform output -raw agent_runtime_arn`\n",
257+
"\n",
258+
"session_id = str(uuid.uuid4()) # Can auto-generate this"
259+
]
260+
},
261+
{
262+
"cell_type": "code",
263+
"execution_count": null,
264+
"id": "9cf235a1",
265+
"metadata": {},
266+
"outputs": [],
267+
"source": [
268+
"result = invoke_agent(\"Hello, can you help me resetting my router?\", agent_arn, access_token, session_id)\n",
269+
"if result:\n",
270+
" print(json.dumps(result, indent=2))"
271+
]
272+
},
273+
{
274+
"cell_type": "markdown",
275+
"id": "583c6b54",
276+
"metadata": {},
277+
"source": [
278+
"### Testing math functionality"
279+
]
280+
},
281+
{
282+
"cell_type": "code",
283+
"execution_count": null,
284+
"id": "60914206",
285+
"metadata": {},
286+
"outputs": [],
287+
"source": [
288+
"math_test = \"What is 25 + 17?\"\n",
289+
"print(f\"Testing math with: {math_test}\")\n",
290+
"result = invoke_agent(math_test, agent_arn, access_token, session_id)\n",
291+
"if result:\n",
292+
" print(json.dumps(result, indent=2))\n",
293+
"print(\"\\n\" + \"=\"*50 + \"\\n\")"
294+
]
295+
},
296+
{
297+
"cell_type": "markdown",
298+
"id": "ceac8013",
299+
"metadata": {},
300+
"source": [
301+
"### Testing memory persistence"
302+
]
303+
},
304+
{
305+
"cell_type": "code",
306+
"execution_count": null,
307+
"id": "68de9444",
308+
"metadata": {},
309+
"outputs": [],
310+
"source": [
311+
"message = \"Add 10 to the result\"\n",
312+
"print(message)\n",
313+
"result = invoke_agent(message, agent_arn, access_token, session_id)\n",
314+
"if result:\n",
315+
" print(json.dumps(result, indent=2))\n",
316+
"print(\"\\n\" + \"=\"*50 + \"\\n\")"
317+
]
318+
},
319+
{
320+
"cell_type": "code",
321+
"execution_count": null,
322+
"id": "5caf85c9",
323+
"metadata": {},
324+
"outputs": [],
325+
"source": [
326+
"result = invoke_agent(\"What device did I want to reset?\", agent_arn, access_token, session_id)\n",
327+
"if result:\n",
328+
" print(json.dumps(result, indent=2))"
329+
]
330+
},
331+
{
332+
"cell_type": "code",
333+
"execution_count": null,
334+
"id": "646cef68",
335+
"metadata": {},
336+
"outputs": [],
337+
"source": []
338+
}
339+
],
340+
"metadata": {
341+
"kernelspec": {
342+
"display_name": ".venv",
343+
"language": "python",
344+
"name": "python3"
345+
},
346+
"language_info": {
347+
"codemirror_mode": {
348+
"name": "ipython",
349+
"version": 3
350+
},
351+
"file_extension": ".py",
352+
"mimetype": "text/x-python",
353+
"name": "python",
354+
"nbconvert_exporter": "python",
355+
"pygments_lexer": "ipython3",
356+
"version": "3.12.8"
357+
}
358+
},
359+
"nbformat": 4,
360+
"nbformat_minor": 5
361+
}

0 commit comments

Comments
 (0)