From ea1ad383bb5fa0c05a3ab1471bc8b6cacaafbe18 Mon Sep 17 00:00:00 2001 From: Laine Date: Wed, 12 Mar 2025 16:54:13 -0400 Subject: [PATCH] Add in bare bones yet functional HR goal: goal_hr_schedule_pto --- adding-goals-and-tools.md | 71 ++++++++++++++++++++++++++++++++++ todo.md | 25 ++++++------ tools/__init__.py | 13 +++++++ tools/book_pto.py | 9 +++++ tools/calendar_conflict.py | 17 +++++++++ tools/current_pto.py | 14 +++++++ tools/future_pto.py | 17 +++++++++ tools/goal_registry.py | 78 +++++++++++++++++++++++++++++++------- tools/tool_registry.py | 71 ++++++++++++++++++++++++++++++++++ 9 files changed, 290 insertions(+), 25 deletions(-) create mode 100644 adding-goals-and-tools.md create mode 100644 tools/book_pto.py create mode 100644 tools/calendar_conflict.py create mode 100644 tools/current_pto.py create mode 100644 tools/future_pto.py diff --git a/adding-goals-and-tools.md b/adding-goals-and-tools.md new file mode 100644 index 0000000..57a8517 --- /dev/null +++ b/adding-goals-and-tools.md @@ -0,0 +1,71 @@ +## Customizing the Agent +The agent is set up to allow for multiple goals and to switch back to choosing a new goal at the end of every successful goal. A goal is made up of a list of tools that the agent will guide the user through. + +### Adding a Goal +1. Open [/tools/goal_registry.py](tools/goal_registry.py) - this file contains descriptions of goals and the tools used to achieve them +2. Pick a name for your goal! +3. Fill out the required elements: +- `id`: needs to be the same as the name +- `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` +- `description`: +- `starter-prompt`: +- `example_conversation_history`: +4. Add your new goal to the `goal_list` at the bottom using `goal_list.append(your_super_sweet_new_goal)` + +### Adding Tools + +#### Add to Tool Registry +- `tool_registry.py` contains the mapping of tool names to tool definitions (so the AI understands how to use them) +- `name`: +- `description`: +- `arguments`: These are the _input_ arguments to the tool. + +#### Create Each Tool +- The tools themselves are defined in their own files in `/tools` - you can add a subfolder to organize them +- The file name and function name will be the same as each other and should also be the same as the name of the tool, without "tool" - so future_pto_tool would be future_pto.py with a function named future_pto within it. +- The function should have `args: dict` as the input and also return a `dict` +- The return dict should match the output format you specified in the goal's `example_conversation_history` + +#### Add to `tools/__init__.py` +- In `tools/__init__.py`, add an import statement for each new tool as well as an applicable return statement in `get_handler`. The tool name here should match the tool name as described in the goal's `description` field. + +### Configuring the Starting Goal + +The agent can be configured to pursue different goals using the `AGENT_GOAL` environment variable in your `.env` file. + +#### Goal: Find an event in Australia / New Zealand, book flights to it and invoice the user for the cost +- `AGENT_GOAL=goal_event_flight_invoice` (default) - Helps users find events, book flights, and arrange train travel with invoice generation + - This is the scenario in the video above + +#### Goal: Find a Premier League match, book train tickets to it and invoice the user for the cost +- `AGENT_GOAL=goal_match_train_invoice` - Focuses on Premier League match attendance with train booking and invoice generation + - This is a new goal that is part of an upcoming conference talk + +If not specified, the agent defaults to `goal_event_flight_invoice`. Each goal comes with its own set of tools and conversation flows designed for specific use cases. You can examine `tools/goal_registry.py` to see the detailed configuration of each goal. + +See the next section for tool configuration for each goal. + +### Configuring Existing Tools + +#### Agent Goal: goal_event_flight_invoice (default) +* The agent uses a mock function to search for events. This has zero configuration. +* By default the agent uses a mock function to search for flights. + * If you want to use the real flights API, go to `tools/search_flights.py` and replace the `search_flights` function with `search_flights_real_api` that exists in the same file. + * It's free to sign up at [RapidAPI](https://rapidapi.com/apiheya/api/sky-scrapper) + * This api might be slow to respond, so you may want to increase the start to close timeout, `TOOL_ACTIVITY_START_TO_CLOSE_TIMEOUT` in `workflows/workflow_helpers.py` +* Requires a Stripe key for the `create_invoice` tool. Set this in the `STRIPE_API_KEY` environment variable in .env + * It's free to sign up and get a key at [Stripe](https://stripe.com/) + * If you're lazy go to `tools/create_invoice.py` and replace the `create_invoice` function with the mock `create_invoice_example` that exists in the same file. + +#### Agent Goal: goal_match_train_invoice + +* Finding a match requires a key from [Football Data](https://www.football-data.org). Sign up for a free account, then see the 'My Account' page to get your API token. Set `FOOTBALL_DATA_API_KEY` to this value. + * If you're lazy go to `tools/search_fixtures.py` and replace the `search_fixtures` function with the mock `search_fixtures_example` that exists in the same file. +* We use a mock function to search for trains. Start the train API server to use the real API: `python thirdparty/train_api.py` +* * The train activity is 'enterprise' so it's written in C# and requires a .NET runtime. See the [.NET backend](#net-(enterprise)-backend) section for details on running it. +* Requires a Stripe key for the `create_invoice` tool. Set this in the `STRIPE_API_KEY` environment variable in .env + * It's free to sign up and get a key at [Stripe](https://stripe.com/) + * If you're lazy go to `tools/create_invoice.py` and replace the `create_invoice` function with the mock `create_invoice_example` that exists in the same file. \ No newline at end of file diff --git a/todo.md b/todo.md index d477f4c..ca4fdb2 100644 --- a/todo.md +++ b/todo.md @@ -13,19 +13,22 @@ [ ] setup readme, why readme, architecture readme, what this is in main readme with temporal value props and pictures
[ ] how to add more scenarios, tools
-
-
+ [ ] create tests
-[ ] create people management scenario
-- check pay status
-- book work travel
-- check PTO levels
-- check insurance coverages
-- book PTO around a date (https://developers.google.com/calendar/api/guides/overview)?
-- scenario should use multiple tools
-- expense management
-- check in on the health of the team
+[ ] create people management scenarios
+[ ] 1. Book PTO +-- check current PTO level
+-- determine PTO available as of date
+-- check for personal calendar conflicts
+-- check for team calendar conflicts
+-- book PTO around a date (send calendar invite?) (https://developers.google.com/calendar/api/guides/overview)?
+[ ] 2. Others: +-- check pay status
+-- book work travel
+-- check insurance coverages
+-- expense management
+-- check in on the health of the team
[ ] demo the reasons why:
- Orchestrate interactions across distributed data stores and tools
diff --git a/tools/__init__.py b/tools/__init__.py index 1ca7da2..89e8f74 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -8,6 +8,11 @@ from .list_agents import list_agents from .change_goal import change_goal 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 import future_pto + def get_handler(tool_name: str): if tool_name == "SearchFixtures": @@ -28,5 +33,13 @@ def get_handler(tool_name: str): return change_goal if tool_name == "TransferControl": return transfer_control + if tool_name == "CurrentPTO": + return current_pto + if tool_name == "BookPTO": + return book_pto + if tool_name == "CalendarConflict": + return calendar_conflict + if tool_name == "FuturePTO": + return future_pto raise ValueError(f"Unknown tool: {tool_name}") diff --git a/tools/book_pto.py b/tools/book_pto.py new file mode 100644 index 0000000..4d4b821 --- /dev/null +++ b/tools/book_pto.py @@ -0,0 +1,9 @@ +def book_pto(args: dict) -> dict: + + email = args.get("email") + start_date = args.get("start_date") + end_date = args.get("end_date") + + return { + "status": "success" + } diff --git a/tools/calendar_conflict.py b/tools/calendar_conflict.py new file mode 100644 index 0000000..00b190c --- /dev/null +++ b/tools/calendar_conflict.py @@ -0,0 +1,17 @@ +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/current_pto.py b/tools/current_pto.py new file mode 100644 index 0000000..b8d2d40 --- /dev/null +++ b/tools/current_pto.py @@ -0,0 +1,14 @@ +def current_pto(args: dict) -> dict: + + email = args.get("email") + if email == "bob.johnson@emailzzz.com": + num_hours = 40 + else: + num_hours = 20 + + num_days = float(num_hours/8) + + return { + "num_hours": num_hours, + "num_days": num_days, + } diff --git a/tools/future_pto.py b/tools/future_pto.py new file mode 100644 index 0000000..eaf9a12 --- /dev/null +++ b/tools/future_pto.py @@ -0,0 +1,17 @@ +def future_pto(args: dict) -> dict: + + start_date = args.get("start_date") + end_date = args.get("end_date") + + # 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 + + # 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 + + return { + "enough_pto": True, + "pto_hrs_remaining_after": 410, + } diff --git a/tools/goal_registry.py b/tools/goal_registry.py index 39e2187..ee7dfaf 100644 --- a/tools/goal_registry.py +++ b/tools/goal_registry.py @@ -1,6 +1,7 @@ from typing import List from models.tool_definitions import AgentGoal -from tools.tool_registry import ( +import tools.tool_registry as tool_registry +'''from tools.tool_registry import ( search_fixtures_tool, search_flights_tool, search_trains_tool, @@ -8,8 +9,12 @@ from tools.tool_registry import ( create_invoice_tool, find_events_tool, change_goal_tool, - list_agents_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" @@ -18,8 +23,8 @@ goal_choose_agent_type = AgentGoal( agent_name="Choose Agent", agent_friendly_description="Choose the type of agent to assist you today.", tools=[ - list_agents_tool, - change_goal_tool, + tool_registry.list_agents_tool, + tool_registry.change_goal_tool, ], description="The user wants to choose which type of agent they will interact with. " "Help the user gather args for these tools, in order: " @@ -46,11 +51,11 @@ goal_match_train_invoice = AgentGoal( 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=[ - search_fixtures_tool, - search_trains_tool, - book_trains_tool, - create_invoice_tool, - list_agents_tool, #last tool must be list_agents to fasciliate changing back to picking an agent again at the end + tool_registry.search_fixtures_tool, + tool_registry.search_trains_tool, + tool_registry.book_trains_tool, + tool_registry.create_invoice_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="The user wants to book a trip to a city in the UK around the dates of a premier league match. " "Help the user find a premier league match to attend, search and book trains for that match and offers to invoice them for the cost of train tickets. " @@ -93,10 +98,10 @@ goal_event_flight_invoice = AgentGoal( 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=[ - find_events_tool, - search_flights_tool, - create_invoice_tool, - list_agents_tool, #last tool must be list_agents to fasciliate changing back to picking an agent again at the end + tool_registry.find_events_tool, + tool_registry.search_flights_tool, + tool_registry.create_invoice_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: " "1. FindEvents: Find an event to travel to " @@ -126,8 +131,53 @@ 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.", + 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: " + "1. CurrentPTO: Tell the user how much PTO they currently have " + "2. FuturePTO: 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 " + "4. BookPTO: Book PTO ", + starter_prompt=starter_prompt_generic, + example_conversation_history="\n ".join( + [ + "user: I'd like to schedule some time off", + "agent: Sure! Let's start by determining how much PTO you currently have. May I have your email address?", + "user: bob.johnson@emailzzz.com", + "agent: Great! I can tell you how much PTO you currently have accrued.", + "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_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 " + "user: both ", + "agent: Okay, checking both calendars for conflicts ", + "user_confirmed_tool_run: ", + 'tool_result: { "calendar": "self", "title": "Meeting with Karen", "date": "2025-12-02", "time": "10:00AM"}', + "agent: On your calendar, you have a conflict: Meeting with Karen at 10AM Dec 2, 2025. Do you want to book the PTO?" + "user: yes " + "user_confirmed_tool_run: ", + 'tool_result: { "status": "success" }', + "agent: PTO successfully booked! Would you like to speak to another agent? ", + ] + ), +) + #Add the goals to a list for more generic processing, like listing available agents goal_list: List[AgentGoal] = [] goal_list.append(goal_choose_agent_type) goal_list.append(goal_event_flight_invoice) goal_list.append(goal_match_train_invoice) +goal_list.append(goal_hr_schedule_pto) diff --git a/tools/tool_registry.py b/tools/tool_registry.py index f80aa8b..c438282 100644 --- a/tools/tool_registry.py +++ b/tools/tool_registry.py @@ -142,3 +142,74 @@ find_events_tool = ToolDefinition( ), ], ) + +current_pto_tool = ToolDefinition( + name="CurrentPTO", + description="Find how much PTO a user currently has accrued. " + "Returns the number of hours and (calculated) number of days of PTO. ", + arguments=[ + ToolArgument( + name="email", + type="string", + description="name of user, used to look up current PTO", + ), + ], +) + +future_pto_calc_tool = ToolDefinition( + name="FuturePTO", + 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. ", + arguments=[ + ToolArgument( + name="start_date", + type="string", + description="Start date of proposed PTO", + ), + ToolArgument( + name="end_date", + type="string", + description="End date of proposed PTO", + ), + ], +) + +calendar_conflict_tool = ToolDefinition( + name="CalendarConflict", + description="Determine if the proposed PTO date(s) have conflicts. Returns list of conflicts. ", + arguments=[ + ToolArgument( + name="check_self_calendar", + type="boolean", + description="Check self calendar for conflicts?", + ), + ToolArgument( + name="check_team_calendar", + type="boolean", + description="Check team calendar for conflicts?", + ), + ], +) + +book_pto_tool = ToolDefinition( + name="BookPTO", + description="Book PTO start and end date. Either 1) makes calendar item, or 2) sends calendar invite to self and boss? " + "Returns a success indicator. ", + arguments=[ + ToolArgument( + name="start_date", + type="string", + description="Start date of proposed PTO", + ), + ToolArgument( + name="end_date", + type="string", + description="End date of proposed PTO", + ), + ToolArgument( + name="email", + type="string", + description="Email address of user, used to look up current PTO", + ), + ], +) \ No newline at end of file