52 Commits

Author SHA1 Message Date
Steve Androulakis
5ede58519c setup readme 2025-05-26 13:59:12 -07:00
znack
87afa718d5 Add Docker for better DX from Znack's PR 2025-05-26 13:37:03 -07:00
Mason Egger
2f3afd6954 relocking Poetry lock file to align with pyproject.toml (#32)
Approved, thanks Mason!
2025-05-08 11:12:38 -07:00
Steve Androulakis
edb7df5b3c 0.2.0 changelog 2025-04-28 09:16:34 -07:00
Steve Androulakis
05041f9433 prompt eng on money movement args 2025-04-25 07:25:29 -07:00
Steve Androulakis
0767533ca1 prompt goal fix: not money order 2025-04-25 07:17:17 -07:00
Steve Androulakis
611a6b6238 Merge pull request #29 from temporal-community/josh-multiagent-steve-fixes
Josh multiagent capability, Steve fixes. Original PR: https://github.com/temporal-community/temporal-ai-agent/pull/25
2025-04-24 20:28:37 -07:00
Steve Androulakis
7850af6be2 prompt engineering 2025-04-24 20:25:02 -07:00
Steve Androulakis
a29b100017 mucho fixes and prompt engineering 2025-04-24 20:16:24 -07:00
Steve Androulakis
a5fc6ad477 Josh multi-agent changes
Merge remote-tracking branch 'josh/main' into josh-merge-branch
2025-04-24 18:54:18 -07:00
Joshua Smith
c9ae8b29bf Polishing before Webinar: Merge pull request #18 from joshmsmith/development
- updates to pyproject.toml to add contributors and update some pytest config
- updates to documentation - clarification cleanup
- defaulting to finserv goals
2025-04-24 12:44:58 -04:00
Joshua Smith
7ef10e2481 - updates to pyproject.toml to add contributors and update some pytest config
- updates to documentation - clarification cleanup
- defaulting to finserv goals
2025-04-24 12:37:16 -04:00
Joshua Smith
183f834635 Merge pull request #17 from joshmsmith/development
testing complete
2025-04-23 11:21:15 -04:00
Joshua Smith
47c4b99f2c adjusting loan sample conversation 2025-04-22 15:16:33 -04:00
Joshua Smith
823208db3c - adding Steve's updated confirm box UI
- goal prompts and agent changes to smooth out that interaction and remove listagents duplication
adding extra confirmation for money movement tool
2025-04-22 12:22:42 -04:00
Steve Androulakis
b4aa929451 confirm box is pretty now 2025-04-21 09:00:06 -07:00
Joshua Smith
b4d57cfad6 Merge main changes into development 2025-04-21 09:56:58 -04:00
Joshua Smith
2a1624f621 back to claud 3.5
prompts and adding list-agents automatically in goal registry
fixing some finserv tool args
2025-04-21 09:55:45 -04:00
Joshua Smith
326a5a5beb Merge pull request #16 from joshmsmith/ecommerce
Fixes coming in delivered by the ecommerce branch:

new ecommerce scenarios
fixes for multi-goal:post first real goal goal switch: duplicate listagents behavior from the toolplanner
adding ecommerce initial guidance
fixed new-goal guidance prompts for multi-goal mode
(minor) fixed abug in money movement so it won't connect to temporal cloud if it's not doing a real workflow
(minor) fixed abug in loan application so it won't connect to temporal cloud if it's not doing a real workflow
some todo notes cleanup
2025-04-18 17:13:03 -04:00
Joshua Smith
163477c066 Merge branch 'main' into ecommerce 2025-04-18 17:11:26 -04:00
Joshua Smith
d48dafcaa5 - fixes for multi-goal:post first real goal goal switch: duplicate listagents behavior from the toolplanner
- adding ecommerce initial guidance
- fixed new-goal guidance prompts for multi-goal mode
- (minor) fixed abug in money movement so it won't connect to temporal cloud if it's not doing a real workflow
- (minor) fixed abug in loan application so it won't connect to temporal cloud if it's not doing a real workflow
- some todo notes cleanup
2025-04-18 17:08:44 -04:00
Joshua Smith
32e856e494 fixing second goal selection loop with prompts 2025-04-17 15:20:17 -04:00
Joshua Smith
a07af3e7bf Merge pull request #15 from joshmsmith/financial-services-demo-scenarios
merging commits/no change
2025-04-17 11:34:39 -04:00
Joshua Smith
fc07315358 Merge pull request #14 from joshmsmith/development
Development
2025-04-17 11:34:01 -04:00
Joshua Smith
ebb12feafa Merge pull request #13 from joshmsmith/main
sync development with changes from main
2025-04-17 10:58:19 -04:00
Joshua Smith
7b2c7cef8f Merge pull request #12 from joshmsmith/main
fixing some post-merge bugs
2025-04-17 10:56:14 -04:00
Laine
4410f30642 Default to dummy data for tracking package 2025-04-17 10:09:58 -04:00
Laine
77942b19a1 Merge branch 'ecommerce' of https://github.com/joshmsmith/temporal-ai-agent into ecommerce 2025-04-17 09:19:33 -04:00
Laine
247bac0a28 Try to fix multiple ListAgents steps 2025-04-17 09:19:29 -04:00
Joshua Smith
cf55f0eaee fixing some post-merge bugs 2025-04-17 08:59:15 -04:00
Joshua Smith
0c678a120a Merge pull request #11 from joshmsmith/main
pushing changes from main to finserv branch
2025-04-17 08:54:38 -04:00
Joshua Smith
6383614076 Merge branch 'financial-services-demo-scenarios' into main 2025-04-17 08:54:24 -04:00
Joshua Smith
92bce11151 Merge pull request #10 from joshmsmith/main
pushing main changes down to ecommerce
2025-04-17 08:52:32 -04:00
Joshua Smith
e5405907fa Merge branch 'ecommerce' into main 2025-04-17 08:52:17 -04:00
Joshua Smith
dddf7f4ccd Merge pull request #9 from joshmsmith/main
pushing main changes down to development
2025-04-17 08:48:26 -04:00
Joshua Smith
6f245a1998 Merge branch 'development' into main 2025-04-17 08:48:15 -04:00
Joshua Smith
86a6dfe991 renaming signal from confirmed to confirm 2025-04-17 06:00:17 -04:00
Joshua Smith
83c6a2454d 1. These aren't the tests you're looking for
2. fixing confirmed signal for now
2025-04-17 05:57:55 -04:00
Joshua Smith
463ae581ac adding .env* stuff to gitignore, fixing a minor docs formatting bug 2025-04-17 05:32:55 -04:00
Joshua Smith
e62b105872 adding to gitignore 2025-04-17 05:23:17 -04:00
Joshua Smith
7b52b8a817 adding to todo, gitignore 2025-04-16 16:26:37 -04:00
Joshua Smith
6f9079ba12 updates to todo 2025-04-16 16:23:31 -04:00
Joshua Smith
ac44d35acb changes to .gitignore 2025-04-15 16:46:09 -04:00
Joshua Smith
50463170d2 Merge branch 'temporal-community:main' into ecommerce 2025-04-15 16:34:32 -04:00
Joshua Smith
13d0085882 Merge branch 'temporal-community:main' into development 2025-04-15 16:34:23 -04:00
Joshua Smith
e92e3f43c9 changes to make the project more python compliant and fix the bug tracker URL 2025-04-15 11:01:18 -04:00
Joshua Smith
812e295f3a switching to method activity calls 2025-04-14 09:27:33 -04:00
Joshua Smith
dddd2977b9 adding ecommerce to .env.example and some error handling in track_package 2025-04-13 17:03:50 -04:00
Laine
4e0ff0e535 Rename get_order_status to get_order, add ecommerce list orders goal 2025-04-11 17:14:10 -04:00
Laine
aba934e8b4 Initial add of ecommercie - order status goal and tools 2025-04-10 16:57:15 -04:00
Joshua Smith
2539436a79 adding testing config 2025-04-10 10:26:55 -04:00
Joshua Smith
6517b624ee Merge pull request #6 from joshmsmith/main
syncing dev
2025-04-10 09:48:29 -04:00
34 changed files with 1421 additions and 322 deletions

View File

@@ -1,5 +1,6 @@
RAPIDAPI_KEY=9df2cb5... 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=.... FOOTBALL_DATA_API_KEY=....
STRIPE_API_KEY=sk_test_51J... 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 #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. # - 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 #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) # 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
View File

@@ -32,4 +32,4 @@ coverage.xml
.idea/ .idea/
.env .env
*.env .env*

30
CHANGELOG.md Normal file
View File

@@ -0,0 +1,30 @@
# Changelog
All notable changes to this project will be documented in this file.
## [0.2.0] - 2025-04-24
![0.2.0 Changes Screenshot](./assets/0.2.0_changes.jpeg)
### Added
- **Multigoal 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), Ecommerce 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

30
Dockerfile Normal file
View File

@@ -0,0 +1,30 @@
FROM python:3.10-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc build-essential && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Copy requirements first for better caching
RUN pip install --no-cache-dir poetry
# Install Python dependencies without creating a virtualenv
COPY pyproject.toml poetry.lock ./
RUN poetry config virtualenvs.create false \
&& poetry install --without dev --no-interaction --no-ansi --no-root
# Copy application code
COPY . .
# Set Python to run in unbuffered mode (recommended for Docker)
ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH=/app
# Expose the port the app will run on
EXPOSE 8000
# Default to running only the API server; worker and train-api are separate Compose services
CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -8,19 +8,22 @@ It's really helpful to [watch the demo (5 minute YouTube video)](https://www.you
[![Watch the demo](./assets/agent-youtube-screenshot.jpeg)](https://www.youtube.com/watch?v=GEXllEH2XiQ) [![Watch the demo](./assets/agent-youtube-screenshot.jpeg)](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? ## 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. 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). For more, check out [architecture-decisions](./architecture-decisions.md).
## What is "Agentic AI"? ## What is "Agentic AI"?
These are the key elements of an agentic framework: 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 1. Goals that a system can accomplish, 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 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 human input and approval 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 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 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) 6. Prompt construction made of system prompts, conversation history, and tool metadata - sent to the LLM to create user questions and confirmations
7. Bonus: durable tool execution via Temporal Activities 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). For a deeper dive into this, check out the [architecture guide](./architecture.md).
@@ -45,15 +48,6 @@ 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. See [the guide to adding goals and tools](./adding-goals-and-tools.md) for more ways you can add features.
## Enablement Guide (internal resource for Temporal employees) ## 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 here (TODO). 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:
poetry run poe test

View File

@@ -370,8 +370,8 @@ class ToolActivities:
print("Initialized Anthropic client on demand") print("Initialized Anthropic client on demand")
response = self.anthropic_client.messages.create( response = self.anthropic_client.messages.create(
#model="claude-3-5-sonnet-20241022", # todo try claude-3-7-sonnet-20250219 model="claude-3-5-sonnet-20241022",
model="claude-3-7-sonnet-20250219", # todo try claude-3-7-sonnet-20250219 #model="claude-3-7-sonnet-20250219", # doesn't do as well
max_tokens=1024, max_tokens=1024,
system=input.context_instructions system=input.context_instructions
+ ". The current date is " + ". The current date is "

View File

@@ -1,5 +1,6 @@
# Customizing the Agent # 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. It may be helpful to review the [architecture](./architecture.md) for a guide and definition of goals, tools, etc.

View File

@@ -1,58 +1,65 @@
# Elements # 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.
![Architecture Elements](./assets/Architecture_elements.png "Architecture Elements") 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 ## 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. Temporal is used to make the process scalable, durable, reliable, secure, and visible.
### Workflow Responsibilities: ### Workflow Responsibilities:
- Orchestrates interactive loop - Orchestrates interactive loops:
- Prompts LLM, Users - 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)) - 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 - 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 ## 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). 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
Tools define the capabilities of the system. They are simple Python functions (could be in any language). 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 "hard" business logic is: you can validate and retry actions using code you write. 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. Failures are handled gracefully by Temporal.
Activities + Tools turn the probabalistic input from the user and LLM into deterministic action. Activities + Tools turn the probabalistic input from the user and LLM into deterministic action.
## Prompts ## 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). 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 ## LLM
Probabalistic execution: it will _probably_ do what you tell it to do. 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 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). You have a choice of providers - see [setup](./setup.md).
The LLM: The LLM:
- Validates user input for tools - Drives toward the initial Goal and any subsequent Goals selected by user
- Drives toward goal selected by user - Decides what to do based on input, such as:
- Decides when to execute tools - Validates user input for Tools
- Formats input and interprets output 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 - is executed by Temporal Activities
- API failures and logical failures are handled transparently - API failures and logical failures are handled transparently
## Interaction ## Interaction
Interaction is managed with Temporal Signals and Queries. These are durably stored in Workflow History. 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. 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:
![Interaction Loop](./assets/interaction_loop.png) ![Interaction Loop](./assets/interaction_loop.png)
Here's a more detailed example for gathering parameters for tools: Here's a more detailed example for gathering inputs for Tools:
![Tool Gathering](./assets/argument_gathering_cycle.png) ![Tool Gathering](./assets/argument_gathering_cycle.png)
@@ -64,4 +71,4 @@ Now that we have the pieces and what they do, here is a more complete diagram of
# Adding features # 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 129 KiB

View File

@@ -0,0 +1,20 @@
services:
api:
volumes:
- ./:/app:cached
command: uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload
worker:
volumes:
- ./:/app:cached
command: python scripts/run_worker.py
train-api:
volumes:
- ./:/app:cached
command: python thirdparty/train_api.py
frontend:
volumes:
- ./frontend:/app:cached
command: sh -c "apk update && apk add --no-cache xdg-utils && npm install && npx vite --host 0.0.0.0 --port 5173"

120
docker-compose.yml Normal file
View File

@@ -0,0 +1,120 @@
services:
# Database service
postgresql:
image: postgres:14
container_name: temporal-postgresql
environment:
POSTGRES_USER: temporal
POSTGRES_PASSWORD: temporal
POSTGRES_DB: temporal
volumes:
- postgresql:/var/lib/postgresql/data
networks:
- temporal-network
# Temporal services
temporal:
image: temporalio/auto-setup:1.27.2
container_name: temporal
ports:
- "7233:7233"
environment:
- DB=postgres12
- DB_PORT=5432
- POSTGRES_USER=temporal
- POSTGRES_PWD=temporal
- POSTGRES_SEEDS=postgresql
depends_on:
- postgresql
networks:
- temporal-network
temporal-admin-tools:
image: temporalio/admin-tools:1.27
container_name: temporal-admin-tools
depends_on:
- temporal
environment:
- TEMPORAL_CLI_ADDRESS=temporal:7233
networks:
- temporal-network
temporal-ui:
image: temporalio/ui:2.37.2
container_name: temporal-ui
ports:
- "8080:8080"
environment:
- TEMPORAL_ADDRESS=temporal:7233
- TEMPORAL_CORS_ORIGINS=http://localhost:8080
depends_on:
- temporal
networks:
- temporal-network
api:
build:
context: .
dockerfile: Dockerfile
container_name: temporal-ai-agent-api
ports:
- "8000:8000"
depends_on:
- temporal
networks:
- temporal-network
env_file:
- .env
environment:
- TEMPORAL_ADDRESS=temporal:7233
worker:
build:
context: .
dockerfile: Dockerfile
container_name: temporal-ai-agent-worker
depends_on:
- temporal
env_file:
- .env
environment:
- TEMPORAL_ADDRESS=temporal:7233
command: python scripts/run_worker.py
networks:
- temporal-network
train-api:
build:
context: .
dockerfile: Dockerfile
container_name: temporal-ai-agent-train-api
depends_on:
- temporal
env_file:
- .env
environment:
- TEMPORAL_ADDRESS=temporal:7233
command: python thirdparty/train_api.py
networks:
- temporal-network
frontend:
image: node:18-alpine
container_name: temporal-ai-agent-frontend
working_dir: /app
volumes:
- ./frontend:/app
command: sh -c "apk update && apk add --no-cache xdg-utils && npm install && npx vite --host 0.0.0.0"
ports:
- "5173:5173"
depends_on:
- api
networks:
- temporal-network
networks:
temporal-network:
driver: bridge
volumes:
postgresql:

View File

@@ -1,65 +1,153 @@
import React, { memo } from "react"; import React, { memo, useState } from "react";
/** Inline SVG icons so we dont need an extra library */
const PlayIcon = ({ className }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className={className}
aria-hidden="true"
>
<path d="M5 3.868v16.264c0 1.04 1.12 1.675 2.025 1.16l13.11-8.132a1.33 1.33 0 000-2.256L7.025 2.773C6.12 2.259 5 2.894 5 3.934z" />
</svg>
);
const SpinnerIcon = ({ className }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={`animate-spin ${className}`}
aria-hidden="true"
>
<circle cx="12" cy="12" r="10" strokeOpacity="0.25" />
<path d="M22 12a10 10 0 00-10-10" />
</svg>
);
/**
* Userfriendly confirmation card that surfaces tool invocation details
* without developer jargon. Tweaks include:
* • Left green accentborder + compact heading (visual hierarchy)
* • Collapsible arg list & array support (argumentlist UX)
* • Mobilefirst, pulsing confirm button (button affordance)
*/
const ConfirmInline = memo(({ data, confirmed, onConfirm }) => { const ConfirmInline = memo(({ data, confirmed, onConfirm }) => {
const { args, tool } = data || {}; const { args = {}, tool } = data || {};
const renderArgs = () => { // Collapsible argument list if we have more than 4 root keys
if (!args) return null; const [showAll, setShowAll] = useState(false);
const argEntries = Object.entries(args);
return ( const shouldCollapse = argEntries.length > 4 && !showAll;
<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>
);
};
if (confirmed) { /** Recursively prettyprint argument values (objects & arrays). */
return ( const RenderValue = ({ value }) => {
<div className="mt-2 p-2 border border-gray-400 dark:border-gray-600 rounded if (value === null || value === undefined) return <span className="italic"></span>;
bg-gray-50 dark:bg-gray-800 transition-colors duration-200">
<div className="text-sm text-gray-600 dark:text-gray-300"> if (Array.isArray(value)) {
<div> return (
<strong>Tool:</strong> {tool ?? "Unknown"} <ol className="pl-4 list-decimal space-y-0.5">
</div> {value.map((v, i) => (
{renderArgs()} <li key={i} className="flex gap-1">
</div> <RenderValue value={v} />
<div className="mt-2 text-green-600 dark:text-green-400 font-medium"> </li>
Running {tool}... ))}
</div> </ol>
</div> );
);
} }
if (typeof value === "object") {
return (
<ul className="pl-4 space-y-0.5 list-disc marker:text-green-500 dark:marker:text-green-400">
{Object.entries(value).map(([k, v]) => (
<li key={k} className="flex gap-1">
<span className="capitalize text-gray-600 dark:text-gray-300">{k}:&nbsp;</span>
<RenderValue value={v} />
</li>
))}
</ul>
);
}
return <span className="font-medium text-gray-800 dark:text-gray-100">{String(value)}</span>;
};
const cardBase =
"mt-2 p-3 rounded-lg border-l-4 border-green-500 bg-gray-100/60 dark:bg-gray-800/60 shadow-sm";
// ===== Running state =====
if (confirmed) {
return ( return (
<div className="mt-2 p-2 border border-gray-400 dark:border-gray-600 rounded <div className={`${cardBase} flex items-center gap-3`} role="status">
bg-gray-50 dark:bg-gray-800 transition-colors duration-200"> <SpinnerIcon className="text-green-600 dark:text-green-400 w-4 h-4" />
<div className="text-gray-600 dark:text-gray-300"> <span className="text-sm text-gray-700 dark:text-gray-200">
<div> Running <strong className="font-semibold">{tool ?? "Unknown"}</strong>
Agent is ready to run the tool: <strong>{tool ?? "Unknown"}</strong> </span>
</div> </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>
); );
}
// ===== 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}:&nbsp;</span>
<RenderValue value={v} />
</div>
))}
{shouldCollapse && (
<button
onClick={() => setShowAll(true)}
className="mt-1 text-green-600 dark:text-green-400 text-xs underline hover:no-underline"
>
show all
</button>
)}
{showAll && argEntries.length > 4 && (
<button
onClick={() => setShowAll(false)}
className="mt-1 block text-green-600 dark:text-green-400 text-xs underline hover:no-underline"
>
show less
</button>
)}
</div>
)}
{/* 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;

113
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
[[package]] [[package]]
name = "annotated-types" name = "annotated-types"
@@ -6,6 +6,7 @@ version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated" description = "Reusable constraint types to use with typing.Annotated"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
@@ -17,6 +18,7 @@ version = "0.47.0"
description = "The official Python library for the anthropic API" description = "The official Python library for the anthropic API"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "anthropic-0.47.0-py3-none-any.whl", hash = "sha256:294b10e9ca800f57949f635be7b611e8ecfe8f9f2a56a2c165200841a61bddb0"}, {file = "anthropic-0.47.0-py3-none-any.whl", hash = "sha256:294b10e9ca800f57949f635be7b611e8ecfe8f9f2a56a2c165200841a61bddb0"},
{file = "anthropic-0.47.0.tar.gz", hash = "sha256:6e19994d3a9fc7527c8505b62b1494ca3f39d6bb993a4885014575c09905ebfc"}, {file = "anthropic-0.47.0.tar.gz", hash = "sha256:6e19994d3a9fc7527c8505b62b1494ca3f39d6bb993a4885014575c09905ebfc"},
@@ -41,6 +43,7 @@ version = "4.5.2"
description = "High level compatibility layer for multiple asynchronous event loop implementations" description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f"}, {file = "anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f"},
{file = "anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b"}, {file = "anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b"},
@@ -54,7 +57,7 @@ typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21.0b1) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""]
trio = ["trio (>=0.26.1)"] trio = ["trio (>=0.26.1)"]
[[package]] [[package]]
@@ -63,6 +66,7 @@ version = "23.12.1"
description = "The uncompromising code formatter." description = "The uncompromising code formatter."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"},
{file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"},
@@ -99,7 +103,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
colorama = ["colorama (>=0.4.3)"] colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"] uvloop = ["uvloop (>=0.15.2)"]
@@ -109,6 +113,7 @@ version = "0.8.1"
description = "Generate complex HTML+JS pages with Python" description = "Generate complex HTML+JS pages with Python"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
files = [ files = [
{file = "branca-0.8.1-py3-none-any.whl", hash = "sha256:d29c5fab31f7c21a92e34bf3f854234e29fecdcf5d2df306b616f20d816be425"}, {file = "branca-0.8.1-py3-none-any.whl", hash = "sha256:d29c5fab31f7c21a92e34bf3f854234e29fecdcf5d2df306b616f20d816be425"},
{file = "branca-0.8.1.tar.gz", hash = "sha256:ac397c2d79bd13af0d04193b26d5ed17031d27609a7f1fab50c438b8ae712390"}, {file = "branca-0.8.1.tar.gz", hash = "sha256:ac397c2d79bd13af0d04193b26d5ed17031d27609a7f1fab50c438b8ae712390"},
@@ -123,6 +128,7 @@ version = "5.5.1"
description = "Extensible memoizing collections and decorators" description = "Extensible memoizing collections and decorators"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
files = [ files = [
{file = "cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb"}, {file = "cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb"},
{file = "cachetools-5.5.1.tar.gz", hash = "sha256:70f238fbba50383ef62e55c6aff6d9673175fe59f7c6782c7a0b9e38f4a9df95"}, {file = "cachetools-5.5.1.tar.gz", hash = "sha256:70f238fbba50383ef62e55c6aff6d9673175fe59f7c6782c7a0b9e38f4a9df95"},
@@ -134,6 +140,7 @@ version = "2024.12.14"
description = "Python package for providing Mozilla's CA Bundle." description = "Python package for providing Mozilla's CA Bundle."
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
groups = ["main"]
files = [ files = [
{file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"},
{file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"},
@@ -145,6 +152,7 @@ version = "3.4.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
files = [ files = [
{file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
@@ -246,6 +254,7 @@ version = "8.1.8"
description = "Composable command line interface toolkit" description = "Composable command line interface toolkit"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main", "dev"]
files = [ files = [
{file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
{file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
@@ -260,10 +269,12 @@ version = "0.4.6"
description = "Cross-platform colored terminal text." description = "Cross-platform colored terminal text."
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["main", "dev"]
files = [ files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
] ]
markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""}
[[package]] [[package]]
name = "deepseek" name = "deepseek"
@@ -271,6 +282,7 @@ version = "1.0.0"
description = "Deepseek API Library" description = "Deepseek API Library"
optional = false optional = false
python-versions = "*" python-versions = "*"
groups = ["main"]
files = [ files = [
{file = "deepseek-1.0.0-py3-none-any.whl", hash = "sha256:ee4175bfcb7ac1154369dbd86a4d8bc1809f6fa20e3e7baa362544567197cb3f"}, {file = "deepseek-1.0.0-py3-none-any.whl", hash = "sha256:ee4175bfcb7ac1154369dbd86a4d8bc1809f6fa20e3e7baa362544567197cb3f"},
] ]
@@ -284,6 +296,7 @@ version = "1.9.0"
description = "Distro - an OS platform information API" description = "Distro - an OS platform information API"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
groups = ["main"]
files = [ files = [
{file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"},
{file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"},
@@ -295,6 +308,8 @@ version = "1.2.2"
description = "Backport of PEP 654 (exception groups)" description = "Backport of PEP 654 (exception groups)"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main", "dev"]
markers = "python_version == \"3.10\""
files = [ files = [
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
@@ -309,6 +324,7 @@ version = "0.115.6"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305"}, {file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305"},
{file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654"}, {file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654"},
@@ -329,6 +345,7 @@ version = "0.19.4"
description = "Make beautiful maps with Leaflet.js & Python" description = "Make beautiful maps with Leaflet.js & Python"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "folium-0.19.4-py2.py3-none-any.whl", hash = "sha256:bea5246b6a6aa61b96d1c51399dd63254bacbd6ba8a826eeb491f45242032dfd"}, {file = "folium-0.19.4-py2.py3-none-any.whl", hash = "sha256:bea5246b6a6aa61b96d1c51399dd63254bacbd6ba8a826eeb491f45242032dfd"},
{file = "folium-0.19.4.tar.gz", hash = "sha256:431a655b52a9bf3efda336f2be022103f0106504a0599e7c349efbfd30bafda6"}, {file = "folium-0.19.4.tar.gz", hash = "sha256:431a655b52a9bf3efda336f2be022103f0106504a0599e7c349efbfd30bafda6"},
@@ -350,6 +367,7 @@ version = "1.0.1"
description = "Geographic pandas extensions" description = "Geographic pandas extensions"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "geopandas-1.0.1-py3-none-any.whl", hash = "sha256:01e147d9420cc374d26f51fc23716ac307f32b49406e4bd8462c07e82ed1d3d6"}, {file = "geopandas-1.0.1-py3-none-any.whl", hash = "sha256:01e147d9420cc374d26f51fc23716ac307f32b49406e4bd8462c07e82ed1d3d6"},
{file = "geopandas-1.0.1.tar.gz", hash = "sha256:b8bf70a5534588205b7a56646e2082fb1de9a03599651b3d80c99ea4c2ca08ab"}, {file = "geopandas-1.0.1.tar.gz", hash = "sha256:b8bf70a5534588205b7a56646e2082fb1de9a03599651b3d80c99ea4c2ca08ab"},
@@ -373,6 +391,7 @@ version = "0.6.15"
description = "Google Ai Generativelanguage API client library" description = "Google Ai Generativelanguage API client library"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
files = [ files = [
{file = "google_ai_generativelanguage-0.6.15-py3-none-any.whl", hash = "sha256:5a03ef86377aa184ffef3662ca28f19eeee158733e45d7947982eb953c6ebb6c"}, {file = "google_ai_generativelanguage-0.6.15-py3-none-any.whl", hash = "sha256:5a03ef86377aa184ffef3662ca28f19eeee158733e45d7947982eb953c6ebb6c"},
{file = "google_ai_generativelanguage-0.6.15.tar.gz", hash = "sha256:8f6d9dc4c12b065fe2d0289026171acea5183ebf2d0b11cefe12f3821e159ec3"}, {file = "google_ai_generativelanguage-0.6.15.tar.gz", hash = "sha256:8f6d9dc4c12b065fe2d0289026171acea5183ebf2d0b11cefe12f3821e159ec3"},
@@ -382,8 +401,8 @@ files = [
google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]}
google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev" google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev"
proto-plus = [ proto-plus = [
{version = ">=1.22.3,<2.0.0dev", markers = "python_version < \"3.13\""},
{version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""}, {version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""},
{version = ">=1.22.3,<2.0.0dev"},
] ]
protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev"
@@ -393,6 +412,7 @@ version = "2.24.0"
description = "Google API client core library" description = "Google API client core library"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
files = [ files = [
{file = "google_api_core-2.24.0-py3-none-any.whl", hash = "sha256:10d82ac0fca69c82a25b3efdeefccf6f28e02ebb97925a8cce8edbfe379929d9"}, {file = "google_api_core-2.24.0-py3-none-any.whl", hash = "sha256:10d82ac0fca69c82a25b3efdeefccf6f28e02ebb97925a8cce8edbfe379929d9"},
{file = "google_api_core-2.24.0.tar.gz", hash = "sha256:e255640547a597a4da010876d333208ddac417d60add22b6851a0c66a831fcaf"}, {file = "google_api_core-2.24.0.tar.gz", hash = "sha256:e255640547a597a4da010876d333208ddac417d60add22b6851a0c66a831fcaf"},
@@ -402,23 +422,23 @@ files = [
google-auth = ">=2.14.1,<3.0.dev0" google-auth = ">=2.14.1,<3.0.dev0"
googleapis-common-protos = ">=1.56.2,<2.0.dev0" googleapis-common-protos = ">=1.56.2,<2.0.dev0"
grpcio = [ grpcio = [
{version = ">=1.33.2,<2.0dev", optional = true, markers = "extra == \"grpc\""},
{version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
{version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
] ]
grpcio-status = [ grpcio-status = [
{version = ">=1.33.2,<2.0.dev0", optional = true, markers = "extra == \"grpc\""},
{version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
{version = ">=1.33.2,<2.0.dev0", optional = true, markers = "extra == \"grpc\""},
] ]
proto-plus = [ proto-plus = [
{version = ">=1.22.3,<2.0.0dev", markers = "python_version < \"3.13\""},
{version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""}, {version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""},
{version = ">=1.22.3,<2.0.0dev", markers = "python_version < \"3.13\""},
] ]
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0"
requests = ">=2.18.0,<3.0.0.dev0" requests = ">=2.18.0,<3.0.0.dev0"
[package.extras] [package.extras]
async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"]
grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""]
grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
@@ -428,6 +448,7 @@ version = "2.159.0"
description = "Google API Client Library for Python" description = "Google API Client Library for Python"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
files = [ files = [
{file = "google_api_python_client-2.159.0-py2.py3-none-any.whl", hash = "sha256:baef0bb631a60a0bd7c0bf12a5499e3a40cd4388484de7ee55c1950bf820a0cf"}, {file = "google_api_python_client-2.159.0-py2.py3-none-any.whl", hash = "sha256:baef0bb631a60a0bd7c0bf12a5499e3a40cd4388484de7ee55c1950bf820a0cf"},
{file = "google_api_python_client-2.159.0.tar.gz", hash = "sha256:55197f430f25c907394b44fa078545ffef89d33fd4dca501b7db9f0d8e224bd6"}, {file = "google_api_python_client-2.159.0.tar.gz", hash = "sha256:55197f430f25c907394b44fa078545ffef89d33fd4dca501b7db9f0d8e224bd6"},
@@ -446,6 +467,7 @@ version = "2.38.0"
description = "Google Authentication Library" description = "Google Authentication Library"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
files = [ files = [
{file = "google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a"}, {file = "google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a"},
{file = "google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4"}, {file = "google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4"},
@@ -470,6 +492,7 @@ version = "0.2.0"
description = "Google Authentication Library: httplib2 transport" description = "Google Authentication Library: httplib2 transport"
optional = false optional = false
python-versions = "*" python-versions = "*"
groups = ["main"]
files = [ files = [
{file = "google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05"}, {file = "google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05"},
{file = "google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d"}, {file = "google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d"},
@@ -485,6 +508,7 @@ version = "0.8.4"
description = "Google Generative AI High level API client library and tools." description = "Google Generative AI High level API client library and tools."
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "google_generativeai-0.8.4-py3-none-any.whl", hash = "sha256:e987b33ea6decde1e69191ddcaec6ef974458864d243de7191db50c21a7c5b82"}, {file = "google_generativeai-0.8.4-py3-none-any.whl", hash = "sha256:e987b33ea6decde1e69191ddcaec6ef974458864d243de7191db50c21a7c5b82"},
] ]
@@ -508,6 +532,7 @@ version = "1.66.0"
description = "Common protobufs used in Google APIs" description = "Common protobufs used in Google APIs"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
files = [ files = [
{file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"},
{file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"},
@@ -525,6 +550,7 @@ version = "1.70.0"
description = "HTTP/2-based RPC framework" description = "HTTP/2-based RPC framework"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "grpcio-1.70.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:95469d1977429f45fe7df441f586521361e235982a0b39e33841549143ae2851"}, {file = "grpcio-1.70.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:95469d1977429f45fe7df441f586521361e235982a0b39e33841549143ae2851"},
{file = "grpcio-1.70.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:ed9718f17fbdb472e33b869c77a16d0b55e166b100ec57b016dc7de9c8d236bf"}, {file = "grpcio-1.70.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:ed9718f17fbdb472e33b869c77a16d0b55e166b100ec57b016dc7de9c8d236bf"},
@@ -592,6 +618,7 @@ version = "1.70.0"
description = "Status proto mapping for gRPC" description = "Status proto mapping for gRPC"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "grpcio_status-1.70.0-py3-none-any.whl", hash = "sha256:fc5a2ae2b9b1c1969cc49f3262676e6854aa2398ec69cb5bd6c47cd501904a85"}, {file = "grpcio_status-1.70.0-py3-none-any.whl", hash = "sha256:fc5a2ae2b9b1c1969cc49f3262676e6854aa2398ec69cb5bd6c47cd501904a85"},
{file = "grpcio_status-1.70.0.tar.gz", hash = "sha256:0e7b42816512433b18b9d764285ff029bde059e9d41f8fe10a60631bd8348101"}, {file = "grpcio_status-1.70.0.tar.gz", hash = "sha256:0e7b42816512433b18b9d764285ff029bde059e9d41f8fe10a60631bd8348101"},
@@ -608,6 +635,7 @@ version = "10.1.1"
description = "A Python library for analyzing GTFS feeds." description = "A Python library for analyzing GTFS feeds."
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.10"
groups = ["main"]
files = [ files = [
{file = "gtfs_kit-10.1.1-py3-none-any.whl", hash = "sha256:2a54982d30993c365ee082eb3f5dc981ecd89c294728199a1f39776dee6c71b2"}, {file = "gtfs_kit-10.1.1-py3-none-any.whl", hash = "sha256:2a54982d30993c365ee082eb3f5dc981ecd89c294728199a1f39776dee6c71b2"},
{file = "gtfs_kit-10.1.1.tar.gz", hash = "sha256:b94135883fbb4a5135b33d66215e12507a0480218f53df8c6a3a88ee359e7ab4"}, {file = "gtfs_kit-10.1.1.tar.gz", hash = "sha256:b94135883fbb4a5135b33d66215e12507a0480218f53df8c6a3a88ee359e7ab4"},
@@ -628,6 +656,7 @@ version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
files = [ files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
@@ -639,6 +668,7 @@ version = "1.0.7"
description = "A minimal low-level HTTP client." description = "A minimal low-level HTTP client."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
{file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
@@ -660,6 +690,7 @@ version = "0.22.0"
description = "A comprehensive HTTP client library." description = "A comprehensive HTTP client library."
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["main"]
files = [ files = [
{file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"}, {file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"},
{file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"}, {file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"},
@@ -674,6 +705,7 @@ version = "0.27.2"
description = "The next generation HTTP client." description = "The next generation HTTP client."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"},
{file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"},
@@ -687,7 +719,7 @@ idna = "*"
sniffio = "*" sniffio = "*"
[package.extras] [package.extras]
brotli = ["brotli", "brotlicffi"] brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"] http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"] socks = ["socksio (==1.*)"]
@@ -699,6 +731,7 @@ version = "3.10"
description = "Internationalized Domain Names in Applications (IDNA)" description = "Internationalized Domain Names in Applications (IDNA)"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
groups = ["main"]
files = [ files = [
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
@@ -713,6 +746,7 @@ version = "2.0.0"
description = "brain-dead simple config-ini parsing" description = "brain-dead simple config-ini parsing"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["dev"]
files = [ files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
@@ -724,6 +758,7 @@ version = "5.13.2"
description = "A Python utility / library to sort Python imports." description = "A Python utility / library to sort Python imports."
optional = false optional = false
python-versions = ">=3.8.0" python-versions = ">=3.8.0"
groups = ["dev"]
files = [ files = [
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
@@ -738,6 +773,7 @@ version = "3.1.5"
description = "A very fast and expressive template engine." description = "A very fast and expressive template engine."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
files = [ files = [
{file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
{file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
@@ -755,6 +791,7 @@ version = "0.8.2"
description = "Fast iterable JSON parser." description = "Fast iterable JSON parser."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "jiter-0.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ca8577f6a413abe29b079bc30f907894d7eb07a865c4df69475e868d73e71c7b"}, {file = "jiter-0.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ca8577f6a413abe29b079bc30f907894d7eb07a865c4df69475e868d73e71c7b"},
{file = "jiter-0.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b25bd626bde7fb51534190c7e3cb97cee89ee76b76d7585580e22f34f5e3f393"}, {file = "jiter-0.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b25bd626bde7fb51534190c7e3cb97cee89ee76b76d7585580e22f34f5e3f393"},
@@ -840,6 +877,7 @@ version = "1.3.0"
description = "JSON to HTML Table Representation" description = "JSON to HTML Table Representation"
optional = false optional = false
python-versions = "*" python-versions = "*"
groups = ["main"]
files = [ files = [
{file = "json2html-1.3.0.tar.gz", hash = "sha256:8951a53662ae9cfd812685facdba693fc950ffc1c1fd1a8a2d3cf4c34600689c"}, {file = "json2html-1.3.0.tar.gz", hash = "sha256:8951a53662ae9cfd812685facdba693fc950ffc1c1fd1a8a2d3cf4c34600689c"},
] ]
@@ -850,6 +888,7 @@ version = "3.0.2"
description = "Safely add untrusted strings to HTML/XML markup." description = "Safely add untrusted strings to HTML/XML markup."
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
@@ -920,6 +959,7 @@ version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker." description = "Type system extensions for programs checked with the mypy type checker."
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
groups = ["dev"]
files = [ files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
@@ -931,6 +971,7 @@ version = "2.2.2"
description = "Fundamental package for array computing in Python" description = "Fundamental package for array computing in Python"
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.10"
groups = ["main"]
files = [ files = [
{file = "numpy-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e"}, {file = "numpy-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e"},
{file = "numpy-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e"}, {file = "numpy-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e"},
@@ -995,6 +1036,7 @@ version = "0.4.5"
description = "The official Python client for Ollama." description = "The official Python client for Ollama."
optional = false optional = false
python-versions = "<4.0,>=3.8" python-versions = "<4.0,>=3.8"
groups = ["main"]
files = [ files = [
{file = "ollama-0.4.5-py3-none-any.whl", hash = "sha256:74936de89a41c87c9745f09f2e1db964b4783002188ac21241bfab747f46d925"}, {file = "ollama-0.4.5-py3-none-any.whl", hash = "sha256:74936de89a41c87c9745f09f2e1db964b4783002188ac21241bfab747f46d925"},
{file = "ollama-0.4.5.tar.gz", hash = "sha256:e7fb71a99147046d028ab8b75e51e09437099aea6f8f9a0d91a71f787e97439e"}, {file = "ollama-0.4.5.tar.gz", hash = "sha256:e7fb71a99147046d028ab8b75e51e09437099aea6f8f9a0d91a71f787e97439e"},
@@ -1010,6 +1052,7 @@ version = "1.59.2"
description = "The official Python library for the openai API" description = "The official Python library for the openai API"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "openai-1.59.2-py3-none-any.whl", hash = "sha256:3de721df4d2ccc5e845afa7235dce496bfbdd572692a876d2ae6211e0290ff22"}, {file = "openai-1.59.2-py3-none-any.whl", hash = "sha256:3de721df4d2ccc5e845afa7235dce496bfbdd572692a876d2ae6211e0290ff22"},
{file = "openai-1.59.2.tar.gz", hash = "sha256:1bf2d5e8a93533f6dd3fb7b1bcf082ddd4ae61cc6d89ca1343e5957e4720651c"}, {file = "openai-1.59.2.tar.gz", hash = "sha256:1bf2d5e8a93533f6dd3fb7b1bcf082ddd4ae61cc6d89ca1343e5957e4720651c"},
@@ -1035,6 +1078,7 @@ version = "24.2"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main", "dev"]
files = [ files = [
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
@@ -1046,6 +1090,7 @@ version = "2.2.3"
description = "Powerful data structures for data analysis, time series, and statistics" description = "Powerful data structures for data analysis, time series, and statistics"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"},
{file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"},
@@ -1093,9 +1138,9 @@ files = [
[package.dependencies] [package.dependencies]
numpy = [ numpy = [
{version = ">=1.22.4", markers = "python_version < \"3.11\""},
{version = ">=1.23.2", markers = "python_version == \"3.11\""},
{version = ">=1.26.0", markers = "python_version >= \"3.12\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""},
{version = ">=1.23.2", markers = "python_version == \"3.11\""},
{version = ">=1.22.4", markers = "python_version < \"3.11\""},
] ]
python-dateutil = ">=2.8.2" python-dateutil = ">=2.8.2"
pytz = ">=2020.1" pytz = ">=2020.1"
@@ -1132,6 +1177,7 @@ version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths." description = "Utility library for gitignore style pattern matching of file paths."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
@@ -1143,6 +1189,7 @@ version = "4.3.6"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
{file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
@@ -1159,6 +1206,7 @@ version = "1.5.0"
description = "plugin and hook calling mechanisms for python" description = "plugin and hook calling mechanisms for python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
@@ -1174,6 +1222,7 @@ version = "1.25.0"
description = "Beautiful, Pythonic protocol buffers." description = "Beautiful, Pythonic protocol buffers."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
files = [ files = [
{file = "proto_plus-1.25.0-py3-none-any.whl", hash = "sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961"}, {file = "proto_plus-1.25.0-py3-none-any.whl", hash = "sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961"},
{file = "proto_plus-1.25.0.tar.gz", hash = "sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91"}, {file = "proto_plus-1.25.0.tar.gz", hash = "sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91"},
@@ -1191,6 +1240,7 @@ version = "5.29.2"
description = "" description = ""
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "protobuf-5.29.2-cp310-abi3-win32.whl", hash = "sha256:c12ba8249f5624300cf51c3d0bfe5be71a60c63e4dcf51ffe9a68771d958c851"}, {file = "protobuf-5.29.2-cp310-abi3-win32.whl", hash = "sha256:c12ba8249f5624300cf51c3d0bfe5be71a60c63e4dcf51ffe9a68771d958c851"},
{file = "protobuf-5.29.2-cp310-abi3-win_amd64.whl", hash = "sha256:842de6d9241134a973aab719ab42b008a18a90f9f07f06ba480df268f86432f9"}, {file = "protobuf-5.29.2-cp310-abi3-win_amd64.whl", hash = "sha256:842de6d9241134a973aab719ab42b008a18a90f9f07f06ba480df268f86432f9"},
@@ -1211,6 +1261,7 @@ version = "0.6.1"
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"},
{file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"},
@@ -1222,6 +1273,7 @@ version = "0.4.1"
description = "A collection of ASN.1-based protocols modules" description = "A collection of ASN.1-based protocols modules"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"},
{file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"},
@@ -1236,6 +1288,7 @@ version = "2.10.4"
description = "Data validation using Python type hints" description = "Data validation using Python type hints"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"},
{file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"},
@@ -1248,7 +1301,7 @@ typing-extensions = ">=4.12.2"
[package.extras] [package.extras]
email = ["email-validator (>=2.0.0)"] email = ["email-validator (>=2.0.0)"]
timezone = ["tzdata"] timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
[[package]] [[package]]
name = "pydantic-core" name = "pydantic-core"
@@ -1256,6 +1309,7 @@ version = "2.27.2"
description = "Core functionality for Pydantic validation and serialization" description = "Core functionality for Pydantic validation and serialization"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
@@ -1368,6 +1422,7 @@ version = "0.10.0"
description = "Vectorized spatial vector file format I/O using GDAL/OGR" description = "Vectorized spatial vector file format I/O using GDAL/OGR"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "pyogrio-0.10.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:046eeeae12a03a3ebc3dc5ff5a87664e4f5fc0a4fb1ea5d5c45d547fa941072b"}, {file = "pyogrio-0.10.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:046eeeae12a03a3ebc3dc5ff5a87664e4f5fc0a4fb1ea5d5c45d547fa941072b"},
{file = "pyogrio-0.10.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:44380f4d9245c776f432526e29ce4d29238aea26adad991803c4f453474f51d3"}, {file = "pyogrio-0.10.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:44380f4d9245c776f432526e29ce4d29238aea26adad991803c4f453474f51d3"},
@@ -1419,6 +1474,7 @@ version = "3.2.1"
description = "pyparsing module - Classes and methods to define and execute parsing grammars" description = "pyparsing module - Classes and methods to define and execute parsing grammars"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1"}, {file = "pyparsing-3.2.1-py3-none-any.whl", hash = "sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1"},
{file = "pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"}, {file = "pyparsing-3.2.1.tar.gz", hash = "sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a"},
@@ -1433,6 +1489,7 @@ version = "3.7.0"
description = "Python interface to PROJ (cartographic projections and coordinate transformations library)" description = "Python interface to PROJ (cartographic projections and coordinate transformations library)"
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.10"
groups = ["main"]
files = [ files = [
{file = "pyproj-3.7.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:d5c7e7d24b967e328a5efd013f466804a1f226d1106ac7efc47dcc99360dbc8f"}, {file = "pyproj-3.7.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:d5c7e7d24b967e328a5efd013f466804a1f226d1106ac7efc47dcc99360dbc8f"},
{file = "pyproj-3.7.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:448958c46bd3fe2da91c89ba551ac5835e63073ca861422c6eb1af89979dfab1"}, {file = "pyproj-3.7.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:448958c46bd3fe2da91c89ba551ac5835e63073ca861422c6eb1af89979dfab1"},
@@ -1470,6 +1527,7 @@ version = "8.3.5"
description = "pytest: simple powerful testing with Python" description = "pytest: simple powerful testing with Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"},
{file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"},
@@ -1492,6 +1550,7 @@ version = "0.26.0"
description = "Pytest support for asyncio" description = "Pytest support for asyncio"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["dev"]
files = [ files = [
{file = "pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0"}, {file = "pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0"},
{file = "pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f"}, {file = "pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f"},
@@ -1510,6 +1569,7 @@ version = "2.9.0.post0"
description = "Extensions to the standard Python datetime module" description = "Extensions to the standard Python datetime module"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
groups = ["main"]
files = [ files = [
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
@@ -1524,6 +1584,7 @@ version = "1.0.1"
description = "Read key-value pairs from a .env file and set them as environment variables" description = "Read key-value pairs from a .env file and set them as environment variables"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
@@ -1538,6 +1599,7 @@ version = "2025.1"
description = "World timezone definitions, modern and historical" description = "World timezone definitions, modern and historical"
optional = false optional = false
python-versions = "*" python-versions = "*"
groups = ["main"]
files = [ files = [
{file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"},
{file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"},
@@ -1549,6 +1611,7 @@ version = "6.0.2"
description = "YAML parser and emitter for Python" description = "YAML parser and emitter for Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
@@ -1611,6 +1674,7 @@ version = "2.32.3"
description = "Python HTTP for Humans." description = "Python HTTP for Humans."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
@@ -1632,6 +1696,7 @@ version = "4.9"
description = "Pure-Python RSA implementation" description = "Pure-Python RSA implementation"
optional = false optional = false
python-versions = ">=3.6,<4" python-versions = ">=3.6,<4"
groups = ["main"]
files = [ files = [
{file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
{file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
@@ -1646,6 +1711,7 @@ version = "1.3.0"
description = "R-Tree spatial index for Python GIS" description = "R-Tree spatial index for Python GIS"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "Rtree-1.3.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:80879d9db282a2273ca3a0d896c84583940e9777477727a277624ebfd424c517"}, {file = "Rtree-1.3.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:80879d9db282a2273ca3a0d896c84583940e9777477727a277624ebfd424c517"},
{file = "Rtree-1.3.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4328e9e421797c347e6eb08efbbade962fe3664ebd60c1dffe82c40911b1e125"}, {file = "Rtree-1.3.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4328e9e421797c347e6eb08efbbade962fe3664ebd60c1dffe82c40911b1e125"},
@@ -1665,6 +1731,7 @@ version = "2.0.7"
description = "Manipulation and analysis of geometric objects" description = "Manipulation and analysis of geometric objects"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
files = [ files = [
{file = "shapely-2.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:33fb10e50b16113714ae40adccf7670379e9ccf5b7a41d0002046ba2b8f0f691"}, {file = "shapely-2.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:33fb10e50b16113714ae40adccf7670379e9ccf5b7a41d0002046ba2b8f0f691"},
{file = "shapely-2.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f44eda8bd7a4bccb0f281264b34bf3518d8c4c9a8ffe69a1a05dabf6e8461147"}, {file = "shapely-2.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f44eda8bd7a4bccb0f281264b34bf3518d8c4c9a8ffe69a1a05dabf6e8461147"},
@@ -1723,6 +1790,7 @@ version = "1.17.0"
description = "Python 2 and 3 compatibility utilities" description = "Python 2 and 3 compatibility utilities"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
groups = ["main"]
files = [ files = [
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
@@ -1734,6 +1802,7 @@ version = "1.3.1"
description = "Sniff out which async library your code is running under" description = "Sniff out which async library your code is running under"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
files = [ files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
@@ -1745,6 +1814,7 @@ version = "0.41.3"
description = "The little ASGI library that shines." description = "The little ASGI library that shines."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"}, {file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"},
{file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"}, {file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"},
@@ -1762,6 +1832,7 @@ version = "11.4.1"
description = "Python bindings for the Stripe API" description = "Python bindings for the Stripe API"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
groups = ["main"]
files = [ files = [
{file = "stripe-11.4.1-py2.py3-none-any.whl", hash = "sha256:8aa47a241de0355c383c916c4ef7273ab666f096a44ee7081e357db4a36f0cce"}, {file = "stripe-11.4.1-py2.py3-none-any.whl", hash = "sha256:8aa47a241de0355c383c916c4ef7273ab666f096a44ee7081e357db4a36f0cce"},
{file = "stripe-11.4.1.tar.gz", hash = "sha256:7ddd251b622d490fe57d78487855dc9f4d95b1bb113607e81fd377037a133d5a"}, {file = "stripe-11.4.1.tar.gz", hash = "sha256:7ddd251b622d490fe57d78487855dc9f4d95b1bb113607e81fd377037a133d5a"},
@@ -1777,6 +1848,7 @@ version = "1.9.0"
description = "Temporal.io Python SDK" description = "Temporal.io Python SDK"
optional = false optional = false
python-versions = "<4.0,>=3.8" python-versions = "<4.0,>=3.8"
groups = ["main"]
files = [ files = [
{file = "temporalio-1.9.0-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ee941702e8925e2c018b5c2d7b296f811205043654d7f9c4564d7fa6597f1989"}, {file = "temporalio-1.9.0-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ee941702e8925e2c018b5c2d7b296f811205043654d7f9c4564d7fa6597f1989"},
{file = "temporalio-1.9.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:101040090238d97b61d769e009f732409894d8f26596a3827662f2dde2862097"}, {file = "temporalio-1.9.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:101040090238d97b61d769e009f732409894d8f26596a3827662f2dde2862097"},
@@ -1802,6 +1874,8 @@ version = "2.2.1"
description = "A lil' TOML parser" description = "A lil' TOML parser"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version == \"3.10\""
files = [ files = [
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
@@ -1843,6 +1917,7 @@ version = "4.67.1"
description = "Fast, Extensible Progress Meter" description = "Fast, Extensible Progress Meter"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
files = [ files = [
{file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"},
{file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"},
@@ -1864,6 +1939,7 @@ version = "5.29.1.20241207"
description = "Typing stubs for protobuf" description = "Typing stubs for protobuf"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "types_protobuf-5.29.1.20241207-py3-none-any.whl", hash = "sha256:92893c42083e9b718c678badc0af7a9a1307b92afe1599e5cba5f3d35b668b2f"}, {file = "types_protobuf-5.29.1.20241207-py3-none-any.whl", hash = "sha256:92893c42083e9b718c678badc0af7a9a1307b92afe1599e5cba5f3d35b668b2f"},
{file = "types_protobuf-5.29.1.20241207.tar.gz", hash = "sha256:2ebcadb8ab3ef2e3e2f067e0882906d64ba0dc65fc5b0fd7a8b692315b4a0be9"}, {file = "types_protobuf-5.29.1.20241207.tar.gz", hash = "sha256:2ebcadb8ab3ef2e3e2f067e0882906d64ba0dc65fc5b0fd7a8b692315b4a0be9"},
@@ -1875,10 +1951,12 @@ version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+" description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main", "dev"]
files = [ files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
] ]
markers = {dev = "python_version == \"3.10\""}
[[package]] [[package]]
name = "tzdata" name = "tzdata"
@@ -1886,6 +1964,7 @@ version = "2025.1"
description = "Provider of IANA time zone data" description = "Provider of IANA time zone data"
optional = false optional = false
python-versions = ">=2" python-versions = ">=2"
groups = ["main"]
files = [ files = [
{file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"},
{file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"},
@@ -1897,6 +1976,7 @@ version = "4.1.1"
description = "Implementation of RFC 6570 URI Templates" description = "Implementation of RFC 6570 URI Templates"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
groups = ["main"]
files = [ files = [
{file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
{file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"},
@@ -1908,13 +1988,14 @@ version = "2.3.0"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
{file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
] ]
[package.extras] [package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
h2 = ["h2 (>=4,<5)"] h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"] zstd = ["zstandard (>=0.18.0)"]
@@ -1925,6 +2006,7 @@ version = "0.34.0"
description = "The lightning-fast ASGI server." description = "The lightning-fast ASGI server."
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"}, {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"},
{file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"}, {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"},
@@ -1936,7 +2018,7 @@ h11 = ">=0.8"
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"]
[[package]] [[package]]
name = "xyzservices" name = "xyzservices"
@@ -1944,12 +2026,13 @@ version = "2025.1.0"
description = "Source of XYZ tiles providers" description = "Source of XYZ tiles providers"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "xyzservices-2025.1.0-py3-none-any.whl", hash = "sha256:fa599956c5ab32dad1689960b3bb08fdcdbe0252cc82d84fc60ae415dc648907"}, {file = "xyzservices-2025.1.0-py3-none-any.whl", hash = "sha256:fa599956c5ab32dad1689960b3bb08fdcdbe0252cc82d84fc60ae415dc648907"},
{file = "xyzservices-2025.1.0.tar.gz", hash = "sha256:5cdbb0907c20be1be066c6e2dc69c645842d1113a4e83e642065604a21f254ba"}, {file = "xyzservices-2025.1.0.tar.gz", hash = "sha256:5cdbb0907c20be1be066c6e2dc69c645842d1113a4e83e642065604a21f254ba"},
] ]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.1"
python-versions = ">=3.10,<4.0" python-versions = ">=3.10,<4.0"
content-hash = "ae793854c4c87fba6ddd666299e04883038ff7af65f1707af797d4d0fa1f3c67" content-hash = "ae793854c4c87fba6ddd666299e04883038ff7af65f1707af797d4d0fa1f3c67"

View File

@@ -2,10 +2,14 @@ from models.tool_definitions import AgentGoal
from typing import Optional from typing import Optional
import json import json
MULTI_GOAL_MODE:bool = None MULTI_GOAL_MODE: bool = None
def generate_genai_prompt( 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: ) -> str:
""" """
Generates a concise prompt for producing or validating JSON instructions Generates a concise prompt for producing or validating JSON instructions
@@ -25,10 +29,12 @@ def generate_genai_prompt(
prompt_lines.append( prompt_lines.append(
"This is the ongoing history to determine which tool and arguments to gather:" "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(json.dumps(conversation_history, indent=2))
prompt_lines.append("END CONVERSATION HISTORY") prompt_lines.append("*END CONVERSATION HISTORY*")
prompt_lines.append("") prompt_lines.append(
"REMINDER: You can use the conversation history to infer arguments for the tools."
)
# Example Conversation History (from agent_goal) # Example Conversation History (from agent_goal)
if agent_goal.example_conversation_history: 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" "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" " The user will confirm before the tool is run.\n"
f"3) {generate_toolchain_complete_guidance()}\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) # Validation Task (If raw_json is provided)
@@ -110,14 +131,15 @@ def generate_genai_prompt(
return "\n".join(prompt_lines) return "\n".join(prompt_lines)
def generate_tool_completion_prompt(current_tool: str, dynamic_result: dict) -> str: def generate_tool_completion_prompt(current_tool: str, dynamic_result: dict) -> str:
""" """
Generates a prompt for handling tool completion and determining next steps. Generates a prompt for handling tool completion and determining next steps.
Args: Args:
current_tool: The name of the tool that just completed current_tool: The name of the tool that just completed
dynamic_result: The result data from the tool execution dynamic_result: The result data from the tool execution
Returns: Returns:
str: A formatted prompt string for the agent to process the tool completion 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()}" 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. Generates a prompt for handling missing arguments for a tool.
Args: Args:
current_tool: The name of the tool that needs arguments current_tool: The name of the tool that needs arguments
tool_data: The current tool data containing the response tool_data: The current tool data containing the response
missing_args: List of argument names that are missing missing_args: List of argument names that are missing
Returns: Returns:
str: A formatted prompt string for requesting missing arguments 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." "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) Set multi-mode (used to pass workflow)
Args: Args:
None None
Returns: Returns:
bool: True if in multi-goal mode, false if not 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: if MULTI_GOAL_MODE is None:
MULTI_GOAL_MODE = mode 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. Centralized logic for if we're in multi-goal mode.
Args: Args:
None None
Returns: Returns:
bool: True if in multi-goal mode, false if not bool: True if in multi-goal mode, false if not
""" """
return MULTI_GOAL_MODE 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. Generates a prompt for guiding the LLM to pick a new goal or be done depending on multi-goal mode.
Args: Args:
None None
Returns: Returns:
str: A prompt string prompting the LLM to when to go to pick-new-goal str: A prompt string prompting the LLM to when to go to pick-new-goal
""" """
if is_multi_goal_mode(): if is_multi_goal_mode():
return 'Next should only be "pick-new-goal" if all tools have been run (use the system prompt to figure that out) or the user explicitly requested to pick a new goal.' return 'Next should only be "pick-new-goal" if all tools have been run for the current goal (use the system prompt to figure that out), or the user explicitly requested to pick a new goal.'
else: else:
return 'Next should never be "pick-new-goal".' return 'Next should never be "pick-new-goal".'
def generate_toolchain_complete_guidance() -> str: def generate_toolchain_complete_guidance() -> str:
""" """
Generates a prompt for guiding the LLM to handle the end of the toolchain. Generates a prompt for guiding the LLM to handle the end of the toolchain.
Args: Args:
None None
Returns: Returns:
str: A prompt string prompting the LLM to prompt for a new goal, or be done 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'." return "If no more tools are needed (user_confirmed_tool_run has been run for all), set next='confirm' and tool='ListAgents'."
else : else:
return "If no more tools are needed (user_confirmed_tool_run has been run for all), set next='done' and tool=''." return "If no more tools are needed (user_confirmed_tool_run has been run for all), set next='done' and tool=''."

View File

@@ -1,9 +1,13 @@
[tool.poetry] [tool.poetry]
name = "temporal-AI-agent" name = "temporal_AI_agent"
version = "0.1.0" version = "0.2.0"
description = "Temporal AI Agent" description = "Temporal AI Agent"
license = "MIT" 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" readme = "README.md"
# By default, Poetry will find packages automatically, # By default, Poetry will find packages automatically,
@@ -13,7 +17,7 @@ packages = [
] ]
[tool.poetry.urls] [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] [tool.poe.tasks]
format = [{cmd = "black ."}, {cmd = "isort ."}] format = [{cmd = "black ."}, {cmd = "isort ."}]
@@ -43,10 +47,17 @@ gtfs-kit = "^10.1.1"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
pytest = ">=8.2" pytest = ">=8.2"
pytest-asyncio = "^0.26.0"
black = "^23.7" black = "^23.7"
isort = "^5.12" isort = "^5.12"
pytest-asyncio = "^0.26.0"
[build-system] [build-system]
requires = ["poetry-core>=1.4.0"] 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"

View File

@@ -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`. 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 ```bash
GOAL_CATEGORIES=hr,travel-flights,travel-trains,fin GOAL_CATEGORIES=hr,travel-flights,travel-trains,fin
``` ```
@@ -93,10 +93,32 @@ temporal server start-dev
``` ```
See the [Temporal documentation](https://learn.temporal.io/getting_started/python/dev_environment/) for other platforms. See the [Temporal documentation](https://learn.temporal.io/getting_started/python/dev_environment/) for other platforms.
You can also run a local Temporal server using Docker Compose. See the `Development with Docker` section below.
## Running the Application ## Running the Application
### Python Backend ### Docker
- All services are defined in `docker-compose.yml` (includes a Temporal server).
- **Dev overrides** (mounted code, livereload commands) live in `docker-compose.override.yml` and are **automerged** on `docker compose up`.
- To start **development** mode (with hotreload):
```bash
docker compose up -d
# quick rebuild without infra:
docker compose up -d --no-deps --build api train-api worker frontend
```
- To run **production** mode (ignore dev overrides):
```bash
docker compose -f docker-compose.yml up -d
```
Default urls:
* Temporal UI: [http://localhost:8080](http://localhost:8080)
* API: [http://localhost:8000](http://localhost:8000)
* Frontend: [http://localhost:5173](http://localhost:5173)
### Local Machine (no docker)
**Python Backend**
Requires [Poetry](https://python-poetry.org/) to manage dependencies. Requires [Poetry](https://python-poetry.org/) to manage dependencies.
@@ -104,7 +126,7 @@ Requires [Poetry](https://python-poetry.org/) to manage dependencies.
2. `source venv/bin/activate` 2. `source venv/bin/activate`
3. `poetry install --with dev` 3. `poetry install`
Run the following commands in separate terminal windows: Run the following commands in separate terminal windows:
@@ -119,7 +141,7 @@ poetry run uvicorn api.main:app --reload
``` ```
Access the API at `/docs` to see the available endpoints. Access the API at `/docs` to see the available endpoints.
### React UI **React UI**
Start the frontend: Start the frontend:
```bash ```bash
cd frontend cd frontend
@@ -127,8 +149,7 @@ npm install
npx vite npx vite
``` ```
Access the UI at `http://localhost:5173` Access the UI at `http://localhost:5173`
## Goal-Specific Tool Configuration ## Goal-Specific Tool Configuration
Here is configuration guidance for specific goals. Travel and financial goals have configuration & setup as below. Here is configuration guidance for specific goals. Travel and financial goals have configuration & setup as below.
@@ -204,11 +225,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): 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 ```bash
FIN_START_REAL_WORKFLOW=FALSE #set this to true to start a real workflow FIN_START_REAL_WORKFLOW=FALSE #set this to true to start a real workflow
```
#### Goals: HR/PTO #### 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). 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 ## Customizing the Agent Further
- `tool_registry.py` contains the mapping of tool names to tool definitions (so the AI understands how to use them) - `tool_registry.py` contains the mapping of tool names to tool definitions (so the AI understands how to use them)
@@ -216,4 +240,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` - The tools themselves are defined in their own files in `/tools`
- Note the mapping in `tools/__init__.py` to each tool - 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
View File

@@ -1,5 +1,6 @@
# todo list # todo list
[ ] expand [tests](./tests/agent_goal_workflow_test.py)<br /> [x] take steve's confirm box changes https://temporaltechnologies.slack.com/archives/D062SV8KEEM/p1745251279164319 <br />
[ ] consider adding goal categories to goal picker
[ ] adding fintech goals <br /> [ ] adding fintech goals <br />
- Fraud Detection and Prevention - The AI monitors transactions across accounts, flagging suspicious activities (e.g., unusual spending patterns or login attempts) and autonomously freezing accounts or notifying customers and compliance teams.<br /> - Fraud Detection and Prevention - The AI monitors transactions across accounts, flagging suspicious activities (e.g., unusual spending patterns or login attempts) and autonomously freezing accounts or notifying customers and compliance teams.<br />
@@ -10,11 +11,19 @@
[ ] financial advise - args being freeform customer input about their financial situation, goals [ ] financial advise - args being freeform customer input about their financial situation, goals
[ ] tool is maybe a new tool asking the LLM to advise [ ] 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 /> [ ] LLM failure->autoswitch: <br />
- detect failure in the activity using failurecount <br /> - detect failure in the activity using failurecount <br />
- activity switches to secondary LLM defined in .env - activity switches to secondary LLM defined in .env
- activity reports switch to workflow - 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 /> [ ] ask the ai agent how it did at the end of the conversation, was it efficient? successful? insert a search attribute to document that before return <br />
- Insight into the agents performance <br /> - Insight into the agents performance <br />
[ ] non-retry the api key error - "Invalid API Key provided: sk_test_**J..." and "AuthenticationError" <br /> [ ] non-retry the api key error - "Invalid API Key provided: sk_test_**J..." and "AuthenticationError" <br />

View File

@@ -18,6 +18,10 @@ from .fin.get_account_balances import get_account_balance
from .fin.move_money import move_money from .fin.move_money import move_money
from .fin.submit_loan_application import submit_loan_application 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 .give_hint import give_hint
from .guess_location import guess_location from .guess_location import guess_location
@@ -52,11 +56,17 @@ def get_handler(tool_name: str):
if tool_name == "FinCheckAccountIsValid": if tool_name == "FinCheckAccountIsValid":
return check_account_valid return check_account_valid
if tool_name == "FinCheckAccountBalance": if tool_name == "FinCheckAccountBalance":
return get_account_balance return get_account_balance
if tool_name == "FinMoveMoneyOrder": if tool_name == "FinMoveMoney":
return move_money return move_money
if tool_name == "FinCheckAccountSubmitLoanApproval": 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": if tool_name == "GiveHint":
return give_hint return give_hint
if tool_name == "GuessLocation": if tool_name == "GuessLocation":

View File

@@ -1,11 +1,11 @@
{ {
"accounts": [ "accounts": [
{ {
"name": "Matt Murdock", "name": "Matt Murdock",
"email": "matt.murdock@nelsonmurdock.com", "email": "matt.murdock@nelsonmurdock.com",
"account_id": "11235", "account_id": "11235",
"checking_balance": 875.40, "checking_balance": "1275.4",
"savings_balance": 3200.15, "savings_balance": "2800.15",
"bitcoin_balance": 0.1378, "bitcoin_balance": 0.1378,
"account_creation_date": "2014-03-10" "account_creation_date": "2014-03-10"
}, },
@@ -13,8 +13,8 @@
"name": "Foggy Nelson", "name": "Foggy Nelson",
"email": "foggy.nelson@nelsonmurdock.com", "email": "foggy.nelson@nelsonmurdock.com",
"account_id": "112358", "account_id": "112358",
"checking_balance": 1523.67, "checking_balance": "1523.66",
"savings_balance": 4875.90, "savings_balance": "4875.89",
"bitcoin_balance": 0.0923, "bitcoin_balance": 0.0923,
"account_creation_date": "2014-03-10" "account_creation_date": "2014-03-10"
}, },
@@ -23,7 +23,7 @@
"email": "karen.page@nelsonmurdock.com", "email": "karen.page@nelsonmurdock.com",
"account_id": "112", "account_id": "112",
"checking_balance": 645.25, "checking_balance": 645.25,
"savings_balance": 1830.50, "savings_balance": "830.5",
"bitcoin_balance": 0.0456, "bitcoin_balance": 0.0456,
"account_creation_date": "2015-01-15" "account_creation_date": "2015-01-15"
}, },
@@ -31,7 +31,7 @@
"name": "Wilson Fisk", "name": "Wilson Fisk",
"email": "wilson.fisk@fiskcorp.com", "email": "wilson.fisk@fiskcorp.com",
"account_id": "11", "account_id": "11",
"checking_balance": 25000.00, "checking_balance": 25000.0,
"savings_balance": 150000.75, "savings_balance": 150000.75,
"bitcoin_balance": 5987.6721, "bitcoin_balance": 5987.6721,
"account_creation_date": "2013-09-20" "account_creation_date": "2013-09-20"
@@ -40,8 +40,8 @@
"name": "Frank Castle", "name": "Frank Castle",
"email": "frank.castle@vigilante.net", "email": "frank.castle@vigilante.net",
"account_id": "1", "account_id": "1",
"checking_balance": 320.10, "checking_balance": 320.1,
"savings_balance": 0.30, "savings_balance": 0.3,
"bitcoin_balance": 15.2189, "bitcoin_balance": 15.2189,
"account_creation_date": "2016-02-05" "account_creation_date": "2016-02-05"
}, },
@@ -49,8 +49,8 @@
"name": "Joshua Smith", "name": "Joshua Smith",
"email": "joshmsmith@gmail.com", "email": "joshmsmith@gmail.com",
"account_id": "11235813", "account_id": "11235813",
"checking_balance": 3021.90, "checking_balance": 3021.9,
"savings_balance": 500.50, "savings_balance": 500.5,
"bitcoin_balance": 0.001, "bitcoin_balance": 0.001,
"account_creation_date": "2020-03-19" "account_creation_date": "2020-03-19"
} }

View 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"
}
]
}

View File

@@ -0,0 +1,158 @@
{
"packages": [
{
"TrackingNumber": "USPS12345",
"Delivered": false,
"Carrier": "USPS",
"ServiceType": "USPS Ground Advantage<SUP>&#153;</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"
}
]
}
]
}

View 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}

View 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}

View 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>&#153;</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
}

View File

@@ -5,7 +5,7 @@ import json
# this assumes it's a valid account - use check_account_valid() to verify that first # this assumes it's a valid account - use check_account_valid() to verify that first
def get_account_balance(args: dict) -> dict: 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" file_path = Path(__file__).resolve().parent.parent / "data" / "customer_account_data.json"
if not file_path.exists(): if not file_path.exists():

View File

@@ -11,7 +11,7 @@ from shared.config import get_temporal_client
from enum import Enum, auto from enum import Enum, auto
#enums for the java enum # enums for the java enum
# class ExecutionScenarios(Enum): # class ExecutionScenarios(Enum):
# HAPPY_PATH = 0 # HAPPY_PATH = 0
# ADVANCED_VISIBILITY = auto() # 1 # ADVANCED_VISIBILITY = auto() # 1
@@ -20,6 +20,7 @@ from enum import Enum, auto
# BUG_IN_WORKFLOW = auto() # 4 # BUG_IN_WORKFLOW = auto() # 4
# INVALID_ACCOUNT = auto() # 5 # INVALID_ACCOUNT = auto() # 5
# these dataclasses are for calling the Temporal Workflow # these dataclasses are for calling the Temporal Workflow
# Python equivalent of the workflow we're calling's Java WorkflowParameterObj # Python equivalent of the workflow we're calling's Java WorkflowParameterObj
@dataclass @dataclass
@@ -27,103 +28,130 @@ class MoneyMovementWorkflowParameterObj:
amount: int # Using snake_case as per Python conventions amount: int # Using snake_case as per Python conventions
scenario: str scenario: str
# this is made to demonstrate functionality but it could just as durably be an API call # 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 # this assumes it's a valid account - use check_account_valid() to verify that first
async def move_money(args: dict) -> dict: 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") account_type: str = args.get("accounttype")
amount = args.get("amount") amount = args.get("amount")
destinationaccount = args.get("destinationaccount") 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(): if not file_path.exists():
return {"error": "Data file not found."} return {"error": "Data file not found."}
# todo validate there's enough money in the account
with open(file_path, "r") as file: with open(file_path, "r") as file:
data = json.load(file) data = json.load(file)
account_list = data["accounts"] account_list = data["accounts"]
for account in account_list: for account in account_list:
if account["email"] == account_key or account["account_id"] == account_key: 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 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) transfer_workflow_id = await start_workflow(
amount_cents=str_dollars_to_cents(amount_str),
account_type_key = 'checking_balance' from_account_name=from_account_combo,
if(account_type.casefold() == "checking" ): to_account_name=destinationaccount,
account_type = "checking" )
account_type_key = 'checking_balance'
if account_type.casefold() == "checking":
elif(account_type.casefold() == "savings" ): from_key = "checking_balance"
account_type = "savings" elif account_type.casefold() == "savings":
account_type_key = 'savings_balance' from_key = "savings_balance"
else: else:
raise NotImplementedError("money order for account types other than checking or savings is not implemented.") return_msg = "Money order for account types other than checking or savings is not implemented."
return {"error": return_msg}
new_balance: float = float(str_dollars_to_cents(str(account[account_type_key])))
new_balance = new_balance - float(str_dollars_to_cents(amount_str)) to_key = (
account[account_type_key] = str(new_balance / 100 ) #to dollars "savings_balance"
with open(file_path, 'w') as file: if destinationaccount.casefold() == "savings"
json.dump(data, file, indent=4) 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_msg = "Account not found with for " + account_key
return {"error": return_msg} return {"error": return_msg}
# Async function to start workflow # Async function to start workflow
async def start_workflow(amount_cents: int, from_account_name: str, to_account_name: str)-> str: async def start_workflow(
amount_cents: int, from_account_name: str, to_account_name: str
# Connect to Temporal ) -> str:
client = await get_temporal_client()
start_real_workflow = os.getenv("FIN_START_REAL_WORKFLOW") start_real_workflow = os.getenv("FIN_START_REAL_WORKFLOW")
if start_real_workflow is not None and start_real_workflow.lower() == "false": if start_real_workflow is not None and start_real_workflow.lower() == "false":
START_REAL_WORKFLOW = False START_REAL_WORKFLOW = False
else: else:
START_REAL_WORKFLOW = True START_REAL_WORKFLOW = True
if START_REAL_WORKFLOW: if START_REAL_WORKFLOW:
# Connect to Temporal
client = await get_temporal_client()
# Create the parameter object # Create the parameter object
params = MoneyMovementWorkflowParameterObj( params = MoneyMovementWorkflowParameterObj(
amount=amount_cents, amount=amount_cents, scenario="HAPPY_PATH"
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( handle = await client.start_workflow(
"moneyTransferWorkflow", # Workflow name "moneyTransferWorkflow", # Workflow name
params, # Workflow parameters params, # Workflow parameters
id=workflow_id, id=workflow_id,
task_queue="MoneyTransferJava" # Task queue name task_queue="MoneyTransferJava", # Task queue name
) )
return handle.id return handle.id
except WorkflowAlreadyStartedError as e: except WorkflowAlreadyStartedError as e:
existing_handle = client.get_workflow_handle(workflow_id=workflow_id) existing_handle = client.get_workflow_handle(workflow_id=workflow_id)
return existing_handle.id return existing_handle.id
else: else:
return "TRANSFER-ACCT-" + from_account_name + "-TO-" + to_account_name + "not-real" 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: def str_dollars_to_cents(dollar_str: str) -> int:
try: try:
# Remove '$' and any whitespace # Remove '$' and any whitespace
cleaned_str = dollar_str.replace('$', '').strip() cleaned_str = dollar_str.replace("$", "").strip()
# Handle empty string or invalid input # Handle empty string or invalid input
if not cleaned_str: if not cleaned_str:
raise ValueError("Empty amount provided") raise ValueError("Empty amount provided")
# Convert to float and then to cents # Convert to float and then to cents
amount = float(cleaned_str) amount = float(cleaned_str)
if amount < 0: if amount < 0:
raise ValueError("Negative amounts not allowed") raise ValueError("Negative amounts not allowed")
return int(amount * 100) return int(amount * 100)
except ValueError as e: 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

View File

@@ -31,7 +31,7 @@ class TxResult:
#demonstrate starting a workflow and early return pattern while the workflow continues #demonstrate starting a workflow and early return pattern while the workflow continues
async def submit_loan_application(args: dict) -> dict: 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") amount = args.get("amount")
loan_status: dict = await start_workflow(amount=amount,account_name=account_key) 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 function to start workflow
async def start_workflow(amount: str, account_name: str, )-> dict: 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") start_real_workflow = os.getenv("FIN_START_REAL_WORKFLOW")
if start_real_workflow is not None and start_real_workflow.lower() == "false": if start_real_workflow is not None and start_real_workflow.lower() == "false":
START_REAL_WORKFLOW = 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", } 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: else:
START_REAL_WORKFLOW = True START_REAL_WORKFLOW = True
# Connect to Temporal
client = await get_temporal_client()
# Define the workflow ID and task queue # Define the workflow ID and task queue
workflow_id = "LOAN_APPLICATION-"+account_name+"-"+date.today().strftime('%Y-%m-%d') workflow_id = "LOAN_APPLICATION-"+account_name+"-"+date.today().strftime('%Y-%m-%d')

View File

@@ -1,46 +1,51 @@
import os
from typing import List from typing import List
from models.tool_definitions import AgentGoal from models.tool_definitions import AgentGoal
import tools.tool_registry as tool_registry 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. # 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: # 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="Christopher Walken"
#SILLY_MODE="belligerent" # SILLY_MODE="belligerent"
# #
# Example if you want it to take on a persona (include 'a'): # 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. # 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": 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) print("Silly mode is on: " + SILLY_MODE)
else: else:
silly_prompt = "" 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( goal_choose_agent_type = AgentGoal(
id = "goal_choose_agent_type", id="goal_choose_agent_type",
category_tag="agent_selection", category_tag="agent_selection",
agent_name="Choose Agent", agent_name="Choose Agent",
agent_friendly_description="Choose the type of agent to assist you today.", agent_friendly_description="Choose the type of agent to assist you today. You can always interrupt an existing agent to pick a new one.",
tools=[ tools=[
tool_registry.list_agents_tool, tool_registry.list_agents_tool,
tool_registry.change_goal_tool, tool_registry.change_goal_tool,
], ],
description="The user wants to choose which type of agent they will interact with. " description="The user wants to choose which type of agent they will interact with. "
"Help the user gather args for these tools, in order: " "Help the user select an agent by gathering args for the Changegoal tool, in order: "
"1. ListAgents: List agents available to interact with. Do not ask for user confirmation for this tool. " "1. ListAgents: List agents available to interact with. Do not ask for user confirmation for this tool. "
"2. ChangeGoal: Change goal of agent " "2. ChangeGoal: Change goal of agent "
"After these tools are complete, change your goal to the new goal as chosen by the user. ", "After these tools are complete, change your goal to the new goal as chosen by the user. ",
starter_prompt=starter_prompt_generic + " Begin by listing all details of all agents as provided by the output of the first tool included in this goal. ", starter_prompt=silly_prompt
+ "Welcome me, give me a description of what you can do, then ask me for the details you need to do your job. List all details of all agents as provided by the output of the first tool included in this goal. ",
example_conversation_history="\n ".join( example_conversation_history="\n ".join(
[ [
"agent: Here are the currently available agents.", "agent: Here are the currently available agents.",
"user_confirmed_tool_run: <user clicks confirm on ListAgents tool>", "tool_result: { agents: 'agent_name': 'Event Flight Finder', 'goal_id': 'goal_event_flight_invoice', 'agent_description': 'Helps users find interesting events and arrange travel to them',"
"tool_result: { '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: 1. Event Flight Finder. \n Which agent would you like to speak to? (You can respond with name or number.)", "agent: The available agents are: Event Flight Finder and Schedule PTO. \n Which agent would you like to work with? ",
"user: 1, Event Flight Finder", "user: I'd like to find an event and book flights using the Event Flight Finder",
"user_confirmed_tool_run: <user clicks confirm on ChangeGoal tool>", "user_confirmed_tool_run: <user clicks confirm on ChangeGoal tool>",
"tool_result: { 'new_goal': 'goal_event_flight_invoice' }", "tool_result: { 'new_goal': 'goal_event_flight_invoice' }",
] ]
@@ -54,7 +59,7 @@ pirate_category_tag = "pirate"
if SILLY_MODE == "a pirate": if SILLY_MODE == "a pirate":
pirate_category_tag = "system" pirate_category_tag = "system"
goal_pirate_treasure = AgentGoal( goal_pirate_treasure = AgentGoal(
id = "goal_pirate_treasure", id="goal_pirate_treasure",
category_tag=pirate_category_tag, category_tag=pirate_category_tag,
agent_name="Arrr, Find Me Treasure!", agent_name="Arrr, Find Me Treasure!",
agent_friendly_description="Sail the high seas and find me pirate treasure, ye land lubber!", 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, tool_registry.guess_location_tool,
], ],
description="The user wants to find a pirate treasure. " 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: " "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." "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. ", "2. GuessLocation: The user guesses where the treasure is, by giving an address. ",
starter_prompt=starter_prompt_generic, starter_prompt=starter_prompt_generic,
example_conversation_history="\n ".join( example_conversation_history="\n ".join(
[ [
@@ -95,8 +100,9 @@ goal_pirate_treasure = AgentGoal(
), ),
) )
# ----- Travel Goals ---
goal_match_train_invoice = AgentGoal( goal_match_train_invoice = AgentGoal(
id = "goal_match_train_invoice", id="goal_match_train_invoice",
category_tag="travel-trains", category_tag="travel-trains",
agent_name="UK Premier League Match Trip Booking", 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.", 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( goal_event_flight_invoice = AgentGoal(
id = "goal_event_flight_invoice", id="goal_event_flight_invoice",
category_tag="travel-flights", category_tag="travel-flights",
agent_name="Australia and New Zealand Event Flight Booking", 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=[ tools=[
tool_registry.find_events_tool, tool_registry.find_events_tool,
tool_registry.search_flights_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. # This goal uses the data/employee_pto_data.json file as dummy data.
goal_hr_schedule_pto = AgentGoal( goal_hr_schedule_pto = AgentGoal(
id = "goal_hr_schedule_pto", id="goal_hr_schedule_pto",
category_tag="hr", category_tag="hr",
agent_name="Schedule PTO", 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=[ tools=[
tool_registry.current_pto_tool, tool_registry.current_pto_tool,
tool_registry.future_pto_calc_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. # This goal uses the data/employee_pto_data.json file as dummy data.
goal_hr_check_pto = AgentGoal( goal_hr_check_pto = AgentGoal(
id = "goal_hr_check_pto", id="goal_hr_check_pto",
category_tag="hr", category_tag="hr",
agent_name="Check PTO Amount", agent_name="Check PTO Amount",
agent_friendly_description="Check your available PTO.", agent_friendly_description="Check your available PTO.",
tools=[ tools=[
tool_registry.current_pto_tool, tool_registry.current_pto_tool,
], ],
@@ -245,10 +252,10 @@ goal_hr_check_pto = AgentGoal(
# check integration with bank # check integration with bank
goal_hr_check_paycheck_bank_integration_status = AgentGoal( 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", category_tag="hr",
agent_name="Check paycheck deposit status", 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=[ tools=[
tool_registry.paycheck_bank_integration_status_check, 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 # this tool checks account balances, and uses ./data/customer_account_data.json as dummy data
goal_fin_check_account_balances = AgentGoal( goal_fin_check_account_balances = AgentGoal(
id = "goal_fin_check_account_balances", id="goal_fin_check_account_balances",
category_tag="fin", category_tag="fin",
agent_name="Check balances", agent_name="Account Balances",
agent_friendly_description="Check your account balances in Checking, Savings, etc.", agent_friendly_description="Check your account balances in Checking, Savings, etc.",
tools=[ tools=[
tool_registry.financial_check_account_is_valid, tool_registry.financial_check_account_is_valid,
tool_registry.financial_get_account_balances, tool_registry.financial_get_account_balances,
@@ -285,7 +293,7 @@ goal_fin_check_account_balances = AgentGoal(
example_conversation_history="\n ".join( example_conversation_history="\n ".join(
[ [
"user: I'd like to check my account balances", "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: email is bob.johnson@emailzzz.com ",
"user_confirmed_tool_run: <user clicks confirm on FincheckAccountIsValid tool>", "user_confirmed_tool_run: <user clicks confirm on FincheckAccountIsValid tool>",
"tool_result: { 'status': account valid }", "tool_result: { 'status': account valid }",
@@ -293,10 +301,10 @@ goal_fin_check_account_balances = AgentGoal(
"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 }", "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 " "agent: Your account balances are as follows: \n "
"Checking: $875.40. \n " "Checking: $875.40. \n "
"Savings: $3200.15. \n " "Savings: $3200.15. \n "
"Bitcoint: 0.1378 \n " "Bitcoint: 0.1378 \n "
"Thanks for being a customer since 2014!", "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 # 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 # it also uses a separate workflow/tool, see ./setup.md for details
goal_fin_move_money = AgentGoal( goal_fin_move_money = AgentGoal(
id = "goal_fin_move_money", id="goal_fin_move_money",
category_tag="fin", category_tag="fin",
agent_name="Money Order", agent_name="Money Movement",
agent_friendly_description="Initiate a money movement order.", agent_friendly_description="Initiate money movement.",
tools=[ tools=[
tool_registry.financial_check_account_is_valid, tool_registry.financial_check_account_is_valid,
tool_registry.financial_get_account_balances, 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: " 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" "1. FinCheckAccountIsValid: validate the user's account is valid"
"2. FinCheckAccountBalance: Tell the user their account balance at the bank or financial institution" "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, starter_prompt=starter_prompt_generic,
example_conversation_history="\n ".join( example_conversation_history="\n ".join(
[ [
"user: I'd like to transfer some money", "user: I'd like to transfer some money",
"agent: Sure! I can help you out with that. May I have account number and email address?", "agent: Sure! I can help you out with that. May I have account number and email address?",
"user: account number is 11235813", "user: my account number is 11235 and my email address is matt.murdock@nelsonmurdock.com",
"user_confirmed_tool_run: <user clicks confirm on FincheckAccountIsValid tool>", "user_confirmed_tool_run: <user clicks confirm on FincheckAccountIsValid tool>",
"tool_result: { 'status': account valid }", "tool_result: { 'status': account valid }",
"agent: Great! Here are your account balances:", "agent: Great! Here are your account balances:",
"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 }", "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 " "agent: Your account balances are as follows: \n "
"Checking: $875.40. \n " "Checking: $875.40. \n "
"Savings: $3200.15. \n " "Savings: $3200.15. \n "
"Bitcoint: 0.1378 \n " "Bitcoint: 0.1378 \n "
"agent: how much would you like to move, from which account type, and to which account number?", "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: I'd like to move $500 from savings to account number #56789",
"user_confirmed_tool_run: <user clicks confirm on FinMoveMoney tool>", "user_confirmed_tool_run: <user clicks confirm on FinMoveMoney tool>",
"tool_result: { 'status': money movement complete, 'confirmation id': 333421, 'new_balance': $2700.15 }", "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 # 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( goal_fin_loan_application = AgentGoal(
id = "goal_fin_loan_application", id="goal_fin_loan_application",
category_tag="fin", category_tag="fin",
agent_name="Easy Loan Apply", agent_name="Easy Loan",
agent_friendly_description="Initiate loan application.", agent_friendly_description="Initiate a simple loan application.",
tools=[ tools=[
tool_registry.financial_check_account_is_valid, tool_registry.financial_check_account_is_valid,
tool_registry.financial_submit_loan_approval, #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: " 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" "1. FinCheckAccountIsValid: validate the user's account is valid"
@@ -359,7 +367,7 @@ goal_fin_loan_application = AgentGoal(
example_conversation_history="\n ".join( example_conversation_history="\n ".join(
[ [
"user: I'd like to apply for a loan", "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: account number is 11235813",
"user_confirmed_tool_run: <user clicks confirm on FincheckAccountIsValid tool>", "user_confirmed_tool_run: <user clicks confirm on FincheckAccountIsValid tool>",
"tool_result: { 'status': account valid }", "tool_result: { 'status': account valid }",
@@ -367,12 +375,86 @@ goal_fin_loan_application = AgentGoal(
"user: I'd like a loan for $500", "user: I'd like a loan for $500",
"user_confirmed_tool_run: <user clicks confirm on FinCheckAccountSubmitLoanApproval tool>", "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 }", "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: List[AgentGoal] = []
goal_list.append(goal_choose_agent_type) goal_list.append(goal_choose_agent_type)
goal_list.append(goal_pirate_treasure) 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_check_account_balances)
goal_list.append(goal_fin_move_money) goal_list.append(goal_fin_move_money)
goal_list.append(goal_fin_loan_application) 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

View File

@@ -11,7 +11,7 @@ def search_airport(query: str) -> list:
""" """
load_dotenv(override=True) load_dotenv(override=True)
api_key = os.getenv("RAPIDAPI_KEY", "YOUR_DEFAULT_KEY") 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) conn = http.client.HTTPSConnection(api_host)
headers = { headers = {
@@ -73,7 +73,7 @@ def search_flights_real_api(
# Step 2: Call flight search with resolved codes # Step 2: Call flight search with resolved codes
load_dotenv(override=True) load_dotenv(override=True)
api_key = os.getenv("RAPIDAPI_KEY", "YOUR_DEFAULT_KEY") 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) conn = http.client.HTTPSConnection(api_host)
headers = { headers = {

View File

@@ -1,4 +1,5 @@
from models.tool_definitions import ToolDefinition, ToolArgument from models.tool_definitions import ToolDefinition, ToolArgument
# ----- System tools ----- # ----- System tools -----
list_agents_tool = ToolDefinition( list_agents_tool = ToolDefinition(
name="ListAgents", name="ListAgents",
@@ -21,12 +22,13 @@ change_goal_tool = ToolDefinition(
give_hint_tool = ToolDefinition( give_hint_tool = ToolDefinition(
name="GiveHint", 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 ", 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( ToolArgument(
name="hint_total", name="hint_total",
type="number", type="number",
description="How many hints have been given", description="How many hints have been given",
),], ),
],
) )
guess_location_tool = ToolDefinition( guess_location_tool = ToolDefinition(
@@ -54,7 +56,8 @@ guess_location_tool = ToolDefinition(
# ----- Travel use cases tools ----- # ----- Travel use cases tools -----
search_flights_tool = ToolDefinition( search_flights_tool = ToolDefinition(
name="SearchFlights", name="SearchFlights",
description="Search for return flights from an origin to a destination within a date range (dateDepart, dateReturn).", description="Search for return flights from an origin to a destination within a date range (dateDepart, dateReturn). "
"You are allowed to suggest dates from the conversation history, but ALWAYS ask the user if ok.",
arguments=[ arguments=[
ToolArgument( ToolArgument(
name="origin", name="origin",
@@ -76,6 +79,12 @@ search_flights_tool = ToolDefinition(
type="ISO8601", type="ISO8601",
description="End of date range in human readable format, when you want to return", 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", type="string",
description="The IDs of the trains to book, comma separated", 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", type="string",
description="A description of the item details to be invoiced, inferred from the conversation history.", 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", name="FinCheckAccountBalance",
description="Get account balance for your accounts. " description="Get account balance for your accounts. "
"Returns the account balances of your accounts. ", "Returns the account balances of your accounts. ",
arguments=[ arguments=[
ToolArgument( ToolArgument(
name="accountkey", name="email_address_or_account_ID",
type="string", type="string",
description="email address or account ID of user", description="email address or account ID of user",
), ),
@@ -289,50 +307,93 @@ financial_get_account_balances = ToolDefinition(
) )
financial_move_money = ToolDefinition( financial_move_money = ToolDefinition(
name="FinMoveMoneyOrder", name="FinMoveMoney",
description="Execute a money movement order. " 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 account balance of the account money was moved from. ", "Returns the status of the order and the new balances in each account. ",
arguments=[ arguments=[
ToolArgument( ToolArgument(
name="accountkey", name="email_address_or_account_ID",
type="string", 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", name="accounttype",
type="string", type="string",
description="account type, such as checking or savings", description="account type, such as checking or savings",
), ),
ToolArgument( ToolArgument(
name="amount", name="amount",
type="string", 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", name="destinationaccount",
type="string", 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( financial_submit_loan_approval = ToolDefinition(
name="FinCheckAccountSubmitLoanApproval", name="FinCheckAccountSubmitLoanApproval",
description="Submit a loan application. " description="Submit a loan application. " "Returns the loan status. ",
"Returns the loan status. ",
arguments=[ arguments=[
ToolArgument( ToolArgument(
name="accountkey", name="email_address_or_account_ID",
type="string", type="string",
description="email address or account ID of user", description="email address or account ID of user",
), ),
ToolArgument( ToolArgument(
name="amount", name="amount",
type="string", type="string",
description="amount requested for the loan", 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",
),
],
)

View File

@@ -108,7 +108,7 @@ class AgentGoalWorkflow:
conversation_history=self.conversation_history, conversation_history=self.conversation_history,
agent_goal=self.goal, agent_goal=self.goal,
) )
validation_result = await workflow.execute_activity( validation_result = await workflow.execute_activity_method(
ToolActivities.agent_validatePrompt, ToolActivities.agent_validatePrompt,
args=[validation_input], args=[validation_input],
schedule_to_close_timeout=LLM_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT, 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) prompt_input = ToolPromptInput(prompt=prompt, context_instructions=context_instructions)
# connect to LLM and execute to get next steps # 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, ToolActivities.agent_toolPlanner,
prompt_input, prompt_input,
schedule_to_close_timeout=LLM_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT, 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: # if we have all needed arguments (handled above) and not holding for a debugging confirm, proceed:
else: else:
self.confirmed = True self.confirmed = True
# else if the next step is to pick a new goal, set that to be the goal
# else if the next step is to pick a new goal, set the goal and tool to do it
elif next_step == "pick-new-goal": elif next_step == "pick-new-goal":
workflow.logger.info("All steps completed. Resetting goal.") workflow.logger.info("All steps completed. Resetting goal.")
self.change_goal("goal_choose_agent_type") self.change_goal("goal_choose_agent_type")
next_step = tool_data["next"] = "confirm"
current_tool = tool_data["tool"] = "ListAgents"
waiting_for_confirm = True
self.confirmed = True
# else if the next step is to be done with the conversation such as if the user requests it via asking to "end conversation" # else if the next step is to be done with the conversation such as if the user requests it via asking to "end conversation"
elif next_step == "done": elif next_step == "done":
@@ -210,7 +207,7 @@ class AgentGoalWorkflow:
#Signal that comes from api/main.py via a post to /confirm #Signal that comes from api/main.py via a post to /confirm
@workflow.signal @workflow.signal
async def confirmed(self) -> None: async def confirm(self) -> None:
"""Signal handler for user confirmation of tool execution.""" """Signal handler for user confirmation of tool execution."""
workflow.logger.info("Received user signal: confirmation") workflow.logger.info("Received user signal: confirmation")
self.confirmed = True self.confirmed = True
@@ -316,8 +313,9 @@ class AgentGoalWorkflow:
async def lookup_wf_env_settings(self, combined_input: CombinedInput)->None: async def lookup_wf_env_settings(self, combined_input: CombinedInput)->None:
env_lookup_input = EnvLookupInput( env_lookup_input = EnvLookupInput(
show_confirm_env_var_name = "SHOW_CONFIRM", show_confirm_env_var_name = "SHOW_CONFIRM",
show_confirm_default = True) show_confirm_default = True,
env_output:EnvLookupOutput = await workflow.execute_activity( )
env_output:EnvLookupOutput = await workflow.execute_activity_method(
ToolActivities.get_wf_env_vars, ToolActivities.get_wf_env_vars,
env_lookup_input, env_lookup_input,
start_to_close_timeout=LLM_ACTIVITY_START_TO_CLOSE_TIMEOUT, start_to_close_timeout=LLM_ACTIVITY_START_TO_CLOSE_TIMEOUT,
@@ -347,14 +345,12 @@ class AgentGoalWorkflow:
self.prompt_queue self.prompt_queue
) )
#set new goal if we should # set new goal if we should
if len(self.tool_results) > 0: if len(self.tool_results) > 0:
if "ChangeGoal" in self.tool_results[-1].values() and "new_goal" in self.tool_results[-1].keys(): 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") new_goal = self.tool_results[-1].get("new_goal")
workflow.logger.info(f"Booya new goal!: {new_goal}")
self.change_goal(new_goal) self.change_goal(new_goal)
elif "ListAgents" in self.tool_results[-1].values() and self.goal.id != "goal_choose_agent_type": 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") self.change_goal("goal_choose_agent_type")
return waiting_for_confirm 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 # 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: def print_useful_workflow_vars(self, status_or_step:str) -> None:
print(f"***{status_or_step}:***") print(f"***{status_or_step}:***")
print(f"force confirm? {self.tool_data['force_confirm']}") if self.goal:
print(f"next step: {self.tool_data.get('next')}") print(f"current goal: {self.goal.id}")
print(f"current_tool: {self.tool_data.get('tool')}") if self.tool_data:
print(f"self.confirm: {self.confirmed}") print(f"force confirm? {self.tool_data['force_confirm']}")
print(f"waiting_for_confirm (about to be set to true): {self.waiting_for_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}")