diff --git a/activities/tool_activities.py b/activities/tool_activities.py
index 39cc228..1250a42 100644
--- a/activities/tool_activities.py
+++ b/activities/tool_activities.py
@@ -1,3 +1,4 @@
+import inspect
from temporalio import activity
from ollama import chat, ChatResponse
from openai import OpenAI
@@ -483,16 +484,21 @@ def get_current_date_human_readable():
@activity.defn(dynamic=True)
-def dynamic_tool_activity(args: Sequence[RawValue]) -> dict:
+async def dynamic_tool_activity(args: Sequence[RawValue]) -> dict:
from tools import get_handler
+ # if current_tool == "move_money":
+ # workflow.logger.warning(f"trying for move_money direct")
tool_name = activity.info().activity_type # e.g. "FindEvents"
tool_args = activity.payload_converter().from_payload(args[0].payload, dict)
activity.logger.info(f"Running dynamic tool '{tool_name}' with args: {tool_args}")
# Delegate to the relevant function
handler = get_handler(tool_name)
- result = handler(tool_args)
+ if inspect.iscoroutinefunction(handler):
+ result = await handler(tool_args)
+ else:
+ result = handler(tool_args)
# Optionally log or augment the result
activity.logger.info(f"Tool '{tool_name}' result: {result}")
diff --git a/todo.md b/todo.md
index 44653c0..6319155 100644
--- a/todo.md
+++ b/todo.md
@@ -1,34 +1,27 @@
# todo list
-[x] add confirmation env setting to setup guide
-
[ ] try claude-3-7-sonnet-20250219, see [tool_activities.py](./activities/tool_activities.py)
-[ ] make agent respond to name of goals and not just numbers
-[x] L look at slides
+[x] make agent respond to name of goals and not just numbers
[ ] josh to do fintech scenarios
[ ] expand [tests](./tests/agent_goal_workflow_test.py)
-[x] fix logging statements not to be all warn, maybe set logging level to info
-
-[x] create people management scenarios
-
-[ ] 2. Others HR goals:
-- book work travel
-- check insurance coverages
-- expense management
-- check in on the health of the team
[ ] 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.
- Personalized Financial Advice - An AI agent analyzes a customer’s financial data (e.g., income, spending habits, savings, investments) and provides tailored advice, such as budgeting tips, investment options, or debt repayment strategies.
- Portfolio Management and Rebalancing - The AI monitors a customer’s investment portfolio, rebalancing it automatically based on market trends, risk tolerance, and financial goals (e.g., shifting assets between stocks, bonds, or crypto).
-- money movement - start money transfer
-- [x] account balance -
+[x] money movement - start money transfer
+[ ] todo use env vars to do connect to local or non-local
+[x] account balance -
+[ ] new loan/fraud check/update with start
[ ] 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
[ ] non-retry the api key error - "Invalid API Key provided: sk_test_**J..." and "AuthenticationError"
-[ ] make it so you can yeet yourself out of a goal and pick a new one
[ ] add visual feedback when workflow starting
-[ ] figure out how to allow user to list agents at any time - like end conversation
\ No newline at end of file
+[ ] figure out how to allow user to list agents at any time - like end conversation
+
+[ ] change initial goal selection prompt to list capabilities and prompt more nicely - not a bulleted list - see how that works
+
+[ ] todo use env vars to do connect to local or non-local cloud for activities for money scenarios
\ No newline at end of file
diff --git a/tools/__init__.py b/tools/__init__.py
index 0dd10c0..61d3545 100644
--- a/tools/__init__.py
+++ b/tools/__init__.py
@@ -15,6 +15,7 @@ from .hr.checkpaybankstatus import checkpaybankstatus
from .fin.check_account_valid import check_account_valid
from .fin.get_account_balances import get_account_balance
+from .fin.move_money import move_money
from .give_hint import give_hint
from .guess_location import guess_location
@@ -51,6 +52,8 @@ def get_handler(tool_name: str):
return check_account_valid
if tool_name == "FinCheckAccountBalance":
return get_account_balance
+ if tool_name == "FinMoveMoneyOrder":
+ return move_money
if tool_name == "GiveHint":
return give_hint
if tool_name == "GuessLocation":
diff --git a/tools/fin/get_account_balances.py b/tools/fin/get_account_balances.py
index ca44fb6..be0854c 100644
--- a/tools/fin/get_account_balances.py
+++ b/tools/fin/get_account_balances.py
@@ -17,8 +17,7 @@ def get_account_balance(args: dict) -> dict:
for account in account_list:
if account["email"] == account_key or account["account_id"] == account_key:
- #return{"status": "account valid"}
return{ "name": account["name"], "email": account["email"], "account_id": account["account_id"], "checking_balance": account["checking_balance"], "savings_balance": account["savings_balance"], "bitcoin_balance": account["bitcoin_balance"], "account_creation_date": account["account_creation_date"] }
- return_msg = "Account not found with email address " + email + " or account ID: " + account_id
+ return_msg = "Account not found with for " + account_key
return {"error": return_msg}
\ No newline at end of file
diff --git a/tools/fin/move_money.py b/tools/fin/move_money.py
new file mode 100644
index 0000000..d33119e
--- /dev/null
+++ b/tools/fin/move_money.py
@@ -0,0 +1,122 @@
+from pathlib import Path
+import json
+from temporalio.client import Client
+from dataclasses import dataclass
+from typing import Optional
+import asyncio
+from temporalio.exceptions import WorkflowAlreadyStartedError
+
+from enum import Enum, auto
+
+#enums for the java enum
+# class ExecutionScenarios(Enum):
+# HAPPY_PATH = 0
+# ADVANCED_VISIBILITY = auto() # 1
+# HUMAN_IN_LOOP = auto() # 2
+# API_DOWNTIME = auto() # 3
+# BUG_IN_WORKFLOW = auto() # 4
+# INVALID_ACCOUNT = auto() # 5
+
+# these dataclasses are for calling the Temporal Workflow
+# Python equivalent of the workflow we're calling's Java WorkflowParameterObj
+@dataclass
+class MoneyMovementWorkflowParameterObj:
+ amount: int # Using snake_case as per Python conventions
+ scenario: str
+
+# this is made to demonstrate functionality but it could just as durably be an API call
+# this assumes it's a valid account - use check_account_valid() to verify that first
+async def move_money(args: dict) -> dict:
+
+ print("in move_money")
+ account_key = args.get("accountkey")
+ account_type: str = args.get("accounttype")
+ amount = args.get("amount")
+ destinationaccount = args.get("destinationaccount")
+
+ file_path = Path(__file__).resolve().parent.parent / "data" / "customer_account_data.json"
+ if not file_path.exists():
+ return {"error": "Data file not found."}
+
+ # todo validate there's enough money in the account
+ with open(file_path, "r") as file:
+ data = json.load(file)
+ account_list = data["accounts"]
+
+ for account in account_list:
+ if account["email"] == account_key or account["account_id"] == account_key:
+ amount_str: str = str(amount)
+
+ transfer_workflow_id = await start_workflow(amount_cents=str_dollars_to_cents(amount_str),from_account_name=account_key, to_account_name=destinationaccount)
+
+ account_type_key = 'checking_balance'
+ if(account_type.casefold() == "checking" ):
+ account_type = "checking"
+ account_type_key = 'checking_balance'
+
+ elif(account_type.casefold() == "savings" ):
+ account_type = "savings"
+ account_type_key = 'savings_balance'
+ else:
+ raise NotImplementedError("money order for account types other than checking or savings is not implemented.")
+
+ new_balance: float = float(str_dollars_to_cents(str(account[account_type_key])))
+ new_balance = new_balance - float(str_dollars_to_cents(amount_str))
+ account[account_type_key] = str(new_balance / 100 ) #to dollars
+ with open(file_path, 'w') as file:
+ json.dump(data, file, indent=4)
+
+ return {'status': "money movement complete", 'confirmation id': transfer_workflow_id, 'new_balance': account["checking_balance"]}
+
+ return_msg = "Account not found with for " + account_key
+ return {"error": return_msg}
+
+# Async function to start workflow
+async def start_workflow(amount_cents: int, from_account_name: str, to_account_name: str)-> str:
+
+
+ # Connect to Temporal
+ # todo use env vars to do connect to local or non-local
+ client:Client = await Client.connect("localhost:7233")
+
+ # Create the parameter object
+ params = MoneyMovementWorkflowParameterObj(
+ amount=amount_cents*100,
+ scenario="HAPPY_PATH"
+ )
+
+ workflow_id="TRANSFER-ACCT-" + from_account_name + "-TO-" + to_account_name # business-relevant workflow ID
+
+ try:
+ handle = await client.start_workflow(
+ "moneyTransferWorkflow", # Workflow name
+ params, # Workflow parameters
+ id=workflow_id,
+ task_queue="MoneyTransferJava" # Task queue name
+ )
+ return handle.id
+ except WorkflowAlreadyStartedError as e:
+ existing_handle = client.get_workflow_handle(workflow_id=workflow_id)
+ return existing_handle.id
+
+
+
+
+#cleans a string dollar amount description to cents value
+def str_dollars_to_cents(dollar_str: str) -> int:
+ try:
+ # Remove '$' and any whitespace
+ cleaned_str = dollar_str.replace('$', '').strip()
+
+ # Handle empty string or invalid input
+ if not cleaned_str:
+ raise ValueError("Empty amount provided")
+
+ # Convert to float and then to cents
+ amount = float(cleaned_str)
+ if amount < 0:
+ raise ValueError("Negative amounts not allowed")
+
+ return int(amount * 100)
+ except ValueError as e:
+ raise ValueError(f"Invalid dollar amount format: {dollar_str}") from e
\ No newline at end of file
diff --git a/tools/goal_registry.py b/tools/goal_registry.py
index 343bb32..d422749 100644
--- a/tools/goal_registry.py
+++ b/tools/goal_registry.py
@@ -308,6 +308,46 @@ goal_fin_check_account_balances = AgentGoal(
),
)
+# this tool checks account balances, and uses ./data/customer_account_data.json as dummy data
+goal_fin_move_money = AgentGoal(
+ id = "goal_fin_move_money",
+ category_tag="fin",
+ agent_name="Money Order",
+ agent_friendly_description="Initiate a money movement order.",
+ tools=[
+ 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"
+ "2. FinCheckAccountBalance: Tell the user their account balance at the bank or financial institution"
+ "3. FinMoveMoney: Initiate a money movement order",
+ starter_prompt=starter_prompt_generic,
+ example_conversation_history="\n ".join(
+ [
+ "user: I'd like transfer some money",
+ "agent: Sure! I can help you out with that. May I have account number and email address?",
+ "user: account number is 11235813",
+ "user_confirmed_tool_run: ",
+ "tool_result: { 'status': account valid }",
+ "agent: Great! Here are your account balances:",
+ "user_confirmed_tool_run: ", #todo is this needed?
+ "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 "
+ "Savings: $3200.15. \n "
+ "Bitcoint: 0.1378 \n "
+ "agent: how much would you like to move, from which account type, and to which account number?",
+ "user: I'd like to move $500 from savings to account number #56789",
+ "user_confirmed_tool_run: ",
+ "tool_result: { 'status': money movement complete, 'confirmation id': 333421, 'new_balance': $2700.15 }",
+ "agent: Money movement order completed! New account balance: $2700.15. Your confirmation id is 333421. "
+ ]
+ ),
+)
+
#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] = []
@@ -319,5 +359,7 @@ goal_list.append(goal_hr_schedule_pto)
goal_list.append(goal_hr_check_pto)
goal_list.append(goal_hr_check_paycheck_bank_integration_status)
goal_list.append(goal_fin_check_account_balances)
+goal_list.append(goal_fin_move_money)
+
diff --git a/tools/tool_registry.py b/tools/tool_registry.py
index 3930cc6..ffbafb4 100644
--- a/tools/tool_registry.py
+++ b/tools/tool_registry.py
@@ -285,4 +285,34 @@ financial_get_account_balances = ToolDefinition(
description="email address or account ID of user",
),
],
+)
+
+financial_move_money = ToolDefinition(
+ name="FinMoveMoneyOrder",
+ description="Execute a money movement order. "
+ "Returns the status of the order and the account balance of the account money was moved from. ",
+
+ arguments=[
+ ToolArgument(
+ name="accountkey",
+ type="string",
+ description="email address or account ID of user",
+ ),
+ ToolArgument(
+ name="accounttype",
+ type="string",
+ description="account type, such as checking or savings",
+ ),
+ ToolArgument(
+ name="amount",
+ type="string",
+ description="amount to move in the order",
+ ),
+
+ ToolArgument(
+ name="destinationaccount",
+ type="string",
+ description="account number to move the money to",
+ ),
+ ],
)
\ No newline at end of file