Add new goal to choose agent type - only kind of working

This commit is contained in:
Laine
2025-03-07 16:12:21 -05:00
parent 64d2a92630
commit 4117d5d62d
9 changed files with 170 additions and 16 deletions

View File

@@ -6,13 +6,12 @@ from temporalio.api.enums.v1 import WorkflowExecutionStatus
from fastapi import HTTPException from fastapi import HTTPException
from dotenv import load_dotenv from dotenv import load_dotenv
import asyncio import asyncio
import os
from workflows.agent_goal_workflow import AgentGoalWorkflow from workflows.agent_goal_workflow import AgentGoalWorkflow
from models.data_types import CombinedInput, AgentGoalWorkflowParams from models.data_types import CombinedInput, AgentGoalWorkflowParams
from tools.goal_registry import goal_match_train_invoice, goal_event_flight_invoice from tools.goal_registry import goal_match_train_invoice, goal_event_flight_invoice, goal_choose_agent_type
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from shared.config import get_temporal_client, TEMPORAL_TASK_QUEUE from shared.config import get_temporal_client, TEMPORAL_TASK_QUEUE, AGENT_GOAL
app = FastAPI() app = FastAPI()
temporal_client: Optional[Client] = None temporal_client: Optional[Client] = None
@@ -26,15 +25,11 @@ def get_agent_goal():
goals = { goals = {
"goal_match_train_invoice": goal_match_train_invoice, "goal_match_train_invoice": goal_match_train_invoice,
"goal_event_flight_invoice": goal_event_flight_invoice, "goal_event_flight_invoice": goal_event_flight_invoice,
"goal_choose_agent_type": goal_choose_agent_type,
} }
# Agent Goal Configuration
#AGENT_GOAL=goal_event_flight_invoice
#AGENT_GOAL=goal_match_train_invoice
#goal_name = os.getenv("AGENT_GOAL") if AGENT_GOAL is not None:
goal_name = "goal_event_flight_invoice" return goals.get(AGENT_GOAL)
if goal_name is not None:
return goals.get(goal_name)
else: else:
#if no goal is set in the env file, default to event/flight use case #if no goal is set in the env file, default to event/flight use case
return goals.get("goal_event_flight_invoice", goal_event_flight_invoice) return goals.get("goal_event_flight_invoice", goal_event_flight_invoice)

View File

@@ -16,6 +16,9 @@ TEMPORAL_TLS_CERT = os.getenv("TEMPORAL_TLS_CERT", "")
TEMPORAL_TLS_KEY = os.getenv("TEMPORAL_TLS_KEY", "") TEMPORAL_TLS_KEY = os.getenv("TEMPORAL_TLS_KEY", "")
TEMPORAL_API_KEY = os.getenv("TEMPORAL_API_KEY", "") TEMPORAL_API_KEY = os.getenv("TEMPORAL_API_KEY", "")
#Starting agent goal - 1st goal is always to help user pick a next goal
AGENT_GOAL = "goal_choose_agent_type"
async def get_temporal_client() -> Client: async def get_temporal_client() -> Client:
""" """

View File

@@ -4,6 +4,9 @@ from .search_trains import search_trains
from .search_trains import book_trains from .search_trains import book_trains
from .create_invoice import create_invoice from .create_invoice import create_invoice
from .find_events import find_events from .find_events import find_events
from .choose_agent import choose_agent
from .change_goal import change_goal
from .transfer_control import transfer_control
def get_handler(tool_name: str): def get_handler(tool_name: str):
@@ -19,5 +22,11 @@ def get_handler(tool_name: str):
return create_invoice return create_invoice
if tool_name == "FindEvents": if tool_name == "FindEvents":
return find_events return find_events
if tool_name == "ChooseAgent":
return choose_agent
if tool_name == "ChangeGoal":
return change_goal
if tool_name == "TransferControl":
return transfer_control
raise ValueError(f"Unknown tool: {tool_name}") raise ValueError(f"Unknown tool: {tool_name}")

13
tools/change_goal.py Normal file
View File

@@ -0,0 +1,13 @@
# can this just call the API endpoint to set the goal, if that changes to allow a param?
# --- OR ---
# end this workflow and start a new one with the new goal
import shared.config
def change_goal(args: dict) -> dict:
new_goal = args.get("goalID")
shared.config.AGENT_GOAL = new_goal
return {
"new_goal": shared.config.AGENT_GOAL,
}

27
tools/choose_agent.py Normal file
View File

@@ -0,0 +1,27 @@
from pathlib import Path
import json
def choose_agent(args: dict) -> dict:
# file_path = Path(__file__).resolve().parent / "goal_regsitry.py"
#if not file_path.exists():
# return {"error": "Data file not found."}
agents = []
agents.append(
{
"agent_name": "Event Flight Helper",
"goal_id": "goal_event_flight_invoice",
"agent_description": "Helps users find interesting events and arrange travel to them",
}
)
agents.append(
{
"agent_name": "Soccer Train Thing Guy",
"goal_id": "goal_match_train_invoice",
"agent_description": "Something about soccer and trains and stuff",
}
)
return {
"agents": agents,
}

View File

@@ -6,6 +6,42 @@ from tools.tool_registry import (
book_trains_tool, book_trains_tool,
create_invoice_tool, create_invoice_tool,
find_events_tool, find_events_tool,
change_goal_tool,
choose_agent_tool,
transfer_control_tool
)
starter_prompt_generic = "Welcome me, give me a description of what you can do, then ask me for the details you need to do your job"
goal_choose_agent_type = AgentGoal(
tools=[
choose_agent_tool,
change_goal_tool,
transfer_control_tool
],
description="The user wants to choose which type of agent they will interact with. "
"Help the user gather args for these tools, in order: "
"1. ChooseAgent: Choose which agent to interact with "
"2. ChangeGoal: Change goal of agent "
"3. TransferControl: Transfer control to new agent "
"After these tools are complete, change your goal to the new goal as chosen by the user. ",
starter_prompt=starter_prompt_generic,
example_conversation_history="\n ".join(
[
"user: I'd like to choose an agent",
"agent: Sure! Would you like me to list the available agents?",
"user_confirmed_tool_run: <user clicks confirm on ChooseAgent tool>",
"tool_result: { 'agent_name': 'Event Flight Finder', 'goal_id': 'goal_event_flight_invoice', 'agent_description': 'Helps users find interesting events and arrange travel to them' }",
"agent: The available agents are: 1. Event Flight Finder. Which agent would you like to speak to?",
"user: 1",
"user_confirmed_tool_run: <user clicks confirm on ChangeGoal tool>",
# bot changes goal here and hopefully just...switches??
# could also end 1 workflow and start another with new goal
"tool_result: { 'new_goal': 'goal_event_flight_invoice' }",
"agent: Would you like to transfer control to the new agent now?",
"user: yes",
]
),
) )
goal_match_train_invoice = AgentGoal( goal_match_train_invoice = AgentGoal(
@@ -23,7 +59,7 @@ goal_match_train_invoice = AgentGoal(
"2. SearchTrains: Search for trains to the city of the match and list them for the customer to choose from " "2. SearchTrains: Search for trains to the city of the match and list them for the customer to choose from "
"3. BookTrains: Book the train tickets, used to invoice the user for the cost of the train tickets " "3. BookTrains: Book the train tickets, used to invoice the user for the cost of the train tickets "
"4. CreateInvoice: Invoices the user for the cost of train tickets, with total and details inferred from the conversation history ", "4. CreateInvoice: Invoices the user for the cost of train tickets, with total and details inferred from the conversation history ",
starter_prompt="Welcome me, give me a description of what you can do, then ask me for the details you need to begin your job as an agent ", starter_prompt=starter_prompt_generic,
example_conversation_history="\n ".join( example_conversation_history="\n ".join(
[ [
"user: I'd like to travel to a premier league match", "user: I'd like to travel to a premier league match",
@@ -61,7 +97,7 @@ goal_event_flight_invoice = AgentGoal(
"1. FindEvents: Find an event to travel to " "1. FindEvents: Find an event to travel to "
"2. SearchFlights: search for a flight around the event dates " "2. SearchFlights: search for a flight around the event dates "
"3. CreateInvoice: Create a simple invoice for the cost of that flight ", "3. CreateInvoice: Create a simple invoice for the cost of that flight ",
starter_prompt="Welcome me, give me a description of what you can do, then ask me for the details you need to do your job", starter_prompt=starter_prompt_generic,
example_conversation_history="\n ".join( example_conversation_history="\n ".join(
[ [
"user: I'd like to travel to an event", "user: I'd like to travel to an event",

View File

@@ -1,5 +1,43 @@
from models.tool_definitions import ToolDefinition, ToolArgument from models.tool_definitions import ToolDefinition, ToolArgument
#This also doesn't help...
transfer_control_tool = ToolDefinition(
name="TransferControl",
description="Do one extra input from user to apply the new goal to the workflow (Hacky, hopefully temp). ",
arguments=[
ToolArgument(
name="userConfirmation",
type="string",
description="dummy variable to make thing work",
),
],
)
choose_agent_tool = ToolDefinition(
name="ChooseAgent",
description="List available agents to interact with, pulled from goal_registry. ",
arguments=[
ToolArgument(
name="userConfirmation",
type="string",
description="dummy variable to make thing work",
),
],
)
change_goal_tool = ToolDefinition(
name="ChangeGoal",
description="Change the goal of the active agent. ",
arguments=[
ToolArgument(
name="goalID",
type="string",
description="Which goal to change to",
),
],
)
search_flights_tool = ToolDefinition( search_flights_tool = ToolDefinition(
name="SearchFlights", name="SearchFlights",
description="Search for return flights from an origin to a destination within a date range (dateDepart, dateReturn).", description="Search for return flights from an origin to a destination within a date range (dateDepart, dateReturn).",

View File

@@ -0,0 +1,7 @@
import shared.config
def transfer_control(args: dict) -> dict:
return {
"new_goal": shared.config.AGENT_GOAL,
}

View File

@@ -1,5 +1,6 @@
from collections import deque from collections import deque
from datetime import timedelta from datetime import timedelta
import importlib
from typing import Dict, Any, Union, List, Optional, Deque, TypedDict from typing import Dict, Any, Union, List, Optional, Deque, TypedDict
from temporalio.common import RetryPolicy from temporalio.common import RetryPolicy
@@ -10,6 +11,9 @@ from workflows.workflow_helpers import LLM_ACTIVITY_START_TO_CLOSE_TIMEOUT, \
LLM_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT LLM_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT
from workflows import workflow_helpers as helpers from workflows import workflow_helpers as helpers
#importlib.reload(my_module)
with workflow.unsafe.imports_passed_through(): with workflow.unsafe.imports_passed_through():
from activities.tool_activities import ToolActivities from activities.tool_activities import ToolActivities
from prompts.agent_prompt_generators import ( from prompts.agent_prompt_generators import (
@@ -19,6 +23,10 @@ with workflow.unsafe.imports_passed_through():
CombinedInput, CombinedInput,
ToolPromptInput, ToolPromptInput,
) )
import shared.config
importlib.reload(shared.config)
#from shared.config import AGENT_GOAL
from tools.goal_registry import goal_match_train_invoice, goal_event_flight_invoice, goal_choose_agent_type
# Constants # Constants
MAX_TURNS_BEFORE_CONTINUE = 250 MAX_TURNS_BEFORE_CONTINUE = 250
@@ -46,11 +54,11 @@ class AgentGoalWorkflow:
@workflow.run @workflow.run
async def run(self, combined_input: CombinedInput) -> str: async def run(self, combined_input: CombinedInput) -> str:
"""Main workflow execution method.""" """Main workflow execution method."""
# setup phase # setup phase, starts with blank tool_params and agent_goal prompt as defined in tools/goal_registry.py
params = combined_input.tool_params params = combined_input.tool_params
agent_goal = combined_input.agent_goal agent_goal = combined_input.agent_goal
# set sample conversation to start # add message from sample conversation provided in tools/goal_registry.py, if it exists
if params and params.conversation_summary: if params and params.conversation_summary:
self.add_message("conversation_summary", params.conversation_summary) self.add_message("conversation_summary", params.conversation_summary)
self.conversation_summary = params.conversation_summary self.conversation_summary = params.conversation_summary
@@ -63,12 +71,25 @@ class AgentGoalWorkflow:
# interactive loop # interactive loop
while True: while True:
# wait for signals # wait for 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
) )
#update the goal, in case it's changed - doesn't help
goals = {
"goal_match_train_invoice": goal_match_train_invoice,
"goal_event_flight_invoice": goal_event_flight_invoice,
"goal_choose_agent_type": goal_choose_agent_type,
}
if shared.config.AGENT_GOAL is not None:
agent_goal = goals.get(shared.config.AGENT_GOAL)
workflow.logger.warning("AGENT_GOAL: " + shared.config.AGENT_GOAL)
# workflow.logger.warning("agent_goal", agent_goal)
#process signals of various kinds #process signals of various kinds
#chat-end signal #chat-end signal
if self.chat_ended: if self.chat_ended:
workflow.logger.info("Chat ended.") workflow.logger.info("Chat ended.")
@@ -83,6 +104,7 @@ class AgentGoalWorkflow:
confirmed_tool_data["next"] = "user_confirmed_tool_run" confirmed_tool_data["next"] = "user_confirmed_tool_run"
self.add_message("user_confirmed_tool_run", confirmed_tool_data) 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( await helpers.handle_tool_execution(
current_tool, current_tool,
self.tool_data, self.tool_data,
@@ -113,6 +135,7 @@ class AgentGoalWorkflow:
), ),
) )
#If validation fails, provide that feedback to the user - i.e., "your words make no sense, human"
if not validation_result.validationResult: if not validation_result.validationResult:
workflow.logger.warning( workflow.logger.warning(
f"Prompt validation failed: {validation_result.validationFailedReason}" f"Prompt validation failed: {validation_result.validationFailedReason}"
@@ -132,7 +155,7 @@ class AgentGoalWorkflow:
context_instructions=context_instructions, context_instructions=context_instructions,
) )
# connect to LLM and get which tool to run # connect to LLM and get...its feedback? which tool to run? ??
tool_data = await workflow.execute_activity( tool_data = await workflow.execute_activity(
ToolActivities.agent_toolPlanner, ToolActivities.agent_toolPlanner,
prompt_input, prompt_input,
@@ -171,6 +194,7 @@ class AgentGoalWorkflow:
self.add_message self.add_message
) )
#Signal that comes from api/main.py via a post to /send-prompt
@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."""
@@ -179,12 +203,14 @@ class AgentGoalWorkflow:
return return
self.prompt_queue.append(prompt) self.prompt_queue.append(prompt)
#Signal that comes from api/main.py via a post to /confirm
@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 confirmation")
self.confirm = True self.confirm = True
#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."""