Compare commits
49 Commits
review-jos
...
0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edb7df5b3c | ||
|
|
05041f9433 | ||
|
|
0767533ca1 | ||
|
|
611a6b6238 | ||
|
|
7850af6be2 | ||
|
|
a29b100017 | ||
|
|
a5fc6ad477 | ||
|
|
c9ae8b29bf | ||
|
|
7ef10e2481 | ||
|
|
183f834635 | ||
|
|
47c4b99f2c | ||
|
|
823208db3c | ||
|
|
b4aa929451 | ||
|
|
b4d57cfad6 | ||
|
|
2a1624f621 | ||
|
|
326a5a5beb | ||
|
|
163477c066 | ||
|
|
d48dafcaa5 | ||
|
|
32e856e494 | ||
|
|
a07af3e7bf | ||
|
|
fc07315358 | ||
|
|
ebb12feafa | ||
|
|
7b2c7cef8f | ||
|
|
4410f30642 | ||
|
|
77942b19a1 | ||
|
|
247bac0a28 | ||
|
|
cf55f0eaee | ||
|
|
0c678a120a | ||
|
|
6383614076 | ||
|
|
92bce11151 | ||
|
|
e5405907fa | ||
|
|
dddf7f4ccd | ||
|
|
6f245a1998 | ||
|
|
86a6dfe991 | ||
|
|
83c6a2454d | ||
|
|
463ae581ac | ||
|
|
e62b105872 | ||
|
|
7b52b8a817 | ||
|
|
6f9079ba12 | ||
|
|
ac44d35acb | ||
|
|
50463170d2 | ||
|
|
13d0085882 | ||
|
|
e92e3f43c9 | ||
|
|
812e295f3a | ||
|
|
dddd2977b9 | ||
|
|
4e0ff0e535 | ||
|
|
aba934e8b4 | ||
|
|
2539436a79 | ||
|
|
6517b624ee |
@@ -1,5 +1,6 @@
|
||||
RAPIDAPI_KEY=9df2cb5...
|
||||
RAPIDAPI_HOST=sky-scrapper.p.rapidapi.com
|
||||
RAPIDAPI_HOST_FLIGHTS=sky-scrapper.p.rapidapi.com #For travel flight information tool
|
||||
RAPIDAPI_HOST_PACKAGE=trackingpackage.p.rapidapi.com #For eCommerce order status package tracking tool
|
||||
FOOTBALL_DATA_API_KEY=....
|
||||
|
||||
STRIPE_API_KEY=sk_test_51J...
|
||||
@@ -42,7 +43,7 @@ AGENT_GOAL=goal_choose_agent_type # for multi-goal start
|
||||
|
||||
#Choose which category(ies) of goals you want to be listed by the Agent Goal picker if enabled above
|
||||
# - options are system (always included), hr, travel, or all.
|
||||
GOAL_CATEGORIES=hr,travel-flights,travel-trains,fin # default is all
|
||||
GOAL_CATEGORIES=fin # default is all
|
||||
#GOAL_CATEGORIES=travel-flights
|
||||
|
||||
# Set if the workflow should wait for the user to click a confirm button (and if the UI should show the confirm button and tool args)
|
||||
|
||||
2
.gitignore
vendored
@@ -32,4 +32,4 @@ coverage.xml
|
||||
.idea/
|
||||
|
||||
.env
|
||||
*.env
|
||||
.env*
|
||||
|
||||
30
CHANGELOG.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.2.0] - 2025-04-24
|
||||
|
||||

|
||||
|
||||
### Added
|
||||
- **Multi‑goal agent architecture** with dynamic goal switching (`goal_choose_agent_type`, `ListAgents`, `ChangeGoal`).
|
||||
- See [the architecture guide](./architecture.md) and [setup guide](./setup.md).
|
||||
- **New goal categories & agents**: HR PTO scheduling/checking, paycheck integration, Financial (balances, money movement, loan application), E‑commerce order tracking.
|
||||
- See [the guide for adding goals and tools](./adding-goals-and-tools.md).
|
||||
- **Force Confirmation**: `SHOW_CONFIRM` will show a confirmation box before allowing the agent to run a tool.
|
||||
- **Grok (`x.ai`) LLM provider** support via `GROK_API_KEY`.
|
||||
- Extensive **docs**: `setup.md`, `architecture.md`, `architecture-decisions.md`, `adding-goals-and-tools.md`, plus new diagrams & assets.
|
||||
|
||||
### Changed
|
||||
- **UI Confirmation Box** is less 'debug' looking and prettier.
|
||||
- Package renamed to **`temporal_AI_agent`** and version bumped to **0.2.0** in `pyproject.toml`.
|
||||
- Environment variables changed (see `.env_example`): (`RAPIDAPI_HOST_*`, `AGENT_GOAL` defaults, `GOAL_CATEGORIES`, `SHOW_CONFIRM`, `FIN_START_REAL_WORKFLOW`).
|
||||
|
||||
## [0.1.0] - 2025-01-04
|
||||
|
||||
### Added
|
||||
- **Initial release** of the Temporal AI Agent demo.
|
||||
- **Single goal agent** architecture with a single goal and agent type.
|
||||
- This is the agent demoed in the [YouTube video](https://www.youtube.com/watch?v=GEXllEH2XiQ).
|
||||
|
||||
[0.2.0]: https://github.com/temporal-community/temporal-ai-agent/pull/29
|
||||
31
README.md
@@ -8,19 +8,22 @@ It's really helpful to [watch the demo (5 minute YouTube video)](https://www.you
|
||||
|
||||
[](https://www.youtube.com/watch?v=GEXllEH2XiQ)
|
||||
|
||||
### Multi-Agent Demo Video
|
||||
See multi-agent execution in action [here](https://www.youtube.com/watch?v=8Dc_0dC14yY).
|
||||
|
||||
## Why Temporal?
|
||||
There are a lot of AI and Agentic AI tools out there, and more on the way. But why Temporal? Temporal gives this system reliablity, state management, a code-first approach that we really like, built-in observability and easy error handling.
|
||||
For more, check out [architecture-decisions](./architecture-decisions.md).
|
||||
|
||||
## What is "Agentic AI"?
|
||||
These are the key elements of an agentic framework:
|
||||
1. Goals a human can get done, made up of tools that can execute individual steps
|
||||
2. The "agent loop" - call LLM, either call tools or prompt human, repeat until goal(s) are done
|
||||
3. Support for tool calls that require human input and approval
|
||||
1. Goals that a system can accomplish, made up of tools that can execute individual steps
|
||||
2. Agent loops - executing an LLM, executing tools, and eliciting input from an external source such as a human: repeat until goal(s) are done
|
||||
3. Support for tool calls that require input and approval
|
||||
4. Use of an LLM to check human input for relevance before calling the 'real' LLM
|
||||
5. use of an LLM to summarize and compact the conversation history
|
||||
6. Prompt construction (made of system prompts, conversation history, and tool metadata - sent to the LLM to create user prompts)
|
||||
7. Bonus: durable tool execution via Temporal Activities
|
||||
5. Use of an LLM to summarize and compact the conversation history
|
||||
6. Prompt construction made of system prompts, conversation history, and tool metadata - sent to the LLM to create user questions and confirmations
|
||||
7. Ideally high durability (done in this system with Temporal Workflow and Activities)
|
||||
|
||||
For a deeper dive into this, check out the [architecture guide](./architecture.md).
|
||||
|
||||
@@ -35,8 +38,7 @@ See [the architecture guide](./architecture.md).
|
||||
|
||||
## Productionalization & Adding Features
|
||||
- In a prod setting, I would need to ensure that payload data is stored separately (e.g. in S3 or a noSQL db - the claim-check pattern), or otherwise 'garbage collected'. Without these techniques, long conversations will fill up the workflow's conversation history, and start to breach Temporal event history payload limits.
|
||||
- A single worker can easily support many workflows - setting workflow ID differently would enable this.
|
||||
- Continue-as-new shouldn't be a big consideration for this use case (as it would take many conversational turns to trigger). Regardless, we should verify that it's able to carry the agent state over to the new workflow execution.
|
||||
- A single worker can easily support many agent workflows (chats) running at the same time. Currently the workflow ID is the same each time, so it will only run one agent at a time. To run multiple agents, you can use a different workflow ID each time (e.g. by using a UUID or timestamp).
|
||||
- Perhaps the UI should show when the LLM response is being retried (i.e. activity retry attempt because the LLM provided bad output)
|
||||
- Tests would be nice! [See tests](./tests/).
|
||||
|
||||
@@ -45,16 +47,7 @@ See [the todo](./todo.md) for more details.
|
||||
|
||||
See [the guide to adding goals and tools](./adding-goals-and-tools.md) for more ways you can add features.
|
||||
|
||||
## For Temporal SAs
|
||||
Check out the [slides](https://docs.google.com/presentation/d/1wUFY4v17vrtv8llreKEBDPLRtZte3FixxBUn0uWy5NU/edit#slide=id.g3333e5deaa9_0_0) here and the enablement guide here (TODO).
|
||||
## Enablement Guide (internal resource for Temporal employees)
|
||||
Check out the [slides](https://docs.google.com/presentation/d/1wUFY4v17vrtv8llreKEBDPLRtZte3FixxBUn0uWy5NU/edit#slide=id.g3333e5deaa9_0_0) here and the [enablement guide](https://docs.google.com/document/d/14E0cEOibUAgHPBqConbWXgPUBY0Oxrnt6_AImdiheW4/edit?tab=t.0#heading=h.ajnq2v3xqbu1).
|
||||
|
||||
## Tests
|
||||
|
||||
Running the tests requires `poe` and `pytest_asyncio` to be installed.
|
||||
|
||||
python -m pip install poethepoet
|
||||
python -m pip install pytest_asyncio
|
||||
|
||||
Once you have `poe` and `pytest_asyncio` installed you can run:
|
||||
|
||||
poe test
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
# Customizing the Agent
|
||||
The agent is set up to allow for multiple goals and to switch back to choosing a new goal at the end of every successful goal. A goal is made up of a list of tools that the agent will guide the user through.
|
||||
The agent is set up to have multiple agents, each with their own goal. It supports switching back to choosing a new goal at the end of every successful goal (or even mid-goal).
|
||||
A goal is made up of a list of tools that the agent will guide the user through.
|
||||
|
||||
It may be helpful to review the [architecture](./architecture.md) for a guide and definition of goals, tools, etc.
|
||||
|
||||
|
||||
@@ -1,58 +1,65 @@
|
||||
# Elements
|
||||
These are the main elements of this system.
|
||||

|
||||
These are the main elements of this system. See [architecture decisions](./architecture-decisions.md) for information beind these choices.
|
||||
In this document we will explain each element and their interactions, and then connect them all at the end.
|
||||
<img src="./assets/Architecture_elements.png" width="50%" alt="Architecture Elements">
|
||||
|
||||
## Workflow
|
||||
This is a [Temporal Workflow](https://docs.temporal.io/workflows) - a durable straightforward description of the process to be executed. For our example see [agent_goal_workflow.py](./workflows/agent_goal_workflow.py).
|
||||
This is a [Temporal Workflow](https://docs.temporal.io/workflows) - a durable straightforward description of the process to be executed. See [agent_goal_workflow.py](./workflows/agent_goal_workflow.py).
|
||||
Temporal is used to make the process scalable, durable, reliable, secure, and visible.
|
||||
|
||||
### Workflow Responsibilities:
|
||||
- Orchestrates interactive loop
|
||||
- Prompts LLM, Users
|
||||
- Orchestrates interactive loops:
|
||||
- LLM Loop: Prompts LLM, durably executes LLM, stores responses
|
||||
- Interactive Loop: Elicits responses from input (in our case a human) and validates input responses
|
||||
- Tool Execution Loop: Durably executes Tools
|
||||
- Keeps record of all interactions ([Signals, Queries, Updates](https://docs.temporal.io/develop/python/message-passing))
|
||||
- Executes LLM durably
|
||||
- Executes Tools durably
|
||||
- Handles failures gracefully
|
||||
- Human, LLM and tool interaction history stored for debugging and analysis
|
||||
- Input, LLM and Tool interaction history stored for debugging and analysis
|
||||
|
||||
## Activities
|
||||
These are [Temporal Activities](https://docs.temporal.io/activities). Defined as simple functions, they are auto-retried async/event driven behind the scenes. Activities durably execute Tools and the LLM. See [a sample activity](./activities/tool_activities.py).
|
||||
|
||||
## Tools
|
||||
Tools define the capabilities of the system. They are simple Python functions (could be in any language).
|
||||
They are executed by Temporal Activities. They are “just code” - can connect to any API or system. They also are where the "hard" business logic is: you can validate and retry actions using code you write.
|
||||
Tools define the capabilities of the system. They are simple Python functions (could be in any language as Temporal supports multiple languages).
|
||||
They are executed by Temporal Activities. They are “just code” - can connect to any API or system. They also are where the deterministic business logic is: you can validate and retry actions using code you write.
|
||||
Failures are handled gracefully by Temporal.
|
||||
|
||||
Activities + Tools turn the probabalistic input from the user and LLM into deterministic action.
|
||||
|
||||
## Prompts
|
||||
Prompts are where the instructions to the LLM & users is. Prompts are made up of initial instructions, goal instructions, and tool instructions.
|
||||
Prompts are where the instructions to the LLM are. Prompts are made up of initial instructions, goal instructions, and tool instructions.
|
||||
See [agent prompts](./prompts/agent_prompt_generators.py) and [goal & tool prompts](./tools/goal_registry.py).
|
||||
|
||||
This is where you can add probabalistic business logic, to control process flow, describe what to do, and give instruction and validation for the LLM.
|
||||
This is where you can add probabalistic business logic to
|
||||
- to control process flow
|
||||
- describe what to do
|
||||
- give examples of interactions
|
||||
- give instruction and validation for the LLM
|
||||
|
||||
## LLM
|
||||
Probabalistic execution: it will _probably_ do what you tell it to do.
|
||||
Turns the guidance from the prompts (see [agent prompts](./prompts/agent_prompt_generators.py) and [goal prompts](./tools/goal_registry.py)) into
|
||||
You have a choice of providers - see [setup](./setup.md).
|
||||
The LLM:
|
||||
- Validates user input for tools
|
||||
- Drives toward goal selected by user
|
||||
- Decides when to execute tools
|
||||
- Formats input and interprets output for tools
|
||||
- Drives toward the initial Goal and any subsequent Goals selected by user
|
||||
- Decides what to do based on input, such as:
|
||||
- Validates user input for Tools
|
||||
- Decides when to execute Tools
|
||||
- Decides on next step for Goal
|
||||
- Formats input and interprets output for Tools
|
||||
- is executed by Temporal Activities
|
||||
- API failures and logical failures are handled transparently
|
||||
|
||||
## Interaction
|
||||
Interaction is managed with Temporal Signals and Queries. These are durably stored in Workflow History.
|
||||
Can be used for analysis and debugging. It's all “just code” so it's easy to add new Signals and Queries.
|
||||
History can be used for analysis and debugging. It's all “just code” so it's easy to add new Signals and Queries.
|
||||
Input can be very dynamic, just needs to be serializable.
|
||||
|
||||
The workflow executes in a loop: gathering input, validating input, executing tools, managing prompts, and then waiting for input.
|
||||
The Workflow executes the Interaction Loop: gathering input, validating input, and providing a response:
|
||||
|
||||

|
||||
|
||||
Here's a more detailed example for gathering parameters for tools:
|
||||
Here's a more detailed example for gathering inputs for Tools:
|
||||
|
||||

|
||||
|
||||
@@ -64,4 +71,4 @@ Now that we have the pieces and what they do, here is a more complete diagram of
|
||||
|
||||
|
||||
# Adding features
|
||||
Want to add more tools, See [adding goals and tools](./adding-goals-and-tools.md).
|
||||
Want to add more Goals and Tools? See [adding goals and tools](./adding-goals-and-tools.md). Have fun!
|
||||
BIN
assets/0.2.0_changes.jpeg
Normal file
|
After Width: | Height: | Size: 398 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 129 KiB |
@@ -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;
|
||||
@@ -2,10 +2,14 @@ from models.tool_definitions import AgentGoal
|
||||
from typing import Optional
|
||||
import json
|
||||
|
||||
MULTI_GOAL_MODE:bool = None
|
||||
MULTI_GOAL_MODE: bool = None
|
||||
|
||||
|
||||
def generate_genai_prompt(
|
||||
agent_goal: AgentGoal, conversation_history: str, multi_goal_mode:bool, raw_json: Optional[str] = None
|
||||
agent_goal: AgentGoal,
|
||||
conversation_history: str,
|
||||
multi_goal_mode: bool,
|
||||
raw_json: Optional[str] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Generates a concise prompt for producing or validating JSON instructions
|
||||
@@ -25,10 +29,12 @@ def generate_genai_prompt(
|
||||
prompt_lines.append(
|
||||
"This is the ongoing history to determine which tool and arguments to gather:"
|
||||
)
|
||||
prompt_lines.append("BEGIN CONVERSATION HISTORY")
|
||||
prompt_lines.append("*BEGIN CONVERSATION HISTORY*")
|
||||
prompt_lines.append(json.dumps(conversation_history, indent=2))
|
||||
prompt_lines.append("END CONVERSATION HISTORY")
|
||||
prompt_lines.append("")
|
||||
prompt_lines.append("*END CONVERSATION HISTORY*")
|
||||
prompt_lines.append(
|
||||
"REMINDER: You can use the conversation history to infer arguments for the tools."
|
||||
)
|
||||
|
||||
# Example Conversation History (from agent_goal)
|
||||
if agent_goal.example_conversation_history:
|
||||
@@ -84,7 +90,22 @@ def generate_genai_prompt(
|
||||
"2) If all required arguments are known, set next='confirm' and specify the tool.\n"
|
||||
" The user will confirm before the tool is run.\n"
|
||||
f"3) {generate_toolchain_complete_guidance()}\n"
|
||||
"4) response should be short and user-friendly.\n"
|
||||
"4) response should be short and user-friendly.\n\n"
|
||||
"Guardrails (always remember!)\n"
|
||||
"1) If any required argument is missing, set next='question' and ask the user.\n"
|
||||
"1) ALWAYS ask a question in your response if next='question'.\n"
|
||||
"2) ALWAYS set next='confirm' if you have arguments\n "
|
||||
'And respond with "let\'s proceed with <tool> (and any other useful info)" \n '
|
||||
+ "DON'T set next='confirm' if you have a question to ask.\n"
|
||||
"EXAMPLE: If you have a question to ask, set next='question' and ask the user.\n"
|
||||
"3) You can carry over arguments from one tool to another.\n "
|
||||
"EXAMPLE: If you asked for an account ID, then use the conversation history to infer that argument "
|
||||
"going forward."
|
||||
"4) If ListAgents in the conversation history is force_confirm='False', you MUST check "
|
||||
+ "if the current tool contains userConfirmation. If it does, please ask the user to confirm details "
|
||||
+ "with the user. userConfirmation overrides force_confirm='False'.\n"
|
||||
+ "EXAMPLE: (force_confirm='False' AND userConfirmation exists on tool) Would you like me to <run tool> "
|
||||
+ "with the following details: <details>?\n"
|
||||
)
|
||||
|
||||
# Validation Task (If raw_json is provided)
|
||||
@@ -110,14 +131,15 @@ def generate_genai_prompt(
|
||||
|
||||
return "\n".join(prompt_lines)
|
||||
|
||||
|
||||
def generate_tool_completion_prompt(current_tool: str, dynamic_result: dict) -> str:
|
||||
"""
|
||||
Generates a prompt for handling tool completion and determining next steps.
|
||||
|
||||
|
||||
Args:
|
||||
current_tool: The name of the tool that just completed
|
||||
dynamic_result: The result data from the tool execution
|
||||
|
||||
|
||||
Returns:
|
||||
str: A formatted prompt string for the agent to process the tool completion
|
||||
"""
|
||||
@@ -132,15 +154,18 @@ def generate_tool_completion_prompt(current_tool: str, dynamic_result: dict) ->
|
||||
f"{generate_pick_new_goal_guidance()}"
|
||||
)
|
||||
|
||||
def generate_missing_args_prompt(current_tool: str, tool_data: dict, missing_args: list[str]) -> str:
|
||||
|
||||
def generate_missing_args_prompt(
|
||||
current_tool: str, tool_data: dict, missing_args: list[str]
|
||||
) -> str:
|
||||
"""
|
||||
Generates a prompt for handling missing arguments for a tool.
|
||||
|
||||
|
||||
Args:
|
||||
current_tool: The name of the tool that needs arguments
|
||||
tool_data: The current tool data containing the response
|
||||
missing_args: List of argument names that are missing
|
||||
|
||||
|
||||
Returns:
|
||||
str: A formatted prompt string for requesting missing arguments
|
||||
"""
|
||||
@@ -150,13 +175,14 @@ def generate_missing_args_prompt(current_tool: str, tool_data: dict, missing_arg
|
||||
"Only provide a valid JSON response without any comments or metadata."
|
||||
)
|
||||
|
||||
def set_multi_goal_mode_if_unset(mode:bool)->None:
|
||||
|
||||
def set_multi_goal_mode_if_unset(mode: bool) -> None:
|
||||
"""
|
||||
Set multi-mode (used to pass workflow)
|
||||
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
|
||||
Returns:
|
||||
bool: True if in multi-goal mode, false if not
|
||||
"""
|
||||
@@ -164,44 +190,47 @@ def set_multi_goal_mode_if_unset(mode:bool)->None:
|
||||
if MULTI_GOAL_MODE is None:
|
||||
MULTI_GOAL_MODE = mode
|
||||
|
||||
def is_multi_goal_mode()-> bool:
|
||||
|
||||
def is_multi_goal_mode() -> bool:
|
||||
"""
|
||||
Centralized logic for if we're in multi-goal mode.
|
||||
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
|
||||
Returns:
|
||||
bool: True if in multi-goal mode, false if not
|
||||
"""
|
||||
return MULTI_GOAL_MODE
|
||||
|
||||
def generate_pick_new_goal_guidance()-> str:
|
||||
|
||||
def generate_pick_new_goal_guidance() -> str:
|
||||
"""
|
||||
Generates a prompt for guiding the LLM to pick a new goal or be done depending on multi-goal mode.
|
||||
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
|
||||
Returns:
|
||||
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 (use the system prompt to figure that out) or the user explicitly requested to pick a new goal.'
|
||||
else:
|
||||
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), or the user explicitly requested to pick a new goal.'
|
||||
else:
|
||||
return 'Next should never be "pick-new-goal".'
|
||||
|
||||
|
||||
def generate_toolchain_complete_guidance() -> str:
|
||||
"""
|
||||
Generates a prompt for guiding the LLM to handle the end of the toolchain.
|
||||
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
|
||||
Returns:
|
||||
str: A prompt string prompting the LLM to prompt for a new goal, or be done
|
||||
"""
|
||||
if is_multi_goal_mode():
|
||||
if is_multi_goal_mode():
|
||||
return "If no more tools are needed (user_confirmed_tool_run has been run for all), set next='confirm' and tool='ListAgents'."
|
||||
else :
|
||||
return "If no more tools are needed (user_confirmed_tool_run has been run for all), set next='done' and tool=''."
|
||||
else:
|
||||
return "If no more tools are needed (user_confirmed_tool_run has been run for all), set next='done' and tool=''."
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
[tool.poetry]
|
||||
name = "temporal-AI-agent"
|
||||
version = "0.1.0"
|
||||
name = "temporal_AI_agent"
|
||||
version = "0.2.0"
|
||||
description = "Temporal AI Agent"
|
||||
license = "MIT"
|
||||
authors = ["Steve Androulakis <steve.androulakis@temporal.io>"]
|
||||
authors = [
|
||||
"Steve Androulakis <steve.androulakis@temporal.io>",
|
||||
"Laine Smith <lainecaseysmith@gmail.com>",
|
||||
"Joshua Smith <josh.smith@temporal.io>"
|
||||
]
|
||||
readme = "README.md"
|
||||
|
||||
# By default, Poetry will find packages automatically,
|
||||
@@ -13,7 +17,7 @@ packages = [
|
||||
]
|
||||
|
||||
[tool.poetry.urls]
|
||||
"Bug Tracker" = "https://github.com/temporalio/samples-python/issues"
|
||||
"Bug Tracker" = "https://github.com/temporal-community/temporal-ai-agent/issues"
|
||||
|
||||
[tool.poe.tasks]
|
||||
format = [{cmd = "black ."}, {cmd = "isort ."}]
|
||||
@@ -42,10 +46,18 @@ pandas = "^2.2.3"
|
||||
gtfs-kit = "^10.1.1"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest = "^7.3"
|
||||
pytest = ">=8.2"
|
||||
pytest-asyncio = "^0.26.0"
|
||||
black = "^23.7"
|
||||
isort = "^5.12"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.4.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
log_cli = true
|
||||
log_cli_level = "INFO"
|
||||
log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
21
setup.md
@@ -18,7 +18,7 @@ SHOW_CONFIRM=True
|
||||
|
||||
The agent can be configured to pursue different goals using the `AGENT_GOAL` environment variable in your `.env` file. If unset, default is `goal_choose_agent_type`.
|
||||
|
||||
If the first goal is `goal_choose_agent_type` the agent will support multiple goals using goal categories defined by `GOAL_CATEGORIES` in your .env file. If unset, default is all.
|
||||
If the first goal is `goal_choose_agent_type` the agent will support multiple goals using goal categories defined by `GOAL_CATEGORIES` in your .env file. If unset, default is all. We recommend starting with `fin`.
|
||||
```bash
|
||||
GOAL_CATEGORIES=hr,travel-flights,travel-trains,fin
|
||||
```
|
||||
@@ -204,11 +204,14 @@ FIN_START_REAL_WORKFLOW=FALSE #set this to true to start a real workflow
|
||||
By default it will _not_ make a real workflow, it'll just fake it. If you get the worker running and want to start a workflow, in your [.env](./.env):
|
||||
```bash
|
||||
FIN_START_REAL_WORKFLOW=FALSE #set this to true to start a real workflow
|
||||
|
||||
```
|
||||
|
||||
#### Goals: HR/PTO
|
||||
Make sure you have the mock users you want in (such as yourself) in [the PTO mock data file](./tools/data/employee_pto_data.json).
|
||||
|
||||
#### Goals: Ecommerce
|
||||
Make sure you have the mock orders you want in (such as those with real tracking numbers) in [the mock orders file](./tools/data/customer_order_data.json).
|
||||
|
||||
|
||||
## Customizing the Agent Further
|
||||
- `tool_registry.py` contains the mapping of tool names to tool definitions (so the AI understands how to use them)
|
||||
@@ -216,4 +219,16 @@ Make sure you have the mock users you want in (such as yourself) in [the PTO moc
|
||||
- The tools themselves are defined in their own files in `/tools`
|
||||
- Note the mapping in `tools/__init__.py` to each tool
|
||||
|
||||
For more details, check out [adding goals and tools guide](./adding-goals-and-tools.md).
|
||||
For more details, check out [adding goals and tools guide](./adding-goals-and-tools.md).
|
||||
|
||||
## Setup Checklist
|
||||
[ ] copy `.env.example` to `.env` <br />
|
||||
[ ] Select an LLM and add your API key to `.env` <br />
|
||||
[ ] (Optional) set your starting goal and goal category in `.env` <br />
|
||||
[ ] (Optional) configure your Temporal Cloud settings in `.env` <br />
|
||||
[ ] `poetry run python scripts/run_worker.py` <br />
|
||||
[ ] `poetry run uvicorn api.main:app --reload` <br />
|
||||
[ ] `cd frontend`, `npm install`, `npx vite` <br />
|
||||
[ ] Access the UI at `http://localhost:5173` <br />
|
||||
|
||||
And that's it! Happy AI Agent Exploring!
|
||||
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 />
|
||||
@@ -10,11 +11,19 @@
|
||||
[ ] financial advise - args being freeform customer input about their financial situation, goals
|
||||
[ ] tool is maybe a new tool asking the LLM to advise
|
||||
|
||||
[ ] for demo simulate failure - add utilities/simulated failures from pipeline demo <br />
|
||||
|
||||
[ ] LLM failure->autoswitch: <br />
|
||||
- detect failure in the activity using failurecount <br />
|
||||
- activity switches to secondary LLM defined in .env
|
||||
- activity reports switch to workflow
|
||||
|
||||
[ ] 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 />
|
||||
|
||||
@@ -18,6 +18,10 @@ from .fin.get_account_balances import get_account_balance
|
||||
from .fin.move_money import move_money
|
||||
from .fin.submit_loan_application import submit_loan_application
|
||||
|
||||
from .ecommerce.get_order import get_order
|
||||
from .ecommerce.track_package import track_package
|
||||
from .ecommerce.list_orders import list_orders
|
||||
|
||||
from .give_hint import give_hint
|
||||
from .guess_location import guess_location
|
||||
|
||||
@@ -52,11 +56,17 @@ def get_handler(tool_name: str):
|
||||
if tool_name == "FinCheckAccountIsValid":
|
||||
return check_account_valid
|
||||
if tool_name == "FinCheckAccountBalance":
|
||||
return get_account_balance
|
||||
if tool_name == "FinMoveMoneyOrder":
|
||||
return get_account_balance
|
||||
if tool_name == "FinMoveMoney":
|
||||
return move_money
|
||||
if tool_name == "FinCheckAccountSubmitLoanApproval":
|
||||
return submit_loan_application
|
||||
return submit_loan_application
|
||||
if tool_name == "GetOrder":
|
||||
return get_order
|
||||
if tool_name == "TrackPackage":
|
||||
return track_package
|
||||
if tool_name == "ListOrders":
|
||||
return list_orders
|
||||
if tool_name == "GiveHint":
|
||||
return give_hint
|
||||
if tool_name == "GuessLocation":
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
{
|
||||
"accounts": [
|
||||
{
|
||||
"name": "Matt Murdock",
|
||||
"email": "matt.murdock@nelsonmurdock.com",
|
||||
"account_id": "11235",
|
||||
"checking_balance": 875.40,
|
||||
"savings_balance": 3200.15,
|
||||
"checking_balance": "1275.4",
|
||||
"savings_balance": "2800.15",
|
||||
"bitcoin_balance": 0.1378,
|
||||
"account_creation_date": "2014-03-10"
|
||||
},
|
||||
@@ -13,8 +13,8 @@
|
||||
"name": "Foggy Nelson",
|
||||
"email": "foggy.nelson@nelsonmurdock.com",
|
||||
"account_id": "112358",
|
||||
"checking_balance": 1523.67,
|
||||
"savings_balance": 4875.90,
|
||||
"checking_balance": "1523.66",
|
||||
"savings_balance": "4875.89",
|
||||
"bitcoin_balance": 0.0923,
|
||||
"account_creation_date": "2014-03-10"
|
||||
},
|
||||
@@ -23,7 +23,7 @@
|
||||
"email": "karen.page@nelsonmurdock.com",
|
||||
"account_id": "112",
|
||||
"checking_balance": 645.25,
|
||||
"savings_balance": 1830.50,
|
||||
"savings_balance": "830.5",
|
||||
"bitcoin_balance": 0.0456,
|
||||
"account_creation_date": "2015-01-15"
|
||||
},
|
||||
@@ -31,7 +31,7 @@
|
||||
"name": "Wilson Fisk",
|
||||
"email": "wilson.fisk@fiskcorp.com",
|
||||
"account_id": "11",
|
||||
"checking_balance": 25000.00,
|
||||
"checking_balance": 25000.0,
|
||||
"savings_balance": 150000.75,
|
||||
"bitcoin_balance": 5987.6721,
|
||||
"account_creation_date": "2013-09-20"
|
||||
@@ -40,8 +40,8 @@
|
||||
"name": "Frank Castle",
|
||||
"email": "frank.castle@vigilante.net",
|
||||
"account_id": "1",
|
||||
"checking_balance": 320.10,
|
||||
"savings_balance": 0.30,
|
||||
"checking_balance": 320.1,
|
||||
"savings_balance": 0.3,
|
||||
"bitcoin_balance": 15.2189,
|
||||
"account_creation_date": "2016-02-05"
|
||||
},
|
||||
@@ -49,8 +49,8 @@
|
||||
"name": "Joshua Smith",
|
||||
"email": "joshmsmith@gmail.com",
|
||||
"account_id": "11235813",
|
||||
"checking_balance": 3021.90,
|
||||
"savings_balance": 500.50,
|
||||
"checking_balance": 3021.9,
|
||||
"savings_balance": 500.5,
|
||||
"bitcoin_balance": 0.001,
|
||||
"account_creation_date": "2020-03-19"
|
||||
}
|
||||
|
||||
81
tools/data/customer_order_data.json
Normal file
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"orders": [
|
||||
{
|
||||
"id": "100",
|
||||
"summary": "Lawyer Books",
|
||||
"email": "matt.murdock@nelsonmurdock.com",
|
||||
"status": "cancelled",
|
||||
"order_date": "2025-03-30",
|
||||
"last_update": "2025-04-01"
|
||||
},
|
||||
{
|
||||
"id": "101",
|
||||
"summary": "Bonking Sticks",
|
||||
"email": "matt.murdock@nelsonmurdock.com",
|
||||
"status": "paid",
|
||||
"order_date": "2025-04-01",
|
||||
"last_order_update": "2025-04-01"
|
||||
},
|
||||
{
|
||||
"id": "102",
|
||||
"summary": "Red Sunglasses",
|
||||
"email": "matt.murdock@nelsonmurdock.com",
|
||||
"status": "shipped",
|
||||
"order_date": "2025-04-01",
|
||||
"last_order_update": "2025-04-01",
|
||||
"tracking_id": "UPS67890"
|
||||
},
|
||||
{
|
||||
"id": "200",
|
||||
"summary": "Paper",
|
||||
"email": "foggy.nelson@nelsonmurdock.com",
|
||||
"status": "shipped",
|
||||
"order_date": "2025-04-03",
|
||||
"last_update": "2025-04-06",
|
||||
"tracking_id": "USPS12345"
|
||||
},
|
||||
{
|
||||
"id": "300",
|
||||
"summary": "Chemistry Books",
|
||||
"email": "heisenberg@blue-meth.com",
|
||||
"status": "shipped",
|
||||
"order_date": "2025-03-30",
|
||||
"last_update": "2025-04-06",
|
||||
"tracking_id": "USPS12345"
|
||||
},
|
||||
{
|
||||
"id": "301",
|
||||
"summary": "Book: Being a Cool Bro",
|
||||
"email": "heisenberg@blue-meth.com",
|
||||
"status": "cancelled",
|
||||
"order_date": "2025-04-01",
|
||||
"last_update": "2025-04-02"
|
||||
},
|
||||
{
|
||||
"id": "302",
|
||||
"summary": "Black Hat",
|
||||
"email": "heisenberg@blue-meth.com",
|
||||
"status": "delivered",
|
||||
"order_date": "2025-04-01",
|
||||
"last_update": "2025-04-06",
|
||||
"tracking_id": "UPS67890"
|
||||
},
|
||||
{
|
||||
"id": "400",
|
||||
"summary": "Giant Graphic Hoodie",
|
||||
"email": "jessenotpinkman@blue-meth.com",
|
||||
"status": "shipped",
|
||||
"order_date": "2025-04-03",
|
||||
"last_update": "2025-04-09",
|
||||
"tracking_id": "UPS67890"
|
||||
},
|
||||
{
|
||||
"id": "401",
|
||||
"summary": "Giant Pants",
|
||||
"email": "jessenotpinkman@blue-meth.com",
|
||||
"status": "processing",
|
||||
"order_date": "2025-04-03",
|
||||
"last_update": "2025-04-09"
|
||||
}
|
||||
]
|
||||
}
|
||||
158
tools/data/dummy_tracking_data.json
Normal file
@@ -0,0 +1,158 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"TrackingNumber": "USPS12345",
|
||||
"Delivered": false,
|
||||
"Carrier": "USPS",
|
||||
"ServiceType": "USPS Ground Advantage<SUP>™</SUP>",
|
||||
"PickupDate": "",
|
||||
"ScheduledDeliveryDate": "April 14, 2025",
|
||||
"ScheduledDeliveryDateInDateTimeFromat": "2025-04-14T00:00:00",
|
||||
"StatusCode": "In Transit from Origin Processing",
|
||||
"Status": "Departed Post Office",
|
||||
"StatusSummary": "Your item has left our acceptance facility and is in transit to a sorting facility on April 10, 2025 at 7:06 am in IRON RIDGE, WI 53035.",
|
||||
"Message": "",
|
||||
"DeliveredDateTime": "",
|
||||
"DeliveredDateTimeInDateTimeFormat": null,
|
||||
"SignatureName": "",
|
||||
"DestinationCity": "CITY",
|
||||
"DestinationState": "ST",
|
||||
"DestinationZip": "12345",
|
||||
"DestinationCountry": null,
|
||||
"EventDate": "2025-04-10T07:06:00",
|
||||
"TrackingDetails": [
|
||||
{
|
||||
"EventDateTime": "April 10, 2025 7:06 am",
|
||||
"Event": "Departed Post Office",
|
||||
"EventAddress": "IRON RIDGE WI 53035",
|
||||
"State": "WI",
|
||||
"City": "IRON RIDGE",
|
||||
"Zip": "53035",
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-10T07:06:00"
|
||||
},
|
||||
{
|
||||
"EventDateTime": "April 9, 2025 11:29 am",
|
||||
"Event": "USPS picked up item",
|
||||
"EventAddress": "IRON RIDGE WI 53035",
|
||||
"State": "WI",
|
||||
"City": "IRON RIDGE",
|
||||
"Zip": "53035",
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-09T11:29:00"
|
||||
},
|
||||
{
|
||||
"EventDateTime": "April 7, 2025 6:29 am",
|
||||
"Event": "Shipping Label Created, USPS Awaiting Item",
|
||||
"EventAddress": "IRON RIDGE WI 53035",
|
||||
"State": "WI",
|
||||
"City": "IRON RIDGE",
|
||||
"Zip": "53035",
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-07T06:29:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"TrackingNumber": "UPS67890",
|
||||
"Delivered": true,
|
||||
"Carrier": "UPS",
|
||||
"ServiceType": "UPS Ground Saver®",
|
||||
"PickupDate": "",
|
||||
"ScheduledDeliveryDate": "",
|
||||
"ScheduledDeliveryDateInDateTimeFromat": null,
|
||||
"StatusCode": "D",
|
||||
"Status": "DELIVERED",
|
||||
"StatusSummary": "DELIVERED",
|
||||
"Message": "",
|
||||
"DeliveredDateTime": "20250415 154315",
|
||||
"DeliveredDateTimeInDateTimeFormat": "2025-04-15T15:43:15",
|
||||
"SignatureName": "",
|
||||
"DestinationCity": "CHICAGO",
|
||||
"DestinationState": "IL",
|
||||
"DestinationZip": "",
|
||||
"DestinationCountry": "US",
|
||||
"EventDate": "2025-04-15T15:43:15",
|
||||
"TrackingDetails": [
|
||||
{
|
||||
"EventDateTime": "20250415 154315",
|
||||
"Event": "DELIVERED ",
|
||||
"EventAddress": "CHICAGO IL US",
|
||||
"State": "IL",
|
||||
"City": "CHICAGO",
|
||||
"Zip": null,
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-15T15:43:15"
|
||||
},
|
||||
{
|
||||
"EventDateTime": "20250415 090938",
|
||||
"Event": "Out For Delivery Today",
|
||||
"EventAddress": "Chicago IL US",
|
||||
"State": "IL",
|
||||
"City": "Chicago",
|
||||
"Zip": null,
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-15T09:09:38"
|
||||
},
|
||||
{
|
||||
"EventDateTime": "20250415 074141",
|
||||
"Event": "Loaded on Delivery Vehicle ",
|
||||
"EventAddress": "Chicago IL US",
|
||||
"State": "IL",
|
||||
"City": "Chicago",
|
||||
"Zip": null,
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-15T07:41:41"
|
||||
},
|
||||
{
|
||||
"EventDateTime": "20250415 032200",
|
||||
"Event": "Arrived at Facility",
|
||||
"EventAddress": "Chicago IL US",
|
||||
"State": "IL",
|
||||
"City": "Chicago",
|
||||
"Zip": null,
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-15T03:22:00"
|
||||
},
|
||||
{
|
||||
"EventDateTime": "20250414 223000",
|
||||
"Event": "Departed from Facility",
|
||||
"EventAddress": "Hodgkins IL US",
|
||||
"State": "IL",
|
||||
"City": "Hodgkins",
|
||||
"Zip": null,
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-14T22:30:00"
|
||||
},
|
||||
{
|
||||
"EventDateTime": "20250414 002700",
|
||||
"Event": "Arrived at Facility",
|
||||
"EventAddress": "Hodgkins IL US",
|
||||
"State": "IL",
|
||||
"City": "Hodgkins",
|
||||
"Zip": null,
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-14T00:27:00"
|
||||
},
|
||||
{
|
||||
"EventDateTime": "20250410 211700",
|
||||
"Event": "Departed from Facility",
|
||||
"EventAddress": "Las Vegas NV US",
|
||||
"State": "NV",
|
||||
"City": "Las Vegas",
|
||||
"Zip": null,
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-10T21:17:00"
|
||||
},
|
||||
{
|
||||
"EventDateTime": "20250410 132625",
|
||||
"Event": "Arrived at Facility",
|
||||
"EventAddress": "Las Vegas NV US",
|
||||
"State": "NV",
|
||||
"City": "Las Vegas",
|
||||
"Zip": null,
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-10T13:26:25"
|
||||
},
|
||||
{
|
||||
"EventDateTime": "20250409 100659",
|
||||
"Event": "Shipper created a label, UPS has not received the package yet. ",
|
||||
"EventAddress": " US",
|
||||
"State": null,
|
||||
"City": null,
|
||||
"Zip": null,
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-09T10:06:59"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
23
tools/ecommerce/get_order.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
# this is made to demonstrate functionality but it could just as durably be an API call
|
||||
# called as part of a temporal activity with automatic retries
|
||||
def get_order(args: dict) -> dict:
|
||||
|
||||
order_id = args.get("order_id")
|
||||
|
||||
file_path = Path(__file__).resolve().parent.parent / "data" / "customer_order_data.json"
|
||||
if not file_path.exists():
|
||||
return {"error": "Data file not found."}
|
||||
|
||||
with open(file_path, "r") as file:
|
||||
data = json.load(file)
|
||||
order_list = data["orders"]
|
||||
|
||||
for order in order_list:
|
||||
if order["id"] == order_id:
|
||||
return order
|
||||
|
||||
return_msg = "Order " + order_id + " not found."
|
||||
return {"error": return_msg}
|
||||
30
tools/ecommerce/list_orders.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
def sorting(e):
|
||||
return e['order_date']
|
||||
|
||||
def list_orders(args: dict) -> dict:
|
||||
|
||||
email_address = args.get("email_address")
|
||||
|
||||
file_path = Path(__file__).resolve().parent.parent / "data" / "customer_order_data.json"
|
||||
if not file_path.exists():
|
||||
return {"error": "Data file not found."}
|
||||
|
||||
with open(file_path, "r") as file:
|
||||
data = json.load(file)
|
||||
order_list = data["orders"]
|
||||
|
||||
rtn_order_list = []
|
||||
for order in order_list:
|
||||
if order["email"] == email_address:
|
||||
rtn_order_list.append(order)
|
||||
|
||||
if len(rtn_order_list) > 0:
|
||||
rtn_order_list.sort(key=sorting)
|
||||
return {"orders": rtn_order_list}
|
||||
else:
|
||||
return_msg = "No orders for customer " + email_address + " found."
|
||||
return {"error": return_msg}
|
||||
|
||||
144
tools/ecommerce/track_package.py
Normal file
@@ -0,0 +1,144 @@
|
||||
import http
|
||||
import os
|
||||
import json
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
#Send back dummy data in the correct format - to use the real API, 1) change this to be track_package_fake and 2) change the below track_package_real to be track_package
|
||||
def track_package(args: dict) -> dict:
|
||||
|
||||
tracking_id = args.get("tracking_id")
|
||||
file_path = Path(__file__).resolve().parent.parent / "data" / "dummy_tracking_data.json"
|
||||
if not file_path.exists():
|
||||
return {"error": "Data file not found."}
|
||||
|
||||
with open(file_path, "r") as file:
|
||||
data = json.load(file)
|
||||
package_list = data["packages"]
|
||||
|
||||
for package in package_list:
|
||||
if package["TrackingNumber"] == tracking_id:
|
||||
scheduled_delivery_date = package["ScheduledDeliveryDate"]
|
||||
carrier = package["Carrier"]
|
||||
status_summary = package["StatusSummary"]
|
||||
tracking_details = package.get("TrackingDetails", [])
|
||||
last_tracking_update = ""
|
||||
if tracking_details and tracking_details is not None and tracking_details[0] is not None:
|
||||
last_tracking_update = tracking_details[0]["EventDateTimeInDateTimeFormat"]
|
||||
|
||||
tracking_link = ""
|
||||
if carrier == "USPS":
|
||||
tracking_link = f"https://tools.usps.com/go/TrackConfirmAction?qtc_tLabels1={tracking_id}"
|
||||
elif carrier == "UPS":
|
||||
tracking_link = f"https://www.ups.com/track?track=yes&trackNums={tracking_id}"
|
||||
|
||||
return {
|
||||
"scheduled_delivery_date": scheduled_delivery_date,
|
||||
"carrier": carrier,
|
||||
"status_summary": status_summary,
|
||||
"tracking_link": tracking_link,
|
||||
"last_tracking_update": last_tracking_update
|
||||
}
|
||||
|
||||
return_msg = "Package not found with tracking info " + tracking_id
|
||||
return {"error": return_msg}
|
||||
|
||||
'''Format of response:
|
||||
{
|
||||
"TrackingNumber": "",
|
||||
"Delivered": false,
|
||||
"Carrier": "USPS",
|
||||
"ServiceType": "USPS Ground Advantage<SUP>™</SUP>",
|
||||
"PickupDate": "",
|
||||
"ScheduledDeliveryDate": "April 14, 2025",
|
||||
"ScheduledDeliveryDateInDateTimeFromat": "2025-04-14T00:00:00",
|
||||
"StatusCode": "In Transit from Origin Processing",
|
||||
"Status": "Departed Post Office",
|
||||
"StatusSummary": "Your item has left our acceptance facility and is in transit to a sorting facility on April 10, 2025 at 7:06 am in IRON RIDGE, WI 53035.",
|
||||
"Message": "",
|
||||
"DeliveredDateTime": "",
|
||||
"DeliveredDateTimeInDateTimeFormat": null,
|
||||
"SignatureName": "",
|
||||
"DestinationCity": "CITY",
|
||||
"DestinationState": "ST",
|
||||
"DestinationZip": "12345",
|
||||
"DestinationCountry": null,
|
||||
"EventDate": "2025-04-10T07:06:00",
|
||||
"TrackingDetails": [
|
||||
{
|
||||
"EventDateTime": "April 10, 2025 7:06 am",
|
||||
"Event": "Departed Post Office",
|
||||
"EventAddress": "IRON RIDGE WI 53035",
|
||||
"State": "WI",
|
||||
"City": "IRON RIDGE",
|
||||
"Zip": "53035",
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-10T07:06:00"
|
||||
},
|
||||
{
|
||||
"EventDateTime": "April 9, 2025 11:29 am",
|
||||
"Event": "USPS picked up item",
|
||||
"EventAddress": "IRON RIDGE WI 53035",
|
||||
"State": "WI",
|
||||
"City": "IRON RIDGE",
|
||||
"Zip": "53035",
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-09T11:29:00"
|
||||
},
|
||||
{
|
||||
"EventDateTime": "April 7, 2025 6:29 am",
|
||||
"Event": "Shipping Label Created, USPS Awaiting Item",
|
||||
"EventAddress": "IRON RIDGE WI 53035",
|
||||
"State": "WI",
|
||||
"City": "IRON RIDGE",
|
||||
"Zip": "53035",
|
||||
"EventDateTimeInDateTimeFormat": "2025-04-07T06:29:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
def track_package_real(args: dict) -> dict:
|
||||
|
||||
tracking_id = args.get("tracking_id")
|
||||
|
||||
api_key = os.getenv("RAPIDAPI_KEY")
|
||||
api_host = os.getenv("RAPIDAPI_HOST_PACKAGE", "trackingpackage.p.rapidapi.com")
|
||||
|
||||
conn = http.client.HTTPSConnection(api_host)
|
||||
headers = {
|
||||
"x-rapidapi-key": api_key,
|
||||
"x-rapidapi-host": api_host,
|
||||
"Authorization": "Basic Ym9sZGNoYXQ6TGZYfm0zY2d1QzkuKz9SLw==",
|
||||
}
|
||||
|
||||
path = f"/TrackingPackage?trackingNumber={tracking_id}"
|
||||
|
||||
conn.request("GET", path, headers=headers)
|
||||
res = conn.getresponse()
|
||||
data = res.read()
|
||||
data_decoded = data.decode("utf-8")
|
||||
conn.close()
|
||||
|
||||
try:
|
||||
json_data = json.loads(data_decoded)
|
||||
except json.JSONDecodeError:
|
||||
return {"error": "Invalid JSON response"}
|
||||
|
||||
scheduled_delivery_date = json_data["ScheduledDeliveryDate"]
|
||||
carrier = json_data["Carrier"]
|
||||
status_summary = json_data["StatusSummary"]
|
||||
tracking_details = json_data.get("TrackingDetails", [])
|
||||
last_tracking_update = ""
|
||||
if tracking_details and tracking_details is not None and tracking_details[0] is not None:
|
||||
last_tracking_update = tracking_details[0]["EventDateTimeInDateTimeFormat"]
|
||||
tracking_link = ""
|
||||
if carrier == "USPS":
|
||||
tracking_link = f"https://tools.usps.com/go/TrackConfirmAction?qtc_tLabels1={tracking_id}"
|
||||
elif carrier == "UPS":
|
||||
tracking_link = f"https://www.ups.com/track?track=yes&trackNums={tracking_id}"
|
||||
|
||||
return {
|
||||
"scheduled_delivery_date": scheduled_delivery_date,
|
||||
"carrier": carrier,
|
||||
"status_summary": status_summary,
|
||||
"tracking_link": tracking_link,
|
||||
"last_tracking_update": last_tracking_update
|
||||
}
|
||||
@@ -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():
|
||||
|
||||
@@ -11,7 +11,7 @@ from shared.config import get_temporal_client
|
||||
|
||||
from enum import Enum, auto
|
||||
|
||||
#enums for the java enum
|
||||
# enums for the java enum
|
||||
# class ExecutionScenarios(Enum):
|
||||
# HAPPY_PATH = 0
|
||||
# ADVANCED_VISIBILITY = auto() # 1
|
||||
@@ -20,6 +20,7 @@ from enum import Enum, auto
|
||||
# BUG_IN_WORKFLOW = auto() # 4
|
||||
# INVALID_ACCOUNT = auto() # 5
|
||||
|
||||
|
||||
# these dataclasses are for calling the Temporal Workflow
|
||||
# Python equivalent of the workflow we're calling's Java WorkflowParameterObj
|
||||
@dataclass
|
||||
@@ -27,103 +28,130 @@ class MoneyMovementWorkflowParameterObj:
|
||||
amount: int # Using snake_case as per Python conventions
|
||||
scenario: str
|
||||
|
||||
|
||||
# this is made to demonstrate functionality but it could just as durably be an API call
|
||||
# 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")
|
||||
|
||||
file_path = Path(__file__).resolve().parent.parent / "data" / "customer_account_data.json"
|
||||
file_path = (
|
||||
Path(__file__).resolve().parent.parent / "data" / "customer_account_data.json"
|
||||
)
|
||||
if not file_path.exists():
|
||||
return {"error": "Data file not found."}
|
||||
|
||||
# todo validate there's enough money in the account
|
||||
|
||||
with open(file_path, "r") as file:
|
||||
data = json.load(file)
|
||||
account_list = data["accounts"]
|
||||
|
||||
for account in account_list:
|
||||
if account["email"] == account_key or account["account_id"] == account_key:
|
||||
amount_str: str = str(amount) # LLM+python gets sassy about types but we need it to be str
|
||||
amount_str: str = str(amount)
|
||||
from_account_combo = account_key + account_type
|
||||
|
||||
transfer_workflow_id = await start_workflow(amount_cents=str_dollars_to_cents(amount_str),from_account_name=from_account_combo, to_account_name=destinationaccount)
|
||||
|
||||
account_type_key = 'checking_balance'
|
||||
if(account_type.casefold() == "checking" ):
|
||||
account_type = "checking"
|
||||
account_type_key = 'checking_balance'
|
||||
|
||||
elif(account_type.casefold() == "savings" ):
|
||||
account_type = "savings"
|
||||
account_type_key = 'savings_balance'
|
||||
else:
|
||||
raise NotImplementedError("money order for account types other than checking or savings is not implemented.")
|
||||
|
||||
new_balance: float = float(str_dollars_to_cents(str(account[account_type_key])))
|
||||
new_balance = new_balance - float(str_dollars_to_cents(amount_str))
|
||||
account[account_type_key] = str(new_balance / 100 ) #to dollars
|
||||
with open(file_path, 'w') as file:
|
||||
json.dump(data, file, indent=4)
|
||||
transfer_workflow_id = await start_workflow(
|
||||
amount_cents=str_dollars_to_cents(amount_str),
|
||||
from_account_name=from_account_combo,
|
||||
to_account_name=destinationaccount,
|
||||
)
|
||||
|
||||
if account_type.casefold() == "checking":
|
||||
from_key = "checking_balance"
|
||||
elif account_type.casefold() == "savings":
|
||||
from_key = "savings_balance"
|
||||
else:
|
||||
return_msg = "Money order for account types other than checking or savings is not implemented."
|
||||
return {"error": return_msg}
|
||||
|
||||
to_key = (
|
||||
"savings_balance"
|
||||
if destinationaccount.casefold() == "savings"
|
||||
else "checking_balance"
|
||||
)
|
||||
|
||||
# Update from-account balance
|
||||
from_balance = float(str_dollars_to_cents(str(account[from_key])))
|
||||
from_balance -= float(str_dollars_to_cents(amount_str))
|
||||
account[from_key] = str(from_balance / 100)
|
||||
|
||||
# Update destination-account balance
|
||||
to_balance = float(str_dollars_to_cents(str(account[to_key])))
|
||||
to_balance += float(str_dollars_to_cents(amount_str))
|
||||
account[to_key] = str(to_balance / 100)
|
||||
|
||||
with open(file_path, "w") as file:
|
||||
json.dump(data, file, indent=4)
|
||||
|
||||
return {
|
||||
"status": "money movement complete",
|
||||
"confirmation id": transfer_workflow_id,
|
||||
"new_balance": account[from_key],
|
||||
"destination_balance": account[to_key],
|
||||
}
|
||||
|
||||
return {'status': "money movement complete", 'confirmation id': transfer_workflow_id, 'new_balance': account[account_type_key]}
|
||||
|
||||
return_msg = "Account not found with for " + account_key
|
||||
return {"error": return_msg}
|
||||
|
||||
|
||||
# Async function to start workflow
|
||||
async def start_workflow(amount_cents: int, from_account_name: str, to_account_name: str)-> str:
|
||||
|
||||
# Connect to Temporal
|
||||
client = await get_temporal_client()
|
||||
async def start_workflow(
|
||||
amount_cents: int, from_account_name: str, to_account_name: str
|
||||
) -> str:
|
||||
|
||||
start_real_workflow = os.getenv("FIN_START_REAL_WORKFLOW")
|
||||
if start_real_workflow is not None and start_real_workflow.lower() == "false":
|
||||
START_REAL_WORKFLOW = False
|
||||
else:
|
||||
START_REAL_WORKFLOW = True
|
||||
|
||||
|
||||
if START_REAL_WORKFLOW:
|
||||
# Connect to Temporal
|
||||
client = await get_temporal_client()
|
||||
# Create the parameter object
|
||||
params = MoneyMovementWorkflowParameterObj(
|
||||
amount=amount_cents,
|
||||
scenario="HAPPY_PATH"
|
||||
amount=amount_cents, scenario="HAPPY_PATH"
|
||||
)
|
||||
|
||||
workflow_id="TRANSFER-ACCT-" + from_account_name + "-TO-" + to_account_name # business-relevant workflow ID
|
||||
workflow_id = (
|
||||
"TRANSFER-ACCT-" + from_account_name + "-TO-" + to_account_name
|
||||
) # business-relevant workflow ID
|
||||
|
||||
try:
|
||||
try:
|
||||
handle = await client.start_workflow(
|
||||
"moneyTransferWorkflow", # Workflow name
|
||||
params, # Workflow parameters
|
||||
params, # Workflow parameters
|
||||
id=workflow_id,
|
||||
task_queue="MoneyTransferJava" # Task queue name
|
||||
task_queue="MoneyTransferJava", # Task queue name
|
||||
)
|
||||
return handle.id
|
||||
except WorkflowAlreadyStartedError as e:
|
||||
existing_handle = client.get_workflow_handle(workflow_id=workflow_id)
|
||||
return existing_handle.id
|
||||
else:
|
||||
return "TRANSFER-ACCT-" + from_account_name + "-TO-" + to_account_name + "not-real"
|
||||
else:
|
||||
return (
|
||||
"TRANSFER-ACCT-" + from_account_name + "-TO-" + to_account_name + "not-real"
|
||||
)
|
||||
|
||||
|
||||
#cleans a string dollar amount description to cents value
|
||||
|
||||
# cleans a string dollar amount description to cents value
|
||||
def str_dollars_to_cents(dollar_str: str) -> int:
|
||||
try:
|
||||
# Remove '$' and any whitespace
|
||||
cleaned_str = dollar_str.replace('$', '').strip()
|
||||
|
||||
cleaned_str = dollar_str.replace("$", "").strip()
|
||||
|
||||
# Handle empty string or invalid input
|
||||
if not cleaned_str:
|
||||
raise ValueError("Empty amount provided")
|
||||
|
||||
|
||||
# Convert to float and then to cents
|
||||
amount = float(cleaned_str)
|
||||
if amount < 0:
|
||||
raise ValueError("Negative amounts not allowed")
|
||||
|
||||
|
||||
return int(amount * 100)
|
||||
except ValueError as e:
|
||||
raise ValueError(f"Invalid dollar amount format: {dollar_str}") from e
|
||||
raise ValueError(f"Invalid dollar amount format: {dollar_str}") from e
|
||||
|
||||
@@ -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)
|
||||
@@ -46,14 +46,14 @@ async def submit_loan_application(args: dict) -> dict:
|
||||
# Async function to start workflow
|
||||
async def start_workflow(amount: str, account_name: str, )-> dict:
|
||||
|
||||
# Connect to Temporal
|
||||
client = await get_temporal_client()
|
||||
start_real_workflow = os.getenv("FIN_START_REAL_WORKFLOW")
|
||||
if start_real_workflow is not None and start_real_workflow.lower() == "false":
|
||||
START_REAL_WORKFLOW = False
|
||||
return {'loan_application_status': "applied", 'application_details': "loan application is submitted and initial validation is complete",'transaction_id': "APPLICATION"+account_name, 'advisement': "You'll receive a confirmation for final approval in three business days", }
|
||||
else:
|
||||
START_REAL_WORKFLOW = True
|
||||
# Connect to Temporal
|
||||
client = await get_temporal_client()
|
||||
|
||||
# Define the workflow ID and task queue
|
||||
workflow_id = "LOAN_APPLICATION-"+account_name+"-"+date.today().strftime('%Y-%m-%d')
|
||||
|
||||
@@ -1,46 +1,51 @@
|
||||
import os
|
||||
from typing import List
|
||||
from models.tool_definitions import AgentGoal
|
||||
import tools.tool_registry as tool_registry
|
||||
|
||||
# Turn on Silly Mode - this should be a description of the persona you'd like the bot to have and can be a single word or a phrase.
|
||||
# Example if you want the bot to be a specific person, like Mario or Christopher Walken, or to describe a specific tone:
|
||||
#SILLY_MODE="Christopher Walken"
|
||||
#SILLY_MODE="belligerent"
|
||||
#
|
||||
# SILLY_MODE="Christopher Walken"
|
||||
# SILLY_MODE="belligerent"
|
||||
#
|
||||
# Example if you want it to take on a persona (include 'a'):
|
||||
#SILLY_MODE="a pirate"
|
||||
# SILLY_MODE="a pirate"
|
||||
# Note - this only works with certain LLMs. Grok for sure will stay in character, while OpenAI will not.
|
||||
SILLY_MODE="off"
|
||||
SILLY_MODE = "off"
|
||||
if SILLY_MODE is not None and SILLY_MODE != "off":
|
||||
silly_prompt = "You are " + SILLY_MODE +", stay in character at all times. "
|
||||
silly_prompt = "You are " + SILLY_MODE + ", stay in character at all times. "
|
||||
print("Silly mode is on: " + SILLY_MODE)
|
||||
else:
|
||||
silly_prompt = ""
|
||||
|
||||
starter_prompt_generic = 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."
|
||||
starter_prompt_generic = (
|
||||
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."
|
||||
)
|
||||
|
||||
goal_choose_agent_type = AgentGoal(
|
||||
id = "goal_choose_agent_type",
|
||||
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.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: "
|
||||
"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. ",
|
||||
"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=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' }",
|
||||
]
|
||||
@@ -54,7 +59,7 @@ pirate_category_tag = "pirate"
|
||||
if SILLY_MODE == "a pirate":
|
||||
pirate_category_tag = "system"
|
||||
goal_pirate_treasure = AgentGoal(
|
||||
id = "goal_pirate_treasure",
|
||||
id="goal_pirate_treasure",
|
||||
category_tag=pirate_category_tag,
|
||||
agent_name="Arrr, Find Me Treasure!",
|
||||
agent_friendly_description="Sail the high seas and find me pirate treasure, ye land lubber!",
|
||||
@@ -63,9 +68,9 @@ goal_pirate_treasure = AgentGoal(
|
||||
tool_registry.guess_location_tool,
|
||||
],
|
||||
description="The user wants to find a pirate treasure. "
|
||||
"Help the user gather args for these tools, in a loop, until treasure_found is True or the user requests to be done: "
|
||||
"1. GiveHint: If the user wants a hint regarding the location of the treasure, give them a hint. If they do not want a hint, this tool is optional."
|
||||
"2. GuessLocation: The user guesses where the treasure is, by giving an address. ",
|
||||
"Help the user gather args for these tools, in a loop, until treasure_found is True or the user requests to be done: "
|
||||
"1. GiveHint: If the user wants a hint regarding the location of the treasure, give them a hint. If they do not want a hint, this tool is optional."
|
||||
"2. GuessLocation: The user guesses where the treasure is, by giving an address. ",
|
||||
starter_prompt=starter_prompt_generic,
|
||||
example_conversation_history="\n ".join(
|
||||
[
|
||||
@@ -95,8 +100,9 @@ goal_pirate_treasure = AgentGoal(
|
||||
),
|
||||
)
|
||||
|
||||
# ----- Travel Goals ---
|
||||
goal_match_train_invoice = AgentGoal(
|
||||
id = "goal_match_train_invoice",
|
||||
id="goal_match_train_invoice",
|
||||
category_tag="travel-trains",
|
||||
agent_name="UK Premier League Match Trip Booking",
|
||||
agent_friendly_description="Book a trip to a city in the UK around the dates of a premier league match.",
|
||||
@@ -143,10 +149,10 @@ goal_match_train_invoice = AgentGoal(
|
||||
)
|
||||
|
||||
goal_event_flight_invoice = AgentGoal(
|
||||
id = "goal_event_flight_invoice",
|
||||
id="goal_event_flight_invoice",
|
||||
category_tag="travel-flights",
|
||||
agent_name="Australia and New Zealand Event Flight Booking",
|
||||
agent_friendly_description="Book a trip to a city in Australia or New Zealand around the dates of events in that city.",
|
||||
agent_friendly_description="Book a trip to a city in Australia or New Zealand around the dates of events in that city.",
|
||||
tools=[
|
||||
tool_registry.find_events_tool,
|
||||
tool_registry.search_flights_tool,
|
||||
@@ -180,12 +186,13 @@ goal_event_flight_invoice = AgentGoal(
|
||||
),
|
||||
)
|
||||
|
||||
# ----- HR Goals ---
|
||||
# This goal uses the data/employee_pto_data.json file as dummy data.
|
||||
goal_hr_schedule_pto = AgentGoal(
|
||||
id = "goal_hr_schedule_pto",
|
||||
id="goal_hr_schedule_pto",
|
||||
category_tag="hr",
|
||||
agent_name="Schedule PTO",
|
||||
agent_friendly_description="Schedule PTO based on your available PTO.",
|
||||
agent_friendly_description="Schedule PTO based on your available PTO.",
|
||||
tools=[
|
||||
tool_registry.current_pto_tool,
|
||||
tool_registry.future_pto_calc_tool,
|
||||
@@ -220,10 +227,10 @@ goal_hr_schedule_pto = AgentGoal(
|
||||
|
||||
# This goal uses the data/employee_pto_data.json file as dummy data.
|
||||
goal_hr_check_pto = AgentGoal(
|
||||
id = "goal_hr_check_pto",
|
||||
id="goal_hr_check_pto",
|
||||
category_tag="hr",
|
||||
agent_name="Check PTO Amount",
|
||||
agent_friendly_description="Check your available PTO.",
|
||||
agent_friendly_description="Check your available PTO.",
|
||||
tools=[
|
||||
tool_registry.current_pto_tool,
|
||||
],
|
||||
@@ -245,10 +252,10 @@ goal_hr_check_pto = AgentGoal(
|
||||
|
||||
# check integration with bank
|
||||
goal_hr_check_paycheck_bank_integration_status = AgentGoal(
|
||||
id = "goal_hr_check_paycheck_bank_integration_status",
|
||||
id="goal_hr_check_paycheck_bank_integration_status",
|
||||
category_tag="hr",
|
||||
agent_name="Check paycheck deposit status",
|
||||
agent_friendly_description="Check your integration between your employer and your financial institution.",
|
||||
agent_friendly_description="Check your integration between your employer and your financial institution.",
|
||||
tools=[
|
||||
tool_registry.paycheck_bank_integration_status_check,
|
||||
],
|
||||
@@ -268,12 +275,13 @@ goal_hr_check_paycheck_bank_integration_status = AgentGoal(
|
||||
),
|
||||
)
|
||||
|
||||
# ----- FinServ Goals ---
|
||||
# this tool checks account balances, and uses ./data/customer_account_data.json as dummy data
|
||||
goal_fin_check_account_balances = AgentGoal(
|
||||
id = "goal_fin_check_account_balances",
|
||||
id="goal_fin_check_account_balances",
|
||||
category_tag="fin",
|
||||
agent_name="Check balances",
|
||||
agent_friendly_description="Check your account balances in Checking, Savings, etc.",
|
||||
agent_name="Account Balances",
|
||||
agent_friendly_description="Check your account balances in Checking, Savings, etc.",
|
||||
tools=[
|
||||
tool_registry.financial_check_account_is_valid,
|
||||
tool_registry.financial_get_account_balances,
|
||||
@@ -285,7 +293,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 }",
|
||||
@@ -293,10 +301,10 @@ goal_fin_check_account_balances = AgentGoal(
|
||||
"user_confirmed_tool_run: <user clicks confirm on FinCheckAccountBalance tool>",
|
||||
"tool_result: { 'name': Matt Murdock, 'email': matt.murdock@nelsonmurdock.com, 'account_id': 11235, 'checking_balance': 875.40, 'savings_balance': 3200.15, 'bitcoin_balance': 0.1378, 'account_creation_date': 2014-03-10 }",
|
||||
"agent: Your account balances are as follows: \n "
|
||||
"Checking: $875.40. \n "
|
||||
"Savings: $3200.15. \n "
|
||||
"Bitcoint: 0.1378 \n "
|
||||
"Thanks for being a customer since 2014!",
|
||||
"Checking: $875.40. \n "
|
||||
"Savings: $3200.15. \n "
|
||||
"Bitcoint: 0.1378 \n "
|
||||
"Thanks for being a customer since 2014!",
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -304,10 +312,10 @@ goal_fin_check_account_balances = AgentGoal(
|
||||
# this tool checks account balances, and uses ./data/customer_account_data.json as dummy data
|
||||
# it also uses a separate workflow/tool, see ./setup.md for details
|
||||
goal_fin_move_money = AgentGoal(
|
||||
id = "goal_fin_move_money",
|
||||
id="goal_fin_move_money",
|
||||
category_tag="fin",
|
||||
agent_name="Money Order",
|
||||
agent_friendly_description="Initiate a money movement order.",
|
||||
agent_name="Money Movement",
|
||||
agent_friendly_description="Initiate money movement.",
|
||||
tools=[
|
||||
tool_registry.financial_check_account_is_valid,
|
||||
tool_registry.financial_get_account_balances,
|
||||
@@ -316,41 +324,41 @@ goal_fin_move_money = AgentGoal(
|
||||
description="The user wants to transfer money in their account at the bank or financial institution. To assist with that goal, help the user gather args for these tools in order: "
|
||||
"1. FinCheckAccountIsValid: validate the user's account is valid"
|
||||
"2. FinCheckAccountBalance: Tell the user their account balance at the bank or financial institution"
|
||||
"3. FinMoveMoney: Initiate a money movement order",
|
||||
"3. FinMoveMoney: Initiate money movement (transfer)",
|
||||
starter_prompt=starter_prompt_generic,
|
||||
example_conversation_history="\n ".join(
|
||||
[
|
||||
"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:",
|
||||
"user_confirmed_tool_run: <user clicks confirm on FinCheckAccountBalance tool>",
|
||||
"user_confirmed_tool_run: <user clicks confirm on FinCheckAccountBalance tool>",
|
||||
"tool_result: { 'name': Matt Murdock, 'email': matt.murdock@nelsonmurdock.com, 'account_id': 11235, 'checking_balance': 875.40, 'savings_balance': 3200.15, 'bitcoin_balance': 0.1378, 'account_creation_date': 2014-03-10 }",
|
||||
"agent: Your account balances are as follows: \n "
|
||||
"Checking: $875.40. \n "
|
||||
"Savings: $3200.15. \n "
|
||||
"Bitcoint: 0.1378 \n "
|
||||
"Checking: $875.40. \n "
|
||||
"Savings: $3200.15. \n "
|
||||
"Bitcoint: 0.1378 \n "
|
||||
"agent: how much would you like to move, from which account type, and to which account number?",
|
||||
"user: I'd like to move $500 from savings to account number #56789",
|
||||
"user_confirmed_tool_run: <user clicks confirm on FinMoveMoney tool>",
|
||||
"tool_result: { 'status': money movement complete, 'confirmation id': 333421, 'new_balance': $2700.15 }",
|
||||
"agent: Money movement order completed! New account balance: $2700.15. Your confirmation id is 333421. "
|
||||
"agent: Money movement completed! New account balance: $2700.15. Your confirmation id is 333421. ",
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
# this starts a loan approval process
|
||||
# it also uses a separate workflow/tool, see ./setup.md for details #todo
|
||||
# it also uses a separate workflow/tool, see ./setup.md for details
|
||||
goal_fin_loan_application = AgentGoal(
|
||||
id = "goal_fin_loan_application",
|
||||
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, #todo
|
||||
tool_registry.financial_submit_loan_approval,
|
||||
],
|
||||
description="The user wants to apply for a loan at the financial institution. To assist with that goal, help the user gather args for these tools in order: "
|
||||
"1. FinCheckAccountIsValid: validate the user's account is valid"
|
||||
@@ -359,7 +367,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 }",
|
||||
@@ -367,12 +375,86 @@ goal_fin_loan_application = AgentGoal(
|
||||
"user: I'd like a loan for $500",
|
||||
"user_confirmed_tool_run: <user clicks confirm on FinCheckAccountSubmitLoanApproval tool>",
|
||||
"tool_result: { 'status': submitted, 'detailed_status': loan application is submitted and initial validation is complete, 'confirmation id': 333421, 'next_step': You'll receive a confirmation for final approval in three business days }",
|
||||
"agent: I have submitted your loan application process and the initial validation is successful. Your application ID is 333421. You'll receive a notification for final approval from us in three business days. "
|
||||
"agent: I have submitted your loan application process and the initial validation is successful. Your application ID is 333421. You'll receive a notification for final approval from us in three business days. ",
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
#Add the goals to a list for more generic processing, like listing available agents
|
||||
# ----- E-Commerce Goals ---
|
||||
# this tool checks account balances, and uses ./data/customer_account_data.json as dummy data
|
||||
goal_ecomm_order_status = AgentGoal(
|
||||
id="goal_ecomm_order_status",
|
||||
category_tag="ecommerce",
|
||||
agent_name="Check Order Status",
|
||||
agent_friendly_description="Check the status of your order.",
|
||||
tools=[
|
||||
tool_registry.ecomm_get_order,
|
||||
tool_registry.ecomm_track_package,
|
||||
],
|
||||
description="The user wants to learn the status of a specific order. If the status is 'shipped' or 'delivered', they might want to get the package tracking information. To assist with that goal, help the user gather args for these tools in order: "
|
||||
"1. GetOrder: get information about an order"
|
||||
"2. TrackPackage: provide tracking information for the package. This tool is optional and should only be offered if the status is 'shipped' OR 'delivered' - otherwise, skip this tool and do not mention it to the user.",
|
||||
starter_prompt=starter_prompt_generic,
|
||||
example_conversation_history="\n ".join(
|
||||
[
|
||||
"user: I'd like to know the status of my order",
|
||||
"agent: Sure! I can help you out with that. May I have your order number?",
|
||||
"user: 102 ",
|
||||
"user_confirmed_tool_run: <user clicks confirm on GetOrderStatus tool>",
|
||||
"tool_result: { 'id': '102', 'summary': 'Red Sunglasses', 'email': 'matt.murdock@nelsonmurdock.com', 'status': 'shipped', 'order_date': '2025-04-01', 'last_order_update': '2025-04-06', 'tracking_id': '039813852990618' }",
|
||||
"agent: Your order 'Red Sunglasses,' placed April 1, 2025, was shipped on April 6, 2025. Would you like to see the tracking inforation?",
|
||||
"user: Yes",
|
||||
"user_confirmed_tool_run: <user clicks confirm on TrackPackage tool>",
|
||||
"tool_result: { 'scheduled_delivery_date': 'April 30, 2025', 'carrier': 'USPS', 'status_summary': 'Your item has left our acceptance facility and is in transit to a sorting facility on April 10, 2025 at 7:06 am in IRON RIDGE, WI 53035.', 'tracking_link': 'https://tools.usps.com/go/TrackConfirmAction?qtc_tLabels1=12345','last_tracking_update': '2025-03-22T16:14:48'}",
|
||||
"agent: Your package is scheduled to be delivered on April 30, 2025 via USPS. Here is the most recent status from them regarding your package, updated as of March 22: \n"
|
||||
"Your item has left our acceptance facility and is in transit to a sorting facility on April 10, 2025 at 7:06 am in IRON RIDGE, WI 53035. \n"
|
||||
"You can find the full tracking details here: tracking_link !",
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
goal_ecomm_list_orders = AgentGoal(
|
||||
id="goal_ecomm_list_orders",
|
||||
category_tag="ecommerce",
|
||||
agent_name="List All Orders",
|
||||
agent_friendly_description="List all orders for a user.",
|
||||
tools=[
|
||||
tool_registry.ecomm_list_orders,
|
||||
tool_registry.ecomm_get_order,
|
||||
tool_registry.ecomm_track_package,
|
||||
],
|
||||
description="The user wants to see all of their orders. They may want more details about specific orders, and if the status of an order is 'shipped' or 'delivered', they might want to get the package tracking information. To assist with that goal, help the user gather args for this tool: "
|
||||
"1. ListOrders: list orders for a user"
|
||||
" and then offer the following tools, in a loop, until the user indicates they are done:"
|
||||
"2. GetOrder: get information about an order. This tool is optional."
|
||||
"3. TrackPackage: provide tracking information for the package. This tool is optional and should only be offered if the status is 'shipped' OR 'delivered' - otherwise, skip this tool and do not mention it to the user.",
|
||||
starter_prompt=starter_prompt_generic,
|
||||
example_conversation_history="\n ".join(
|
||||
[
|
||||
"user: I'd like to see all of my orders.",
|
||||
"agent: Sure! I can help you out with that. May I have your email address?",
|
||||
"user: email is bob.johnson@emailzzz.com ",
|
||||
"user_confirmed_tool_run: <user clicks confirm on ListOrders tool>",
|
||||
"tool_result: a list of orders including [{'id': '102', 'summary': 'Red Sunglasses', 'email': 'matt.murdock@nelsonmurdock.com', 'status': 'shipped', 'order_date': '2025-04-01', 'last_order_update': '2025-04-06', 'tracking_id': '039813852990618' }, { 'id': '103', 'summary': 'Blue Sunglasses', 'email': 'matt.murdock@nelsonmurdock.com', 'status': 'paid', 'order_date': '2025-04-03', 'last_order_update': '2025-04-07' }]",
|
||||
"agent: Your orders are as follows: \n",
|
||||
"1. Red Sunglasses, ordered 4/1/2025 \n",
|
||||
"2. Blue Sunglasses, ordered 4/3/2025 \n",
|
||||
"Would you like more information about any of your orders?"
|
||||
"user: Yes, the Red Sunglasses",
|
||||
"agent: Your order 'Red Sunglasses,' placed April 1, 2025, was shipped on April 6, 2025. Would you like to see the tracking inforation?",
|
||||
"user: Yes",
|
||||
"user_confirmed_tool_run: <user clicks confirm on TrackPackage tool>",
|
||||
"tool_result: { 'scheduled_delivery_date': 'April 30, 2025', 'carrier': 'USPS', 'status_summary': 'Your item has left our acceptance facility and is in transit to a sorting facility on April 10, 2025 at 7:06 am in IRON RIDGE, WI 53035.', 'tracking_link': 'https://tools.usps.com/go/TrackConfirmAction?qtc_tLabels1=12345','last_tracking_update': '2025-03-22T16:14:48'}",
|
||||
"agent: Your package is scheduled to be delivered on April 30, 2025 via USPS. Here is the most recent status from them regarding your package \n, updated as of March 22: \n"
|
||||
"Your item has left our acceptance facility and is in transit to a sorting facility on April 10, 2025 at 7:06 am in IRON RIDGE, WI 53035. \n"
|
||||
"You can find the full tracking details here: tracking_link ! \n"
|
||||
"Would you like more information about any of your other orders?",
|
||||
"user: No" "agent: Thanks, and have a great day!",
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
# Add the goals to a list for more generic processing, like listing available agents
|
||||
goal_list: List[AgentGoal] = []
|
||||
goal_list.append(goal_choose_agent_type)
|
||||
goal_list.append(goal_pirate_treasure)
|
||||
@@ -384,6 +466,29 @@ goal_list.append(goal_hr_check_paycheck_bank_integration_status)
|
||||
goal_list.append(goal_fin_check_account_balances)
|
||||
goal_list.append(goal_fin_move_money)
|
||||
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
|
||||
|
||||
@@ -11,7 +11,7 @@ def search_airport(query: str) -> list:
|
||||
"""
|
||||
load_dotenv(override=True)
|
||||
api_key = os.getenv("RAPIDAPI_KEY", "YOUR_DEFAULT_KEY")
|
||||
api_host = os.getenv("RAPIDAPI_HOST", "sky-scrapper.p.rapidapi.com")
|
||||
api_host = os.getenv("RAPIDAPI_HOST_FLIGHTS", "sky-scrapper.p.rapidapi.com")
|
||||
|
||||
conn = http.client.HTTPSConnection(api_host)
|
||||
headers = {
|
||||
@@ -73,7 +73,7 @@ def search_flights_real_api(
|
||||
# Step 2: Call flight search with resolved codes
|
||||
load_dotenv(override=True)
|
||||
api_key = os.getenv("RAPIDAPI_KEY", "YOUR_DEFAULT_KEY")
|
||||
api_host = os.getenv("RAPIDAPI_HOST", "sky-scrapper.p.rapidapi.com")
|
||||
api_host = os.getenv("RAPIDAPI_HOST_FLIGHTS", "sky-scrapper.p.rapidapi.com")
|
||||
|
||||
conn = http.client.HTTPSConnection(api_host)
|
||||
headers = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from models.tool_definitions import ToolDefinition, ToolArgument
|
||||
|
||||
# ----- System tools -----
|
||||
list_agents_tool = ToolDefinition(
|
||||
name="ListAgents",
|
||||
@@ -21,12 +22,13 @@ change_goal_tool = ToolDefinition(
|
||||
give_hint_tool = ToolDefinition(
|
||||
name="GiveHint",
|
||||
description="Give a hint to the user regarding the location of the pirate treasure. Use previous conversation to determine the hint_total, it should initially be 0 ",
|
||||
arguments=[
|
||||
arguments=[
|
||||
ToolArgument(
|
||||
name="hint_total",
|
||||
type="number",
|
||||
description="How many hints have been given",
|
||||
),],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
guess_location_tool = ToolDefinition(
|
||||
@@ -54,7 +56,8 @@ guess_location_tool = ToolDefinition(
|
||||
# ----- Travel use cases tools -----
|
||||
search_flights_tool = ToolDefinition(
|
||||
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). "
|
||||
"You are allowed to suggest dates from the conversation history, but ALWAYS ask the user if ok.",
|
||||
arguments=[
|
||||
ToolArgument(
|
||||
name="origin",
|
||||
@@ -76,6 +79,12 @@ search_flights_tool = ToolDefinition(
|
||||
type="ISO8601",
|
||||
description="End of date range in human readable format, when you want to return",
|
||||
),
|
||||
ToolArgument(
|
||||
name="userConfirmation",
|
||||
type="string",
|
||||
description="Indication of the user's desire to search flights, and to confirm the details "
|
||||
+ "before moving on to the next step",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -115,6 +124,11 @@ book_trains_tool = ToolDefinition(
|
||||
type="string",
|
||||
description="The IDs of the trains to book, comma separated",
|
||||
),
|
||||
ToolArgument(
|
||||
name="userConfirmation",
|
||||
type="string",
|
||||
description="Indication of user's desire to book train tickets",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -132,6 +146,11 @@ create_invoice_tool = ToolDefinition(
|
||||
type="string",
|
||||
description="A description of the item details to be invoiced, inferred from the conversation history.",
|
||||
),
|
||||
ToolArgument(
|
||||
name="userConfirmation",
|
||||
type="string",
|
||||
description="Indication of user's desire to create an invoice",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -278,10 +297,9 @@ financial_get_account_balances = ToolDefinition(
|
||||
name="FinCheckAccountBalance",
|
||||
description="Get account balance for your accounts. "
|
||||
"Returns the account balances of your accounts. ",
|
||||
|
||||
arguments=[
|
||||
ToolArgument(
|
||||
name="accountkey",
|
||||
name="email_address_or_account_ID",
|
||||
type="string",
|
||||
description="email address or account ID of user",
|
||||
),
|
||||
@@ -289,50 +307,93 @@ financial_get_account_balances = ToolDefinition(
|
||||
)
|
||||
|
||||
financial_move_money = ToolDefinition(
|
||||
name="FinMoveMoneyOrder",
|
||||
description="Execute a money movement order. "
|
||||
"Returns the status of the order and the account balance of the account money was moved from. ",
|
||||
|
||||
name="FinMoveMoney",
|
||||
description="Send money from one account to another under the same acount ID (e.g. checking to savings). "
|
||||
"Returns the status of the order and the new balances in each account. ",
|
||||
arguments=[
|
||||
ToolArgument(
|
||||
name="accountkey",
|
||||
name="email_address_or_account_ID",
|
||||
type="string",
|
||||
description="email address or account ID of user",
|
||||
description="email address or account ID of user (you will need both to find the account)",
|
||||
),
|
||||
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",
|
||||
description="amount to move in the order (e.g. checking or savings)",
|
||||
),
|
||||
|
||||
ToolArgument(
|
||||
ToolArgument(
|
||||
name="destinationaccount",
|
||||
type="string",
|
||||
description="account number to move the money to",
|
||||
description="account to move the money to (e.g. checking or savings)",
|
||||
),
|
||||
ToolArgument(
|
||||
name="userConfirmation",
|
||||
type="string",
|
||||
description="Indication of user's desire to move money",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
financial_submit_loan_approval = ToolDefinition(
|
||||
name="FinCheckAccountSubmitLoanApproval",
|
||||
description="Submit a loan application. "
|
||||
"Returns the loan status. ",
|
||||
|
||||
description="Submit a loan application. " "Returns the loan status. ",
|
||||
arguments=[
|
||||
ToolArgument(
|
||||
name="accountkey",
|
||||
name="email_address_or_account_ID",
|
||||
type="string",
|
||||
description="email address or account ID of user",
|
||||
),
|
||||
ToolArgument(
|
||||
),
|
||||
ToolArgument(
|
||||
name="amount",
|
||||
type="string",
|
||||
description="amount requested for the loan",
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
# ----- ECommerce Use Case Tools -----
|
||||
ecomm_list_orders = ToolDefinition(
|
||||
name="ListOrders",
|
||||
description="Get all orders for a certain email address.",
|
||||
arguments=[
|
||||
ToolArgument(
|
||||
name="email_address",
|
||||
type="string",
|
||||
description="Email address of user by which to find orders",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
ecomm_get_order = ToolDefinition(
|
||||
name="GetOrder",
|
||||
description="Get infromation about an order by order ID.",
|
||||
arguments=[
|
||||
ToolArgument(
|
||||
name="order_id",
|
||||
type="string",
|
||||
description="ID of order to determine status of",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
ecomm_track_package = ToolDefinition(
|
||||
name="TrackPackage",
|
||||
description="Get tracking information for a package by shipping provider and tracking ID",
|
||||
arguments=[
|
||||
ToolArgument(
|
||||
name="tracking_id",
|
||||
type="string",
|
||||
description="ID of package to track",
|
||||
),
|
||||
ToolArgument(
|
||||
name="userConfirmation",
|
||||
type="string",
|
||||
description="Indication of user's desire to get package tracking information",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -108,7 +108,7 @@ class AgentGoalWorkflow:
|
||||
conversation_history=self.conversation_history,
|
||||
agent_goal=self.goal,
|
||||
)
|
||||
validation_result = await workflow.execute_activity(
|
||||
validation_result = await workflow.execute_activity_method(
|
||||
ToolActivities.agent_validatePrompt,
|
||||
args=[validation_input],
|
||||
schedule_to_close_timeout=LLM_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT,
|
||||
@@ -134,7 +134,7 @@ class AgentGoalWorkflow:
|
||||
prompt_input = ToolPromptInput(prompt=prompt, context_instructions=context_instructions)
|
||||
|
||||
# connect to LLM and execute to get next steps
|
||||
tool_data = await workflow.execute_activity(
|
||||
tool_data = await workflow.execute_activity_method(
|
||||
ToolActivities.agent_toolPlanner,
|
||||
prompt_input,
|
||||
schedule_to_close_timeout=LLM_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT,
|
||||
@@ -169,15 +169,12 @@ 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":
|
||||
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.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"
|
||||
elif next_step == "done":
|
||||
@@ -210,7 +207,7 @@ class AgentGoalWorkflow:
|
||||
|
||||
#Signal that comes from api/main.py via a post to /confirm
|
||||
@workflow.signal
|
||||
async def confirmed(self) -> None:
|
||||
async def confirm(self) -> None:
|
||||
"""Signal handler for user confirmation of tool execution."""
|
||||
workflow.logger.info("Received user signal: confirmation")
|
||||
self.confirmed = True
|
||||
@@ -316,8 +313,9 @@ class AgentGoalWorkflow:
|
||||
async def lookup_wf_env_settings(self, combined_input: CombinedInput)->None:
|
||||
env_lookup_input = EnvLookupInput(
|
||||
show_confirm_env_var_name = "SHOW_CONFIRM",
|
||||
show_confirm_default = True)
|
||||
env_output:EnvLookupOutput = await workflow.execute_activity(
|
||||
show_confirm_default = True,
|
||||
)
|
||||
env_output:EnvLookupOutput = await workflow.execute_activity_method(
|
||||
ToolActivities.get_wf_env_vars,
|
||||
env_lookup_input,
|
||||
start_to_close_timeout=LLM_ACTIVITY_START_TO_CLOSE_TIMEOUT,
|
||||
@@ -347,14 +345,12 @@ class AgentGoalWorkflow:
|
||||
self.prompt_queue
|
||||
)
|
||||
|
||||
#set new goal if we should
|
||||
# set new goal if we should
|
||||
if len(self.tool_results) > 0:
|
||||
if "ChangeGoal" in self.tool_results[-1].values() and "new_goal" in self.tool_results[-1].keys():
|
||||
new_goal = self.tool_results[-1].get("new_goal")
|
||||
workflow.logger.info(f"Booya new goal!: {new_goal}")
|
||||
self.change_goal(new_goal)
|
||||
elif "ListAgents" in self.tool_results[-1].values() and self.goal.id != "goal_choose_agent_type":
|
||||
workflow.logger.info("setting goal to goal_choose_agent_type")
|
||||
self.change_goal("goal_choose_agent_type")
|
||||
return waiting_for_confirm
|
||||
|
||||
@@ -362,9 +358,13 @@ class AgentGoalWorkflow:
|
||||
# also don't forget you can look at the workflow itself and do queries if you want
|
||||
def print_useful_workflow_vars(self, status_or_step:str) -> None:
|
||||
print(f"***{status_or_step}:***")
|
||||
print(f"force confirm? {self.tool_data['force_confirm']}")
|
||||
print(f"next step: {self.tool_data.get('next')}")
|
||||
print(f"current_tool: {self.tool_data.get('tool')}")
|
||||
print(f"self.confirm: {self.confirmed}")
|
||||
print(f"waiting_for_confirm (about to be set to true): {self.waiting_for_confirm}")
|
||||
if self.goal:
|
||||
print(f"current goal: {self.goal.id}")
|
||||
if self.tool_data:
|
||||
print(f"force confirm? {self.tool_data['force_confirm']}")
|
||||
print(f"next step: {self.tool_data.get('next')}")
|
||||
print(f"current_tool: {self.tool_data.get('tool')}")
|
||||
else:
|
||||
print("no tool data initialized yet")
|
||||
print(f"self.confirmed: {self.confirmed}")
|
||||
|
||||
|
||||