mirror of
https://github.com/temporal-community/temporal-ai-agent.git
synced 2026-03-15 14:08:08 +01:00
adding move money scenario - still a bit rough but it works
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import inspect
|
||||||
from temporalio import activity
|
from temporalio import activity
|
||||||
from ollama import chat, ChatResponse
|
from ollama import chat, ChatResponse
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
@@ -483,16 +484,21 @@ def get_current_date_human_readable():
|
|||||||
|
|
||||||
|
|
||||||
@activity.defn(dynamic=True)
|
@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
|
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_name = activity.info().activity_type # e.g. "FindEvents"
|
||||||
tool_args = activity.payload_converter().from_payload(args[0].payload, dict)
|
tool_args = activity.payload_converter().from_payload(args[0].payload, dict)
|
||||||
activity.logger.info(f"Running dynamic tool '{tool_name}' with args: {tool_args}")
|
activity.logger.info(f"Running dynamic tool '{tool_name}' with args: {tool_args}")
|
||||||
|
|
||||||
# Delegate to the relevant function
|
# Delegate to the relevant function
|
||||||
handler = get_handler(tool_name)
|
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
|
# Optionally log or augment the result
|
||||||
activity.logger.info(f"Tool '{tool_name}' result: {result}")
|
activity.logger.info(f"Tool '{tool_name}' result: {result}")
|
||||||
|
|||||||
27
todo.md
27
todo.md
@@ -1,34 +1,27 @@
|
|||||||
# todo list
|
# todo list
|
||||||
[x] add confirmation env setting to setup guide <br />
|
|
||||||
<br />
|
|
||||||
[ ] try claude-3-7-sonnet-20250219, see [tool_activities.py](./activities/tool_activities.py) <br />
|
[ ] try claude-3-7-sonnet-20250219, see [tool_activities.py](./activities/tool_activities.py) <br />
|
||||||
[ ] make agent respond to name of goals and not just numbers <br />
|
[x] make agent respond to name of goals and not just numbers <br />
|
||||||
[x] L look at slides <br />
|
|
||||||
[ ] josh to do fintech scenarios <br />
|
[ ] josh to do fintech scenarios <br />
|
||||||
[ ] expand [tests](./tests/agent_goal_workflow_test.py)<br />
|
[ ] expand [tests](./tests/agent_goal_workflow_test.py)<br />
|
||||||
[x] fix logging statements not to be all warn, maybe set logging level to info <br />
|
|
||||||
|
|
||||||
[x] create people management scenarios <br />
|
|
||||||
|
|
||||||
[ ] 2. Others HR goals: <br />
|
|
||||||
- book work travel <br />
|
|
||||||
- check insurance coverages <br />
|
|
||||||
- expense management <br />
|
|
||||||
- check in on the health of the team <br />
|
|
||||||
|
|
||||||
[ ] fintech goals <br />
|
[ ] fintech goals <br />
|
||||||
- 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.<br />
|
- 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.<br />
|
||||||
- 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.<br />
|
- 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.<br />
|
||||||
- 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).<br />
|
- 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).<br />
|
||||||
- money movement - start money transfer <br />
|
[x] money movement - start money transfer <br />
|
||||||
- [x] account balance - <br />
|
[ ] todo use env vars to do connect to local or non-local
|
||||||
|
[x] account balance - <br />
|
||||||
|
[ ] new loan/fraud check/update with start <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 <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 <br />
|
||||||
- Insight into the agent’s performance <br />
|
- Insight into the agent’s performance <br />
|
||||||
|
|
||||||
[ ] non-retry the api key error - "Invalid API Key provided: sk_test_**J..." and "AuthenticationError" <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 />
|
|
||||||
|
|
||||||
[ ] add visual feedback when workflow starting <br />
|
[ ] add visual feedback when workflow starting <br />
|
||||||
[ ] figure out how to allow user to list agents at any time - like end conversation <br />
|
[ ] figure out how to allow user to list agents at any time - like end conversation <br />
|
||||||
|
|
||||||
|
[ ] 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
|
||||||
@@ -15,6 +15,7 @@ from .hr.checkpaybankstatus import checkpaybankstatus
|
|||||||
|
|
||||||
from .fin.check_account_valid import check_account_valid
|
from .fin.check_account_valid import check_account_valid
|
||||||
from .fin.get_account_balances import get_account_balance
|
from .fin.get_account_balances import get_account_balance
|
||||||
|
from .fin.move_money import move_money
|
||||||
|
|
||||||
from .give_hint import give_hint
|
from .give_hint import give_hint
|
||||||
from .guess_location import guess_location
|
from .guess_location import guess_location
|
||||||
@@ -51,6 +52,8 @@ def get_handler(tool_name: str):
|
|||||||
return check_account_valid
|
return check_account_valid
|
||||||
if tool_name == "FinCheckAccountBalance":
|
if tool_name == "FinCheckAccountBalance":
|
||||||
return get_account_balance
|
return get_account_balance
|
||||||
|
if tool_name == "FinMoveMoneyOrder":
|
||||||
|
return move_money
|
||||||
if tool_name == "GiveHint":
|
if tool_name == "GiveHint":
|
||||||
return give_hint
|
return give_hint
|
||||||
if tool_name == "GuessLocation":
|
if tool_name == "GuessLocation":
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ def get_account_balance(args: dict) -> dict:
|
|||||||
|
|
||||||
for account in account_list:
|
for account in account_list:
|
||||||
if account["email"] == account_key or account["account_id"] == account_key:
|
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{ "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}
|
return {"error": return_msg}
|
||||||
122
tools/fin/move_money.py
Normal file
122
tools/fin/move_money.py
Normal file
@@ -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
|
||||||
@@ -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: <user clicks confirm on FincheckAccountIsValid tool>",
|
||||||
|
"tool_result: { 'status': account valid }",
|
||||||
|
"agent: Great! Here are your account balances:",
|
||||||
|
"user_confirmed_tool_run: <user clicks confirm on FinCheckAccountBalance tool>", #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: <user clicks confirm on FinMoveMoney tool>",
|
||||||
|
"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)
|
#todo add money movement, fraud check (update with start)
|
||||||
#Add the goals to a list for more generic processing, like listing available agents
|
#Add the goals to a list for more generic processing, like listing available agents
|
||||||
goal_list: List[AgentGoal] = []
|
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_pto)
|
||||||
goal_list.append(goal_hr_check_paycheck_bank_integration_status)
|
goal_list.append(goal_hr_check_paycheck_bank_integration_status)
|
||||||
goal_list.append(goal_fin_check_account_balances)
|
goal_list.append(goal_fin_check_account_balances)
|
||||||
|
goal_list.append(goal_fin_move_money)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -285,4 +285,34 @@ financial_get_account_balances = ToolDefinition(
|
|||||||
description="email address or account ID of user",
|
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",
|
||||||
|
),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
Reference in New Issue
Block a user