Skip to content

Commit afad446

Browse files
sachsrysachsrs
andauthored
Human in loop example (#71)
* Create example for human-in-the-loop for accompanying blog * Implement linting using Black * final documentation tweaks, add clean up section, contributors, release notes * add arch diagram and sample prompt sections per template * rename "human-in-the-loop" to "human_in_the_loop" * Add custom policy for cfn_nag * PR comments for readme, update lambda function to remove magic number, mask accountIDs in images, add cdk log group * remove all user_id references * ensure that roc complies with the function based protocol, re-run linter and remove unnecessary suppression and other cdk comments * Implement another round of feedback with updated archictecture image. Updated the readmes to have copy/paste terminal commands. Lastly, clarified the cleanup process due to issue if you don't delete the additional alias/versions. --------- Co-authored-by: sachsrs <sachsrs@amazon.com>
1 parent 0c03f60 commit afad446

35 files changed

+1078
-2
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,7 @@ cython_debug/
181181
# and can be added to the global gitignore or merged into this file. For a more nuclear
182182
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
183183
#.idea/
184-
tmp-code-README.md
184+
tmp-code-README.md
185+
186+
# cdk files
187+
cdk.out*

CONTRIBUTORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111
- [Niklas Palm](https://github.com/niklas-palm)
1212
- [Yannie Wu](https://github.com/yanniewu)
1313
- [Bhavin Patel](https://github.com/bhavinjpatel)
14+
- [Ryan Sachs](https://github.com/sachsry)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ The solutions presented here use the [boto3 SDK in Python](https://boto3.amazona
105105
- [Agents with user confirmation before action execution](/examples/agents/user_confirmation_agents/)
106106
- [Agents with access to house security camera in cloudformation](/examples/agents/connected_house_agent/)
107107
- [Agents with metadata filtering](/examples/agents/metadata_filtering_amazon_bedrock_agents/)
108+
- [Agents with human_in_the_loop](/examples/agents/human_in_the_loop/)
108109

109110
## Multi-agent collaboration examples
110111

RELEASE_NOTES.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,8 @@ Adding [Agent with access to house security camera in cloudformation](/examples/
9191

9292
## 03/10/2025
9393

94-
[Computer use Agent](/examples/agents/computer_use/)
94+
[Computer use Agent](/examples/agents/computer_use/)
95+
96+
## 03/17/2025
97+
98+
[Human-in-the-loop Agent](/examples/agents/human_in_the_loop/)

examples/agents/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ This repository is designed to get you started with Amazon Bedrock Agents by pro
1919
- [Agent using OpenAPI schema](/examples/agents/open_api_schema_agent/): Create an insurance claims assistant agent using an OpenAPI schema file for the action groups definition
2020
- [Agents with user confirmation before action execution](/examples/agents/user_confirmation_agents/): Create agents that ask for user confirmation before executing an action from an action group
2121
- [Agent connected house](/examples/agents/connected_house_agent/): Create an agent conected your house surveillance cameras
22+
-- [Agents with human_in_the_loop](/examples/agents/human_in_the_loop/): Create an agent with confirmation and return-of-control capabilities.
2223

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Human-in-the-Loop HR Assistant
2+
3+
## Purpose
4+
This repo provides a backend for showcasing the human-in-the-loop capabilities in Amazon Bedrock, specifically focusing on Return of Control (ROC) functionality. This repo is for demonstrative purposes only, and the application code is not meant for production use.
5+
The implementation demonstrates how to build intelligent HR assistants that can handle time-off requests with varying levels of human oversight.
6+
This allows organizations to maintain appropriate control and verification of automated processes while leveraging AI capabilities.
7+
8+
The provided APIs demonstrate three key patterns:
9+
* Direct automation without human intervention
10+
* Confirmation-based workflows requiring human approval
11+
* Return of Control (ROC) scenarios where human input or verification is needed
12+
13+
## Architecture Diagram
14+
![Architecture Diagram](./images/diagram-overall-arch.png)
15+
16+
## Prerequisites
17+
Before getting started, ensure you have the following installed:
18+
* [Python 3.10+](https://www.python.org/downloads/)
19+
* [Streamlit](https://docs.streamlit.io/library/get-started/installation)
20+
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) configured with appropriate credentials and permissions
21+
* [AWS CLI Environment Variable setup](https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-envvars.html). Credentials used need to have permissions to CloudFormation, IAM, Bedrock, and Lambda. If you use PowerUser or Admin permissions, temporary credentials are recommended.
22+
* [Bedrock Foundation model access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html). Ensure that you have access to Haiku 3.5 in `us-west-2` at a minimum.
23+
* Using `us-west-2` is required to use Haiku 3.5 at the time of publishing.
24+
25+
If you are new to Python, be sure to read through [venv documentation](https://docs.python.org/3/library/venv.html) as you will need to use this to deploy the agent using the CDK and run the frontend application locally.
26+
27+
## Deployment
28+
The application consists of two main components that need to be deployed:
29+
30+
1. **Backend Infrastructure**
31+
* Navigate to the [CDK implementation](./cdk/README.md) for detailed deployment instructions
32+
* This will create the necessary AWS resources including Lambda functions and Bedrock Agent
33+
34+
2. **Frontend Application**
35+
* Follow the setup instructions in the [Frontend guide](./frontend/README.md)
36+
* This will deploy the Streamlit interface for interacting with the Bedrock Agent
37+
38+
## UI Preview
39+
Once deployed, you'll be able to interact with an interface similar to the one shown below:
40+
41+
> [!CAUTION]
42+
> Disclaimer: The current [backend API](./backend/lambda.py) does not do input validation and sanitizaton. After performing return-of-control, the agent does not do further validation on any data. E.g. if you change the request to 100 days, the API layer is responsible for handling the request accordingly and this demonstration does not do that.
43+
44+
![UI Preview](./images/ui-roc-step-one.png)
45+
46+
The interface supports three different interaction modes:
47+
48+
1. **Direct Processing**
49+
* Agent processes time-off requests automatically without human intervention
50+
* Suitable for straightforward requests within standard parameters
51+
52+
2. **Confirmation Required**
53+
* Agent presents the request details and waits for human confirmation
54+
* User must explicitly approve or reject the request before it's processed
55+
56+
3. **Return of Control (ROC)** (This mode is showcased in the image above)
57+
* Agent collects initial request details but returns control to the user
58+
* Allows modification of request parameters (dates, duration)
59+
* Final request is processed only after human review and adjustment
60+
61+
## Recommended Usage
62+
1. Deploy the Amazon Bedrock Agent using the the [CDK implementation](./cdk/README.md) guide in the repo.
63+
2. Confirm the default behavior of the agent in the AWS console by using a prompt like "I'd like to request time off for 3 days starting 2025-07-05."
64+
3. Set up the frontend using the [Frontend guide](./frontend/README.md).
65+
4. Update the Bedrock agent manually to use confirmation and/or return of control. See the [CDK implementation](./cdk/README.md#updating-agent-to-use-human-in-the-loop-capabilities) guide for more details.
66+
5. Update the frontend environment variables and rerun. More details can be found in the [Frontend guide](./frontend/README.md).
67+
68+
## Sample Prompts
69+
I'd like to request 3 days off for 2025-07-03.
70+
71+
## Next Steps
72+
* Learn more about what other models are on [Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/foundation-models-reference.html).
73+
* Host your frontend on [AWS Amplify](https://docs.aws.amazon.com/amplify/latest/userguide/welcome.html).
74+
* Implement Responsible AI on your Agent utilizing [Guardrails on Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html).
75+
76+
## Clean Up
77+
See [clean up](./cdk/README.md/#clean-up).
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import json
2+
3+
4+
def get_named_parameter(event, name):
5+
return next(item for item in event["parameters"] if item["name"] == name)["value"]
6+
7+
8+
def get_named_property(event, name):
9+
return next(
10+
item
11+
for item in event["requestBody"]["content"]["application/json"]["properties"]
12+
if item["name"] == name
13+
)["value"]
14+
15+
16+
def create_success_response(event, payload):
17+
response_code = 200
18+
action_group = event["actionGroup"]
19+
api_path = event["apiPath"]
20+
21+
response_body = {"application/json": {"body": json.dumps(payload)}}
22+
23+
action_response = {
24+
"messageVersion": "1.0",
25+
"response": {
26+
"actionGroup": action_group,
27+
"apiPath": api_path,
28+
"httpMethod": event["httpMethod"],
29+
"httpStatusCode": response_code,
30+
"responseBody": response_body,
31+
},
32+
}
33+
34+
return action_response
35+
36+
37+
def create_success_function_response(event, payload):
38+
action_group = event["actionGroup"]
39+
function = event["function"]
40+
41+
response_body = {"TEXT": {"body": json.dumps(payload)}}
42+
43+
function_response = {
44+
"messageVersion": "1.0",
45+
"response": {
46+
"actionGroup": action_group,
47+
"function": function,
48+
"functionResponse": {
49+
"responseState": "REPROMPT",
50+
"responseBody": response_body,
51+
},
52+
},
53+
}
54+
55+
return function_response
56+
57+
58+
def create_error_response(event, errorString, code=500):
59+
response_code = code
60+
action_group = event["actionGroup"]
61+
api_path = event["apiPath"]
62+
payload = {"error": errorString}
63+
64+
response_body = {"application/json": {"body": json.dumps(payload)}}
65+
66+
action_response = {
67+
"messageVersion": "1.0",
68+
"response": {
69+
"actionGroup": action_group,
70+
"apiPath": api_path,
71+
"httpMethod": event["httpMethod"],
72+
"httpStatusCode": response_code,
73+
"responseBody": response_body,
74+
},
75+
}
76+
77+
return action_response
78+
79+
80+
def get_employee_time_off_balance():
81+
resp = {
82+
"remaining_balance": 10.25,
83+
}
84+
85+
return resp
86+
87+
88+
def create_employee_time_off_request(event):
89+
ndays = get_named_parameter(event, "number_of_days")
90+
sdate = get_named_parameter(event, "start_date")
91+
print(f"Got ndays: {ndays}, sdate: {sdate}")
92+
93+
resp = {"start_date": sdate, "number_of_days": ndays, "id": 456}
94+
95+
return resp
96+
97+
98+
def lambda_handler(event, context):
99+
promptAttributes = None
100+
if "promptAttributes" in event:
101+
print(f"Got prompt details: {promptAttributes}")
102+
promptAttributes = event["promptAttributes"]
103+
104+
actionGroup = event["actionGroup"]
105+
if actionGroup == "RequestTimeOff":
106+
print("Got action group: RequestTimeOff")
107+
return create_success_function_response(
108+
event, create_employee_time_off_request(event)
109+
)
110+
elif actionGroup == "GetTimeOff":
111+
print("Got action group: GetTimeOffBalance")
112+
return create_success_function_response(event, get_employee_time_off_balance())
113+
else:
114+
print(f"Unknown action group: {actionGroup}")
115+
return create_error_response(event, "Unknown action group", 400)

0 commit comments

Comments
 (0)