diff --git a/.gitignore b/.gitignore
index 2d708e1..2fe1958 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,4 +31,6 @@ coverage.xml
# PyCharm / IntelliJ settings
.idea/
-.env
\ No newline at end of file
+.env
+.env*
+
diff --git a/README.md b/README.md
index 6a0d212..8a97fc7 100644
--- a/README.md
+++ b/README.md
@@ -47,3 +47,5 @@ See [the guide to adding goals and tools](./adding-goals-and-tools.md) for more
## For Temporal SAs
Check out the [slides](https://docs.google.com/presentation/d/1wUFY4v17vrtv8llreKEBDPLRtZte3FixxBUn0uWy5NU/edit#slide=id.g3333e5deaa9_0_0) here and the enablement guide here (TODO).
+
+
diff --git a/adding-goals-and-tools.md b/adding-goals-and-tools.md
index 910b39d..6ec806a 100644
--- a/adding-goals-and-tools.md
+++ b/adding-goals-and-tools.md
@@ -4,9 +4,10 @@ The agent is set up to allow for multiple goals and to switch back to choosing a
It may be helpful to review the [architecture](./architecture.md) for a guide and definition of goals, tools, etc.
## Adding a New Goal Category
-Goal Categories lets you pick which groups of goals to show. Set via an .env setting, GOAL_CATEGORIES.
+Goal Categories lets you pick which groups of goals to show. Set via an .env setting, `GOAL_CATEGORIES`.
+Even if you don't intend to use the goal in a multi-goal scenario, goal categories are useful for others.
1. Pick a unique one that has some business meaning
-2. Use it in your .env file
+2. Use it in your [.env](./.env) file
3. Add to [.env.example](./.env.example)
4. Use it in your Goal definition, see below.
@@ -35,7 +36,7 @@ tools=[
## Adding Tools
-### Optional Tools
+### Note on Optional Tools
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:
```
@@ -84,4 +85,15 @@ There are three ways to manage confirmation of tool runs:
),
```
If you really want to wait for user confirmation, record it on the workflow (as a Signal) and not rely on the LLM to probably get it, use option #3.
-I recommend exploring all three. For a demo, I would decide if you want the Arguments confirmation in the UI, and if not I'd generally go with option #2 but use #3 for tools that make business sense to confirm, e.g. those tools that take action/write data.
\ No newline at end of file
+I recommend exploring all three. For a demo, I would decide if you want the Arguments confirmation in the UI, and if not I'd generally go with option #2 but use #3 for tools that make business sense to confirm, e.g. those tools that take action/write data.
+
+## Add a Goal & Tools Checklist
+[ ] Add goal in [/tools/goal_registry.py](tools/goal_registry.py)
+- [ ] If a new category, add Goal Category to [.env](./.env) and [.env.example](./.env.example)
+- [ ] don't forget the goal list at the bottom of the [goal_registry.py](tools/goal_registry.py)
+
+[ ] Add Tools listed in the Goal Registry to the [tool_registry.py](tools/tool_registry.py)
+[ ] Define your tools as Activities in `/tools`
+[ ] Add your tools to [tool list](tools/__init__.py) in the tool get_handler()
+
+And that's it! Happy AI Agent building!
diff --git a/enterprise/Activities/TrainActivities.cs b/enterprise/Activities/TrainActivities.cs
index 4f71b33..bf1b2b5 100644
--- a/enterprise/Activities/TrainActivities.cs
+++ b/enterprise/Activities/TrainActivities.cs
@@ -2,6 +2,7 @@ using System.Net.Http.Json;
using System.Text.Json;
using Temporalio.Activities;
using TrainSearchWorker.Models;
+using Microsoft.Extensions.Logging;
namespace TrainSearchWorker.Activities;
@@ -23,6 +24,7 @@ public class TrainActivities
[Activity]
public async Task SearchTrains(SearchTrainsRequest request)
{
+ ActivityExecutionContext.Current.Logger.LogInformation($"SearchTrains from {request.From} to {request.To}");
var response = await _client.GetAsync(
$"api/search?from={Uri.EscapeDataString(request.From)}" +
$"&to={Uri.EscapeDataString(request.To)}" +
@@ -30,17 +32,21 @@ public class TrainActivities
$"&return_time={Uri.EscapeDataString(request.ReturnTime)}");
response.EnsureSuccessStatusCode();
-
+
// Deserialize into JourneyResponse rather than List
var journeyResponse = await response.Content.ReadFromJsonAsync(_jsonOptions)
?? throw new InvalidOperationException("Received null response from API");
+ ActivityExecutionContext.Current.Logger.LogInformation("SearchTrains completed");
+
return journeyResponse;
}
[Activity]
public async Task BookTrains(BookTrainsRequest request)
{
+ ActivityExecutionContext.Current.Logger.LogInformation($"Booking trains with IDs: {request.TrainIds}");
+
// Build the URL using the train IDs from the request
var url = $"api/book/{Uri.EscapeDataString(request.TrainIds)}";
@@ -52,6 +58,8 @@ public class TrainActivities
var bookingResponse = await response.Content.ReadFromJsonAsync(_jsonOptions)
?? throw new InvalidOperationException("Received null response from API");
+ ActivityExecutionContext.Current.Logger.LogInformation("BookTrains completed");
+
return bookingResponse;
}
diff --git a/enterprise/Program.cs b/enterprise/Program.cs
index c8fbaa5..098e3e0 100644
--- a/enterprise/Program.cs
+++ b/enterprise/Program.cs
@@ -2,10 +2,19 @@ using Microsoft.Extensions.DependencyInjection;
using Temporalio.Client;
using Temporalio.Worker;
using TrainSearchWorker.Activities;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Console;
// Set up dependency injection
var services = new ServiceCollection();
+var loggerFactory = LoggerFactory.Create(builder =>
+{
+ builder
+ .AddSimpleConsole(options => options.TimestampFormat = "[HH:mm:ss] ")
+ .SetMinimumLevel(LogLevel.Information);
+});
+
// Add HTTP client
services.AddHttpClient("TrainApi", client =>
{
@@ -31,7 +40,10 @@ Console.WriteLine($"Connecting to Temporal at address: {address}");
Console.WriteLine($"Using namespace: {ns}");
// Create worker options
-var options = new TemporalWorkerOptions("agent-task-queue-legacy");
+var options = new TemporalWorkerOptions("agent-task-queue-legacy")
+{
+ LoggerFactory = loggerFactory
+};
// Register activities
var activities = serviceProvider.GetRequiredService();
diff --git a/enterprise/TrainSearchWorker.csproj b/enterprise/TrainSearchWorker.csproj
index e18bff4..b4eec41 100644
--- a/enterprise/TrainSearchWorker.csproj
+++ b/enterprise/TrainSearchWorker.csproj
@@ -7,6 +7,7 @@
+
diff --git a/pyproject.toml b/pyproject.toml
index 52bbd43..6f3c4f9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,5 +1,5 @@
[tool.poetry]
-name = "temporal-AI-agent"
+name = "temporal_AI_agent"
version = "0.1.0"
description = "Temporal AI Agent"
license = "MIT"
@@ -13,7 +13,13 @@ packages = [
]
[tool.poetry.urls]
-"Bug Tracker" = "https://github.com/temporalio/samples-python/issues"
+"Bug Tracker" = "https://github.com/temporal-community/temporal-ai-agent/issues"
+
+[tool.poe.tasks]
+format = [{cmd = "black ."}, {cmd = "isort ."}]
+lint = [{cmd = "black --check ."}, {cmd = "isort --check-only ."}, {ref = "lint-types" }]
+lint-types = "mypy --check-untyped-defs --namespace-packages ."
+test = "pytest"
[tool.poetry.dependencies]
python = ">=3.10,<4.0"
@@ -37,9 +43,16 @@ gtfs-kit = "^10.1.1"
[tool.poetry.group.dev.dependencies]
pytest = "^7.3"
+pytest-asyncio = "^0.18.3"
black = "^23.7"
isort = "^5.12"
[build-system]
requires = ["poetry-core>=1.4.0"]
-build-backend = "poetry.core.masonry.api"
\ No newline at end of file
+build-backend = "poetry.core.masonry.api"
+
+[tool.pytest.ini_options]
+asyncio_mode = "auto"
+log_cli = true
+log_cli_level = "INFO"
+log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
\ No newline at end of file
diff --git a/scripts/find_events_test.py b/scripts/find_events_test.py
index fa8f613..6e75c9d 100644
--- a/scripts/find_events_test.py
+++ b/scripts/find_events_test.py
@@ -1,8 +1,8 @@
-from tools.search_events import find_events
+from tools.search_flights import search_flights
import json
# Example usage
if __name__ == "__main__":
search_args = {"city": "Sydney", "month": "July"}
- results = find_events(search_args)
+ results = search_flights(search_args)
print(json.dumps(results, indent=2))
diff --git a/setup.md b/setup.md
index a31df80..6a41558 100644
--- a/setup.md
+++ b/setup.md
@@ -192,7 +192,7 @@ dotnet run
```
If you're running your train API above on a different host/port then change the API URL in `Program.cs`. Otherwise, be sure to run it using `python thirdparty/train_api.py`.
-#### Goals: FIN/Money Movement
+#### Goals: FIN - Money Movement and Loan Application
Make sure you have the mock users you want (such as yourself) in [the account mock data file](./tools/data/customer_account_data.json).
- `AGENT_GOAL=goal_fin_move_money` - This scenario _can_ initiate a secondary workflow to move money. Check out [this repo](https://github.com/temporal-sa/temporal-money-transfer-java) - you'll need to get the worker running and connected to the same account as the agentic worker.
@@ -200,6 +200,12 @@ By default it will _not_ make a real workflow, it'll just fake it. If you get th
```bash
FIN_START_REAL_WORKFLOW=FALSE #set this to true to start a real workflow
```
+- `AGENT_GOAL=goal_fin_loan_application` - This scenario _can_ initiate a secondary workflow to apply for a loan. Check out [this repo](https://github.com/temporal-sa/temporal-latency-optimization-scenarios) - you'll need to get the worker running and connected to the same account as the agentic worker.
+By default it will _not_ make a real workflow, it'll just fake it. If you get the worker running and want to start a workflow, in your [.env](./.env):
+```bash
+FIN_START_REAL_WORKFLOW=FALSE #set this to true to start a real workflow
+```
+
#### Goals: HR/PTO
Make sure you have the mock users you want in (such as yourself) in [the PTO mock data file](./tools/data/employee_pto_data.json).
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/agent_goal_workflow_test.py b/tests/agent_goal_workflow_test.py
deleted file mode 100644
index c94206b..0000000
--- a/tests/agent_goal_workflow_test.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import asyncio
-
-from temporalio.client import Client, WorkflowExecutionStatus
-from temporalio.worker import Worker
-from temporalio.testing import TestWorkflowEnvironment
-from api.main import get_initial_agent_goal
-from models.data_types import AgentGoalWorkflowParams, CombinedInput
-from workflows.agent_goal_workflow import AgentGoalWorkflow
-from activities.tool_activities import ToolActivities, dynamic_tool_activity
-
-
-async def asyncSetUp(self):
- # Set up the test environment
- self.env = await TestWorkflowEnvironment.create_local()
-
-async def asyncTearDown(self):
- # Clean up after tests
- await self.env.shutdown()
-
-async def test_flight_booking(client: Client):
-
- task_queue_name = "agent-ai-workflow"
- workflow_id = "agent-workflow"
-
- initial_agent_goal = get_initial_agent_goal()
-
- # Create combined input
- combined_input = CombinedInput(
- tool_params=AgentGoalWorkflowParams(None, None),
- agent_goal=initial_agent_goal,
- )
-
- workflow_id = "agent-workflow"
- async with Worker(client, task_queue=task_queue_name, workflows=[AgentGoalWorkflow], activities=[ToolActivities.agent_validatePrompt, ToolActivities.agent_toolPlanner, dynamic_tool_activity]):
-
- # todo set goal categories for scenarios
- handle = await client.start_workflow(
- AgentGoalWorkflow.run, id=workflow_id, task_queue=task_queue_name
- )
- # todo send signals based on
- await handle.signal(AgentGoalWorkflow.user_prompt, "book flights")
- await handle.signal(AgentGoalWorkflow.user_prompt, "sydney in september")
- assert WorkflowExecutionStatus.RUNNING == (await handle.describe()).status
-
-
- #assert ["Hello, user1", "Hello, user2"] == await handle.result()
- await handle.signal(AgentGoalWorkflow.user_prompt, "I'm all set, end conversation")
- assert WorkflowExecutionStatus.COMPLETED == (await handle.describe()).status
-
-
-
-
-
\ No newline at end of file
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..95294fb
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,55 @@
+import asyncio
+import multiprocessing
+import sys
+from typing import AsyncGenerator
+
+import pytest
+import pytest_asyncio
+from temporalio.client import Client
+from temporalio.testing import WorkflowEnvironment
+
+# Due to https://github.com/python/cpython/issues/77906, multiprocessing on
+# macOS starting with Python 3.8 has changed from "fork" to "spawn". For
+# pre-3.8, we are changing it for them.
+if sys.version_info < (3, 8) and sys.platform.startswith("darwin"):
+ multiprocessing.set_start_method("spawn", True)
+
+
+def pytest_addoption(parser):
+ parser.addoption(
+ "--workflow-environment",
+ default="local",
+ help="Which workflow environment to use ('local', 'time-skipping', or target to existing server)",
+ )
+
+
+@pytest.fixture(scope="session")
+def event_loop():
+ # See https://github.com/pytest-dev/pytest-asyncio/issues/68
+ # See https://github.com/pytest-dev/pytest-asyncio/issues/257
+ # Also need ProactorEventLoop on older versions of Python with Windows so
+ # that asyncio subprocess works properly
+ if sys.version_info < (3, 8) and sys.platform == "win32":
+ loop = asyncio.ProactorEventLoop()
+ else:
+ loop = asyncio.get_event_loop_policy().new_event_loop()
+ yield loop
+ loop.close()
+
+
+@pytest_asyncio.fixture(scope="session")
+async def env(request) -> AsyncGenerator[WorkflowEnvironment, None]:
+ env_type = request.config.getoption("--workflow-environment")
+ if env_type == "local":
+ env = await WorkflowEnvironment.start_local()
+ elif env_type == "time-skipping":
+ env = await WorkflowEnvironment.start_time_skipping()
+ else:
+ env = WorkflowEnvironment.from_client(await Client.connect(env_type))
+ yield env
+ await env.shutdown()
+
+
+@pytest_asyncio.fixture
+async def client(env: WorkflowEnvironment) -> Client:
+ return env.client
diff --git a/tests/workflowtests/agent_goal_workflow_test.py b/tests/workflowtests/agent_goal_workflow_test.py
new file mode 100644
index 0000000..ab67478
--- /dev/null
+++ b/tests/workflowtests/agent_goal_workflow_test.py
@@ -0,0 +1,80 @@
+from temporalio.client import Client, WorkflowExecutionStatus
+from temporalio.worker import Worker
+import concurrent.futures
+from temporalio.testing import WorkflowEnvironment
+from api.main import get_initial_agent_goal
+from models.data_types import AgentGoalWorkflowParams, CombinedInput
+from workflows.agent_goal_workflow import AgentGoalWorkflow
+from activities.tool_activities import ToolActivities, dynamic_tool_activity
+from unittest.mock import patch
+from dotenv import load_dotenv
+import os
+from contextlib import contextmanager
+
+
+@contextmanager
+def my_context():
+ print("Setup")
+ yield "some_value" # Value assigned to 'as' variable
+ print("Cleanup")
+
+
+
+async def test_flight_booking(client: Client):
+
+ #load_dotenv("test_flights_single.env")
+
+ with my_context() as value:
+ print(f"Working with {value}")
+
+
+ # Create the test environment
+ #env = await WorkflowEnvironment.start_local()
+ #client = env.client
+ task_queue_name = "agent-ai-workflow"
+ workflow_id = "agent-workflow"
+
+ with concurrent.futures.ThreadPoolExecutor(max_workers=100) as activity_executor:
+ worker = Worker(
+ client,
+ task_queue=task_queue_name,
+ workflows=[AgentGoalWorkflow],
+ activities=[ToolActivities.agent_validatePrompt, ToolActivities.agent_toolPlanner, ToolActivities.get_wf_env_vars, dynamic_tool_activity],
+ activity_executor=activity_executor,
+ )
+
+ async with worker:
+ initial_agent_goal = get_initial_agent_goal()
+ # Create combined input
+ combined_input = CombinedInput(
+ tool_params=AgentGoalWorkflowParams(None, None),
+ agent_goal=initial_agent_goal,
+ )
+
+ prompt="Hello!"
+
+ #async with Worker(client, task_queue=task_queue_name, workflows=[AgentGoalWorkflow], activities=[ToolActivities.agent_validatePrompt, ToolActivities.agent_toolPlanner, dynamic_tool_activity]):
+
+ # todo set goal categories for scenarios
+ handle = await client.start_workflow(
+ AgentGoalWorkflow.run,
+ combined_input,
+ id=workflow_id,
+ task_queue=task_queue_name,
+ start_signal="user_prompt",
+ start_signal_args=[prompt],
+ )
+ # todo send signals to simulate user input
+ # await handle.signal(AgentGoalWorkflow.user_prompt, "book flights") # for multi-goal
+ await handle.signal(AgentGoalWorkflow.user_prompt, "sydney in september")
+ assert WorkflowExecutionStatus.RUNNING == (await handle.describe()).status
+
+
+ #assert ["Hello, user1", "Hello, user2"] == await handle.result()
+ await handle.signal(AgentGoalWorkflow.user_prompt, "I'm all set, end conversation")
+
+ #assert WorkflowExecutionStatus.COMPLETED == (await handle.describe()).status
+
+ result = await handle.result()
+ #todo dump workflow history for analysis optional
+ #todo assert result is good
\ No newline at end of file
diff --git a/todo.md b/todo.md
index f06d42b..00ad1d0 100644
--- a/todo.md
+++ b/todo.md
@@ -1,15 +1,5 @@
# todo list
-[ ] goal change management tweaks
- - [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)
-[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.
@@ -17,7 +7,21 @@
- 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).
[ ] new loan/fraud check/update with start
+[ ] financial advise - args being freeform customer input about their financial situation, goals
+ [ ] tool is maybe a new tool asking the LLM to advise
+[ ] for demo simulate failure - add utilities/simulated failures from pipeline demo
+
+[ ] ecommerce goals
+- [ ] add to docs
+- [ ] decide about api key names with Laine
+
+[ ] LLM failure->autoswitch:
+ - detect failure in the activity using failurecount
+ - activity switches to secondary LLM defined in .env
+ - activity reports switch to workflow
+
+[ ] for demo simulate failure - add utilities/simulated failures from pipeline demo
[ ] 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
diff --git a/tools/__init__.py b/tools/__init__.py
index 4bb37b3..6b7ab44 100644
--- a/tools/__init__.py
+++ b/tools/__init__.py
@@ -16,6 +16,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 .fin.submit_loan_application import submit_loan_application
from .ecommerce.get_order import get_order
from .ecommerce.track_package import track_package
@@ -57,7 +58,9 @@ def get_handler(tool_name: str):
if tool_name == "FinCheckAccountBalance":
return get_account_balance
if tool_name == "FinMoveMoneyOrder":
- return move_money
+ return move_money
+ if tool_name == "FinCheckAccountSubmitLoanApproval":
+ return submit_loan_application
if tool_name == "GetOrder":
return get_order
if tool_name == "TrackPackage":
diff --git a/tools/fin/move_money.py b/tools/fin/move_money.py
index 1f17f3e..f9569b7 100644
--- a/tools/fin/move_money.py
+++ b/tools/fin/move_money.py
@@ -31,7 +31,6 @@ class MoneyMovementWorkflowParameterObj:
# 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")
diff --git a/tools/fin/submit_loan_application.py b/tools/fin/submit_loan_application.py
new file mode 100644
index 0000000..4093ec0
--- /dev/null
+++ b/tools/fin/submit_loan_application.py
@@ -0,0 +1,103 @@
+from datetime import date, timedelta
+import os
+from pathlib import Path
+import json
+from temporalio.client import (
+ Client,
+ WithStartWorkflowOperation,
+ WorkflowHandle,
+ WorkflowUpdateFailedError,
+)
+from temporalio import common
+from dataclasses import dataclass
+from typing import Optional
+import asyncio
+from temporalio.exceptions import WorkflowAlreadyStartedError
+from shared.config import get_temporal_client
+
+
+# Define data structures to match the Java workflow's expected input/output
+# see https://github.com/temporal-sa/temporal-latency-optimization-scenarios for more details
+@dataclass
+class TransactionRequest:
+ amount: float
+ sourceAccount: str
+ targetAccount: str
+
+@dataclass
+class TxResult:
+ transactionId: str
+ status: str
+
+#demonstrate starting a workflow and early return pattern while the workflow continues
+async def submit_loan_application(args: dict) -> dict:
+ account_key = args.get("accountkey")
+ amount = args.get("amount")
+
+ loan_status: dict = await start_workflow(amount=amount,account_name=account_key)
+
+ if loan_status.get("error") is None:
+ return {'status': loan_status.get("loan_application_status"), 'detailed_status': loan_status.get("application_details"), 'next_step': loan_status.get("advisement"), 'confirmation_id': loan_status.get("transaction_id")}
+ else:
+ print(loan_status)
+ return loan_status
+
+
+# Async function to start workflow
+async def start_workflow(amount: str, account_name: str, )-> dict:
+
+ # Connect to Temporal
+ client = await get_temporal_client()
+ start_real_workflow = os.getenv("FIN_START_REAL_WORKFLOW")
+ if start_real_workflow is not None and start_real_workflow.lower() == "false":
+ START_REAL_WORKFLOW = False
+ return {'loan_application_status': "applied", 'application_details': "loan application is submitted and initial validation is complete",'transaction_id': "APPLICATION"+account_name, 'advisement': "You'll receive a confirmation for final approval in three business days", }
+ else:
+ START_REAL_WORKFLOW = True
+
+ # Define the workflow ID and task queue
+ workflow_id = "LOAN_APPLICATION-"+account_name+"-"+date.today().strftime('%Y-%m-%d')
+ task_queue = "LatencyOptimizationTEST"
+
+ # Create a TransactionRequest (matching the Java workflow's expected input)
+ tx_request = TransactionRequest(
+ amount=float(amount),
+ targetAccount=account_name,
+ sourceAccount=account_name,
+ )
+
+ start_op = WithStartWorkflowOperation(
+ "TransactionWorkflowLocalBeforeUpdate",
+ tx_request,
+ id=workflow_id,
+ id_conflict_policy=common.WorkflowIDConflictPolicy.USE_EXISTING,
+ task_queue=task_queue,
+ )
+
+ try:
+ print("trying update-with-start")
+ tx_result = TxResult(
+ await client.execute_update_with_start_workflow(
+ "returnInitResult",
+ start_workflow_operation=start_op,
+ )
+ )
+ except WorkflowUpdateFailedError:
+ print("aww man got exception WorkflowUpdateFailedError" )
+ tx_result = None
+ return_msg = "Loan could not be processed for " + account_name
+ return {"error": return_msg}
+
+ workflow_handle = await start_op.workflow_handle()
+ print(tx_result)
+
+ print(f"Update result: Transaction ID = {tx_result.transactionId}, Message = {tx_result.status}")
+
+ # Optionally, wait for the workflow to complete and get the final result
+ # final_result = await handle.result()
+ # print(f"Workflow completed with result: {final_result}")
+
+
+ # return {'status': loan_status.get("loan_status"), 'detailed_status': loan_status.get("results"), 'next_step': loan_status.get("advisement"), 'confirmation_id': loan_status.get("workflowID")}
+ return {'loan_application_status': "applied", 'application_details': "loan application is submitted and initial validation is complete",'transaction_id': tx_result.transactionId, 'advisement': "You'll receive a confirmation for final approval in three business days", }
+
\ No newline at end of file
diff --git a/tools/goal_registry.py b/tools/goal_registry.py
index 5dea149..a164498 100644
--- a/tools/goal_registry.py
+++ b/tools/goal_registry.py
@@ -305,6 +305,7 @@ goal_fin_check_account_balances = AgentGoal(
)
# this tool checks account balances, and uses ./data/customer_account_data.json as dummy data
+# it also uses a separate workflow/tool, see ./setup.md for details
goal_fin_move_money = AgentGoal(
id = "goal_fin_move_money",
category_tag="fin",
@@ -322,7 +323,7 @@ goal_fin_move_money = AgentGoal(
starter_prompt=starter_prompt_generic,
example_conversation_history="\n ".join(
[
- "user: I'd like transfer some money",
+ "user: I'd like to 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: ",
@@ -343,6 +344,34 @@ goal_fin_move_money = AgentGoal(
),
)
+# this starts a loan approval process
+# it also uses a separate workflow/tool, see ./setup.md for details #todo
+goal_fin_loan_application = AgentGoal(
+ id = "goal_fin_loan_application",
+ category_tag="fin",
+ agent_name="Easy Loan Apply",
+ agent_friendly_description="Initiate loan application.",
+ tools=[
+ tool_registry.financial_check_account_is_valid,
+ tool_registry.financial_submit_loan_approval, #todo
+ ],
+ description="The user wants to apply for a loan at the 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. FinCheckAccountSubmitLoanApproval: submit the loan for approval",
+ starter_prompt=starter_prompt_generic,
+ example_conversation_history="\n ".join(
+ [
+ "user: I'd like to apply for a loan",
+ "agent: Sure! I can help you out with that. May I have account number for confirmation?",
+ "user: account number is 11235813",
+ "user_confirmed_tool_run: ",
+ "tool_result: { 'status': account valid }",
+ "agent: Great! We've validated your account. What will the loan amount be?",
+ "user: I'd like a loan for $500",
+ "user_confirmed_tool_run: ",
+ "tool_result: { 'status': submitted, 'detailed_status': loan application is submitted and initial validation is complete, 'confirmation id': 333421, 'next_step': You'll receive a confirmation for final approval in three business days }",
+ "agent: I have submitted your loan application process and the initial validation is successful. Your application ID is 333421. You'll receive a notification for final approval from us in three business days. "
+
# ----- E-Commerce Goals ---
#todo: add goal to list all orders for last X amount of time?
# this tool checks account balances, and uses ./data/customer_account_data.json as dummy data
@@ -430,5 +459,7 @@ 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)
+goal_list.append(goal_fin_loan_application)
goal_list.append(goal_ecomm_list_orders)
-goal_list.append(goal_ecomm_order_status)
\ No newline at end of file
+goal_list.append(goal_ecomm_order_status)
+
diff --git a/tools/tool_registry.py b/tools/tool_registry.py
index a8eaa2a..efc3cf3 100644
--- a/tools/tool_registry.py
+++ b/tools/tool_registry.py
@@ -318,6 +318,22 @@ financial_move_money = ToolDefinition(
],
)
+financial_submit_loan_approval = ToolDefinition(
+ name="FinCheckAccountSubmitLoanApproval",
+ description="Submit a loan application. "
+ "Returns the loan status. ",
+
+ arguments=[
+ ToolArgument(
+ name="accountkey",
+ type="string",
+ description="email address or account ID of user",
+ ),
+ ToolArgument(
+ name="amount",
+ type="string",
+ description="amount requested for the loan",
+
# ----- ECommerce Use Case Tools -----
ecomm_list_orders = ToolDefinition(
name="ListOrders",
diff --git a/workflows/agent_goal_workflow.py b/workflows/agent_goal_workflow.py
index 20d1b86..aaee3c5 100644
--- a/workflows/agent_goal_workflow.py
+++ b/workflows/agent_goal_workflow.py
@@ -108,7 +108,7 @@ class AgentGoalWorkflow:
conversation_history=self.conversation_history,
agent_goal=self.goal,
)
- validation_result = await workflow.execute_activity(
+ validation_result = await workflow.execute_activity_method(
ToolActivities.agent_validatePrompt,
args=[validation_input],
schedule_to_close_timeout=LLM_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT,
@@ -134,7 +134,7 @@ class AgentGoalWorkflow:
prompt_input = ToolPromptInput(prompt=prompt, context_instructions=context_instructions)
# connect to LLM and execute to get next steps
- tool_data = await workflow.execute_activity(
+ tool_data = await workflow.execute_activity_method(
ToolActivities.agent_toolPlanner,
prompt_input,
schedule_to_close_timeout=LLM_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT,
@@ -211,7 +211,7 @@ class AgentGoalWorkflow:
#Signal that comes from api/main.py via a post to /confirm
@workflow.signal
- async def confirmed(self) -> None:
+ async def confirm(self) -> None:
"""Signal handler for user confirmation of tool execution."""
workflow.logger.info("Received user signal: confirmation")
self.confirmed = True
@@ -317,8 +317,9 @@ class AgentGoalWorkflow:
async def lookup_wf_env_settings(self, combined_input: CombinedInput)->None:
env_lookup_input = EnvLookupInput(
show_confirm_env_var_name = "SHOW_CONFIRM",
- show_confirm_default = True)
- env_output:EnvLookupOutput = await workflow.execute_activity(
+ show_confirm_default = True,
+ )
+ env_output:EnvLookupOutput = await workflow.execute_activity_method(
ToolActivities.get_wf_env_vars,
env_lookup_input,
start_to_close_timeout=LLM_ACTIVITY_START_TO_CLOSE_TIMEOUT,
@@ -363,9 +364,11 @@ class AgentGoalWorkflow:
# also don't forget you can look at the workflow itself and do queries if you want
def print_useful_workflow_vars(self, status_or_step:str) -> None:
print(f"***{status_or_step}:***")
- print(f"force confirm? {self.tool_data['force_confirm']}")
- print(f"next step: {self.tool_data.get('next')}")
- print(f"current_tool: {self.tool_data.get('tool')}")
- print(f"self.confirm: {self.confirmed}")
- print(f"waiting_for_confirm (about to be set to true): {self.waiting_for_confirm}")
+ if self.tool_data:
+ print(f"force confirm? {self.tool_data['force_confirm']}")
+ print(f"next step: {self.tool_data.get('next')}")
+ print(f"current_tool: {self.tool_data.get('tool')}")
+ else:
+ print("no tool data initialized yet")
+ print(f"self.confirmed: {self.confirmed}")