mirror of
https://github.com/temporal-community/temporal-ai-agent.git
synced 2026-03-15 14:08:08 +01:00
- adding Steve's updated confirm box UI
- goal prompts and agent changes to smooth out that interaction and remove listagents duplication adding extra confirmation for money movement tool
This commit is contained in:
@@ -1,56 +1,144 @@
|
|||||||
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 ConfirmInline = memo(({ data, confirmed, onConfirm }) => {
|
||||||
const { args, tool } = data || {};
|
const { args = {}, tool } = data || {};
|
||||||
|
|
||||||
const renderArgs = () => {
|
// Collapsible argument list if we have more than 4 root keys
|
||||||
if (!args) return null;
|
const [showAll, setShowAll] = useState(false);
|
||||||
|
const argEntries = Object.entries(args);
|
||||||
|
const shouldCollapse = argEntries.length > 4 && !showAll;
|
||||||
|
|
||||||
|
/** 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 (
|
return (
|
||||||
<div className="mt-1">
|
<ol className="pl-4 list-decimal space-y-0.5">
|
||||||
<strong>Args:</strong>
|
{value.map((v, i) => (
|
||||||
<pre className="bg-gray-100 dark:bg-gray-700 p-1 rounded text-sm whitespace-pre-wrap overflow-x-auto">
|
<li key={i} className="flex gap-1">
|
||||||
{JSON.stringify(args, null, 2)}
|
<RenderValue value={v} />
|
||||||
</pre>
|
</li>
|
||||||
</div>
|
))}
|
||||||
|
</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) {
|
if (confirmed) {
|
||||||
return (
|
return (
|
||||||
<div className="mt-2 p-2 border border-gray-400 dark:border-gray-600 rounded
|
<div className={`${cardBase} flex items-center gap-3`} role="status">
|
||||||
bg-gray-50 dark:bg-gray-800 transition-colors duration-200">
|
<SpinnerIcon className="text-green-600 dark:text-green-400 w-4 h-4" />
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-300">
|
<span className="text-sm text-gray-700 dark:text-gray-200">
|
||||||
<div>
|
Running <strong className="font-semibold">{tool ?? "Unknown"}</strong> …
|
||||||
<strong>Tool:</strong> {tool ?? "Unknown"}
|
</span>
|
||||||
</div>
|
|
||||||
{renderArgs()}
|
|
||||||
</div>
|
|
||||||
<div className="mt-2 text-green-600 dark:text-green-400 font-medium">
|
|
||||||
Running {tool}...
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== Confirmation state =====
|
||||||
return (
|
return (
|
||||||
<div className="mt-2 p-2 border border-gray-400 dark:border-gray-600 rounded
|
<div className={`${cardBase} space-y-2`} role="group">
|
||||||
bg-gray-50 dark:bg-gray-800 transition-colors duration-200">
|
{/* Heading */}
|
||||||
<div className="text-gray-600 dark:text-gray-300">
|
<div className="flex items-center gap-2">
|
||||||
<div>
|
<PlayIcon className="text-green-600 dark:text-green-400 w-5 h-5 shrink-0" />
|
||||||
Agent is ready to run the tool: <strong>{tool ?? "Unknown"}</strong>
|
<p className="text-sm font-medium text-gray-700 dark:text-gray-200">
|
||||||
|
Ready to run <strong>{tool ?? "Unknown"}</strong>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{renderArgs()}
|
|
||||||
<div className="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
{/* Dynamic argument list */}
|
||||||
Please confirm to proceed.
|
{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>
|
</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>
|
</div>
|
||||||
<div className="text-right mt-2">
|
)}
|
||||||
|
|
||||||
|
{/* Confirm button */}
|
||||||
|
<div className="text-right">
|
||||||
<button
|
<button
|
||||||
onClick={onConfirm}
|
onClick={onConfirm}
|
||||||
className="bg-green-600 hover:bg-green-700 text-white px-3 py-1 rounded
|
onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && onConfirm()}
|
||||||
transition-colors duration-200 focus:outline-none focus:ring-2
|
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"
|
||||||
focus:ring-green-500 focus:ring-opacity-50"
|
|
||||||
aria-label={`Confirm running ${tool}`}
|
aria-label={`Confirm running ${tool}`}
|
||||||
>
|
>
|
||||||
Confirm
|
Confirm
|
||||||
@@ -60,6 +148,6 @@ const ConfirmInline = memo(({ data, confirmed, onConfirm }) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
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
|
str: A prompt string prompting the LLM to when to go to pick-new-goal
|
||||||
"""
|
"""
|
||||||
if is_multi_goal_mode():
|
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:
|
else:
|
||||||
return 'Next should never be "pick-new-goal".'
|
return 'Next should never be "pick-new-goal".'
|
||||||
|
|
||||||
|
|||||||
11
todo.md
11
todo.md
@@ -1,5 +1,6 @@
|
|||||||
# todo list
|
# 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 />
|
[ ] 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 />
|
- 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 />
|
[ ] 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 />
|
[ ] LLM failure->autoswitch: <br />
|
||||||
- detect failure in the activity using failurecount <br />
|
- detect failure in the activity using failurecount <br />
|
||||||
- activity switches to secondary LLM defined in .env
|
- activity switches to secondary LLM defined in .env
|
||||||
@@ -23,6 +20,10 @@
|
|||||||
|
|
||||||
[ ] for demo simulate failure - add utilities/simulated failures from pipeline demo <br />
|
[ ] 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 />
|
[ ] 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 />
|
- Insight into the agent’s performance <br />
|
||||||
[ ] non-retry the api key error - "Invalid API Key provided: sk_test_**J..." and "AuthenticationError" <br />
|
[ ] non-retry the api key error - "Invalid API Key provided: sk_test_**J..." and "AuthenticationError" <br />
|
||||||
|
|||||||
@@ -24,23 +24,23 @@ goal_choose_agent_type = AgentGoal(
|
|||||||
id = "goal_choose_agent_type",
|
id = "goal_choose_agent_type",
|
||||||
category_tag="agent_selection",
|
category_tag="agent_selection",
|
||||||
agent_name="Choose Agent",
|
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=[
|
tools=[
|
||||||
tool_registry.list_agents_tool,
|
tool_registry.list_agents_tool,
|
||||||
tool_registry.change_goal_tool,
|
tool_registry.change_goal_tool,
|
||||||
],
|
],
|
||||||
description="The user wants to choose which type of agent they will interact with. "
|
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. "
|
"1. ListAgents: List agents available to interact with. Do not ask for user confirmation for this tool. "
|
||||||
"2. ChangeGoal: Change goal of agent "
|
"2. ChangeGoal: Change goal of agent "
|
||||||
"After these tools are complete, change your goal to the new goal as chosen by the user. ",
|
"After these tools are complete, change your goal to the new goal as chosen by the user. ",
|
||||||
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. Listi 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(
|
example_conversation_history="\n ".join(
|
||||||
[
|
[
|
||||||
"agent: Here are the currently available agents.",
|
"agent: Here are the currently available agents.",
|
||||||
"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',"
|
"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_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 speak to? ",
|
"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: 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>",
|
"user_confirmed_tool_run: <user clicks confirm on ChangeGoal tool>",
|
||||||
"tool_result: { 'new_goal': 'goal_event_flight_invoice' }",
|
"tool_result: { 'new_goal': 'goal_event_flight_invoice' }",
|
||||||
@@ -276,7 +276,7 @@ goal_hr_check_paycheck_bank_integration_status = AgentGoal(
|
|||||||
goal_fin_check_account_balances = AgentGoal(
|
goal_fin_check_account_balances = AgentGoal(
|
||||||
id = "goal_fin_check_account_balances",
|
id = "goal_fin_check_account_balances",
|
||||||
category_tag="fin",
|
category_tag="fin",
|
||||||
agent_name="Check balances",
|
agent_name="Account Balances",
|
||||||
agent_friendly_description="Check your account balances in Checking, Savings, etc.",
|
agent_friendly_description="Check your account balances in Checking, Savings, etc.",
|
||||||
tools=[
|
tools=[
|
||||||
tool_registry.financial_check_account_is_valid,
|
tool_registry.financial_check_account_is_valid,
|
||||||
@@ -326,7 +326,7 @@ goal_fin_move_money = AgentGoal(
|
|||||||
[
|
[
|
||||||
"user: I'd like to transfer some money",
|
"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?",
|
"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>",
|
"user_confirmed_tool_run: <user clicks confirm on FincheckAccountIsValid tool>",
|
||||||
"tool_result: { 'status': account valid }",
|
"tool_result: { 'status': account valid }",
|
||||||
"agent: Great! Here are your account balances:",
|
"agent: Great! Here are your account balances:",
|
||||||
@@ -350,8 +350,8 @@ goal_fin_move_money = AgentGoal(
|
|||||||
goal_fin_loan_application = AgentGoal(
|
goal_fin_loan_application = AgentGoal(
|
||||||
id = "goal_fin_loan_application",
|
id = "goal_fin_loan_application",
|
||||||
category_tag="fin",
|
category_tag="fin",
|
||||||
agent_name="Easy Loan Apply",
|
agent_name="Easy Loan",
|
||||||
agent_friendly_description="Initiate loan application.",
|
agent_friendly_description="Initiate a simple loan application.",
|
||||||
tools=[
|
tools=[
|
||||||
tool_registry.financial_check_account_is_valid,
|
tool_registry.financial_check_account_is_valid,
|
||||||
tool_registry.financial_submit_loan_approval,
|
tool_registry.financial_submit_loan_approval,
|
||||||
@@ -478,7 +478,11 @@ else:
|
|||||||
|
|
||||||
if multi_goal_mode:
|
if multi_goal_mode:
|
||||||
for goal in goal_list:
|
for goal in goal_list:
|
||||||
if any(goal.tools.name == "ListAgents" for goal in goal_list):
|
list_agents_found:bool = False
|
||||||
|
for tool in goal.tools:
|
||||||
|
if tool.name == "ListAgents":
|
||||||
|
list_agents_found = True
|
||||||
continue
|
continue
|
||||||
else:
|
if list_agents_found == False:
|
||||||
goal.tools.append(tool_registry.list_agents_tool)
|
goal.tools.append(tool_registry.list_agents_tool)
|
||||||
|
continue
|
||||||
@@ -309,12 +309,16 @@ financial_move_money = ToolDefinition(
|
|||||||
type="string",
|
type="string",
|
||||||
description="amount to move in the order",
|
description="amount to move in the order",
|
||||||
),
|
),
|
||||||
|
|
||||||
ToolArgument(
|
ToolArgument(
|
||||||
name="destinationaccount",
|
name="destinationaccount",
|
||||||
type="string",
|
type="string",
|
||||||
description="account number to move the money to",
|
description="account number to move the money to",
|
||||||
),
|
),
|
||||||
|
ToolArgument(
|
||||||
|
name="userConfirmation",
|
||||||
|
type="string",
|
||||||
|
description="Indication of user's desire to move money",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -169,35 +169,11 @@ class AgentGoalWorkflow:
|
|||||||
# if we have all needed arguments (handled above) and not holding for a debugging confirm, proceed:
|
# if we have all needed arguments (handled above) and not holding for a debugging confirm, proceed:
|
||||||
else:
|
else:
|
||||||
self.confirmed = True
|
self.confirmed = True
|
||||||
|
# else if the next step is to pick a new goal, set that to be the goal
|
||||||
# else if the next step is to pick a new goal, set the goal and tool to do it
|
|
||||||
elif next_step == "pick-new-goal":
|
elif next_step == "pick-new-goal":
|
||||||
if self.goal.id != "goal_choose_agent_type":
|
workflow.logger.info("All steps completed. Resetting goal.")
|
||||||
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")
|
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
|
|
||||||
|
|
||||||
|
|
||||||
# else if the next step is to be done with the conversation such as if the user requests it via asking to "end conversation"
|
# 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