From ef45ca0451912139cee3456e972b9a556e82b7dd Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Thu, 10 Apr 2025 09:38:13 -0400 Subject: [PATCH 01/17] work on tests --- .gitignore | 3 +- README.md | 11 +++ pyproject.toml | 6 ++ scripts/find_events_test.py | 4 +- tests/__init__.py | 0 tests/agent_goal_workflow_test.py | 53 ------------ tests/conftest.py | 55 +++++++++++++ .../workflowtests/agent_goal_workflow_test.py | 80 +++++++++++++++++++ 8 files changed, 156 insertions(+), 56 deletions(-) create mode 100644 tests/__init__.py delete mode 100644 tests/agent_goal_workflow_test.py create mode 100644 tests/conftest.py create mode 100644 tests/workflowtests/agent_goal_workflow_test.py diff --git a/.gitignore b/.gitignore index 2d708e1..5e365eb 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,5 @@ coverage.xml # PyCharm / IntelliJ settings .idea/ -.env \ No newline at end of file +.env +./tests/*.env diff --git a/README.md b/README.md index 6a0d212..91c8c1a 100644 --- a/README.md +++ b/README.md @@ -47,3 +47,14 @@ 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). + +## Tests + +Running the tests requires `poe` and `pytest_asyncio` to be installed. + + python -m pip install poethepoet + python -m pip install pytest_asyncio + +Once you have `poe` and `pytest_asyncio` installed you can run: + + poe test diff --git a/pyproject.toml b/pyproject.toml index 52bbd43..4ab7bec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,12 @@ packages = [ [tool.poetry.urls] "Bug Tracker" = "https://github.com/temporalio/samples-python/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" temporalio = "^1.8.0" 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/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 From 1e22f3ee4c3d49389901937093645412c95e305d Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Thu, 10 Apr 2025 09:41:14 -0400 Subject: [PATCH 02/17] changes to gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5e365eb..4c8f868 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,4 @@ coverage.xml .idea/ .env -./tests/*.env +*.env From 2539436a79551e10c43045f4862520fc1b672023 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Thu, 10 Apr 2025 10:26:55 -0400 Subject: [PATCH 03/17] adding testing config --- pyproject.toml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4ab7bec..00fb129 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,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 From 2b2a5522e928206fb510fd0477ca6ed3dd3e7e6e Mon Sep 17 00:00:00 2001 From: Keith Tenzer Date: Thu, 10 Apr 2025 14:18:16 -0700 Subject: [PATCH 04/17] Added logger factory to worker and logging to activities (#26) Signed-off-by: Keith Tenzer Co-authored-by: Keith Tenzer --- enterprise/Activities/TrainActivities.cs | 10 +++++++++- enterprise/Program.cs | 14 +++++++++++++- enterprise/TrainSearchWorker.csproj | 1 + 3 files changed, 23 insertions(+), 2 deletions(-) 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 @@ + From 585791e826ee148f81ff8e41d9c90008d4bfb68c Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Fri, 11 Apr 2025 09:36:46 -0400 Subject: [PATCH 05/17] todo updates --- todo.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/todo.md b/todo.md index f06d42b..b43dd41 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,10 @@ - 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
- +[ ] LLM failure->autoswitch:
+ - detect failure in the activity using failurecount
+ - activity switches to secondary LLM defined in .env + - activity reports switch to workflow [ ] 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
From 5b58f30e0da97c0aec96d928115dbe03baa74d5d Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Fri, 11 Apr 2025 17:43:34 -0400 Subject: [PATCH 06/17] wip checkin --- adding-goals-and-tools.md | 20 ++++++++++++++++---- setup.md | 7 ++++++- todo.md | 2 ++ tools/__init__.py | 5 ++++- tools/fin/move_money.py | 1 - tools/goal_registry.py | 35 ++++++++++++++++++++++++++++++++++- tools/tool_registry.py | 19 +++++++++++++++++++ 7 files changed, 81 insertions(+), 8 deletions(-) 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/setup.md b/setup.md index a31df80..a94f989 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,11 @@ 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/todo.md b/todo.md index b43dd41..1c2027a 100644 --- a/todo.md +++ b/todo.md @@ -7,6 +7,8 @@ - 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 [ ] LLM failure->autoswitch:
- detect failure in the activity using failurecount
- activity switches to secondary LLM defined in .env diff --git a/tools/__init__.py b/tools/__init__.py index 61d3545..e7df76c 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 .give_hint import give_hint from .guess_location import guess_location @@ -53,7 +54,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 == "GiveHint": return give_hint if tool_name == "GuessLocation": 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/goal_registry.py b/tools/goal_registry.py index 19f62fb..837f412 100644 --- a/tools/goal_registry.py +++ b/tools/goal_registry.py @@ -302,6 +302,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", @@ -319,7 +320,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: ", @@ -340,6 +341,37 @@ 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="Loan Application", + 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. You'll receive a confirmation from us in three business days. " + ] + ), +) + #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) @@ -351,6 +383,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) diff --git a/tools/tool_registry.py b/tools/tool_registry.py index 24fad84..c0805fd 100644 --- a/tools/tool_registry.py +++ b/tools/tool_registry.py @@ -316,4 +316,23 @@ financial_move_money = ToolDefinition( description="account number to move the money to", ), ], +) + +financial_move_money = 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", + ), + ], ) \ No newline at end of file From f0524f2b5fa427eafb5507430eaef76540030437 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Fri, 11 Apr 2025 17:45:46 -0400 Subject: [PATCH 07/17] yeah this won't work --- tools/fin/submit_loan_application.py | 94 ++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 tools/fin/submit_loan_application.py diff --git a/tools/fin/submit_loan_application.py b/tools/fin/submit_loan_application.py new file mode 100644 index 0000000..4aa1e83 --- /dev/null +++ b/tools/fin/submit_loan_application.py @@ -0,0 +1,94 @@ +from datetime import timedelta +import os +from pathlib import Path +import json +from temporalio.client import Client, WorkflowHandle +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 + account_id: str + +@dataclass +class TxResult: + transaction_id: str + message: 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) + + 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")} + + +# 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 + else: + START_REAL_WORKFLOW = True + + if START_REAL_WORKFLOW: + + # Define the workflow ID and task queue + workflow_id = "APPLICATION-"+account_name + task_queue = "LatencyOptimizationTEST" + + # Create a TransactionRequest (matching the Java workflow's expected input) + tx_request = TransactionRequest( + amount=float(amount), + account_id=account_name + ) + + #try: + # Use update-with-start to start the workflow and call the update method + handle: WorkflowHandle = await client.start_workflow( + "TransactionWorkflowLocalBeforeUpdate.processTransaction", # Workflow name + tx_request, # Input to the processTransaction method + id=workflow_id, + task_queue=task_queue, + execution_timeout=timedelta(minutes=5), + # Specify the update to call immediately after starting + update="returnInitResult", + update_args=[] # No arguments needed for returnInitResult + ) + + # Wait for the update result (returnInitResult) + update_result = await handle.result_of_update("returnInitResult") + + # Since the result is a RawValue, we need to deserialize it + # For simplicity, assuming the result is TxResult (adjust based on actual serialization) + #result_dict = update_result.payloads[0].decode() # Simplified; use proper deserialization + tx_result = TxResult( + transaction_id=result_dict.get("transaction_id", ""), + message=result_dict.get("message", "") + ) + + print(f"Update result: Transaction ID = {tx_result.transaction_id}, Message = {tx_result.message}") + + # 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}") + + #except Exception as e: + # print(f"Error executing workflow: {e}") + + + # 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 {'status': "status", 'detailed_status': "loan application is submitted and initial validation is complete",'confirmation id': "11358", 'next_step': "You'll receive a confirmation for final approval in three business days", } + \ No newline at end of file From 79dcd40dded5150a0533b02077a2ab41c40afb88 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Sat, 12 Apr 2025 15:41:47 -0400 Subject: [PATCH 08/17] well it kinda works --- tools/fin/submit_loan_application.py | 79 ++++++++++++++++------------ tools/goal_registry.py | 4 +- tools/tool_registry.py | 2 +- 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/tools/fin/submit_loan_application.py b/tools/fin/submit_loan_application.py index 4aa1e83..4093ec0 100644 --- a/tools/fin/submit_loan_application.py +++ b/tools/fin/submit_loan_application.py @@ -1,8 +1,14 @@ -from datetime import timedelta +from datetime import date, timedelta import os from pathlib import Path import json -from temporalio.client import Client, WorkflowHandle +from temporalio.client import ( + Client, + WithStartWorkflowOperation, + WorkflowHandle, + WorkflowUpdateFailedError, +) +from temporalio import common from dataclasses import dataclass from typing import Optional import asyncio @@ -15,12 +21,13 @@ from shared.config import get_temporal_client @dataclass class TransactionRequest: amount: float - account_id: str + sourceAccount: str + targetAccount: str @dataclass class TxResult: - transaction_id: str - message: str + transactionId: str + status: str #demonstrate starting a workflow and early return pattern while the workflow continues async def submit_loan_application(args: dict) -> dict: @@ -29,7 +36,11 @@ async def submit_loan_application(args: dict) -> dict: loan_status: dict = await start_workflow(amount=amount,account_name=account_key) - 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")} + 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 @@ -40,55 +51,53 @@ async def start_workflow(amount: str, account_name: str, )-> dict: 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 - if START_REAL_WORKFLOW: - # Define the workflow ID and task queue - workflow_id = "APPLICATION-"+account_name + 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), - account_id=account_name + targetAccount=account_name, + sourceAccount=account_name, ) - #try: - # Use update-with-start to start the workflow and call the update method - handle: WorkflowHandle = await client.start_workflow( - "TransactionWorkflowLocalBeforeUpdate.processTransaction", # Workflow name - tx_request, # Input to the processTransaction method + start_op = WithStartWorkflowOperation( + "TransactionWorkflowLocalBeforeUpdate", + tx_request, id=workflow_id, + id_conflict_policy=common.WorkflowIDConflictPolicy.USE_EXISTING, task_queue=task_queue, - execution_timeout=timedelta(minutes=5), - # Specify the update to call immediately after starting - update="returnInitResult", - update_args=[] # No arguments needed for returnInitResult ) - # Wait for the update result (returnInitResult) - update_result = await handle.result_of_update("returnInitResult") - - # Since the result is a RawValue, we need to deserialize it - # For simplicity, assuming the result is TxResult (adjust based on actual serialization) - #result_dict = update_result.payloads[0].decode() # Simplified; use proper deserialization - tx_result = TxResult( - transaction_id=result_dict.get("transaction_id", ""), - message=result_dict.get("message", "") - ) + 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} - print(f"Update result: Transaction ID = {tx_result.transaction_id}, Message = {tx_result.message}") + 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}") - #except Exception as e: - # print(f"Error executing workflow: {e}") - - # 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 {'status': "status", 'detailed_status': "loan application is submitted and initial validation is complete",'confirmation id': "11358", 'next_step': "You'll receive a confirmation for final approval in three business days", } + # 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 837f412..95c329f 100644 --- a/tools/goal_registry.py +++ b/tools/goal_registry.py @@ -346,7 +346,7 @@ goal_fin_move_money = AgentGoal( goal_fin_loan_application = AgentGoal( id = "goal_fin_loan_application", category_tag="fin", - agent_name="Loan Application", + agent_name="Easy Loan Apply", agent_friendly_description="Initiate loan application.", tools=[ tool_registry.financial_check_account_is_valid, @@ -367,7 +367,7 @@ goal_fin_loan_application = AgentGoal( "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. You'll receive a confirmation from us 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. " ] ), ) diff --git a/tools/tool_registry.py b/tools/tool_registry.py index c0805fd..c9b601b 100644 --- a/tools/tool_registry.py +++ b/tools/tool_registry.py @@ -318,7 +318,7 @@ financial_move_money = ToolDefinition( ], ) -financial_move_money = ToolDefinition( +financial_submit_loan_approval = ToolDefinition( name="FinCheckAccountSubmitLoanApproval", description="Submit a loan application. " "Returns the loan status. ", From 812e295f3a7a7f387bcc7e2dae7ec843d8e48a57 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Mon, 14 Apr 2025 09:27:33 -0400 Subject: [PATCH 09/17] switching to method activity calls --- workflows/agent_goal_workflow.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/workflows/agent_goal_workflow.py b/workflows/agent_goal_workflow.py index 083332b..5f73ddf 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, @@ -316,8 +316,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, From 4f953132e075c7ae34119726c1e76d5f54d6bc9e Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Mon, 14 Apr 2025 11:42:40 -0400 Subject: [PATCH 10/17] minor todo updates --- todo.md | 1 + 1 file changed, 1 insertion(+) diff --git a/todo.md b/todo.md index 1c2027a..f67239e 100644 --- a/todo.md +++ b/todo.md @@ -9,6 +9,7 @@ [ ] 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 + [ ] LLM failure->autoswitch:
- detect failure in the activity using failurecount
- activity switches to secondary LLM defined in .env From e92e3f43c9693f36f33ea4f92d8d7a8cd3d4efe8 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Tue, 15 Apr 2025 11:01:18 -0400 Subject: [PATCH 11/17] changes to make the project more python compliant and fix the bug tracker URL --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 00fb129..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,7 @@ 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 ."}] From ac44d35acbd609713bcf119c2c0e088718bf5356 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Tue, 15 Apr 2025 16:46:09 -0400 Subject: [PATCH 12/17] changes to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2d708e1..208ebe6 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,5 @@ coverage.xml # PyCharm / IntelliJ settings .idea/ -.env \ No newline at end of file +.env +.env* \ No newline at end of file From 6f9079ba124682f4e44e44d686305b7dabc35913 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Wed, 16 Apr 2025 16:23:31 -0400 Subject: [PATCH 13/17] updates to todo --- todo.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/todo.md b/todo.md index f06d42b..da49eb0 100644 --- a/todo.md +++ b/todo.md @@ -18,6 +18,11 @@ [ ] new loan/fraud check/update with start
+[ ] for demo simulate failure - add utilities/simulated failures from pipeline demo
+ +[ ] ecommerce goals
+- [ ] add to docs
+- [ ] decide about api key names with Laine
[ ] 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
From 7b52b8a817b7ed3f1807f1eab95b02c62fb4331b Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Wed, 16 Apr 2025 16:26:37 -0400 Subject: [PATCH 14/17] adding to todo, gitignore --- .gitignore | 1 + todo.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 4c8f868..c2d2dde 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ coverage.xml .env *.env +.env* diff --git a/todo.md b/todo.md index f06d42b..af48f52 100644 --- a/todo.md +++ b/todo.md @@ -18,6 +18,7 @@ [ ] new loan/fraud check/update with start
+[ ] 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
From 463ae581acaa9ee93fa0eeffdf0aab43624266f3 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Thu, 17 Apr 2025 05:32:55 -0400 Subject: [PATCH 15/17] adding .env* stuff to gitignore, fixing a minor docs formatting bug --- .gitignore | 1 + setup.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 4c8f868..c2d2dde 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ coverage.xml .env *.env +.env* diff --git a/setup.md b/setup.md index a94f989..6a41558 100644 --- a/setup.md +++ b/setup.md @@ -204,6 +204,7 @@ FIN_START_REAL_WORKFLOW=FALSE #set this to true to start a real workflow 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 From 83c6a2454dc24cc5b021cf04a310e27ae32a3c77 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Thu, 17 Apr 2025 05:57:55 -0400 Subject: [PATCH 16/17] 1. These aren't the tests you're looking for 2. fixing confirmed signal for now --- README.md | 9 --------- workflows/agent_goal_workflow.py | 19 ++++++++++++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 91c8c1a..8a97fc7 100644 --- a/README.md +++ b/README.md @@ -48,13 +48,4 @@ 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). -## Tests -Running the tests requires `poe` and `pytest_asyncio` to be installed. - - python -m pip install poethepoet - python -m pip install pytest_asyncio - -Once you have `poe` and `pytest_asyncio` installed you can run: - - poe test diff --git a/workflows/agent_goal_workflow.py b/workflows/agent_goal_workflow.py index 083332b..78c737d 100644 --- a/workflows/agent_goal_workflow.py +++ b/workflows/agent_goal_workflow.py @@ -215,6 +215,13 @@ class AgentGoalWorkflow: workflow.logger.info("Received user signal: confirmation") self.confirmed = True + #Signal that comes from api/main.py via a post to /confirm + @workflow.signal + async def confirm(self) -> None: + """Signal handler for user confirmation of tool execution.""" + workflow.logger.info("Received user signal: confirmation") + self.confirmed = True + #Signal that comes from api/main.py via a post to /end-chat @workflow.signal async def end_chat(self) -> None: @@ -362,9 +369,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}") From 86a6dfe991aa3d32e3d2c909934f3a57e24cacdb Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Thu, 17 Apr 2025 06:00:17 -0400 Subject: [PATCH 17/17] renaming signal from confirmed to confirm --- workflows/agent_goal_workflow.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/workflows/agent_goal_workflow.py b/workflows/agent_goal_workflow.py index 78c737d..1972547 100644 --- a/workflows/agent_goal_workflow.py +++ b/workflows/agent_goal_workflow.py @@ -208,13 +208,6 @@ class AgentGoalWorkflow: return self.prompt_queue.append(prompt) - #Signal that comes from api/main.py via a post to /confirm - @workflow.signal - async def confirmed(self) -> None: - """Signal handler for user confirmation of tool execution.""" - workflow.logger.info("Received user signal: confirmation") - self.confirmed = True - #Signal that comes from api/main.py via a post to /confirm @workflow.signal async def confirm(self) -> None: