Merge branch 'development' of https://github.com/joshmsmith/temporal-ai-agent into development

This commit is contained in:
Laine
2025-03-14 10:20:14 -04:00
5 changed files with 85 additions and 44 deletions

View File

@@ -41,6 +41,7 @@ description="Help the user gather args for these tools in order: "
- The file name and function name will be the same as each other and should also be the same as the name of the tool, without "tool" - so future_pto_tool would be future_pto.py with a function named future_pto within it.
- The function should have `args: dict` as the input and also return a `dict`
- The return dict should match the output format you specified in the goal's `example_conversation_history`
- tools are where the user input+model output becomes deterministic. Add validation here to make sure what the system is doing is valid and acceptable
#### Add to `tools/__init__.py`
- In `tools/__init__.py`, add an import statement for each new tool as well as an applicable return statement in `get_handler`. The tool name here should match the tool name as described in the goal's `description` field.

View File

@@ -8,5 +8,7 @@ talk through the pieces
explain elements
tools do determinism
# Adding features
link to how to LLM interactions/how to change

View File

@@ -2,6 +2,7 @@ import asyncio
import concurrent.futures
import os
from dotenv import load_dotenv
import logging
from temporalio.worker import Worker
@@ -48,6 +49,9 @@ async def main():
print("===========================================================\n")
print("Worker ready to process tasks!")
logging.basicConfig(level=logging.WARN)
# Run the worker
with concurrent.futures.ThreadPoolExecutor(max_workers=100) as activity_executor:

View File

@@ -16,6 +16,7 @@
[ ] how to add more scenarios, tools <br />
[ ] create tests<br />
[ ] fix logging statements not to be all warn, maybe set logging level to info
[ ] create people management scenarios <br />
[x] 1. Schedule PTO goal

View File

@@ -77,55 +77,32 @@ class AgentGoalWorkflow:
# This is the main interactive loop. Main responsibilities:
# - Selecting and changing goals as directed by the user
# - reacting to user input (from signals)
# - calling activities to determine next steps and prompts
# - executing the selected tools
# - validating user input to make sure it makes sense with the current goal and tools
# - calling the LLM through activities to determine next steps and prompts
# - executing the selected tools via activities
while True:
# wait indefinitely for input from signals - user_prompt, end_chat, or confirm as defined below
await workflow.wait_condition(
lambda: bool(self.prompt_queue) or self.chat_ended or self.confirm
)
# handle chat-end signal
if self.chat_ended:
workflow.logger.warning(f"workflow step: chat-end signal received, ending")
workflow.logger.info("Chat ended.")
# handle chat should end. When chat ends, push conversation history to workflow results.
if self.chat_should_end():
return f"{self.conversation_history}"
# Execute the tool
if self.confirm and waiting_for_confirm and current_tool and self.tool_data:
workflow.logger.warning(f"workflow step: user has confirmed, executing the tool {current_tool}")
self.confirm = False
waiting_for_confirm = False
confirmed_tool_data = self.tool_data.copy()
confirmed_tool_data["next"] = "user_confirmed_tool_run"
self.add_message("user_confirmed_tool_run", confirmed_tool_data)
# execute the tool by key as defined in tools/__init__.py
await helpers.handle_tool_execution(
current_tool,
self.tool_data,
self.tool_results,
self.add_message,
self.prompt_queue
)
#set new goal if we should
if len(self.tool_results) > 0:
if "ChangeGoal" in self.tool_results[-1].values() and "new_goal" in self.tool_results[-1].keys():
new_goal = self.tool_results[-1].get("new_goal")
workflow.logger.warning(f"Booya new goal!: {new_goal}")
self.change_goal(new_goal)
elif "ListAgents" in self.tool_results[-1].values() and self.goal.id != "goal_choose_agent_type":
workflow.logger.warning("setting goal to goal_choose_agent_type")
self.change_goal("goal_choose_agent_type")
if self.ready_for_tool_execution(waiting_for_confirm, current_tool):
waiting_for_confirm = await self.execute_tool(current_tool)
continue
# if we've received messages to be processed on the prompt queue...
# process forward on the prompt queue if any
if self.prompt_queue:
# get most recent prompt
prompt = self.prompt_queue.popleft()
workflow.logger.warning(f"workflow step: processing message on the prompt queue, message is {prompt}")
if not prompt.startswith("###"): #if the message isn't from the LLM but is instead from the user
workflow.logger.info(f"workflow step: processing message on the prompt queue, message is {prompt}")
# Validate user-provided prompts
if self.is_user_prompt(prompt):
self.add_message("user", prompt)
# Validate the prompt before proceeding
@@ -144,7 +121,7 @@ class AgentGoalWorkflow:
),
)
#If validation fails, provide that feedback to the user - i.e., "your words make no sense, puny human" end this iteration of processing
# If validation fails, provide that feedback to the user - i.e., "your words make no sense, puny human" end this iteration of processing
if not validation_result.validationResult:
workflow.logger.warning(f"Prompt validation failed: {validation_result.validationFailedReason}")
self.add_message("agent", validation_result.validationFailedReason)
@@ -171,7 +148,8 @@ class AgentGoalWorkflow:
next_step = tool_data.get("next")
current_tool = tool_data.get("tool")
workflow.logger.warning(f"next_step: {next_step}, current tool is {current_tool}")
workflow.logger.info(f"next_step: {next_step}, current tool is {current_tool}")
#if the next step is to confirm...
if next_step == "confirm" and current_tool:
args = tool_data.get("args", {})
@@ -212,9 +190,9 @@ class AgentGoalWorkflow:
@workflow.signal
async def user_prompt(self, prompt: str) -> None:
"""Signal handler for receiving user prompts."""
workflow.logger.warning(f"signal received: user_prompt, prompt is {prompt}")
workflow.logger.info(f"signal received: user_prompt, prompt is {prompt}")
if self.chat_ended:
workflow.logger.warning(f"Message dropped due to chat closed: {prompt}")
workflow.logger.info(f"Message dropped due to chat closed: {prompt}")
return
self.prompt_queue.append(prompt)
@@ -222,15 +200,14 @@ class AgentGoalWorkflow:
@workflow.signal
async def confirm(self) -> None:
"""Signal handler for user confirmation of tool execution."""
workflow.logger.info("Received user confirmation")
workflow.logger.warning(f"signal recieved: confirm")
workflow.logger.info("Received user signal: confirmation")
self.confirm = True
#Signal that comes from api/main.py via a post to /end-chat
@workflow.signal
async def end_chat(self) -> None:
"""Signal handler for ending the chat session."""
workflow.logger.warning("signal received: end_chat")
workflow.logger.info("signal received: end_chat")
self.chat_ended = True
@workflow.query
@@ -283,5 +260,61 @@ class AgentGoalWorkflow:
if listed_goal.id == goal:
self.goal = listed_goal
# self.goal = goals.get(goal)
workflow.logger.warning("Changed goal to " + goal)
workflow.logger.info("Changed goal to " + goal)
#todo reset goal or tools if this doesn't work or whatever
# workflow function that defines if chat should end
def chat_should_end(self) -> bool:
if self.chat_ended:
workflow.logger.info("Chat-end signal received. Chat ending.")
return True
else:
return False
# define if we're ready for tool execution
def ready_for_tool_execution(self, waiting_for_confirm: bool, current_tool: Any) -> bool:
if self.confirm and waiting_for_confirm and current_tool and self.tool_data:
return True
else:
return False
# LLM-tagged prompts start with "###"
# all others are from the user
def is_user_prompt(self, prompt) -> bool:
if prompt.startswith("###"):
return False
else:
return True
# execute the tool - return False if we're not waiting for confirm anymore (always the case if it works successfully)
#
async def execute_tool(self, current_tool: str)->bool:
workflow.logger.info(f"workflow step: user has confirmed, executing the tool {current_tool}")
self.confirm = False
waiting_for_confirm = False
confirmed_tool_data = self.tool_data.copy()
confirmed_tool_data["next"] = "user_confirmed_tool_run"
self.add_message("user_confirmed_tool_run", confirmed_tool_data)
# execute the tool by key as defined in tools/__init__.py
await helpers.handle_tool_execution(
current_tool,
self.tool_data,
self.tool_results,
self.add_message,
self.prompt_queue
)
#set new goal if we should
if len(self.tool_results) > 0:
if "ChangeGoal" in self.tool_results[-1].values() and "new_goal" in self.tool_results[-1].keys():
new_goal = self.tool_results[-1].get("new_goal")
workflow.logger.info(f"Booya new goal!: {new_goal}")
self.change_goal(new_goal)
elif "ListAgents" in self.tool_results[-1].values() and self.goal.id != "goal_choose_agent_type":
workflow.logger.info("setting goal to goal_choose_agent_type")
self.change_goal("goal_choose_agent_type")
return waiting_for_confirm