diff --git a/01-tutorials/01-fundamentals/09-human-in-the-loop/BedrockAgentStack/config.json b/01-tutorials/01-fundamentals/09-human-in-the-loop/BedrockAgentStack/config.json new file mode 100644 index 0000000..ba97e4d --- /dev/null +++ b/01-tutorials/01-fundamentals/09-human-in-the-loop/BedrockAgentStack/config.json @@ -0,0 +1,21 @@ +{ + "agentName": "HRAssistant", + "agentAliasName": "HRAssistantAlias", + "agentModelId": "us.anthropic.claude-3-7-sonnet-20250219-v1:0", + "agentDescription": "An HR assistant that helps employees manage their time off requests", + "agentInstruction": "You are an HR assistant that helps employees check their available time off and submit time off requests. You should be friendly, helpful, and professional. When a user asks about their time off balance, use the getTimeOff function. When a user wants to request time off, use the requestTimeOff function with the start date and number of days they want to take off.", + + "getTimeOffActionGroupName": "GetTimeOffActionGroup", + "getTimeOffActionGroup": "Action group for retrieving employee time off information", + + "requestTimeOffActionGroupName": "RequestTimeOffActionGroup", + "requestTimeOffActionGroup": "Action group for submitting time off requests", + + "func_gettime_name": "getTimeOff", + "func_gettime_description": "Get the current time off balance for the employee", + + "func_createtimeoff_name": "requestTimeOff", + "func_createtimeoff_description": "Submit a time off request for the employee", + "func_createtimeoff_start_date": "startDate", + "func_createtimeoff_number_of_days": "numberOfDays" +} diff --git a/01-tutorials/01-fundamentals/09-human-in-the-loop/README.md b/01-tutorials/01-fundamentals/09-human-in-the-loop/README.md new file mode 100644 index 0000000..b2ebdbf --- /dev/null +++ b/01-tutorials/01-fundamentals/09-human-in-the-loop/README.md @@ -0,0 +1,91 @@ +# Strands HR Assistant + +A virtual HR assistant built with the Strands SDK that helps employees manage their time off requests. + +## Features + +- Check time off balance +- Submit time off requests +- Natural language interaction + +## Prerequisites + +- Python 3.10 or higher +- AWS account with access to Amazon Bedrock +- AWS CLI configured with appropriate credentials + +## Installation + +1. Clone the repository: + ``` + git clone https://github.com/strands-agents/samples.git + cd strands-samples/01-tutorials/01-fundamentals/09-human-in-the-loop + ``` + +2. Create and activate a virtual environment: + ``` + python -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + ``` + +3. Install the required packages: + ``` + pip install -r requirements.txt + ``` + +4. Configure AWS credentials: + - Make sure you have the AWS CLI installed and configured with your credentials + - Update the `aws_config.py` file with your AWS region and profile name + +## Configuration + +The HR assistant is configured using the `BedrockAgentStack/config.json` file. You can modify the following settings: + +- `agentName`: The name of the HR assistant +- `agentDescription`: A description of the HR assistant +- `agentInstruction`: Instructions for the HR assistant +- Function names and descriptions + +## Usage + +1. Run the HR assistant: + ``` + python hr_assistant.py + ``` + +2. Interact with the assistant using natural language: + - "What is my time off balance?" + - "I want to request time off from 2025-07-01 for 5 days" + - "Show me my pending time off requests" + +3. Type 'exit' to quit the assistant. + +## How It Works + +The HR assistant uses the Strands SDK to create an agent with two main functions: + +1. `get_time_off()`: Retrieves the employee's time off balance +2. `request_time_off(start_date, number_of_days)`: Submits a time off request + +The agent uses Amazon Bedrock's Claude model to understand natural language requests and determine when to call these functions. + +## Customization + +You can extend the HR assistant by adding more functions to handle additional HR-related tasks, such as: + +- Checking pay information +- Updating personal details +- Submitting expense reports +- Accessing company policies + +To add a new function, define it using the `@tool` decorator and add it to the agent's tools list in the `create_hr_assistant()` function. + +## Troubleshooting + +- **AWS Credentials Error**: Make sure your AWS credentials are properly configured and you have access to Amazon Bedrock. +- **Model Not Found**: Verify that the model ID in the code matches an available model in your AWS region. +- **Function Not Called**: Ensure that your natural language request clearly indicates which function should be called. + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. diff --git a/01-tutorials/01-fundamentals/09-human-in-the-loop/aws_config.py b/01-tutorials/01-fundamentals/09-human-in-the-loop/aws_config.py new file mode 100644 index 0000000..7cf21f7 --- /dev/null +++ b/01-tutorials/01-fundamentals/09-human-in-the-loop/aws_config.py @@ -0,0 +1,23 @@ +""" +AWS configuration for the HR assistant agent. +This file contains the AWS configuration for the Bedrock model. +""" + +import os +import boto3 +from botocore.config import Config + +# AWS configuration +AWS_REGION = os.environ.get('AWS_REGION') # Replace with your AWS region if using a region different from the one set in ~/.aws/config +AWS_PROFILE = "default" # Replace with your AWS profile name + +def get_bedrock_session(): + """Get a Bedrock client with the configured AWS credentials. + + Returns: + A Bedrock client. + """ + # Configure AWS session + session = boto3.Session(profile_name=AWS_PROFILE, region_name=AWS_REGION) + + return session diff --git a/01-tutorials/01-fundamentals/09-human-in-the-loop/hr_assistant.ipynb b/01-tutorials/01-fundamentals/09-human-in-the-loop/hr_assistant.ipynb new file mode 100644 index 0000000..84e581c --- /dev/null +++ b/01-tutorials/01-fundamentals/09-human-in-the-loop/hr_assistant.ipynb @@ -0,0 +1,481 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Human-in-the-Loop for Agentic AI: HR Assistant Example\n", + "\n", + "This notebook demonstrates the implementation of a Human-in-the-Loop (HITL) approach in an agentic AI system, specifically an HR assistant that helps employees manage their time off requests." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Introduction to Human-in-the-Loop (HITL) in Agentic AI\n", + "\n", + "Human-in-the-Loop (HITL) is a paradigm where human judgment is incorporated into automated systems to improve decision-making, ensure safety, and enhance user experience. In the context of agentic AI, HITL refers to the collaboration between AI agents and humans, where humans provide oversight, confirmation, or additional input during the agent's operation.\n", + "\n", + "HITL is particularly important in scenarios where:\n", + "- Decisions have significant consequences\n", + "- Complete automation might miss important context\n", + "- User confirmation adds a layer of safety and control\n", + "- The system needs to learn from human feedback" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Core Concepts of Human-in-the-Loop\n", + "\n", + "1. **Confirmation Requests**
\n", + "One of the fundamental HITL patterns is the confirmation request, where the AI agent asks for human approval before executing a potentially impactful action. This creates a checkpoint where humans can review and either confirm or reject the proposed action.\n", + "\n", + "2. **Progressive Disclosure**
\n", + "HITL systems often implement progressive disclosure, where information and options are presented gradually to avoid overwhelming the user. The agent guides the human through a decision-making process step by step.\n", + "\n", + "3. **Feedback Loops**
\n", + "Effective HITL systems incorporate feedback loops where human input is used to improve the agent's future performance. This can range from explicit feedback mechanisms to implicit learning from user interactions.\n", + "\n", + "4. **Transparency**
\n", + "HITL requires transparency in the agent's reasoning and actions. Users need to understand what the agent is doing and why, especially when they're asked to make decisions based on the agent's recommendations.\n", + "\n", + "5. **Graceful Handoffs**
\n", + "Well-designed HITL systems handle transitions between automated and human control smoothly. This includes clear signaling when human input is needed and providing sufficient context for humans to make informed decisions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Implementation Example: HR Assistant Agent\n", + "\n", + "The following code implements an HR assistant agent that helps employees manage their time off requests. This example demonstrates several HITL concepts, particularly the confirmation request pattern where the agent seeks explicit user approval before finalizing time off requests." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.1 Setup and Imports\n", + "\n", + "First, we import the necessary libraries and set up our environment:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import json\n", + "from datetime import datetime, timedelta\n", + "from strands import Agent, tool\n", + "from strands.models import BedrockModel\n", + "from aws_config import get_bedrock_session" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.2 Mock Database\n", + "\n", + "For demonstration purposes, we'll use a simple dictionary to simulate a database of employee time off information:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Mock database for time off information\n", + "employee_time_off = {\n", + " \"total_days\": 25,\n", + " \"used_days\": 10,\n", + " \"pending_requests\": []\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.3 Tool Functions\n", + "\n", + "The agent uses tool functions to interact with the time off system. \n", + "\n", + "#### 3.3.1 Information Retrieval Tool\n", + "\n", + "The `get_time_off()` tool retrieves information without requiring human confirmation, as it's a read-only operation with no significant consequences:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@tool\n", + "def get_time_off() -> str:\n", + " \"\"\"Get the current time off balance for the employee.\n", + " \n", + " Returns:\n", + " A JSON string containing the employee's time off information.\n", + " \"\"\"\n", + " remaining_days = employee_time_off[\"total_days\"] - employee_time_off[\"used_days\"]\n", + " \n", + " response = {\n", + " \"total_days\": employee_time_off[\"total_days\"],\n", + " \"used_days\": employee_time_off[\"used_days\"],\n", + " \"remaining_days\": remaining_days,\n", + " \"pending_requests\": employee_time_off[\"pending_requests\"]\n", + " }\n", + " \n", + " return json.dumps(response)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 3.3.2 HITL Confirmation Request Pattern\n", + "\n", + "The `request_time_off()` tool demonstrates the HITL confirmation request pattern. Instead of immediately submitting the time off request, it returns a confirmation request that requires explicit human approval:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@tool\n", + "def request_time_off(start_date: str, number_of_days: int) -> str:\n", + " \"\"\"Submit a time off request for the employee.\n", + " \n", + " Args:\n", + " start_date: The date that time off starts (format: YYYY-MM-DD)\n", + " number_of_days: The number of days user wants to request off\n", + " \n", + " Returns:\n", + " A JSON string containing the result of the time off request or a confirmation request.\n", + " \"\"\"\n", + " try:\n", + " # Parse the start date\n", + " start_date_obj = datetime.strptime(start_date, \"%Y-%m-%d\")\n", + " \n", + " # Check if the date is in the past\n", + " today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)\n", + " if start_date_obj < today:\n", + " response = {\n", + " \"success\": False,\n", + " \"message\": \"Invalid date. Time off requests cannot be made for dates in the past.\"\n", + " }\n", + " return json.dumps(response)\n", + " \n", + " # Calculate the end date\n", + " end_date_obj = start_date_obj + timedelta(days=number_of_days - 1)\n", + " end_date = end_date_obj.strftime(\"%Y-%m-%d\")\n", + " \n", + " # Return a confirmation request\n", + " response = {\n", + " \"success\": True,\n", + " \"requires_confirmation\": True,\n", + " \"message\": f\"Please confirm your time off request for {start_date} to {end_date} ({number_of_days} days)\",\n", + " \"request_details\": {\n", + " \"start_date\": start_date,\n", + " \"end_date\": end_date,\n", + " \"number_of_days\": number_of_days\n", + " }\n", + " }\n", + " \n", + " return json.dumps(response)\n", + " except ValueError:\n", + " response = {\n", + " \"success\": False,\n", + " \"message\": \"Invalid date format. Please use YYYY-MM-DD format.\"\n", + " }\n", + " return json.dumps(response)\n", + " except Exception as e:\n", + " response = {\n", + " \"success\": False,\n", + " \"message\": f\"Error processing request: {str(e)}\"\n", + " }\n", + " return json.dumps(response)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 3.3.3 Human Decision Implementation\n", + "\n", + "The `confirm_time_off_request()` tool implements the human's decision, either confirming or canceling the time off request:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@tool\n", + "def confirm_time_off_request(start_date: str, end_date: str, number_of_days: int, confirm: bool) -> str:\n", + " \"\"\"Confirm or cancel a time off request.\n", + " \n", + " Args:\n", + " start_date: The date that time off starts (format: YYYY-MM-DD)\n", + " end_date: The date that time off ends (format: YYYY-MM-DD)\n", + " number_of_days: The number of days requested off\n", + " confirm: Boolean indicating whether to confirm (True) or cancel (False) the request\n", + " \n", + " Returns:\n", + " A JSON string containing the result of the confirmation.\n", + " \"\"\"\n", + " try:\n", + " if not confirm:\n", + " response = {\n", + " \"success\": True,\n", + " \"message\": \"Time off request cancelled.\"\n", + " }\n", + " return json.dumps(response)\n", + " \n", + " # Create a new request\n", + " request = {\n", + " \"start_date\": start_date,\n", + " \"end_date\": end_date,\n", + " \"number_of_days\": number_of_days,\n", + " \"status\": \"pending\"\n", + " }\n", + " \n", + " # Add the request to the pending requests\n", + " employee_time_off[\"pending_requests\"].append(request)\n", + " \n", + " response = {\n", + " \"success\": True,\n", + " \"message\": f\"Time off request confirmed and submitted for {start_date} to {end_date} ({number_of_days} days)\",\n", + " \"request\": request\n", + " }\n", + " \n", + " return json.dumps(response)\n", + " except Exception as e:\n", + " response = {\n", + " \"success\": False,\n", + " \"message\": f\"Error confirming request: {str(e)}\"\n", + " }\n", + " return json.dumps(response)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.4 Event Loop Tracking\n", + "\n", + "The event loop tracker function provides transparency into the agent's operation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def event_loop_tracker(**kwargs):\n", + " # Track event loop lifecycle\n", + " if kwargs.get(\"init_event_loop\", False):\n", + " print(\"🔄 Event loop initialized\")\n", + " elif kwargs.get(\"start_event_loop\", False):\n", + " print(\"▶️ Event loop cycle starting\")\n", + " elif kwargs.get(\"start\", False):\n", + " print(\"📝 New cycle started\")\n", + " elif \"message\" in kwargs:\n", + " print(f\"📬 New message created: {kwargs['message']['role']}\")\n", + " elif kwargs.get(\"complete\", False):\n", + " print(\"✅ Cycle completed\")\n", + " elif kwargs.get(\"force_stop\", False):\n", + " print(f\"🛑 Event loop force-stopped: {kwargs.get('force_stop_reason', 'unknown reason')}\")\n", + "\n", + " # Track tool usage\n", + " if \"current_tool_use\" in kwargs and kwargs[\"current_tool_use\"].get(\"name\"):\n", + " tool_name = kwargs[\"current_tool_use\"][\"name\"]\n", + " print(f\"🔧 Using tool: {tool_name}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.5 Agent Creation and Configuration\n", + "\n", + "The `create_hr_assistant()` function configures and creates the agent with the necessary tools:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def create_hr_assistant():\n", + " \"\"\"Create and configure the HR assistant agent.\"\"\"\n", + " # Load configuration\n", + " config_path = os.path.join(os.getcwd(), \"BedrockAgentStack\", \"config.json\")\n", + " with open(config_path, \"r\") as config_file:\n", + " config = json.load(config_file)\n", + " \n", + " # Get the Bedrock session\n", + " bedrock_session = get_bedrock_session()\n", + " \n", + " # Create the Bedrock model with the client\n", + " bedrock_model = BedrockModel(\n", + " model_id=config[\"agentModelId\"],\n", + " # client=bedrock_client\n", + " boto_session=bedrock_session\n", + " )\n", + " \n", + " # Create the agent with system prompt, model, and tools\n", + " agent = Agent(\n", + " system_prompt=config[\"agentInstruction\"],\n", + " model=bedrock_model,\n", + " tools=[get_time_off, request_time_off, confirm_time_off_request],\n", + " callback_handler=event_loop_tracker\n", + " )\n", + " \n", + " return agent" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.6 Main Function\n", + "\n", + "The main function creates the agent and starts an interactive loop for user interaction:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\"\"\"Main function to run the HR assistant agent.\"\"\"\n", + "agent = create_hr_assistant()\n", + "\n", + "print(\"HR Assistant Agent is ready!\")\n", + "print(\"You can now interact with the agent.\")\n", + "\n", + "user_input = input(\"\\nYou: I want to take 3 holidays around the 4th of July weekend\")\n", + "\n", + "# Process the user input and get a response\n", + "response = agent(user_input)\n", + "\n", + "# Print the response\n", + "print(f\"\\nHR Assistant: {response}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Best Practices for Human-in-the-Loop AI Systems\n", + "In this tutorial, we have demonstrated several best practices for implementing HITL in agentic AI systems:\n", + "\n", + "#### **Explicit Confirmation for Consequential Actions**
\n", + "\n", + "The time off request process in the tutorial follows a two-phase commit pattern:\n", + "1. **Request Phase**: The user initiates a time off request with start date and duration\n", + "2. **Confirmation Phase**: The user explicitly confirms or cancels the request\n", + "This pattern ensures that users have a chance to review and approve actions before they're finalized, reducing errors and increasing user confidence.\n", + "\n", + "It is a good practice to always require explicit user confirmation before taking actions with significant consequences. \n", + "\n", + "#### **Clear Communication**
\n", + "\n", + "Provide clear, concise information to users about:\n", + "- What the system is doing\n", + "- What information it needs from the user\n", + "- What the consequences of actions will be\n", + "- Any errors or issues that arise\n", + "\n", + "#### **Appropriate Automation Level**
\n", + "\n", + "Choose the appropriate level of automation for each task:\n", + "- Fully automate low-risk, routine tasks (like checking time off balance)\n", + "- Implement HITL for higher-risk or consequential actions (like submitting time off requests)\n", + "\n", + "#### **Graceful Error Handling**
\n", + "\n", + "Implement robust error handling with clear, actionable feedback to users. In our example, date format errors and past date errors are caught and explained clearly to the user.\n", + "\n", + "#### **Transparent Operation**
\n", + "\n", + "In this tutorial, the event loop tracker provides visibility into the agent's operations, printing messages about what the agent is doing at each step. This transparency helps users understand the system's behavior and builds trust.\n", + "\n", + "Best pracitce: Make the system's operation transparent to users through appropriate logging, status updates, and explanations. \n", + "\n", + "#### **User-Centered Design**
\n", + "\n", + "In this tutorial, the HR asisstant presents information in a progressive manner:\n", + "1. First showing available time off balance\n", + "2. Then presenting request details for confirmation\n", + "3. Finally confirming the submission\n", + "\n", + "The system validates inputs (like date formats and whether dates are in the past) and provides clear, human-readable error messages. This helps users understand and correct issues without frustration.\n", + "\n", + "Best practice: Design the HITL interactions around user needs and capabilities, not system architecture. Consider the user's mental model, cognitive load, and context when designing interaction patterns." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Conclusion\n", + "\n", + "Human-in-the-Loop is a powerful paradigm for agentic AI systems that balances automation with human oversight and control. By implementing HITL patterns like confirmation requests, progressive disclosure, and transparent operation, we can create AI systems that are more trustworthy, effective, and aligned with human values and needs.\n", + "\n", + "The HR assistant example demonstrates how these patterns can be implemented in practice, creating a system that automates routine tasks while keeping humans in control of consequential decisions. This approach combines the efficiency of automation with the judgment and oversight that only humans can provide." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/01-tutorials/01-fundamentals/09-human-in-the-loop/hr_assistant.py b/01-tutorials/01-fundamentals/09-human-in-the-loop/hr_assistant.py new file mode 100644 index 0000000..0155465 --- /dev/null +++ b/01-tutorials/01-fundamentals/09-human-in-the-loop/hr_assistant.py @@ -0,0 +1,205 @@ +import os +import json +from datetime import datetime, timedelta +from strands import Agent, tool +from strands.models import BedrockModel +from aws_config import get_bedrock_session + +# Mock database for time off information +employee_time_off = { + "total_days": 25, + "used_days": 10, + "pending_requests": [] +} + +@tool +def get_time_off() -> str: + """Get the current time off balance for the employee. + + Returns: + A JSON string containing the employee's time off information. + """ + remaining_days = employee_time_off["total_days"] - employee_time_off["used_days"] + + response = { + "total_days": employee_time_off["total_days"], + "used_days": employee_time_off["used_days"], + "remaining_days": remaining_days, + "pending_requests": employee_time_off["pending_requests"] + } + + return json.dumps(response) + +@tool +def request_time_off(start_date: str, number_of_days: int) -> str: + """Submit a time off request for the employee. + + Args: + start_date: The date that time off starts (format: YYYY-MM-DD) + number_of_days: The number of days user wants to request off + + Returns: + A JSON string containing the result of the time off request or a confirmation request. + """ + try: + # Parse the start date + start_date_obj = datetime.strptime(start_date, "%Y-%m-%d") + + # Check if the date is in the past + today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + if start_date_obj < today: + response = { + "success": False, + "message": "Invalid date. Time off requests cannot be made for dates in the past." + } + return json.dumps(response) + + # Calculate the end date + end_date_obj = start_date_obj + timedelta(days=number_of_days - 1) + end_date = end_date_obj.strftime("%Y-%m-%d") + + # Return a confirmation request + response = { + "success": True, + "requires_confirmation": True, + "message": f"Please confirm your time off request for {start_date} to {end_date} ({number_of_days} days)", + "request_details": { + "start_date": start_date, + "end_date": end_date, + "number_of_days": number_of_days + } + } + + return json.dumps(response) + except ValueError: + response = { + "success": False, + "message": "Invalid date format. Please use YYYY-MM-DD format." + } + return json.dumps(response) + except Exception as e: + response = { + "success": False, + "message": f"Error processing request: {str(e)}" + } + return json.dumps(response) + +@tool +def confirm_time_off_request(start_date: str, end_date: str, number_of_days: int, confirm: bool) -> str: + """Confirm or cancel a time off request. + + Args: + start_date: The date that time off starts (format: YYYY-MM-DD) + end_date: The date that time off ends (format: YYYY-MM-DD) + number_of_days: The number of days requested off + confirm: Boolean indicating whether to confirm (True) or cancel (False) the request + + Returns: + A JSON string containing the result of the confirmation. + """ + try: + if not confirm: + response = { + "success": True, + "message": "Time off request cancelled." + } + return json.dumps(response) + + # Create a new request + request = { + "start_date": start_date, + "end_date": end_date, + "number_of_days": number_of_days, + "status": "pending" + } + + # Add the request to the pending requests + employee_time_off["pending_requests"].append(request) + + response = { + "success": True, + "message": f"Time off request confirmed and submitted for {start_date} to {end_date} ({number_of_days} days)", + "request": request + } + + return json.dumps(response) + except Exception as e: + response = { + "success": False, + "message": f"Error confirming request: {str(e)}" + } + return json.dumps(response) + + +def event_loop_tracker(**kwargs): + # Track event loop lifecycle + if kwargs.get("init_event_loop", False): + print("🔄 Event loop initialized") + elif kwargs.get("start_event_loop", False): + print("▶️ Event loop cycle starting") + elif kwargs.get("start", False): + print("📝 New cycle started") + elif "message" in kwargs: + print(f"📬 New message created: {kwargs['message']['role']}") + elif kwargs.get("complete", False): + print("✅ Cycle completed") + elif kwargs.get("force_stop", False): + print(f"🛑 Event loop force-stopped: {kwargs.get('force_stop_reason', 'unknown reason')}") + + # Track tool usage + if "current_tool_use" in kwargs and kwargs["current_tool_use"].get("name"): + tool_name = kwargs["current_tool_use"]["name"] + print(f"🔧 Using tool: {tool_name}") + + +def create_hr_assistant(): + """Create and configure the HR assistant agent.""" + # Load configuration + config_path = os.path.join(os.path.dirname(__file__), "BedrockAgentStack", "config.json") + with open(config_path, "r") as config_file: + config = json.load(config_file) + + # Get the Bedrock session + bedrock_session = get_bedrock_session() + + # Create the Bedrock model with the client + bedrock_model = BedrockModel( + model_id=config["agentModelId"], + # client=bedrock_client + boto_session=bedrock_session + ) + + # Create the agent with system prompt, model, and tools + agent = Agent( + system_prompt=config["agentInstruction"], + model=bedrock_model, + tools=[get_time_off, request_time_off, confirm_time_off_request], + callback_handler=event_loop_tracker + ) + + return agent + +def main(): + """Main function to run the HR assistant agent.""" + agent = create_hr_assistant() + + print("HR Assistant Agent is ready!") + print("You can now interact with the agent.") + print("Type 'exit' to quit.") + + # Start conversation + while True: + user_input = input("\nYou: ") + + if user_input.lower() == "exit": + print("Goodbye!") + break + + # Process the user input and get a response + response = agent(user_input) + + # Print the response + print(f"\nHR Assistant: {response}") + +if __name__ == "__main__": + main() diff --git a/01-tutorials/01-fundamentals/09-human-in-the-loop/images/hr-assistant-architecture.png b/01-tutorials/01-fundamentals/09-human-in-the-loop/images/hr-assistant-architecture.png new file mode 100644 index 0000000..8274659 Binary files /dev/null and b/01-tutorials/01-fundamentals/09-human-in-the-loop/images/hr-assistant-architecture.png differ diff --git a/01-tutorials/01-fundamentals/09-human-in-the-loop/requirements.txt b/01-tutorials/01-fundamentals/09-human-in-the-loop/requirements.txt new file mode 100644 index 0000000..ae32fce --- /dev/null +++ b/01-tutorials/01-fundamentals/09-human-in-the-loop/requirements.txt @@ -0,0 +1,3 @@ +strands-agents>=0.1.0 +boto3>=1.28.0 +botocore>=1.31.0