mirror of
https://github.com/temporal-community/temporal-ai-agent.git
synced 2026-03-17 06:58:09 +01:00
Merge branch 'development' of https://github.com/joshmsmith/temporal-ai-agent into development
This commit is contained in:
@@ -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 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 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`
|
- 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`
|
#### 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.
|
- 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.
|
||||||
|
|||||||
@@ -8,5 +8,7 @@ talk through the pieces
|
|||||||
|
|
||||||
explain elements
|
explain elements
|
||||||
|
|
||||||
|
tools do determinism
|
||||||
|
|
||||||
# Adding features
|
# Adding features
|
||||||
link to how to LLM interactions/how to change
|
link to how to LLM interactions/how to change
|
||||||
@@ -2,6 +2,7 @@ import asyncio
|
|||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import os
|
import os
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
import logging
|
||||||
|
|
||||||
from temporalio.worker import Worker
|
from temporalio.worker import Worker
|
||||||
|
|
||||||
@@ -48,6 +49,9 @@ async def main():
|
|||||||
print("===========================================================\n")
|
print("===========================================================\n")
|
||||||
|
|
||||||
print("Worker ready to process tasks!")
|
print("Worker ready to process tasks!")
|
||||||
|
logging.basicConfig(level=logging.WARN)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Run the worker
|
# Run the worker
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=100) as activity_executor:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=100) as activity_executor:
|
||||||
|
|||||||
1
todo.md
1
todo.md
@@ -16,6 +16,7 @@
|
|||||||
[ ] how to add more scenarios, tools <br />
|
[ ] how to add more scenarios, tools <br />
|
||||||
|
|
||||||
[ ] create tests<br />
|
[ ] create tests<br />
|
||||||
|
[ ] fix logging statements not to be all warn, maybe set logging level to info
|
||||||
|
|
||||||
[ ] create people management scenarios <br />
|
[ ] create people management scenarios <br />
|
||||||
[x] 1. Schedule PTO goal
|
[x] 1. Schedule PTO goal
|
||||||
|
|||||||
@@ -77,55 +77,32 @@ class AgentGoalWorkflow:
|
|||||||
# This is the main interactive loop. Main responsibilities:
|
# This is the main interactive loop. Main responsibilities:
|
||||||
# - Selecting and changing goals as directed by the user
|
# - Selecting and changing goals as directed by the user
|
||||||
# - reacting to user input (from signals)
|
# - reacting to user input (from signals)
|
||||||
# - calling activities to determine next steps and prompts
|
# - validating user input to make sure it makes sense with the current goal and tools
|
||||||
# - executing the selected tools
|
# - calling the LLM through activities to determine next steps and prompts
|
||||||
|
# - executing the selected tools via activities
|
||||||
while True:
|
while True:
|
||||||
# wait indefinitely for input from signals - user_prompt, end_chat, or confirm as defined below
|
# wait indefinitely for input from signals - user_prompt, end_chat, or confirm as defined below
|
||||||
await workflow.wait_condition(
|
await workflow.wait_condition(
|
||||||
lambda: bool(self.prompt_queue) or self.chat_ended or self.confirm
|
lambda: bool(self.prompt_queue) or self.chat_ended or self.confirm
|
||||||
)
|
)
|
||||||
|
|
||||||
# handle chat-end signal
|
# handle chat should end. When chat ends, push conversation history to workflow results.
|
||||||
if self.chat_ended:
|
if self.chat_should_end():
|
||||||
workflow.logger.warning(f"workflow step: chat-end signal received, ending")
|
|
||||||
workflow.logger.info("Chat ended.")
|
|
||||||
return f"{self.conversation_history}"
|
return f"{self.conversation_history}"
|
||||||
|
|
||||||
# Execute the tool
|
# Execute the tool
|
||||||
if self.confirm and waiting_for_confirm and current_tool and self.tool_data:
|
if self.ready_for_tool_execution(waiting_for_confirm, current_tool):
|
||||||
workflow.logger.warning(f"workflow step: user has confirmed, executing the tool {current_tool}")
|
waiting_for_confirm = await self.execute_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")
|
|
||||||
continue
|
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:
|
if self.prompt_queue:
|
||||||
|
# get most recent prompt
|
||||||
prompt = self.prompt_queue.popleft()
|
prompt = self.prompt_queue.popleft()
|
||||||
workflow.logger.warning(f"workflow step: processing message on the prompt queue, message is {prompt}")
|
workflow.logger.info(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
|
|
||||||
|
# Validate user-provided prompts
|
||||||
|
if self.is_user_prompt(prompt):
|
||||||
self.add_message("user", prompt)
|
self.add_message("user", prompt)
|
||||||
|
|
||||||
# Validate the prompt before proceeding
|
# 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:
|
if not validation_result.validationResult:
|
||||||
workflow.logger.warning(f"Prompt validation failed: {validation_result.validationFailedReason}")
|
workflow.logger.warning(f"Prompt validation failed: {validation_result.validationFailedReason}")
|
||||||
self.add_message("agent", validation_result.validationFailedReason)
|
self.add_message("agent", validation_result.validationFailedReason)
|
||||||
@@ -171,7 +148,8 @@ class AgentGoalWorkflow:
|
|||||||
next_step = tool_data.get("next")
|
next_step = tool_data.get("next")
|
||||||
current_tool = tool_data.get("tool")
|
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 the next step is to confirm...
|
||||||
if next_step == "confirm" and current_tool:
|
if next_step == "confirm" and current_tool:
|
||||||
args = tool_data.get("args", {})
|
args = tool_data.get("args", {})
|
||||||
@@ -212,9 +190,9 @@ class AgentGoalWorkflow:
|
|||||||
@workflow.signal
|
@workflow.signal
|
||||||
async def user_prompt(self, prompt: str) -> None:
|
async def user_prompt(self, prompt: str) -> None:
|
||||||
"""Signal handler for receiving user prompts."""
|
"""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:
|
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
|
return
|
||||||
self.prompt_queue.append(prompt)
|
self.prompt_queue.append(prompt)
|
||||||
|
|
||||||
@@ -222,15 +200,14 @@ class AgentGoalWorkflow:
|
|||||||
@workflow.signal
|
@workflow.signal
|
||||||
async def confirm(self) -> None:
|
async def confirm(self) -> None:
|
||||||
"""Signal handler for user confirmation of tool execution."""
|
"""Signal handler for user confirmation of tool execution."""
|
||||||
workflow.logger.info("Received user confirmation")
|
workflow.logger.info("Received user signal: confirmation")
|
||||||
workflow.logger.warning(f"signal recieved: confirm")
|
|
||||||
self.confirm = True
|
self.confirm = True
|
||||||
|
|
||||||
#Signal that comes from api/main.py via a post to /end-chat
|
#Signal that comes from api/main.py via a post to /end-chat
|
||||||
@workflow.signal
|
@workflow.signal
|
||||||
async def end_chat(self) -> None:
|
async def end_chat(self) -> None:
|
||||||
"""Signal handler for ending the chat session."""
|
"""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
|
self.chat_ended = True
|
||||||
|
|
||||||
@workflow.query
|
@workflow.query
|
||||||
@@ -283,5 +260,61 @@ class AgentGoalWorkflow:
|
|||||||
if listed_goal.id == goal:
|
if listed_goal.id == goal:
|
||||||
self.goal = listed_goal
|
self.goal = listed_goal
|
||||||
# self.goal = goals.get(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
|
#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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user