mirror of
https://github.com/temporal-community/temporal-ai-agent.git
synced 2026-03-15 14:08:08 +01:00
Add new goal to choose agent type - only kind of working
This commit is contained in:
15
api/main.py
15
api/main.py
@@ -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)
|
||||||
|
|||||||
@@ -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:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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
13
tools/change_goal.py
Normal 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
27
tools/choose_agent.py
Normal 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,
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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).",
|
||||||
|
|||||||
7
tools/transfer_control.py
Normal file
7
tools/transfer_control.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import shared.config
|
||||||
|
|
||||||
|
def transfer_control(args: dict) -> dict:
|
||||||
|
|
||||||
|
return {
|
||||||
|
"new_goal": shared.config.AGENT_GOAL,
|
||||||
|
}
|
||||||
@@ -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."""
|
||||||
|
|||||||
Reference in New Issue
Block a user