From a7a90c32894eddfca6b0f82ea2bca04eb89aaaf1 Mon Sep 17 00:00:00 2001 From: Laine Date: Thu, 13 Mar 2025 14:19:13 -0400 Subject: [PATCH 1/2] Add functionality to future_pto_calc, remove calendar_conflict step from goal --- adding-goals-and-tools.md | 2 +- tools/__init__.py | 3 -- tools/calendar_conflict.py | 17 -------- tools/data/employee_pto_data.json | 5 +-- tools/future_pto_calc.py | 64 +++++++++++++++++++++++++------ tools/goal_registry.py | 37 +++++------------- tools/tool_registry.py | 16 ++++++-- 7 files changed, 78 insertions(+), 66 deletions(-) delete mode 100644 tools/calendar_conflict.py diff --git a/adding-goals-and-tools.md b/adding-goals-and-tools.md index c20802d..11f8f5e 100644 --- a/adding-goals-and-tools.md +++ b/adding-goals-and-tools.md @@ -9,7 +9,7 @@ The agent is set up to allow for multiple goals and to switch back to choosing a - `agent_name`: user-facing name for the agent/chatbot - `agent_friendly_description`: user-facing description of what the agent/chatbot does - `tools`: the list of tools the goal will walk the user through. -- Important! The last tool listed must be `list_agents_tool` +- Important! The last tool listed must be `list_agents_tool`. This allows the AI to let the user go back to choosing from the list of available goals. - `description`: - `starter-prompt`: - `example_conversation_history`: diff --git a/tools/__init__.py b/tools/__init__.py index ea8a0a8..b37b867 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -10,7 +10,6 @@ from .transfer_control import transfer_control from .current_pto import current_pto from .book_pto import book_pto -from .calendar_conflict import calendar_conflict from .future_pto_calc import future_pto_calc @@ -37,8 +36,6 @@ def get_handler(tool_name: str): return current_pto if tool_name == "BookPTO": return book_pto - if tool_name == "CalendarConflict": - return calendar_conflict if tool_name == "FuturePTOCalc": return future_pto_calc diff --git a/tools/calendar_conflict.py b/tools/calendar_conflict.py deleted file mode 100644 index 00b190c..0000000 --- a/tools/calendar_conflict.py +++ /dev/null @@ -1,17 +0,0 @@ -def calendar_conflict(args: dict) -> dict: - - check_self = args.get("check_self_calendar") - check_team = args.get("check_team_calendar") - - conflict_list = [] - conflict = { - "calendar": "self", - "title": "Meeting with Karen", - "date": "2025-12-02", - "time": "10:00AM", - } - conflict_list.append(conflict) - - return { - "conflicts": conflict_list, - } diff --git a/tools/data/employee_pto_data.json b/tools/data/employee_pto_data.json index bc41ab0..1f31a26 100644 --- a/tools/data/employee_pto_data.json +++ b/tools/data/employee_pto_data.json @@ -1,17 +1,16 @@ { "theCompany": { "weLove": "theCompany", - "accrualPer": "month", "employees": [ { "email": "josh.smith@temporal.io", "currentPTOHrs": 400, - "accrualHrsRate": 8 + "hrsAddedPerMonth": 8 }, { "email": "laine.smith@awesome.com", "currentPTOHrs": 40, - "accrualHrsRate": 12 + "hrsAddedPerMonth": 12 } ] } diff --git a/tools/future_pto_calc.py b/tools/future_pto_calc.py index 3ae81e2..b4e557b 100644 --- a/tools/future_pto_calc.py +++ b/tools/future_pto_calc.py @@ -1,17 +1,57 @@ +import json +import pandas +from pathlib import Path +from datetime import date, datetime +from dateutil.relativedelta import relativedelta + + def future_pto_calc(args: dict) -> dict: - start_date = args.get("start_date") - end_date = args.get("end_date") + file_path = Path(__file__).resolve().parent / "data" / "employee_pto_data.json" + if not file_path.exists(): + return {"error": "Data file not found."} + + start_date = datetime.strptime(args.get("start_date"), "%Y-%m-%d").date() + end_date = datetime.strptime(args.get("end_date"), "%Y-%m-%d").date() + email = args.get("email") - # get rate of accrual - need email? - # get total hrs of PTO available as of start date (accrual * time between today and start date) - # take into account other booked PTO?? - # calculate number of business hours of PTO: between start date and end date + #Next, set up the ability to calculate how much PTO will be added to the user's total by the start of the PTO request + today = date.today() - # enough_pto = total PTO as of start date - num biz hours of PTO > 0 - # pto_hrs_remaining_after = total PTO as of start date - num biz hours of PTO + if today > start_date: + return_msg = "PTO start date " + args.get("start_date") + "cannot be in the past" + return {"error": return_msg} + + if end_date < start_date: + return_msg = "PTO end date " + args.get("end_date") + " must be after PTO start date " + args.get("start_date") + return {"error": return_msg} + + #Get the number of business days, and then business hours (assume 8 hr biz day), included in the PTO request + biz_days_of_request = len(pandas.bdate_range(start=start_date, end=end_date, inclusive="both")) + biz_hours_of_request = biz_days_of_request * 8 + + #Assume PTO is added on the first of every month - month math compares rolling dates, so compare the PTO request with the first day of the current month. + today_first_of_month = date(today.year, today.month, 1) + time_difference = relativedelta(start_date, today_first_of_month) + months_to_accrue = time_difference.years * 12 + time_difference.months + + data = json.load(open(file_path)) + employee_list = data["theCompany"]["employees"] - return { - "enough_pto": True, - "pto_hrs_remaining_after": 410, - } + enough_pto = False + + for employee in employee_list: + if employee["email"] == email: + current_pto_hours = int(employee["currentPTOHrs"]) + hrs_added_per_month = int(employee["hrsAddedPerMonth"]) + pto_available_at_start = current_pto_hours + (months_to_accrue * hrs_added_per_month) + pto_hrs_remaining_after = pto_available_at_start - biz_hours_of_request + if pto_hrs_remaining_after >= 0: + enough_pto = True + return { + "enough_pto": enough_pto, + "pto_hrs_remaining_after": str(pto_hrs_remaining_after), + } + + return_msg = "Employee not found with email address " + email + return {"error": return_msg} diff --git a/tools/goal_registry.py b/tools/goal_registry.py index 015aaf1..6ffa293 100644 --- a/tools/goal_registry.py +++ b/tools/goal_registry.py @@ -1,22 +1,8 @@ from typing import List from models.tool_definitions import AgentGoal import tools.tool_registry as tool_registry -'''from tools.tool_registry import ( - search_fixtures_tool, - search_flights_tool, - search_trains_tool, - book_trains_tool, - create_invoice_tool, - find_events_tool, - change_goal_tool, - list_agents_tool, - current_pto_tool, - future_pto_calc_tool, - calendar_conflict_tool, - book_pto_tool, -)''' -starter_prompt_generic = "Welcome me, give me a description of what you can do, then ask me for the details you need to do your job" +starter_prompt_generic = "Welcome me, give me a description of what you can do, then ask me for the details you need to do your job. " goal_choose_agent_type = AgentGoal( id = "goal_choose_agent_type", @@ -28,14 +14,13 @@ goal_choose_agent_type = AgentGoal( ], description="The user wants to choose which type of agent they will interact with. " "Help the user gather args for these tools, in order: " - "1. ListAgents: List agents available to interact with " + "1. ListAgents: List agents available to interact with. Do not ask for user confirmation for this tool. " "2. ChangeGoal: Change goal of agent " "After these tools are complete, change your goal to the new goal as chosen by the user. ", - starter_prompt=starter_prompt_generic, + starter_prompt=starter_prompt_generic + "Begin by providing the output for the first tool included in this goal. ", example_conversation_history="\n ".join( [ - "user: I'd like to choose an agent", - "agent: Sure! Would you like me to list the available agents?", + "agent: Here are the currently available agents.", "user_confirmed_tool_run: ", "tool_result: { 'agent_name': 'Event Flight Finder', 'goal_id': 'goal_event_flight_invoice', 'agent_description': 'Helps users find interesting events and arrange travel to them' }", "agent: The available agents are: 1. Event Flight Finder. Which agent would you like to speak to?", @@ -135,19 +120,17 @@ goal_event_flight_invoice = AgentGoal( goal_hr_schedule_pto = AgentGoal( id = "goal_hr_schedule_pto", agent_name="Schedule PTO", - agent_friendly_description="Schedule PTO based on your available time, personal calendar, and team calendar.", + agent_friendly_description="Schedule PTO based on your available PTO.", tools=[ tool_registry.current_pto_tool, tool_registry.future_pto_calc_tool, - tool_registry.calendar_conflict_tool, tool_registry.book_pto_tool, tool_registry.list_agents_tool, #last tool must be list_agents to fasciliate changing back to picking an agent again at the end ], - description="Help the user gather args for these tools in order: " + description="The user wants to schedule paid time off (PTO) after today's date. To assist with that goal, help the user gather args for these tools in order: " "1. CurrentPTO: Tell the user how much PTO they currently have " - "2. FuturePTOCalc: Tell the user how much PTO they will have as of the prospective date " - "3. CalendarConflict: Tell the user what conflicts if any exist around the prospective date on a list of calendars. This step is optional and can be skipped by moving to the next tool. " - "4. BookPTO: Book PTO ", + "2. FuturePTOCalc: Tell the user how much PTO they will have as of the prospective future date " + "3. BookPTO: Book PTO after user types 'yes'", starter_prompt=starter_prompt_generic, example_conversation_history="\n ".join( [ @@ -158,8 +141,8 @@ goal_hr_schedule_pto = AgentGoal( "user_confirmed_tool_run: ", "tool_result: { 'num_hours': 400, 'num_days': 50 }", "agent: You have 400 hours, or 50 days, of PTO available. What dates would you like to take your time off? ", - "user: Dec 1 2025 through Dec 5 2025", - "agent: Let's check if you'll have enough PTO accrued by Dec 1 to accomodate that.", + "user: Dec 1 through Dec 5", + "agent: Let's check if you'll have enough PTO accrued by Dec 1 of this year to accomodate that.", "user_confirmed_tool_run: " 'tool_result: {"enough_pto": True, "pto_hrs_remaining_after": 410}', "agent: You do in fact have enough PTO to accommodate that, and will have 410 hours remaining after you come back. Do you want to check calendars for conflicts? If so, please provide one of the following: self, team, or both " diff --git a/tools/tool_registry.py b/tools/tool_registry.py index 28a3345..e641ae3 100644 --- a/tools/tool_registry.py +++ b/tools/tool_registry.py @@ -151,15 +151,15 @@ current_pto_tool = ToolDefinition( ToolArgument( name="email", type="string", - description="name of user, used to look up current PTO", + description="email address of user", ), ], ) future_pto_calc_tool = ToolDefinition( name="FuturePTOCalc", - description="Calculate if the user will have enough PTO as of their proposed date to accommodate the request. Returns a boolean enough_pto and " - "how many hours of PTO they will have if they take the proposed dates. ", + description="Calculate if the user will have enough PTO as of their proposed date to accommodate the request. The proposed start and end dates should be in the future. " + "Returns a boolean enough_pto and how many hours of PTO they will have remaining if they take the proposed dates. ", arguments=[ ToolArgument( name="start_date", @@ -171,6 +171,11 @@ future_pto_calc_tool = ToolDefinition( type="string", description="End date of proposed PTO", ), + ToolArgument( + name="email", + type="string", + description="email address of user", + ), ], ) @@ -211,5 +216,10 @@ book_pto_tool = ToolDefinition( type="string", description="Email address of user, used to look up current PTO", ), + ToolArgument( + name="userConfirmation", + type="string", + description="Indication of user's desire to book PTO", + ), ], ) \ No newline at end of file From ece3ac1d3c44de50cff1a12d3d3bf0d22221e6f0 Mon Sep 17 00:00:00 2001 From: Laine Date: Thu, 13 Mar 2025 14:53:03 -0400 Subject: [PATCH 2/2] Add the category tag to the goals and example env file, and filter the results based on tags in list_agents --- .env.example | 7 +++++-- models/tool_definitions.py | 1 + tools/goal_registry.py | 4 ++++ tools/list_agents.py | 28 ++++++++++++++++++++++------ 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index 4701495..ad5cffd 100644 --- a/.env.example +++ b/.env.example @@ -35,8 +35,11 @@ OPENAI_API_KEY=sk-proj-... # Uncomment if using API key (not needed for local dev server) # TEMPORAL_API_KEY=abcdef1234567890 -# Agent Goal Configuration -# AGENT_GOAL=goal_event_flight_invoice # (default) or goal_match_train_invoice +# Set starting goal of agent +AGENT_GOAL=goal_choose_agent_type # (default) + +#Choose which category(ies) of goals you want to be listed by the Agent - options are system (always included), hr, travel, or all. +GOAL_CATEGORIES=hr,travel # default is all # Set if the UI should force a user confirmation step or not SHOW_CONFIRM=True \ No newline at end of file diff --git a/models/tool_definitions.py b/models/tool_definitions.py index 76d9ab3..59cf27b 100644 --- a/models/tool_definitions.py +++ b/models/tool_definitions.py @@ -18,6 +18,7 @@ class ToolDefinition: @dataclass class AgentGoal: id: str + category_tag: str agent_name: str agent_friendly_description: str tools: List[ToolDefinition] diff --git a/tools/goal_registry.py b/tools/goal_registry.py index 6ffa293..055fee3 100644 --- a/tools/goal_registry.py +++ b/tools/goal_registry.py @@ -6,6 +6,7 @@ starter_prompt_generic = "Welcome me, give me a description of what you can do, goal_choose_agent_type = AgentGoal( id = "goal_choose_agent_type", + category_tag="system", agent_name="Choose Agent", agent_friendly_description="Choose the type of agent to assist you today.", tools=[ @@ -33,6 +34,7 @@ goal_choose_agent_type = AgentGoal( goal_match_train_invoice = AgentGoal( id = "goal_match_train_invoice", + category_tag="travel", agent_name="UK Premier League Match Trip Booking", agent_friendly_description="Book a trip to a city in the UK around the dates of a premier league match.", tools=[ @@ -80,6 +82,7 @@ goal_match_train_invoice = AgentGoal( goal_event_flight_invoice = AgentGoal( id = "goal_event_flight_invoice", + category_tag="travel", agent_name="Australia and New Zealand Event Flight Booking", agent_friendly_description="Book a trip to a city in Australia or New Zealand around the dates of events in that city.", tools=[ @@ -119,6 +122,7 @@ goal_event_flight_invoice = AgentGoal( # This goal uses the data/employee_pto_data.json file as dummy data. goal_hr_schedule_pto = AgentGoal( id = "goal_hr_schedule_pto", + category_tag="hr", agent_name="Schedule PTO", agent_friendly_description="Schedule PTO based on your available PTO.", tools=[ diff --git a/tools/list_agents.py b/tools/list_agents.py index 1fc56f3..ba9aa20 100644 --- a/tools/list_agents.py +++ b/tools/list_agents.py @@ -1,16 +1,32 @@ +import os import tools.goal_registry as goals def list_agents(args: dict) -> dict: + goal_categories_start = os.getenv("GOAL_CATEGORIES") + if goal_categories_start is None: + goal_categories = ["all"] # default to 'all' categories + else: + goal_categories_start.strip().lower() # handle extra spaces or non-lowercase + goal_categories = goal_categories_start.split(",") + + # always show goals labeled as "system," like the goal chooser + if "system" not in goal_categories: + goal_categories.append("system") + agents = [] if goals.goal_list is not None: for goal in goals.goal_list: - agents.append( - { - "agent_name": goal.agent_name, - "goal_id": goal.id, - "agent_description": goal.agent_friendly_description, - } + # add to list if either + # - all + # - current goal's tag is in goal_categories + if "all" in goal_categories or goal.category_tag in goal_categories: + agents.append( + { + "agent_name": goal.agent_name, + "goal_id": goal.id, + "agent_description": goal.agent_friendly_description, + } ) return { "agents": agents,