mirror of
https://github.com/temporal-community/temporal-ai-agent.git
synced 2026-03-15 05:58:08 +01:00
Merge pull request #3 from joshmsmith/development
Major improvements to docs, adding more tools/scenarios for pirate mode
This commit is contained in:
@@ -10,17 +10,26 @@ It may be helpful to review the [architecture](./architecture.md) for a guide an
|
||||
- `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`. This allows the AI to let the user go back to choosing from the list of available goals.
|
||||
- `description`:
|
||||
- `starter-prompt`:
|
||||
- `example_conversation_history`:
|
||||
- `tools`: the list of tools the goal will walk the user through. These will be defined in the [tools/tool_registry.py](tools/tool_registry.py) and should be defined in list form as tool_registry.[name of tool]
|
||||
- Important! The last tool listed must be `list_agents_tool`. This allows the chatbot to guide the user back to choosing from the list of available goals once a goal is complete.<br />
|
||||
Example:
|
||||
```
|
||||
tools=[
|
||||
tool_registry.current_pto_tool,
|
||||
tool_registry.future_pto_calc_tool,
|
||||
tool_registry.book_pto_tool,
|
||||
tool_registry.list_agents_tool,
|
||||
]
|
||||
```
|
||||
- `description`: LLM-facing description of the goal that lists the tools by name and purpose.
|
||||
- `starter-prompt`: LLM-facing first prompt given to begin the scenario. This field can contain instructions that are different from other goals, like "begin by providing the output of the first tool" rather than waiting on user confirmation. (See [goal_choose_agent_type](tools/goal_registry.py) for an example.)
|
||||
- `example_conversation_history`: LLM-facing sample conversation/interaction regarding the goal. See the existing goals for how to structure this.
|
||||
4. Add your new goal to the `goal_list` at the bottom using `goal_list.append(your_super_sweet_new_goal)`
|
||||
|
||||
### Adding Tools
|
||||
|
||||
#### Notes
|
||||
Tools can be optional - you can indicate this in the tool listing of goal description (see above section re: goal registry) by adding something like, "This step is optional and can be skipped by moving to the next tool." Here is an example from the CalendarConflict tool/step of the [goal_hr_schedule_pto](tools/goal_registry.py#L134) goal:
|
||||
Tools can be optional - you can indicate this in the tool listing of goal description (see above section re: goal registry) by adding something like, "This step is optional and can be skipped by moving to the next tool." Here is an example from an older iteration of the `goal_hr_schedule_pto` goal, when it was going to have an optional step to check for existing calendar conflicts:
|
||||
|
||||
```
|
||||
description="Help the user gather args for these tools in order: "
|
||||
@@ -31,20 +40,26 @@ description="Help the user gather args for these tools in order: "
|
||||
```
|
||||
|
||||
#### 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.
|
||||
1. Open [/tools/tool_registry.py](tools/tool_registry.py) - this file contains mapping of tool names to tool definitions (so the AI understands how to use them)
|
||||
2. Define the tool
|
||||
- `name`: name of the tool - this is the name as defined in the goal description list of tools. The name should be (sort of) the same as the tool name given in the goal description. So, if the description lists "CurrentPTO" as a tool, the name here should be `current_pto_tool`.
|
||||
- `description`: LLM-facing description of tool
|
||||
- `arguments`: These are the _input_ arguments to the tool. Each input argument should be defined as a [/models/tool_definitions.py](ToolArgument). Tools don't have to have arguments but the arguments list has to be declared. If the tool you're creating doesn't have inputs, define arguments as `arguments=[]`
|
||||
|
||||
#### 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 tools themselves are defined in their own files in `/tools` - you can add a subfolder to organize them, see the hr tools for an example.
|
||||
- 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 `current_pto_tool` would be `current_pto.py` with a function named `current_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`
|
||||
- tools are where the user input+model output becomes deterministic. Add validation here to make sure what the system is doing is valid and acceptable
|
||||
|
||||
#### 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.
|
||||
Example:
|
||||
```
|
||||
if tool_name == "CurrentPTO":
|
||||
return current_pto
|
||||
```
|
||||
|
||||
### Configuring the Starting Goal
|
||||
|
||||
|
||||
30
todo.md
30
todo.md
@@ -1,47 +1,33 @@
|
||||
# todo list
|
||||
[x] clean up workflow/make functions
|
||||
|
||||
[x] make the debugging confirms optional <br />
|
||||
[ ] add confirmation env setting to setup guide <br />
|
||||
<br />
|
||||
[x] document *why* temporal for ai agents - scalability, durability, visibility in the readme <br />
|
||||
[x] fix readme: move setup to its own page, demo to its own page, add the why /|\ section <br />
|
||||
[x] add architecture to readme <br />
|
||||
- elements of app <br />
|
||||
- dive into llm interaction <br />
|
||||
- workflow breakdown - interactive loop <br />
|
||||
- why temporal <br />
|
||||
|
||||
[x] setup readme, why readme, architecture readme, what this is in main readme with temporal value props and pictures <br />
|
||||
[ ] how to add more scenarios, tools <br />
|
||||
|
||||
[ ] create tests<br />
|
||||
[ ] fix logging statements not to be all warn, maybe set logging level to info
|
||||
|
||||
[ ] create people management scenarios <br />
|
||||
[x] 1. Schedule PTO goal
|
||||
-- [ ] check current PTO level <br />
|
||||
-- [ ] determine PTO available as of date <br />
|
||||
-- [ ] check for personal, team, or both calendar conflicts <br />
|
||||
-- [ ] book PTO around a date (send calendar invite?) (https://developers.google.com/calendar/api/guides/overview)? <br />
|
||||
|
||||
[ ] 2. Others:
|
||||
-- check pay status <br />
|
||||
-- book work travel <br />
|
||||
-- check insurance coverages <br />
|
||||
-- expense management <br />
|
||||
-- check in on the health of the team <br />
|
||||
-- check pto
|
||||
|
||||
[ ] demo the reasons why: <br />
|
||||
[x] demo the reasons why: <br />
|
||||
- Orchestrate interactions across distributed data stores and tools <br />
|
||||
- Hold state, potentially over long periods of time <br />
|
||||
- Ability to ‘self-heal’ and retry until the (probabilistic) LLM returns valid data <br />
|
||||
- Support for human intervention such as approvals <br />
|
||||
- Parallel processing for efficiency of data retrieval and tool use <br />
|
||||
- Insight into the agent’s performance <br />
|
||||
- ask the ai agent how it did at the end of the conversation, was it efficient? successful? insert a search attribute to document that before return
|
||||
|
||||
[ ] customize prompts in [workflow to manage scenario](./workflows/tool_workflow.py)<br />
|
||||
[ ] add in new tools? <br />
|
||||
[ ] ask the ai agent how it did at the end of the conversation, was it efficient? successful? insert a search attribute to document that before return
|
||||
- Insight into the agent’s performance <br />
|
||||
|
||||
[x] customize prompts in [workflow to manage scenario](./workflows/tool_workflow.py)<br />
|
||||
[x] add in new tools? <br />
|
||||
|
||||
[ ] non-retry the api key error - "Invalid API Key provided: sk_test_**J..." and "AuthenticationError" <br />
|
||||
[ ] make it so you can yeet yourself out of a goal and pick a new one <br />
|
||||
|
||||
@@ -12,6 +12,9 @@ from .hr.current_pto import current_pto
|
||||
from .hr.book_pto import book_pto
|
||||
from .hr.future_pto_calc import future_pto_calc
|
||||
|
||||
from .give_hint import give_hint
|
||||
from .guess_location import guess_location
|
||||
|
||||
|
||||
def get_handler(tool_name: str):
|
||||
if tool_name == "SearchFixtures":
|
||||
@@ -38,5 +41,9 @@ def get_handler(tool_name: str):
|
||||
return book_pto
|
||||
if tool_name == "FuturePTOCalc":
|
||||
return future_pto_calc
|
||||
if tool_name == "GiveHint":
|
||||
return give_hint
|
||||
if tool_name == "GuessLocation":
|
||||
return guess_location
|
||||
|
||||
raise ValueError(f"Unknown tool: {tool_name}")
|
||||
|
||||
42
tools/give_hint.py
Normal file
42
tools/give_hint.py
Normal file
@@ -0,0 +1,42 @@
|
||||
TREASURE_LOCATION = {
|
||||
"address": "300 Lenora",
|
||||
"city": "Seattle",
|
||||
"state_full": "Washington",
|
||||
"state_abbrev": "WA",
|
||||
"zip": "98121",
|
||||
"country": "USA"
|
||||
}
|
||||
|
||||
HINTS = [
|
||||
"state of Washington",
|
||||
"city of Seattle",
|
||||
"at a company HQ",
|
||||
]
|
||||
''' Grok provided hints:
|
||||
Here are additional company-specific clues about Temporal that could help players in your game guess the address (300 Lenora St, Seattle, WA) by focusing on the company itself. These are designed to be intriguing and game-friendly:
|
||||
|
||||
"This company was founded by two engineers who previously worked on a system named after a South American river at Uber."
|
||||
"Their platform is all about orchestrating workflows that can survive failures—like a conductor keeping the music going."
|
||||
"They offer a tool that lets developers write code as if it’s running forever, no matter what crashes."
|
||||
"The company’s tech traces its roots to a project called Cadence, which they took to the next level."
|
||||
"Their mission is tied to making distributed systems feel as simple as writing a single app."
|
||||
"They’ve got a knack for ‘durability’—both in their software and their growing reputation."
|
||||
"This outfit spun out of experiences at AWS and Uber, blending cloud and ride-sharing know-how."
|
||||
"Their open-source framework has a community that’s ticking along, fixing bugs and adding features daily."
|
||||
"They’re backed by big venture capital names like Sequoia, betting on their vision for reliable software."
|
||||
"The company’s name might remind you of a word for something fleeting, yet their tech is built to last."'''
|
||||
|
||||
def give_hint(args: dict) -> dict:
|
||||
hint_total = args.get("hint_total")
|
||||
if hint_total is None:
|
||||
hint_total = 0
|
||||
|
||||
index = hint_total % len(HINTS)
|
||||
hint_text = HINTS[index]
|
||||
|
||||
print(f"hint_total: {hint_total}, length: {len(HINTS)}, index: {index}")
|
||||
hint_total = hint_total + 1
|
||||
return {
|
||||
"hint_number": hint_total,
|
||||
"hint": hint_text
|
||||
}
|
||||
@@ -47,6 +47,55 @@ goal_choose_agent_type = AgentGoal(
|
||||
),
|
||||
)
|
||||
|
||||
# Easter egg - if silly mode = a pirate, include goal_pirate_treasure as a "system" goal so it always shows up.
|
||||
# Can also turn make this goal available by setting the GOAL_CATEGORIES in the env file to include 'pirate', but if SILLY_MODE
|
||||
# is not 'a pirate', the interaction as a whole will be less pirate-y.
|
||||
pirate_category_tag = "pirate"
|
||||
if SILLY_MODE == "a pirate":
|
||||
pirate_category_tag = "system"
|
||||
goal_pirate_treasure = AgentGoal(
|
||||
id = "goal_pirate_treasure",
|
||||
category_tag=pirate_category_tag,
|
||||
agent_name="Arrr, Find Me Treasure!",
|
||||
agent_friendly_description="Sail the high seas and find me pirate treasure, ye land lubber!",
|
||||
tools=[
|
||||
tool_registry.give_hint_tool,
|
||||
tool_registry.guess_location_tool,
|
||||
tool_registry.list_agents_tool,
|
||||
],
|
||||
description="The user wants to find a pirate treasure. "
|
||||
"Help the user gather args for these tools, in a loop, until treasure_found is True or the user requests to be done: "
|
||||
"1. GiveHint: If the user wants a hint regarding the location of the treasure, give them a hint. If they do not want a hint, this tool is optional."
|
||||
"2. GuessLocation: The user guesses where the treasure is, by giving an address. ",
|
||||
starter_prompt=starter_prompt_generic,
|
||||
example_conversation_history="\n ".join(
|
||||
[
|
||||
"user: I'd like to try to find the treasure",
|
||||
"agent: Sure! Do you want a hint?",
|
||||
"user: yes",
|
||||
"agent: Here is hint number 1!",
|
||||
"user_confirmed_tool_run: <user clicks confirm on GiveHint tool>",
|
||||
"tool_result: { 'hint_number': 1, 'hint': 'The treasure is in the state of Arizona.' }",
|
||||
"agent: The treasure is in the state of Arizona. Would you like to guess the address of the treasure? ",
|
||||
"user: Yes, address is 123 Main St Phoenix, AZ",
|
||||
"agent: Let's see if you found the treasure...",
|
||||
"user_confirmed_tool_run: <user clicks confirm on GuessLocation tool>"
|
||||
"tool_result: {'treasure_found':False}",
|
||||
"agent: Nope, that's not the right location! Do you want another hint?",
|
||||
"user: yes",
|
||||
"agent: Here is hint number 2.",
|
||||
"user_confirmed_tool_run: <user clicks confirm on GiveHint tool>",
|
||||
"tool_result: { 'hint_number': 2, 'hint': 'The treasure is in the city of Tucson, AZ.' }",
|
||||
"agent: The treasure is in the city of Tucson, AZ. Would you like to guess the address of the treasure? ",
|
||||
"user: Yes, address is 456 Main St Tucson, AZ",
|
||||
"agent: Let's see if you found the treasure...",
|
||||
"user_confirmed_tool_run: <user clicks confirm on GuessLocation tool>",
|
||||
"tool_result: {'treasure_found':True}",
|
||||
"agent: Congratulations, Land Lubber, you've found the pirate treasure!",
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
goal_match_train_invoice = AgentGoal(
|
||||
id = "goal_match_train_invoice",
|
||||
category_tag="travel",
|
||||
@@ -164,13 +213,8 @@ goal_hr_schedule_pto = AgentGoal(
|
||||
"agent: Let's check if you'll have enough PTO accrued by Dec 1 of this year to accomodate that.",
|
||||
"user_confirmed_tool_run: <user clicks confirm on FuturePTO tool>"
|
||||
'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: <user clicks confirm on CheckCalendarConflict tool>",
|
||||
'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 "
|
||||
"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 book the PTO? ",
|
||||
"user: yes ",
|
||||
"user_confirmed_tool_run: <user clicks confirm on BookPTO tool>",
|
||||
'tool_result: { "status": "success" }',
|
||||
"agent: PTO successfully booked! ",
|
||||
@@ -181,6 +225,7 @@ goal_hr_schedule_pto = AgentGoal(
|
||||
#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_pirate_treasure)
|
||||
goal_list.append(goal_event_flight_invoice)
|
||||
goal_list.append(goal_match_train_invoice)
|
||||
goal_list.append(goal_hr_schedule_pto)
|
||||
|
||||
18
tools/guess_location.py
Normal file
18
tools/guess_location.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from .give_hint import TREASURE_LOCATION
|
||||
|
||||
def guess_location(args: dict) -> dict:
|
||||
|
||||
guess_address = args.get("address").lower()
|
||||
guess_city = args.get("city").lower()
|
||||
guess_state = args.get("state").lower()
|
||||
|
||||
if len(guess_state) == 2:
|
||||
compare_state = TREASURE_LOCATION.get("state_abbrev").lower()
|
||||
else:
|
||||
compare_state = TREASURE_LOCATION.get("state_full").lower()
|
||||
|
||||
#Check for the street address to be included in the guess to account for "st" vs "street" or leaving Street off entirely
|
||||
if TREASURE_LOCATION.get("address").lower() in guess_address and TREASURE_LOCATION.get("city").lower() == guess_city and compare_state == guess_state:
|
||||
return {"treasure_found": "True"}
|
||||
else:
|
||||
return {"treasure_found": "False"}
|
||||
@@ -1,5 +1,5 @@
|
||||
from models.tool_definitions import ToolDefinition, ToolArgument
|
||||
|
||||
# ----- System tools -----
|
||||
list_agents_tool = ToolDefinition(
|
||||
name="ListAgents",
|
||||
description="List available agents to interact with, pulled from goal_registry. ",
|
||||
@@ -18,6 +18,39 @@ change_goal_tool = ToolDefinition(
|
||||
],
|
||||
)
|
||||
|
||||
give_hint_tool = ToolDefinition(
|
||||
name="GiveHint",
|
||||
description="Give a hint to the user regarding the location of the pirate treasure. Use previous conversation to determine the hint_total, it should initially be 0 ",
|
||||
arguments=[
|
||||
ToolArgument(
|
||||
name="hint_total",
|
||||
type="number",
|
||||
description="How many hints have been given",
|
||||
),],
|
||||
)
|
||||
|
||||
guess_location_tool = ToolDefinition(
|
||||
name="GuessLocation",
|
||||
description="Allow the user to guess the location (in the form of an address) of the pirate treasure. ",
|
||||
arguments=[
|
||||
ToolArgument(
|
||||
name="address",
|
||||
type="string",
|
||||
description="Address at which the user is guessing the treasure is located",
|
||||
),
|
||||
ToolArgument(
|
||||
name="city",
|
||||
type="string",
|
||||
description="City at which the user is guessing the treasure is located",
|
||||
),
|
||||
ToolArgument(
|
||||
name="state",
|
||||
type="string",
|
||||
description="State at which the user is guessing the treasure is located",
|
||||
),
|
||||
],
|
||||
)
|
||||
# ----- Travel use cases tools -----
|
||||
search_flights_tool = ToolDefinition(
|
||||
name="SearchFlights",
|
||||
description="Search for return flights from an origin to a destination within a date range (dateDepart, dateReturn).",
|
||||
@@ -143,6 +176,7 @@ find_events_tool = ToolDefinition(
|
||||
],
|
||||
)
|
||||
|
||||
# ----- HR use cases tools -----
|
||||
current_pto_tool = ToolDefinition(
|
||||
name="CurrentPTO",
|
||||
description="Find how much PTO a user currently has accrued. "
|
||||
@@ -179,23 +213,6 @@ future_pto_calc_tool = ToolDefinition(
|
||||
],
|
||||
)
|
||||
|
||||
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? "
|
||||
|
||||
Reference in New Issue
Block a user