mirror of
https://github.com/temporal-community/temporal-ai-agent.git
synced 2026-03-15 14:08:08 +01:00
@@ -370,8 +370,8 @@ class ToolActivities:
|
||||
print("Initialized Anthropic client on demand")
|
||||
|
||||
response = self.anthropic_client.messages.create(
|
||||
#model="claude-3-5-sonnet-20241022", # todo try claude-3-7-sonnet-20250219
|
||||
model="claude-3-7-sonnet-20250219", # todo try claude-3-7-sonnet-20250219
|
||||
model="claude-3-5-sonnet-20241022",
|
||||
#model="claude-3-7-sonnet-20250219", # doesn't do as well
|
||||
max_tokens=1024,
|
||||
system=input.context_instructions
|
||||
+ ". The current date is "
|
||||
|
||||
@@ -1,65 +1,153 @@
|
||||
import React, { memo } from "react";
|
||||
import React, { memo, useState } from "react";
|
||||
|
||||
/** Inline SVG icons so we don’t need an extra library */
|
||||
const PlayIcon = ({ className }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M5 3.868v16.264c0 1.04 1.12 1.675 2.025 1.16l13.11-8.132a1.33 1.33 0 000-2.256L7.025 2.773C6.12 2.259 5 2.894 5 3.934z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const SpinnerIcon = ({ className }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={`animate-spin ${className}`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" strokeOpacity="0.25" />
|
||||
<path d="M22 12a10 10 0 00-10-10" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* User‑friendly confirmation card that surfaces tool invocation details
|
||||
* without developer jargon. Tweaks include:
|
||||
* • Left green accent‑border + compact heading (visual hierarchy)
|
||||
* • Collapsible arg list & array support (argument‑list UX)
|
||||
* • Mobile‑first, pulsing confirm button (button affordance)
|
||||
*/
|
||||
const ConfirmInline = memo(({ data, confirmed, onConfirm }) => {
|
||||
const { args, tool } = data || {};
|
||||
const { args = {}, tool } = data || {};
|
||||
|
||||
const renderArgs = () => {
|
||||
if (!args) return null;
|
||||
|
||||
return (
|
||||
<div className="mt-1">
|
||||
<strong>Args:</strong>
|
||||
<pre className="bg-gray-100 dark:bg-gray-700 p-1 rounded text-sm whitespace-pre-wrap overflow-x-auto">
|
||||
{JSON.stringify(args, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// Collapsible argument list if we have more than 4 root keys
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
const argEntries = Object.entries(args);
|
||||
const shouldCollapse = argEntries.length > 4 && !showAll;
|
||||
|
||||
if (confirmed) {
|
||||
return (
|
||||
<div className="mt-2 p-2 border border-gray-400 dark:border-gray-600 rounded
|
||||
bg-gray-50 dark:bg-gray-800 transition-colors duration-200">
|
||||
<div className="text-sm text-gray-600 dark:text-gray-300">
|
||||
<div>
|
||||
<strong>Tool:</strong> {tool ?? "Unknown"}
|
||||
</div>
|
||||
{renderArgs()}
|
||||
</div>
|
||||
<div className="mt-2 text-green-600 dark:text-green-400 font-medium">
|
||||
Running {tool}...
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
/** Recursively pretty‑print argument values (objects & arrays). */
|
||||
const RenderValue = ({ value }) => {
|
||||
if (value === null || value === undefined) return <span className="italic">‑</span>;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return (
|
||||
<ol className="pl-4 list-decimal space-y-0.5">
|
||||
{value.map((v, i) => (
|
||||
<li key={i} className="flex gap-1">
|
||||
<RenderValue value={v} />
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof value === "object") {
|
||||
return (
|
||||
<ul className="pl-4 space-y-0.5 list-disc marker:text-green-500 dark:marker:text-green-400">
|
||||
{Object.entries(value).map(([k, v]) => (
|
||||
<li key={k} className="flex gap-1">
|
||||
<span className="capitalize text-gray-600 dark:text-gray-300">{k}: </span>
|
||||
<RenderValue value={v} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
return <span className="font-medium text-gray-800 dark:text-gray-100">{String(value)}</span>;
|
||||
};
|
||||
|
||||
const cardBase =
|
||||
"mt-2 p-3 rounded-lg border-l-4 border-green-500 bg-gray-100/60 dark:bg-gray-800/60 shadow-sm";
|
||||
|
||||
// ===== Running state =====
|
||||
if (confirmed) {
|
||||
return (
|
||||
<div className="mt-2 p-2 border border-gray-400 dark:border-gray-600 rounded
|
||||
bg-gray-50 dark:bg-gray-800 transition-colors duration-200">
|
||||
<div className="text-gray-600 dark:text-gray-300">
|
||||
<div>
|
||||
Agent is ready to run the tool: <strong>{tool ?? "Unknown"}</strong>
|
||||
</div>
|
||||
{renderArgs()}
|
||||
<div className="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
Please confirm to proceed.
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right mt-2">
|
||||
<button
|
||||
onClick={onConfirm}
|
||||
className="bg-green-600 hover:bg-green-700 text-white px-3 py-1 rounded
|
||||
transition-colors duration-200 focus:outline-none focus:ring-2
|
||||
focus:ring-green-500 focus:ring-opacity-50"
|
||||
aria-label={`Confirm running ${tool}`}
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${cardBase} flex items-center gap-3`} role="status">
|
||||
<SpinnerIcon className="text-green-600 dark:text-green-400 w-4 h-4" />
|
||||
<span className="text-sm text-gray-700 dark:text-gray-200">
|
||||
Running <strong className="font-semibold">{tool ?? "Unknown"}</strong> …
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ===== Confirmation state =====
|
||||
return (
|
||||
<div className={`${cardBase} space-y-2`} role="group">
|
||||
{/* Heading */}
|
||||
<div className="flex items-center gap-2">
|
||||
<PlayIcon className="text-green-600 dark:text-green-400 w-5 h-5 shrink-0" />
|
||||
<p className="text-sm font-medium text-gray-700 dark:text-gray-200">
|
||||
Ready to run <strong>{tool ?? "Unknown"}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Dynamic argument list */}
|
||||
{argEntries.length > 0 && (
|
||||
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||
{argEntries
|
||||
.slice(0, shouldCollapse ? 4 : argEntries.length)
|
||||
.map(([k, v]) => (
|
||||
<div key={k} className="flex gap-1">
|
||||
<span className="capitalize">{k}: </span>
|
||||
<RenderValue value={v} />
|
||||
</div>
|
||||
))}
|
||||
{shouldCollapse && (
|
||||
<button
|
||||
onClick={() => setShowAll(true)}
|
||||
className="mt-1 text-green-600 dark:text-green-400 text-xs underline hover:no-underline"
|
||||
>
|
||||
…show all
|
||||
</button>
|
||||
)}
|
||||
{showAll && argEntries.length > 4 && (
|
||||
<button
|
||||
onClick={() => setShowAll(false)}
|
||||
className="mt-1 block text-green-600 dark:text-green-400 text-xs underline hover:no-underline"
|
||||
>
|
||||
show less
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Confirm button */}
|
||||
<div className="text-right">
|
||||
<button
|
||||
onClick={onConfirm}
|
||||
onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && onConfirm()}
|
||||
className="w-full sm:w-auto bg-green-600 hover:bg-green-700 text-white text-sm px-3 py-1.5 rounded-md shadow-sm transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-1 animate-pulse sm:animate-none"
|
||||
aria-label={`Confirm running ${tool}`}
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
ConfirmInline.displayName = 'ConfirmInline';
|
||||
ConfirmInline.displayName = "ConfirmInline";
|
||||
|
||||
export default ConfirmInline;
|
||||
export default ConfirmInline;
|
||||
@@ -187,7 +187,7 @@ def generate_pick_new_goal_guidance()-> str:
|
||||
str: A prompt string prompting the LLM to when to go to pick-new-goal
|
||||
"""
|
||||
if is_multi_goal_mode():
|
||||
return 'Next should only be "pick-new-goal" if all tools have been run for the current goal (use the system prompt to figure that out) and the last successful tool was not ListAgents, or the user explicitly requested to pick a new goal.'
|
||||
return 'Next should only be "pick-new-goal" if all tools have been run for the current goal (use the system prompt to figure that out), or the user explicitly requested to pick a new goal.'
|
||||
else:
|
||||
return 'Next should never be "pick-new-goal".'
|
||||
|
||||
|
||||
11
todo.md
11
todo.md
@@ -1,5 +1,6 @@
|
||||
# todo list
|
||||
[ ] expand [tests](./tests/agent_goal_workflow_test.py)<br />
|
||||
[x] take steve's confirm box changes https://temporaltechnologies.slack.com/archives/D062SV8KEEM/p1745251279164319 <br />
|
||||
[ ] consider adding goal categories to goal picker
|
||||
|
||||
[ ] adding fintech goals <br />
|
||||
- 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.<br />
|
||||
@@ -12,10 +13,6 @@
|
||||
|
||||
[ ] for demo simulate failure - add utilities/simulated failures from pipeline demo <br />
|
||||
|
||||
[x] ecommerce goals <br />
|
||||
- [x] add to docs <br />
|
||||
- [x] decide about api key names with Laine <br />
|
||||
|
||||
[ ] LLM failure->autoswitch: <br />
|
||||
- detect failure in the activity using failurecount <br />
|
||||
- activity switches to secondary LLM defined in .env
|
||||
@@ -23,6 +20,10 @@
|
||||
|
||||
[ ] for demo simulate failure - add utilities/simulated failures from pipeline demo <br />
|
||||
|
||||
[ ] expand [tests](./tests/agent_goal_workflow_test.py)<br />
|
||||
[ ] collapse history/summarize after goal finished <br />
|
||||
[ ] add aws bedrock <br />
|
||||
|
||||
[ ] 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 <br />
|
||||
- Insight into the agent’s performance <br />
|
||||
[ ] non-retry the api key error - "Invalid API Key provided: sk_test_**J..." and "AuthenticationError" <br />
|
||||
|
||||
@@ -5,7 +5,7 @@ import json
|
||||
# this assumes it's a valid account - use check_account_valid() to verify that first
|
||||
def get_account_balance(args: dict) -> dict:
|
||||
|
||||
account_key = args.get("accountkey")
|
||||
account_key = args.get("email_address_or_account_ID")
|
||||
|
||||
file_path = Path(__file__).resolve().parent.parent / "data" / "customer_account_data.json"
|
||||
if not file_path.exists():
|
||||
|
||||
@@ -31,7 +31,7 @@ class MoneyMovementWorkflowParameterObj:
|
||||
# this assumes it's a valid account - use check_account_valid() to verify that first
|
||||
async def move_money(args: dict) -> dict:
|
||||
|
||||
account_key = args.get("accountkey")
|
||||
account_key = args.get("email_address_or_account_ID")
|
||||
account_type: str = args.get("accounttype")
|
||||
amount = args.get("amount")
|
||||
destinationaccount = args.get("destinationaccount")
|
||||
|
||||
@@ -31,7 +31,7 @@ class TxResult:
|
||||
|
||||
#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")
|
||||
account_key = args.get("email_address_or_account_ID")
|
||||
amount = args.get("amount")
|
||||
|
||||
loan_status: dict = await start_workflow(amount=amount,account_name=account_key)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
from typing import List
|
||||
from models.tool_definitions import AgentGoal
|
||||
import tools.tool_registry as tool_registry
|
||||
@@ -23,24 +24,24 @@ goal_choose_agent_type = AgentGoal(
|
||||
id = "goal_choose_agent_type",
|
||||
category_tag="agent_selection",
|
||||
agent_name="Choose Agent",
|
||||
agent_friendly_description="Choose the type of agent to assist you today.",
|
||||
agent_friendly_description="Choose the type of agent to assist you today. You can always interrupt an existing agent to pick a new one.",
|
||||
tools=[
|
||||
tool_registry.list_agents_tool,
|
||||
tool_registry.change_goal_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: "
|
||||
"Help the user select an agent by gathering args for the Changegoal tool, in order: "
|
||||
"1. ListAgents: List agents available to interact with. Do not ask for user confirmation for this tool. "
|
||||
"2. ChangeGoal: Change goal of agent "
|
||||
"After these tools are complete, change your goal to the new goal as chosen by the user. ",
|
||||
starter_prompt=starter_prompt_generic + " Begin by listing all details of all agents as provided by the output of the first tool included in this goal. ",
|
||||
starter_prompt= silly_prompt + "Welcome me, give me a description of what you can do, then ask me for the details you need to do your job. List all details of all agents as provided by the output of the first tool included in this goal. ",
|
||||
example_conversation_history="\n ".join(
|
||||
[
|
||||
"agent: Here are the currently available agents.",
|
||||
"user_confirmed_tool_run: <user clicks confirm on ListAgents 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. \n Which agent would you like to speak to? (You can respond with name or number.)",
|
||||
"user: 1, Event Flight Finder",
|
||||
"tool_result: { agents: 'agent_name': 'Event Flight Finder', 'goal_id': 'goal_event_flight_invoice', 'agent_description': 'Helps users find interesting events and arrange travel to them',"
|
||||
"'agent_name': 'Schedule PTO', 'goal_id': 'goal_hr_schedule_pto', 'agent_description': 'Schedule PTO based on your available PTO.' }",
|
||||
"agent: The available agents are: Event Flight Finder and Schedule PTO. \n Which agent would you like to work with? ",
|
||||
"user: I'd like to find an event and book flights using the Event Flight Finder",
|
||||
"user_confirmed_tool_run: <user clicks confirm on ChangeGoal tool>",
|
||||
"tool_result: { 'new_goal': 'goal_event_flight_invoice' }",
|
||||
]
|
||||
@@ -275,7 +276,7 @@ goal_hr_check_paycheck_bank_integration_status = AgentGoal(
|
||||
goal_fin_check_account_balances = AgentGoal(
|
||||
id = "goal_fin_check_account_balances",
|
||||
category_tag="fin",
|
||||
agent_name="Check balances",
|
||||
agent_name="Account Balances",
|
||||
agent_friendly_description="Check your account balances in Checking, Savings, etc.",
|
||||
tools=[
|
||||
tool_registry.financial_check_account_is_valid,
|
||||
@@ -288,7 +289,7 @@ goal_fin_check_account_balances = AgentGoal(
|
||||
example_conversation_history="\n ".join(
|
||||
[
|
||||
"user: I'd like to check my account balances",
|
||||
"agent: Sure! I can help you out with that. May I have your email address or account number?",
|
||||
"agent: Sure! I can help you out with that. May I have your email address and account number?",
|
||||
"user: email is bob.johnson@emailzzz.com ",
|
||||
"user_confirmed_tool_run: <user clicks confirm on FincheckAccountIsValid tool>",
|
||||
"tool_result: { 'status': account valid }",
|
||||
@@ -325,7 +326,7 @@ goal_fin_move_money = AgentGoal(
|
||||
[
|
||||
"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: my account number is 11235 and my email address is matt.murdock@nelsonmurdock.com",
|
||||
"user_confirmed_tool_run: <user clicks confirm on FincheckAccountIsValid tool>",
|
||||
"tool_result: { 'status': account valid }",
|
||||
"agent: Great! Here are your account balances:",
|
||||
@@ -349,8 +350,8 @@ goal_fin_move_money = AgentGoal(
|
||||
goal_fin_loan_application = AgentGoal(
|
||||
id = "goal_fin_loan_application",
|
||||
category_tag="fin",
|
||||
agent_name="Easy Loan Apply",
|
||||
agent_friendly_description="Initiate loan application.",
|
||||
agent_name="Easy Loan",
|
||||
agent_friendly_description="Initiate a simple loan application.",
|
||||
tools=[
|
||||
tool_registry.financial_check_account_is_valid,
|
||||
tool_registry.financial_submit_loan_approval,
|
||||
@@ -362,7 +363,7 @@ goal_fin_loan_application = AgentGoal(
|
||||
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?",
|
||||
"agent: Sure! I can help you out with that. May I have account number and email address to validate your account?",
|
||||
"user: account number is 11235813",
|
||||
"user_confirmed_tool_run: <user clicks confirm on FincheckAccountIsValid tool>",
|
||||
"tool_result: { 'status': account valid }",
|
||||
@@ -465,3 +466,23 @@ goal_list.append(goal_fin_loan_application)
|
||||
goal_list.append(goal_ecomm_list_orders)
|
||||
goal_list.append(goal_ecomm_order_status)
|
||||
|
||||
|
||||
# for multi-goal, just set list agents as the last tool
|
||||
first_goal_value = os.getenv("AGENT_GOAL")
|
||||
if first_goal_value is None:
|
||||
multi_goal_mode = True # default if unset
|
||||
elif first_goal_value is not None and first_goal_value.lower() != "goal_choose_agent_type":
|
||||
multi_goal_mode = False
|
||||
else:
|
||||
multi_goal_mode = True
|
||||
|
||||
if multi_goal_mode:
|
||||
for goal in goal_list:
|
||||
list_agents_found:bool = False
|
||||
for tool in goal.tools:
|
||||
if tool.name == "ListAgents":
|
||||
list_agents_found = True
|
||||
continue
|
||||
if list_agents_found == False:
|
||||
goal.tools.append(tool_registry.list_agents_tool)
|
||||
continue
|
||||
@@ -281,7 +281,7 @@ financial_get_account_balances = ToolDefinition(
|
||||
|
||||
arguments=[
|
||||
ToolArgument(
|
||||
name="accountkey",
|
||||
name="email_address_or_account_ID",
|
||||
type="string",
|
||||
description="email address or account ID of user",
|
||||
),
|
||||
@@ -295,26 +295,30 @@ financial_move_money = ToolDefinition(
|
||||
|
||||
arguments=[
|
||||
ToolArgument(
|
||||
name="accountkey",
|
||||
name="email_address_or_account_ID",
|
||||
type="string",
|
||||
description="email address or account ID of user",
|
||||
),
|
||||
ToolArgument(
|
||||
ToolArgument(
|
||||
name="accounttype",
|
||||
type="string",
|
||||
description="account type, such as checking or savings",
|
||||
),
|
||||
ToolArgument(
|
||||
ToolArgument(
|
||||
name="amount",
|
||||
type="string",
|
||||
description="amount to move in the order",
|
||||
),
|
||||
|
||||
ToolArgument(
|
||||
ToolArgument(
|
||||
name="destinationaccount",
|
||||
type="string",
|
||||
description="account number to move the money to",
|
||||
),
|
||||
ToolArgument(
|
||||
name="userConfirmation",
|
||||
type="string",
|
||||
description="Indication of user's desire to move money",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -325,7 +329,7 @@ financial_submit_loan_approval = ToolDefinition(
|
||||
|
||||
arguments=[
|
||||
ToolArgument(
|
||||
name="accountkey",
|
||||
name="email_address_or_account_ID",
|
||||
type="string",
|
||||
description="email address or account ID of user",
|
||||
),
|
||||
|
||||
@@ -169,35 +169,11 @@ class AgentGoalWorkflow:
|
||||
# if we have all needed arguments (handled above) and not holding for a debugging confirm, proceed:
|
||||
else:
|
||||
self.confirmed = True
|
||||
|
||||
# else if the next step is to pick a new goal, set the goal and tool to do it
|
||||
# else if the next step is to pick a new goal, set that to be the goal
|
||||
elif next_step == "pick-new-goal":
|
||||
if self.goal.id != "goal_choose_agent_type":
|
||||
self.add_message("agent", tool_data)
|
||||
workflow.logger.info("All tools completed and new Agent Goal recommended. Resetting goal.")
|
||||
self.change_goal("goal_choose_agent_type")
|
||||
next_step = tool_data["next"] = "confirm"
|
||||
current_tool = tool_data["tool"] = "ListAgents"
|
||||
waiting_for_confirm = True
|
||||
self.tool_data = tool_data
|
||||
if self.show_tool_args_confirmation:
|
||||
self.confirmed = False
|
||||
# if we have all needed arguments (handled above) and not holding for a debugging confirm, proceed:
|
||||
else:
|
||||
self.confirmed = True
|
||||
continue
|
||||
else:
|
||||
if not current_tool == "ListAgents":
|
||||
current_tool = tool_data["tool"] = "ListAgents"
|
||||
waiting_for_confirm = True
|
||||
|
||||
self.tool_data = tool_data
|
||||
next_step = tool_data["next"] = "confirm"
|
||||
if self.show_tool_args_confirmation:
|
||||
self.confirmed = False
|
||||
# if we have all needed arguments (handled above) and not holding for a debugging confirm, proceed:
|
||||
else:
|
||||
self.confirmed = True
|
||||
workflow.logger.info("All steps completed. Resetting goal.")
|
||||
self.change_goal("goal_choose_agent_type")
|
||||
|
||||
|
||||
|
||||
# else if the next step is to be done with the conversation such as if the user requests it via asking to "end conversation"
|
||||
|
||||
Reference in New Issue
Block a user