diff --git a/.env.example b/.env.example
index 5140da8..ef6bf09 100644
--- a/.env.example
+++ b/.env.example
@@ -35,12 +35,13 @@ OPENAI_API_KEY=sk-proj-...
# Uncomment if using API key (not needed for local dev server)
# TEMPORAL_API_KEY=abcdef1234567890
-# Set starting goal of agent - if unset default is all
-AGENT_GOAL=goal_choose_agent_type # default for multi-goal start
+# Set starting goal of agent - if unset default is goal_choose_agent_type
+AGENT_GOAL=goal_choose_agent_type # for multi-goal start
#AGENT_GOAL=goal_event_flight_invoice # for original goal
#AGENT_GOAL=goal_match_train_invoice # for replay goal
-#Choose which category(ies) of goals you want to be listed by the Agent - options are system (always included), hr, travel, or all.
+#Choose which category(ies) of goals you want to be listed by the Agent Goal picker if enabled above
+# - options are system (always included), hr, travel, or all.
GOAL_CATEGORIES=hr,travel-flights,travel-trains,fin # default is all
#GOAL_CATEGORIES=travel-flights
diff --git a/activities/tool_activities.py b/activities/tool_activities.py
index e546414..9d466bc 100644
--- a/activities/tool_activities.py
+++ b/activities/tool_activities.py
@@ -11,7 +11,7 @@ import google.generativeai as genai
import anthropic
import deepseek
from dotenv import load_dotenv
-from models.data_types import ValidationInput, ValidationResult, ToolPromptInput, EnvLookupInput
+from models.data_types import EnvLookupOutput, ValidationInput, ValidationResult, ToolPromptInput, EnvLookupInput
load_dotenv(override=True)
print(
@@ -370,7 +370,8 @@ class ToolActivities:
print("Initialized Anthropic client on demand")
response = self.anthropic_client.messages.create(
- model="claude-3-5-sonnet-20241022", # todo try claude-3-7-sonnet-20250219
+ #model="claude-3-5-sonnet-20241022", # todo try claude-3-7-sonnet-20250219
+ model="claude-3-7-sonnet-20250219", # todo try claude-3-7-sonnet-20250219
max_tokens=1024,
system=input.context_instructions
+ ". The current date is "
@@ -473,17 +474,29 @@ class ToolActivities:
# get env vars for workflow
@activity.defn
- async def get_env_bool(self, input: EnvLookupInput) -> bool:
- """ gets boolean env vars for workflow as an activity result so it's deterministic
+ async def get_wf_env_vars(self, input: EnvLookupInput) -> EnvLookupOutput:
+ """ gets env vars for workflow as an activity result so it's deterministic
handles default/None
"""
- value = os.getenv(input.env_var_name)
- if value is None:
- return input.default
- if value is not None and value.lower() == "false":
- return False
+ output: EnvLookupOutput = EnvLookupOutput(show_confirm=input.show_confirm_default,
+ multi_goal_mode=True)
+ show_confirm_value = os.getenv(input.show_confirm_env_var_name)
+ if show_confirm_value is None:
+ output.show_confirm = input.show_confirm_default
+ elif show_confirm_value is not None and show_confirm_value.lower() == "false":
+ output.show_confirm = False
else:
- return True
+ output.show_confirm = True
+
+ first_goal_value = os.getenv("AGENT_GOAL")
+ if first_goal_value is None:
+ output.multi_goal_mode = True # default if unset
+ elif first_goal_value is not None and first_goal_value.lower() != "goal_choose_agent_type":
+ output.multi_goal_mode = False
+ else:
+ output.multi_goal_mode = True
+
+ return output
def get_current_date_human_readable():
diff --git a/adding-goals-and-tools.md b/adding-goals-and-tools.md
index 67a2171..910b39d 100644
--- a/adding-goals-and-tools.md
+++ b/adding-goals-and-tools.md
@@ -19,14 +19,13 @@ Goal Categories lets you pick which groups of goals to show. Set via an .env set
- `category_tag`: category for the goal
- `agent_friendly_description`: user-facing description of what the agent/chatbot does
- `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! If you want to prompt the user with options for another goal, 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. This is recommended for multi-goal behavior. The `goal_choose_agent_type` is the exception as it handles the changing of goals.
+
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.
diff --git a/models/data_types.py b/models/data_types.py
index b7ee350..9b7d67d 100644
--- a/models/data_types.py
+++ b/models/data_types.py
@@ -45,5 +45,10 @@ class ValidationResult:
@dataclass
class EnvLookupInput:
- env_var_name: str
- default: bool
\ No newline at end of file
+ show_confirm_env_var_name: str
+ show_confirm_default: bool
+
+@dataclass
+class EnvLookupOutput:
+ show_confirm: bool
+ multi_goal_mode: bool
\ No newline at end of file
diff --git a/prompts/agent_prompt_generators.py b/prompts/agent_prompt_generators.py
index 6bae621..71c9d67 100644
--- a/prompts/agent_prompt_generators.py
+++ b/prompts/agent_prompt_generators.py
@@ -2,15 +2,17 @@ from models.tool_definitions import AgentGoal
from typing import Optional
import json
+MULTI_GOAL_MODE:bool = None
def generate_genai_prompt(
- agent_goal: AgentGoal, conversation_history: str, raw_json: Optional[str] = None
+ agent_goal: AgentGoal, conversation_history: str, multi_goal_mode:bool, raw_json: Optional[str] = None
) -> str:
"""
Generates a concise prompt for producing or validating JSON instructions
with the provided tools and conversation history.
"""
prompt_lines = []
+ set_multi_goal_mode_if_unset(multi_goal_mode)
# Intro / Role
prompt_lines.append(
@@ -81,7 +83,7 @@ def generate_genai_prompt(
"1) If any required argument is missing, set next='question' and ask the user.\n"
"2) If all required arguments are known, set next='confirm' and specify the tool.\n"
" The user will confirm before the tool is run.\n"
- "3) If no more tools are needed (user_confirmed_tool_run has been run for all), set next='confirm' and tool='ListAgents'.\n"
+ f"3) {generate_toolchain_complete_guidance()}\n"
"4) response should be short and user-friendly.\n"
)
@@ -127,8 +129,7 @@ def generate_tool_completion_prompt(current_tool: str, dynamic_result: dict) ->
"ONLY return those json keys (next, tool, args, response), nothing else. "
'Next should be "question" if the tool is not the last one in the sequence. '
'Next should be "done" if the user is asking to be done with the chat. '
- 'Next should only be "pick-new-goal" if all tools have been run (use the system prompt to figure that out).'
- #'If all tools have been run (use the system prompt to figure that out) then clear tool history.' todo maybe fix this
+ f"{generate_pick_new_goal_guidance()}"
)
def generate_missing_args_prompt(current_tool: str, tool_data: dict, missing_args: list[str]) -> str:
@@ -148,3 +149,59 @@ def generate_missing_args_prompt(current_tool: str, tool_data: dict, missing_arg
f"and following missing arguments for tool {current_tool}: {missing_args}. "
"Only provide a valid JSON response without any comments or metadata."
)
+
+def set_multi_goal_mode_if_unset(mode:bool)->None:
+ """
+ Set multi-mode (used to pass workflow)
+
+ Args:
+ None
+
+ Returns:
+ bool: True if in multi-goal mode, false if not
+ """
+ global MULTI_GOAL_MODE
+ if MULTI_GOAL_MODE is None:
+ MULTI_GOAL_MODE = mode
+
+def is_multi_goal_mode()-> bool:
+ """
+ Centralized logic for if we're in multi-goal mode.
+
+ Args:
+ None
+
+ Returns:
+ bool: True if in multi-goal mode, false if not
+ """
+ return MULTI_GOAL_MODE
+
+def generate_pick_new_goal_guidance()-> str:
+ """
+ Generates a prompt for guiding the LLM to pick a new goal or be done depending on multi-goal mode.
+
+ Args:
+ None
+
+ Returns:
+ str: A prompt string prompting the LLM to when to go to pick-new-goal
+ """
+ if is_multi_goal_mode():
+ return 'Next should only be "pick-new-goal" if all tools have been run (use the system prompt to figure that out) or the user explicitly requested to pick a new goal.'
+ else:
+ return 'Next should never be "pick-new-goal".'
+
+def generate_toolchain_complete_guidance() -> str:
+ """
+ Generates a prompt for guiding the LLM to handle the end of the toolchain.
+
+ Args:
+ None
+
+ Returns:
+ str: A prompt string prompting the LLM to prompt for a new goal, or be done
+ """
+ if is_multi_goal_mode():
+ return "If no more tools are needed (user_confirmed_tool_run has been run for all), set next='confirm' and tool='ListAgents'."
+ else :
+ return "If no more tools are needed (user_confirmed_tool_run has been run for all), set next='done' and tool=''."
\ No newline at end of file
diff --git a/scripts/run_worker.py b/scripts/run_worker.py
index 60aafbf..c0d2a81 100644
--- a/scripts/run_worker.py
+++ b/scripts/run_worker.py
@@ -62,7 +62,7 @@ async def main():
activities=[
activities.agent_validatePrompt,
activities.agent_toolPlanner,
- activities.get_env_bool,
+ activities.get_wf_env_vars,
dynamic_tool_activity,
],
activity_executor=activity_executor,
diff --git a/setup.md b/setup.md
index 4f6029a..a31df80 100644
--- a/setup.md
+++ b/setup.md
@@ -16,9 +16,12 @@ SHOW_CONFIRM=True
### Agent Goal Configuration
-The agent can be configured to pursue different goals using the `AGENT_GOAL` environment variable in your `.env` file.
+The agent can be configured to pursue different goals using the `AGENT_GOAL` environment variable in your `.env` file. If unset, default is `goal_choose_agent_type`.
-The agent can support multiple goals using goal categories using `GOAL_CATEGORIES` in your .env file. If unset, default is all.
+If the first goal is `goal_choose_agent_type` the agent will support multiple goals using goal categories defined by `GOAL_CATEGORIES` in your .env file. If unset, default is all.
+```bash
+GOAL_CATEGORIES=hr,travel-flights,travel-trains,fin
+```
See the section Goal-Specific Tool Configuration below for tool configuration for specific goals.
diff --git a/todo.md b/todo.md
index a1bf30a..f06d42b 100644
--- a/todo.md
+++ b/todo.md
@@ -1,11 +1,15 @@
# todo list
[ ] goal change management tweaks
- - [ ] maybe make the choose_Agent_goal tag not be system/not always included?
- - [ ] try taking out list-agents as a tool because agent_prompt_generators may do it for you
- - [ ] make goal selection not be a system tool but be an option in .env, see how that works, includes taking it out of the goal/toolset for all goals
+ - [x] maybe make the choose_Agent_goal tag not be system/not always included?
+ - [x] try taking out list-agents as a tool because agent_prompt_generators may do it for you
+ - [x] make goal selection not be a system tool but be an option in .env, see how that works, includes taking it out of the goal/toolset for all goals
+ - [x] test single-goal
+ - [x] test claude and grok
+ - [x] document in sample env and docs how to control
[ ] expand [tests](./tests/agent_goal_workflow_test.py)
-[ ] try claude-3-7-sonnet-20250219, see [tool_activities.py](./activities/tool_activities.py)
+[x] try claude-3-7-sonnet-20250219, see [tool_activities.py](./activities/tool_activities.py)
+[x] test Grok with changes
[ ] adding fintech goals
- Fraud Detection and Prevention - The AI monitors transactions across accounts, flagging suspicious activities (e.g., unusual spending patterns or login attempts) and autonomously freezing accounts or notifying customers and compliance teams.
diff --git a/tools/goal_registry.py b/tools/goal_registry.py
index 7042919..19f62fb 100644
--- a/tools/goal_registry.py
+++ b/tools/goal_registry.py
@@ -21,7 +21,7 @@ starter_prompt_generic = silly_prompt + "Welcome me, give me a description of wh
goal_choose_agent_type = AgentGoal(
id = "goal_choose_agent_type",
- category_tag="system",
+ category_tag="agent_selection",
agent_name="Choose Agent",
agent_friendly_description="Choose the type of agent to assist you today.",
tools=[
@@ -33,13 +33,13 @@ goal_choose_agent_type = AgentGoal(
"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 + "Begin by listing all details of all agents as provided by the output of the first tool included in this goal. ",
+ starter_prompt=starter_prompt_generic + " Begin by listing all details of all agents as provided by the output of the first tool included in this goal. ",
example_conversation_history="\n ".join(
[
"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. \n Which agent would you like to speak to (?",
+ "agent: The available agents are: 1. Event Flight Finder. \n Which agent would you like to speak to? (You can respond with name or number.)",
"user: 1, Event Flight Finder",
"user_confirmed_tool_run: ",
"tool_result: { 'new_goal': 'goal_event_flight_invoice' }",
@@ -61,7 +61,6 @@ goal_pirate_treasure = AgentGoal(
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: "
@@ -106,7 +105,6 @@ goal_match_train_invoice = AgentGoal(
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. "
@@ -153,7 +151,6 @@ goal_event_flight_invoice = AgentGoal(
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 faciliate 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 "
@@ -193,7 +190,6 @@ goal_hr_schedule_pto = AgentGoal(
tool_registry.current_pto_tool,
tool_registry.future_pto_calc_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="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 "
@@ -230,7 +226,6 @@ goal_hr_check_pto = AgentGoal(
agent_friendly_description="Check your available PTO.",
tools=[
tool_registry.current_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="The user wants to check their 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 ",
@@ -252,11 +247,10 @@ goal_hr_check_pto = AgentGoal(
goal_hr_check_paycheck_bank_integration_status = AgentGoal(
id = "goal_hr_check_paycheck_bank_integration_status",
category_tag="hr",
- agent_name="Check paycheck bank integration status",
- agent_friendly_description="Check your integration between paycheck payer and your financial institution.",
+ agent_name="Check paycheck deposit status",
+ agent_friendly_description="Check your integration between your employer and your financial institution.",
tools=[
tool_registry.paycheck_bank_integration_status_check,
- 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 check their bank integration used to deposit their paycheck. To assist with that goal, help the user gather args for these tools in order: "
"1. CheckPayBankStatus: Tell the user the status of their paycheck bank integration ",
@@ -283,7 +277,6 @@ goal_fin_check_account_balances = AgentGoal(
tools=[
tool_registry.financial_check_account_is_valid,
tool_registry.financial_get_account_balances,
- 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 check their account balances at the bank or financial institution. To assist with that goal, help the user gather args for these tools in order: "
"1. FinCheckAccountIsValid: validate the user's account is valid"
@@ -318,7 +311,6 @@ goal_fin_move_money = AgentGoal(
tool_registry.financial_check_account_is_valid,
tool_registry.financial_get_account_balances,
tool_registry.financial_move_money,
- 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 transfer money in their account at the bank or financial institution. To assist with that goal, help the user gather args for these tools in order: "
"1. FinCheckAccountIsValid: validate the user's account is valid"
@@ -333,7 +325,7 @@ goal_fin_move_money = AgentGoal(
"user_confirmed_tool_run: ",
"tool_result: { 'status': account valid }",
"agent: Great! Here are your account balances:",
- "user_confirmed_tool_run: ", #todo is this needed?
+ "user_confirmed_tool_run: ",
"tool_result: { 'name': Matt Murdock, 'email': matt.murdock@nelsonmurdock.com, 'account_id': 11235, 'checking_balance': 875.40, 'savings_balance': 3200.15, 'bitcoin_balance': 0.1378, 'account_creation_date': 2014-03-10 }",
"agent: Your account balances are as follows: \n "
"Checking: $875.40. \n "
@@ -348,7 +340,6 @@ goal_fin_move_money = AgentGoal(
),
)
-#todo add money movement, fraud check (update with start)
#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)
diff --git a/tools/list_agents.py b/tools/list_agents.py
index ba9aa20..d64fb20 100644
--- a/tools/list_agents.py
+++ b/tools/list_agents.py
@@ -10,6 +10,12 @@ def list_agents(args: dict) -> dict:
goal_categories_start.strip().lower() # handle extra spaces or non-lowercase
goal_categories = goal_categories_start.split(",")
+ # if multi-goal-mode, add agent_selection as a goal (defaults to True)
+ if "agent_selection" not in goal_categories :
+ first_goal_value = os.getenv("AGENT_GOAL")
+ if first_goal_value is None or first_goal_value.lower() == "goal_choose_agent_type":
+ goal_categories.append("agent_selection")
+
# always show goals labeled as "system," like the goal chooser
if "system" not in goal_categories:
goal_categories.append("system")
diff --git a/workflows/agent_goal_workflow.py b/workflows/agent_goal_workflow.py
index 660e3b5..083332b 100644
--- a/workflows/agent_goal_workflow.py
+++ b/workflows/agent_goal_workflow.py
@@ -1,12 +1,11 @@
from collections import deque
from datetime import timedelta
-import os
from typing import Dict, Any, Union, List, Optional, Deque, TypedDict
from temporalio.common import RetryPolicy
from temporalio import workflow
-from models.data_types import ConversationHistory, NextStep, ValidationInput, EnvLookupInput
+from models.data_types import ConversationHistory, EnvLookupOutput, NextStep, ValidationInput, EnvLookupInput
from models.tool_definitions import AgentGoal
from workflows.workflow_helpers import LLM_ACTIVITY_START_TO_CLOSE_TIMEOUT, \
LLM_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT
@@ -47,7 +46,8 @@ class AgentGoalWorkflow:
self.confirmed: bool = False # indicates that we have confirmation to proceed to run tool
self.tool_results: List[Dict[str, Any]] = []
self.goal: AgentGoal = {"tools": []}
- self.show_tool_args_confirmation: bool = True
+ self.show_tool_args_confirmation: bool = True # set from env file in activity lookup_wf_env_settings
+ self.multi_goal_mode: bool = False # set from env file in activity lookup_wf_env_settings
# see ../api/main.py#temporal_client.start_workflow() for how the input parameters are set
@workflow.run
@@ -125,7 +125,12 @@ class AgentGoalWorkflow:
continue
# If valid, proceed with generating the context and prompt
- context_instructions = generate_genai_prompt(self.goal, self.conversation_history, self.tool_data)
+ context_instructions = generate_genai_prompt(
+ agent_goal=self.goal,
+ conversation_history = self.conversation_history,
+ multi_goal_mode=self.multi_goal_mode,
+ raw_json=self.tool_data)
+
prompt_input = ToolPromptInput(prompt=prompt, context_instructions=context_instructions)
# connect to LLM and execute to get next steps
@@ -165,17 +170,21 @@ class AgentGoalWorkflow:
else:
self.confirmed = True
- # else if the next step is to pick a new goal...
+ # else if the next step is to pick a new goal, set the goal and tool to do it
elif next_step == "pick-new-goal":
workflow.logger.info("All steps completed. Resetting goal.")
self.change_goal("goal_choose_agent_type")
+ next_step = tool_data["next"] = "confirm"
+ current_tool = tool_data["tool"] = "ListAgents"
+ waiting_for_confirm = True
+ self.confirmed = True
# else if the next step is to be done with the conversation such as if the user requests it via asking to "end conversation"
elif next_step == "done":
self.add_message("agent", tool_data)
- #todo send conversation to AI for analysis
+ #here we could send conversation to AI for analysis
# end the workflow
return str(self.conversation_history)
@@ -266,12 +275,11 @@ class AgentGoalWorkflow:
)
def change_goal(self, goal: str) -> None:
- '''goalsLocal = {
- "goal_match_train_invoice": goal_match_train_invoice,
- "goal_event_flight_invoice": goal_event_flight_invoice,
- "goal_choose_agent_type": goal_choose_agent_type,
- }'''
-
+ """ Change the goal (usually on request of the user).
+
+ Args:
+ goal: goal to change to)
+ """
if goal is not None:
for listed_goal in goal_list:
if listed_goal.id == goal:
@@ -304,17 +312,21 @@ class AgentGoalWorkflow:
else:
return True
- # look up env settings as needed in activities so they're part of history
+ # look up env settings in an activity so they're part of history
async def lookup_wf_env_settings(self, combined_input: CombinedInput)->None:
- env_lookup_input = EnvLookupInput(env_var_name = "SHOW_CONFIRM", default = True)
- self.show_tool_args_confirmation = await workflow.execute_activity(
- ToolActivities.get_env_bool,
+ env_lookup_input = EnvLookupInput(
+ show_confirm_env_var_name = "SHOW_CONFIRM",
+ show_confirm_default = True)
+ env_output:EnvLookupOutput = await workflow.execute_activity(
+ ToolActivities.get_wf_env_vars,
env_lookup_input,
start_to_close_timeout=LLM_ACTIVITY_START_TO_CLOSE_TIMEOUT,
retry_policy=RetryPolicy(
initial_interval=timedelta(seconds=5), backoff_coefficient=1
),
)
+ self.show_tool_args_confirmation = env_output.show_confirm
+ self.multi_goal_mode = env_output.multi_goal_mode
# execute the tool - return False if we're not waiting for confirm anymore (always the case if it works successfully)
#