- 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:
Joshua Smith
2025-04-22 12:22:42 -04:00
parent b4d57cfad6
commit 823208db3c
6 changed files with 177 additions and 104 deletions

View File

@@ -1,56 +1,144 @@
import React, { memo } from "react";
import React, { memo, useState } from "react";
/** Inline SVG icons so we dont 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>
);
/**
* Userfriendly confirmation card that surfaces tool invocation details
* without developer jargon. Tweaks include:
* • Left green accentborder + compact heading (visual hierarchy)
* • Collapsible arg list & array support (argumentlist UX)
* • Mobilefirst, 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;
// 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;
/** Recursively prettyprint argument values (objects & arrays). */
const RenderValue = ({ value }) => {
if (value === null || value === undefined) return <span className="italic"></span>;
if (Array.isArray(value)) {
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>
<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}:&nbsp;</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-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 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="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 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>
{renderArgs()}
<div className="mt-2 text-sm text-gray-500 dark:text-gray-400">
Please confirm to proceed.
{/* 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}:&nbsp;</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>
<div className="text-right mt-2">
)}
{/* Confirm button */}
<div className="text-right">
<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"
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
@@ -60,6 +148,6 @@ const ConfirmInline = memo(({ data, confirmed, onConfirm }) => {
);
});
ConfirmInline.displayName = 'ConfirmInline';
ConfirmInline.displayName = "ConfirmInline";
export default ConfirmInline;

View File

@@ -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
View File

@@ -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 agents performance <br />
[ ] non-retry the api key error - "Invalid API Key provided: sk_test_**J..." and "AuthenticationError" <br />

View File

@@ -24,23 +24,23 @@ 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="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(
[
"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',"
"'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_confirmed_tool_run: <user clicks confirm on ChangeGoal tool>",
"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(
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,
@@ -326,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:",
@@ -350,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,
@@ -478,7 +478,11 @@ else:
if multi_goal_mode:
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
else:
if list_agents_found == False:
goal.tools.append(tool_registry.list_agents_tool)
continue

View File

@@ -309,12 +309,16 @@ financial_move_money = ToolDefinition(
type="string",
description="amount to move in the order",
),
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",
),
],
)

View File

@@ -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.")
workflow.logger.info("All steps completed. 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
# else if the next step is to be done with the conversation such as if the user requests it via asking to "end conversation"