mirror of
https://github.com/temporal-community/temporal-ai-agent.git
synced 2026-03-15 14:08:08 +01:00
merged old agent goal in with keynote goal
This commit is contained in:
@@ -31,3 +31,6 @@ OPENAI_API_KEY=sk-proj-...
|
|||||||
|
|
||||||
# Uncomment if using API key (not needed for local dev server)
|
# Uncomment if using API key (not needed for local dev server)
|
||||||
# TEMPORAL_API_KEY=abcdef1234567890
|
# TEMPORAL_API_KEY=abcdef1234567890
|
||||||
|
|
||||||
|
# Agent Goal Configuration
|
||||||
|
# AGENT_GOAL=goal_event_flight_invoice # (default) or goal_match_train_invoice
|
||||||
|
|||||||
111
README.md
111
README.md
@@ -1,6 +1,8 @@
|
|||||||
# Temporal AI Agent
|
# Temporal AI Agent
|
||||||
|
|
||||||
This demo shows a multi-turn conversation with an AI agent running inside a Temporal workflow. The purpose of the agent is to collect information towards a goal, running tools along the way. There's a simple DSL input for collecting information (currently set up to use mock functions to search for public events, search for flights around those events, then create a test Stripe invoice for the trip). The AI will respond with clarifications and ask for any missing information to that goal. You can configure it to use [ChatGPT 4o](https://openai.com/index/hello-gpt-4o/), [Anthropic Claude](https://www.anthropic.com/claude), [Google Gemini](https://gemini.google.com), [Deepseek-V3](https://www.deepseek.com/) or a local LLM of your choice using [Ollama](https://ollama.com).
|
This demo shows a multi-turn conversation with an AI agent running inside a Temporal workflow. The purpose of the agent is to collect information towards a goal, running tools along the way. There's a simple DSL input for collecting information (currently set up to use mock functions to search for public events, search for flights around those events, then create a test Stripe invoice for the trip).
|
||||||
|
|
||||||
|
The AI will respond with clarifications and ask for any missing information to that goal. You can configure it to use [ChatGPT 4o](https://openai.com/index/hello-gpt-4o/), [Anthropic Claude](https://www.anthropic.com/claude), [Google Gemini](https://gemini.google.com), [Deepseek-V3](https://www.deepseek.com/) or a local LLM of your choice using [Ollama](https://ollama.com).
|
||||||
|
|
||||||
[Watch the demo (5 minute YouTube video)](https://www.youtube.com/watch?v=GEXllEH2XiQ)
|
[Watch the demo (5 minute YouTube video)](https://www.youtube.com/watch?v=GEXllEH2XiQ)
|
||||||
|
|
||||||
@@ -14,6 +16,43 @@ This application uses `.env` files for configuration. Copy the [.env.example](.e
|
|||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Agent Goal Configuration
|
||||||
|
|
||||||
|
The agent can be configured to pursue different goals using the `AGENT_GOAL` environment variable in your `.env` file.
|
||||||
|
|
||||||
|
#### Goal: Find an event in APAC, book flights to it and invoice the user for the cost
|
||||||
|
- `AGENT_GOAL=goal_event_flight_invoice` (default) - Helps users find events, book flights, and arrange train travel with invoice generation
|
||||||
|
- This is the scenario in the video above
|
||||||
|
|
||||||
|
#### Goal: Find a Premier League match, book train tickets to it and invoice the user for the cost
|
||||||
|
- `AGENT_GOAL=goal_match_train_invoice` - Focuses on Premier League match attendance with train booking and invoice generation
|
||||||
|
- This is a new goal that is part of an upcoming conference talk
|
||||||
|
|
||||||
|
If not specified, the agent defaults to `goal_event_flight_invoice`. Each goal comes with its own set of tools and conversation flows designed for specific use cases. You can examine `tools/goal_registry.py` to see the detailed configuration of each goal.
|
||||||
|
|
||||||
|
See the next section for tool configuration for each goal.
|
||||||
|
|
||||||
|
### Tool Configuration
|
||||||
|
|
||||||
|
#### Agent Goal: goal_event_flight_invoice (default)
|
||||||
|
* The agent uses a mock function to search for events. This has zero configuration.
|
||||||
|
* By default the agent uses a mock function to search for flights.
|
||||||
|
* If you want to use the real flights API, go to `tools/search_flights.py` and replace the `search_flights` function with `search_flights_real_api` that exists in the same file.
|
||||||
|
* It's free to sign up at [RapidAPI](https://rapidapi.com/apiheya/api/sky-scrapper)
|
||||||
|
* This api might be slow to respond, so you may want to increase the start to close timeout, `TOOL_ACTIVITY_START_TO_CLOSE_TIMEOUT` in `workflows/workflow_helpers.py`
|
||||||
|
* Requires a Stripe key for the `create_invoice` tool. Set this in the `STRIPE_API_KEY` environment variable in .env
|
||||||
|
* It's free to sign up and get a key at [Stripe](https://stripe.com/)
|
||||||
|
* If you're lazy go to `tools/create_invoice.py` and replace the `create_invoice` function with the mock `create_invoice_example` that exists in the same file.
|
||||||
|
|
||||||
|
#### Agent Goal: goal_match_train_invoice
|
||||||
|
|
||||||
|
* Finding a match requires a key from [Football Data](https://www.football-data.org). Sign up for a free account, then see the 'My Account' page to get your API token. Set `FOOTBALL_DATA_API_KEY` to this value.
|
||||||
|
* We use a mock function to search for trains. Start the train API server to use the real API: `python thirdparty/train_api.py`
|
||||||
|
* * The train activity is 'enterprise' so it's written in C# and requires a .NET runtime. See the [.NET backend](#net-(enterprise)-backend) section for details on running it.
|
||||||
|
* Requires a Stripe key for the `create_invoice` tool. Set this in the `STRIPE_API_KEY` environment variable in .env
|
||||||
|
* It's free to sign up and get a key at [Stripe](https://stripe.com/)
|
||||||
|
* If you're lazy go to `tools/create_invoice.py` and replace the `create_invoice` function with the mock `create_invoice_example` that exists in the same file.
|
||||||
|
|
||||||
### LLM Provider Configuration
|
### LLM Provider Configuration
|
||||||
|
|
||||||
The agent can use OpenAI's GPT-4o, Google Gemini, Anthropic Claude, or a local LLM via Ollama. Set the `LLM_PROVIDER` environment variable in your `.env` file to choose the desired provider:
|
The agent can use OpenAI's GPT-4o, Google Gemini, Anthropic Claude, or a local LLM via Ollama. Set the `LLM_PROVIDER` environment variable in your `.env` file to choose the desired provider:
|
||||||
@@ -35,7 +74,9 @@ To use Google Gemini:
|
|||||||
1. Obtain a Google API key and set it in the `GOOGLE_API_KEY` environment variable in `.env`.
|
1. Obtain a Google API key and set it in the `GOOGLE_API_KEY` environment variable in `.env`.
|
||||||
2. Set `LLM_PROVIDER=google` in your `.env` file.
|
2. Set `LLM_PROVIDER=google` in your `.env` file.
|
||||||
|
|
||||||
### Option 3: Anthropic Claude
|
### Option 3: Anthropic Claude (recommended)
|
||||||
|
|
||||||
|
I find that Claude Sonnet 3.5 performs better than the other hosted LLMs for this use case.
|
||||||
|
|
||||||
To use Anthropic:
|
To use Anthropic:
|
||||||
|
|
||||||
@@ -61,15 +102,6 @@ To use a local LLM with Ollama:
|
|||||||
|
|
||||||
Note: I found the other (hosted) LLMs to be MUCH more reliable for this use case. However, you can switch to Ollama if desired, and choose a suitably large model if your computer has the resources.
|
Note: I found the other (hosted) LLMs to be MUCH more reliable for this use case. However, you can switch to Ollama if desired, and choose a suitably large model if your computer has the resources.
|
||||||
|
|
||||||
## Agent Tools
|
|
||||||
* Requires a Rapidapi key for sky-scrapper (how we find flights). Set this in the `RAPIDAPI_KEY` environment variable in .env
|
|
||||||
* It's free to sign up and get a key at [RapidAPI](https://rapidapi.com/apiheya/api/sky-scrapper)
|
|
||||||
* If you're lazy go to `tools/search_flights.py` and replace the `get_flights` function with the mock `search_flights_example` that exists in the same file.
|
|
||||||
* Requires a Stripe key for the `create_invoice` tool. Set this in the `STRIPE_API_KEY` environment variable in .env
|
|
||||||
* It's free to sign up and get a key at [Stripe](https://stripe.com/)
|
|
||||||
* If you're lazy go to `tools/create_invoice.py` and replace the `create_invoice` function with the mock `create_invoice_example` that exists in the same file.
|
|
||||||
* Requires a key from [Football Data](https://www.football-data.org). Sign up for a free account, then see the 'My Account' page to get your API token. Set `FOOTBALL_DATA_API_KEY` to this value.
|
|
||||||
|
|
||||||
## Configuring Temporal Connection
|
## Configuring Temporal Connection
|
||||||
|
|
||||||
By default, this application will connect to a local Temporal server (`localhost:7233`) in the default namespace, using the `agent-task-queue` task queue. You can override these settings in your `.env` file.
|
By default, this application will connect to a local Temporal server (`localhost:7233`) in the default namespace, using the `agent-task-queue` task queue. You can override these settings in your `.env` file.
|
||||||
@@ -115,24 +147,6 @@ 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.
|
||||||
|
|
||||||
### Python Search Trains API
|
|
||||||
Required to search and book trains!
|
|
||||||
```bash
|
|
||||||
poetry run python thirdparty/train_api.py
|
|
||||||
|
|
||||||
# example url
|
|
||||||
# http://localhost:8080/api/search?from=london&to=liverpool&outbound_time=2025-04-18T09:00:00&inbound_time=2025-04-20T09:00:00
|
|
||||||
```
|
|
||||||
|
|
||||||
### .NET (enterprise) Backend ;)
|
|
||||||
We have activities written in C# to call the train APIs.
|
|
||||||
```bash
|
|
||||||
cd enterprise
|
|
||||||
dotnet build # ensure you brew install dotnet@8 first!
|
|
||||||
dotnet run
|
|
||||||
```
|
|
||||||
If you're running your train API above on a different host/port then change the API URL in `Program.cs`.
|
|
||||||
|
|
||||||
### React UI
|
### React UI
|
||||||
Start the frontend:
|
Start the frontend:
|
||||||
```bash
|
```bash
|
||||||
@@ -142,29 +156,36 @@ npx vite
|
|||||||
```
|
```
|
||||||
Access the UI at `http://localhost:5173`
|
Access the UI at `http://localhost:5173`
|
||||||
|
|
||||||
|
### Python Search Trains API
|
||||||
|
> Agent Goal: goal_match_train_invoice only
|
||||||
|
|
||||||
|
Required to search and book trains!
|
||||||
|
```bash
|
||||||
|
poetry run python thirdparty/train_api.py
|
||||||
|
|
||||||
|
# example url
|
||||||
|
# http://localhost:8080/api/search?from=london&to=liverpool&outbound_time=2025-04-18T09:00:00&inbound_time=2025-04-20T09:00:00
|
||||||
|
```
|
||||||
|
|
||||||
|
### .NET (enterprise) Backend ;)
|
||||||
|
> Agent Goal: goal_match_train_invoice only
|
||||||
|
|
||||||
|
We have activities written in C# to call the train APIs.
|
||||||
|
```bash
|
||||||
|
cd enterprise
|
||||||
|
dotnet build # ensure you brew install dotnet@8 first!
|
||||||
|
dotnet run
|
||||||
|
```
|
||||||
|
If you're running your train API above on a different host/port then change the API URL in `Program.cs`. Otherwise, be sure to run it using `python thirdparty/train_api.py`.
|
||||||
|
|
||||||
## Customizing the Agent
|
## Customizing the Agent
|
||||||
- `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)
|
||||||
- `goal_registry.py` contains descriptions of goals and the tools used to achieve them
|
- `goal_registry.py` contains descriptions of goals and the tools used to achieve them
|
||||||
- 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
|
||||||
- See main.py where some tool-specific logic is defined (todo, move this to the tool definition)
|
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
- I should prove this out with other tool definitions outside of the event/flight search case (take advantage of my nice DSL).
|
|
||||||
- Currently hardcoded to the Temporal dev server at localhost:7233. Need to support options incl Temporal Cloud.
|
|
||||||
- In a prod setting, I would need to ensure that payload data is stored separately (e.g. in S3 or a noSQL db - the claim-check pattern), or otherwise 'garbage collected'. Without these techniques, long conversations will fill up the workflow's conversation history, and start to breach Temporal event history payload limits.
|
- In a prod setting, I would need to ensure that payload data is stored separately (e.g. in S3 or a noSQL db - the claim-check pattern), or otherwise 'garbage collected'. Without these techniques, long conversations will fill up the workflow's conversation history, and start to breach Temporal event history payload limits.
|
||||||
- Continue-as-new shouldn't be a big consideration for this use case (as it would take many conversational turns to trigger). Regardless, I should ensure that it's able to carry the agent state over to the new workflow execution.
|
- Continue-as-new shouldn't be a big consideration for this use case (as it would take many conversational turns to trigger). Regardless, I should ensure that it's able to carry the agent state over to the new workflow execution.
|
||||||
- Perhaps the UI should show when the LLM response is being retried (i.e. activity retry attempt because the LLM provided bad output)
|
- Perhaps the UI should show when the LLM response is being retried (i.e. activity retry attempt because the LLM provided bad output)
|
||||||
- Tests would be nice!
|
- Tests would be nice!
|
||||||
|
|
||||||
# TODO for this branch
|
|
||||||
## Agent
|
|
||||||
- We'll have to figure out which matches are where. No use going to Manchester for a match that isn't there.
|
|
||||||
- The use of `###` in prompts I want excluded from the conversation history is a bit of a hack.
|
|
||||||
|
|
||||||
## UI
|
|
||||||
- Possibly need a 'worker down' type of message? I think I already have one when queries fail
|
|
||||||
|
|
||||||
## Validator function
|
|
||||||
- Probably keep data types, but move the activity and workflow code for the demo
|
|
||||||
- Probably don't need the validator function if its the result from a tool call or confirmation step
|
|
||||||
29
api/main.py
29
api/main.py
@@ -3,16 +3,30 @@ from typing import Optional
|
|||||||
from temporalio.client import Client
|
from temporalio.client import Client
|
||||||
from temporalio.exceptions import TemporalError
|
from temporalio.exceptions import TemporalError
|
||||||
from temporalio.api.enums.v1 import WorkflowExecutionStatus
|
from temporalio.api.enums.v1 import WorkflowExecutionStatus
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
|
||||||
from workflows.agent_goal_workflow import AgentGoalWorkflow
|
from workflows.agent_goal_workflow import AgentGoalWorkflow
|
||||||
from models.data_types import CombinedInput, AgentGoalWorkflowParams
|
from models.data_types import CombinedInput, AgentGoalWorkflowParams
|
||||||
from tools.goal_registry import goal_match_train_invoice
|
from tools.goal_registry import goal_match_train_invoice, goal_event_flight_invoice
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from shared.config import get_temporal_client, TEMPORAL_TASK_QUEUE
|
from shared.config import get_temporal_client, TEMPORAL_TASK_QUEUE
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
temporal_client: Optional[Client] = None
|
temporal_client: Optional[Client] = None
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
def get_agent_goal():
|
||||||
|
"""Get the agent goal from environment variables."""
|
||||||
|
goal_name = os.getenv("AGENT_GOAL", "goal_match_train_invoice")
|
||||||
|
goals = {
|
||||||
|
"goal_match_train_invoice": goal_match_train_invoice,
|
||||||
|
"goal_event_flight_invoice": goal_event_flight_invoice
|
||||||
|
}
|
||||||
|
return goals.get(goal_name, goal_event_flight_invoice)
|
||||||
|
|
||||||
|
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
async def startup_event():
|
async def startup_event():
|
||||||
@@ -92,10 +106,10 @@ async def get_conversation_history():
|
|||||||
|
|
||||||
@app.post("/send-prompt")
|
@app.post("/send-prompt")
|
||||||
async def send_prompt(prompt: str):
|
async def send_prompt(prompt: str):
|
||||||
# Create combined input
|
# Create combined input with goal from environment
|
||||||
combined_input = CombinedInput(
|
combined_input = CombinedInput(
|
||||||
tool_params=AgentGoalWorkflowParams(None, None),
|
tool_params=AgentGoalWorkflowParams(None, None),
|
||||||
agent_goal=goal_match_train_invoice,
|
agent_goal=get_agent_goal(),
|
||||||
)
|
)
|
||||||
|
|
||||||
workflow_id = "agent-workflow"
|
workflow_id = "agent-workflow"
|
||||||
@@ -139,10 +153,13 @@ async def end_chat():
|
|||||||
|
|
||||||
@app.post("/start-workflow")
|
@app.post("/start-workflow")
|
||||||
async def start_workflow():
|
async def start_workflow():
|
||||||
|
# Get the configured goal
|
||||||
|
agent_goal = get_agent_goal()
|
||||||
|
|
||||||
# Create combined input
|
# Create combined input
|
||||||
combined_input = CombinedInput(
|
combined_input = CombinedInput(
|
||||||
tool_params=AgentGoalWorkflowParams(None, None),
|
tool_params=AgentGoalWorkflowParams(None, None),
|
||||||
agent_goal=goal_match_train_invoice,
|
agent_goal=agent_goal,
|
||||||
)
|
)
|
||||||
|
|
||||||
workflow_id = "agent-workflow"
|
workflow_id = "agent-workflow"
|
||||||
@@ -154,9 +171,9 @@ async def start_workflow():
|
|||||||
id=workflow_id,
|
id=workflow_id,
|
||||||
task_queue=TEMPORAL_TASK_QUEUE,
|
task_queue=TEMPORAL_TASK_QUEUE,
|
||||||
start_signal="user_prompt",
|
start_signal="user_prompt",
|
||||||
start_signal_args=["### " + goal_match_train_invoice.starter_prompt],
|
start_signal_args=["### " + agent_goal.starter_prompt],
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"message": f"Workflow started with goal's starter prompt: {goal_match_train_invoice.starter_prompt}."
|
"message": f"Workflow started with goal's starter prompt: {agent_goal.starter_prompt}."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from .search_flights import search_flights
|
|||||||
from .search_trains import search_trains
|
from .search_trains import search_trains
|
||||||
from .search_trains import book_trains
|
from .search_trains import book_trains
|
||||||
from .create_invoice import create_invoice
|
from .create_invoice import create_invoice
|
||||||
|
from .find_events import find_events
|
||||||
|
|
||||||
|
|
||||||
def get_handler(tool_name: str):
|
def get_handler(tool_name: str):
|
||||||
@@ -16,5 +17,7 @@ def get_handler(tool_name: str):
|
|||||||
return book_trains
|
return book_trains
|
||||||
if tool_name == "CreateInvoice":
|
if tool_name == "CreateInvoice":
|
||||||
return create_invoice
|
return create_invoice
|
||||||
|
if tool_name == "FindEvents":
|
||||||
|
return find_events
|
||||||
|
|
||||||
raise ValueError(f"Unknown tool: {tool_name}")
|
raise ValueError(f"Unknown tool: {tool_name}")
|
||||||
|
|||||||
252
tools/data/find_events_data.json
Normal file
252
tools/data/find_events_data.json
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
{
|
||||||
|
"Melbourne": [
|
||||||
|
{
|
||||||
|
"eventName": "Australian Open",
|
||||||
|
"dateFrom": "2025-01-13",
|
||||||
|
"dateTo": "2025-01-26",
|
||||||
|
"description": "A two-week Grand Slam tennis tournament featuring the world's top players, accompanied by various entertainment options including live music and family-friendly activities."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Melbourne International Comedy Festival",
|
||||||
|
"dateFrom": "2025-03-26",
|
||||||
|
"dateTo": "2025-04-20",
|
||||||
|
"description": "One of the world's largest comedy festivals, showcasing stand-up, cabaret, theatre, and street performances across numerous city venues."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Melbourne International Film Festival (MIFF)",
|
||||||
|
"dateFrom": "2025-08-07",
|
||||||
|
"dateTo": "2025-08-23",
|
||||||
|
"description": "Established in 1952, MIFF presents a diverse selection of Australian and international films, including features, documentaries, and shorts."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Melbourne Fringe Festival",
|
||||||
|
"dateFrom": "2025-09-17",
|
||||||
|
"dateTo": "2025-10-04",
|
||||||
|
"description": "An open-access arts festival featuring a wide array of art forms such as theatre, comedy, music, and digital art across various venues."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Moomba Festival",
|
||||||
|
"dateFrom": "2025-03-07",
|
||||||
|
"dateTo": "2025-03-10",
|
||||||
|
"description": "Australia's largest free community festival, celebrated over four days during the Labour Day long weekend, including a parade, live music, fireworks, and the famous Birdman Rally along the Yarra River."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "White Night Melbourne",
|
||||||
|
"dateFrom": "2025-08-22",
|
||||||
|
"dateTo": "2025-08-24",
|
||||||
|
"description": "A dusk-to-dawn arts and cultural festival transforming the city with light installations, projections, music, and performances."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Melbourne Food and Wine Festival",
|
||||||
|
"dateFrom": "2025-03-19",
|
||||||
|
"dateTo": "2025-03-29",
|
||||||
|
"description": "A celebration of Victoria's culinary scene, featuring food and wine events, masterclasses, and dining experiences."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Sydney": [
|
||||||
|
{
|
||||||
|
"eventName": "Sydney Gay and Lesbian Mardi Gras",
|
||||||
|
"dateFrom": "2025-02-14",
|
||||||
|
"dateTo": "2025-03-01",
|
||||||
|
"description": "One of the largest LGBTQ+ festivals globally, featuring a vibrant parade, parties, and cultural events celebrating diversity and inclusion."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Vivid Sydney",
|
||||||
|
"dateFrom": "2025-05-22",
|
||||||
|
"dateTo": "2025-06-13",
|
||||||
|
"description": "An annual festival of light, music, and ideas, transforming the city with mesmerizing light installations and projections."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Sydney Festival",
|
||||||
|
"dateFrom": "2025-01-08",
|
||||||
|
"dateTo": "2025-01-26",
|
||||||
|
"description": "A major arts festival presenting a diverse program of theatre, dance, music, and visual arts across the city."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Sculpture by the Sea, Bondi",
|
||||||
|
"dateFrom": "2025-10-23",
|
||||||
|
"dateTo": "2025-11-09",
|
||||||
|
"description": "An outdoor sculpture exhibition along the Bondi to Tamarama coastal walk, showcasing works by Australian and international artists."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Sydney Writers' Festival",
|
||||||
|
"dateFrom": "2025-04-27",
|
||||||
|
"dateTo": "2025-05-03",
|
||||||
|
"description": "An annual literary festival featuring talks, panel discussions, and workshops with acclaimed authors and thinkers."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Sydney Film Festival",
|
||||||
|
"dateFrom": "2025-06-04",
|
||||||
|
"dateTo": "2025-06-15",
|
||||||
|
"description": "One of the longest-running film festivals in the world, showcasing a diverse selection of local and international films."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Auckland": [
|
||||||
|
{
|
||||||
|
"eventName": "Pasifika Festival",
|
||||||
|
"dateFrom": "2025-03-08",
|
||||||
|
"dateTo": "2025-03-09",
|
||||||
|
"description": "The largest Pacific Islands-themed festival globally, celebrating the diverse cultures of the Pacific with traditional cuisine, performances, and arts."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Auckland Arts Festival",
|
||||||
|
"dateFrom": "2025-03-11",
|
||||||
|
"dateTo": "2025-03-29",
|
||||||
|
"description": "A biennial multi-arts festival showcasing local and international artists in theatre, dance, music, and visual arts."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Auckland Writers Festival",
|
||||||
|
"dateFrom": "2025-05-13",
|
||||||
|
"dateTo": "2025-05-18",
|
||||||
|
"description": "An annual event bringing together international and local writers for discussions, readings, and workshops."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Auckland Diwali Festival",
|
||||||
|
"dateFrom": "2025-10-26",
|
||||||
|
"dateTo": "2025-10-27",
|
||||||
|
"description": "A vibrant celebration of Indian culture and the Hindu festival of Diwali, featuring performances, food stalls, and traditional activities."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Brisbane": [
|
||||||
|
{
|
||||||
|
"eventName": "Brisbane Festival",
|
||||||
|
"dateFrom": "2025-09-05",
|
||||||
|
"dateTo": "2025-09-26",
|
||||||
|
"description": "A major international arts festival featuring theatre, music, dance, and visual arts, culminating in the Riverfire fireworks display."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "NRL Magic Round",
|
||||||
|
"dateFrom": "2025-05-02",
|
||||||
|
"dateTo": "2025-05-04",
|
||||||
|
"description": "A rugby league extravaganza where all NRL matches for the round are played at Suncorp Stadium, attracting fans nationwide."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Brisbane International Film Festival",
|
||||||
|
"dateFrom": "2025-10-01",
|
||||||
|
"dateTo": "2025-10-11",
|
||||||
|
"description": "Showcasing a curated selection of films from around the world, including premieres and special events."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Brisbane Comedy Festival",
|
||||||
|
"dateFrom": "2025-02-22",
|
||||||
|
"dateTo": "2025-03-24",
|
||||||
|
"description": "A month-long comedy festival featuring local and international comedians in stand-up, sketch, and improv performances."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Brisbane Writers Festival",
|
||||||
|
"dateFrom": "2025-09-05",
|
||||||
|
"dateTo": "2025-09-08",
|
||||||
|
"description": "An annual literary festival celebrating books, writing, and ideas with author talks, panel discussions, and workshops."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Brisbane Asia Pacific Film Festival",
|
||||||
|
"dateFrom": "2025-11-29",
|
||||||
|
"dateTo": "2025-12-08",
|
||||||
|
"description": "Showcasing the best cinema from the Asia Pacific region, including features, documentaries, and short films."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Perth": [
|
||||||
|
{
|
||||||
|
"eventName": "Perth Festival",
|
||||||
|
"dateFrom": "2025-02-07",
|
||||||
|
"dateTo": "2025-03-01",
|
||||||
|
"description": "Australia's longest-running cultural festival, offering a diverse program of music, theatre, dance, literature, and visual arts."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Fringe World Festival",
|
||||||
|
"dateFrom": "2025-01-16",
|
||||||
|
"dateTo": "2025-02-15",
|
||||||
|
"description": "One of the largest fringe festivals globally, featuring a vast array of performances including comedy, cabaret, theatre, and street arts."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Sculpture by the Sea",
|
||||||
|
"dateFrom": "2025-03-06",
|
||||||
|
"dateTo": "2025-03-23",
|
||||||
|
"description": "An annual outdoor sculpture exhibition along Cottesloe Beach, showcasing works from Australian and international artists."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Revelation Perth International Film Festival",
|
||||||
|
"dateFrom": "2025-07-03",
|
||||||
|
"dateTo": "2025-07-13",
|
||||||
|
"description": "A showcase of independent cinema, featuring a diverse selection of films, documentaries, and short films."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Perth Comedy Festival",
|
||||||
|
"dateFrom": "2025-04-22",
|
||||||
|
"dateTo": "2025-05-19",
|
||||||
|
"description": "A month-long comedy festival featuring local and international comedians in stand-up, sketch, and improv performances."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Adelaide": [
|
||||||
|
{
|
||||||
|
"eventName": "Adelaide Festival",
|
||||||
|
"dateFrom": "2025-02-28",
|
||||||
|
"dateTo": "2025-03-15",
|
||||||
|
"description": "A premier arts festival offering a rich program of theatre, music, dance, and visual arts from renowned international and local artists."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Adelaide Fringe",
|
||||||
|
"dateFrom": "2025-02-14",
|
||||||
|
"dateTo": "2025-03-15",
|
||||||
|
"description": "The largest open-access arts festival in the Southern Hemisphere, featuring thousands of performances across various genres and venues."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "SALA Festival",
|
||||||
|
"dateFrom": "2025-08-01",
|
||||||
|
"dateTo": "2025-08-31",
|
||||||
|
"description": "South Australia's largest visual arts festival, showcasing the work of local artists in exhibitions, workshops, and events."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "OzAsia Festival",
|
||||||
|
"dateFrom": "2025-09-25",
|
||||||
|
"dateTo": "2025-10-11",
|
||||||
|
"description": "A celebration of Asian arts and culture, featuring performances, exhibitions, and events from across the region."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Adelaide Film Festival",
|
||||||
|
"dateFrom": "2025-10-16",
|
||||||
|
"dateTo": "2025-10-26",
|
||||||
|
"description": "Showcasing a diverse selection of Australian and international films, including features, documentaries, and shorts."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Adelaide Writers' Week",
|
||||||
|
"dateFrom": "2025-03-01",
|
||||||
|
"dateTo": "2025-03-06",
|
||||||
|
"description": "An annual literary festival featuring talks, panel discussions, and readings by acclaimed authors and thinkers."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Wellington": [
|
||||||
|
{
|
||||||
|
"eventName": "New Zealand Festival of the Arts",
|
||||||
|
"dateFrom": "2025-02-21",
|
||||||
|
"dateTo": "2025-03-15",
|
||||||
|
"description": "The nation's largest celebration of contemporary arts and culture, featuring a diverse range of performances and exhibitions across various venues in Wellington.",
|
||||||
|
"url": "https://www.festival.nz/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Wellington Jazz Festival",
|
||||||
|
"dateFrom": "2025-06-05",
|
||||||
|
"dateTo": "2025-06-09",
|
||||||
|
"description": "A five-day festival showcasing local and international jazz musicians in concerts, workshops, and community events.",
|
||||||
|
"url": "https://www.jazzfestival.co.nz/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Wellington on a Plate",
|
||||||
|
"dateFrom": "2025-08-01",
|
||||||
|
"dateTo": "2025-08-16",
|
||||||
|
"description": "A culinary festival celebrating the city's food and beverage industry with special menus, events, and culinary experiences."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "CubaDupa",
|
||||||
|
"dateFrom": "2025-03-28",
|
||||||
|
"dateTo": "2025-03-29",
|
||||||
|
"description": "A vibrant street festival in Wellington's Cuba Street, featuring music, dance, street performers, and food stalls."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventName": "Wellington Pasifika Festival",
|
||||||
|
"dateFrom": "2025-01-18",
|
||||||
|
"dateTo": "2025-01-19",
|
||||||
|
"description": "A celebration of Pacific Island culture with traditional performances, food stalls, and arts and crafts."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
64
tools/find_events.py
Normal file
64
tools/find_events.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def find_events(args: dict) -> dict:
|
||||||
|
search_city = args.get("city", "").lower()
|
||||||
|
search_month = args.get("month", "").capitalize()
|
||||||
|
|
||||||
|
file_path = Path(__file__).resolve().parent / "data" / "find_events_data.json"
|
||||||
|
if not file_path.exists():
|
||||||
|
return {"error": "Data file not found."}
|
||||||
|
|
||||||
|
try:
|
||||||
|
month_number = datetime.strptime(search_month, "%B").month
|
||||||
|
except ValueError:
|
||||||
|
return {"error": "Invalid month provided."}
|
||||||
|
|
||||||
|
# Helper to wrap months into [1..12]
|
||||||
|
def get_adjacent_months(m):
|
||||||
|
prev_m = 12 if m == 1 else (m - 1)
|
||||||
|
next_m = 1 if m == 12 else (m + 1)
|
||||||
|
return [prev_m, m, next_m]
|
||||||
|
|
||||||
|
valid_months = get_adjacent_months(month_number)
|
||||||
|
|
||||||
|
matching_events = []
|
||||||
|
for city_name, events in json.load(open(file_path)).items():
|
||||||
|
if search_city and search_city not in city_name.lower():
|
||||||
|
continue
|
||||||
|
|
||||||
|
for event in events:
|
||||||
|
date_from = datetime.strptime(event["dateFrom"], "%Y-%m-%d")
|
||||||
|
date_to = datetime.strptime(event["dateTo"], "%Y-%m-%d")
|
||||||
|
|
||||||
|
# If the event's start or end month is in our valid months
|
||||||
|
if date_from.month in valid_months or date_to.month in valid_months:
|
||||||
|
# Add metadata explaining how it matches
|
||||||
|
if date_from.month == month_number or date_to.month == month_number:
|
||||||
|
month_context = "requested month"
|
||||||
|
elif (
|
||||||
|
date_from.month == valid_months[0]
|
||||||
|
or date_to.month == valid_months[0]
|
||||||
|
):
|
||||||
|
month_context = "previous month"
|
||||||
|
else:
|
||||||
|
month_context = "next month"
|
||||||
|
|
||||||
|
matching_events.append(
|
||||||
|
{
|
||||||
|
"city": city_name,
|
||||||
|
"eventName": event["eventName"],
|
||||||
|
"dateFrom": event["dateFrom"],
|
||||||
|
"dateTo": event["dateTo"],
|
||||||
|
"description": event["description"],
|
||||||
|
"month": month_context,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add top-level metadata if you wish
|
||||||
|
return {
|
||||||
|
"note": f"Returning events from {search_month} plus one month either side (i.e., {', '.join(datetime(2025, m, 1).strftime('%B') for m in valid_months)}).",
|
||||||
|
"events": matching_events,
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ from tools.tool_registry import (
|
|||||||
search_trains_tool,
|
search_trains_tool,
|
||||||
book_trains_tool,
|
book_trains_tool,
|
||||||
create_invoice_tool,
|
create_invoice_tool,
|
||||||
|
find_events_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
goal_match_train_invoice = AgentGoal(
|
goal_match_train_invoice = AgentGoal(
|
||||||
@@ -53,46 +54,31 @@ goal_match_train_invoice = AgentGoal(
|
|||||||
# unused
|
# unused
|
||||||
goal_event_flight_invoice = AgentGoal(
|
goal_event_flight_invoice = AgentGoal(
|
||||||
tools=[
|
tools=[
|
||||||
search_fixtures_tool,
|
find_events_tool,
|
||||||
search_flights_tool,
|
search_flights_tool,
|
||||||
search_trains_tool,
|
|
||||||
create_invoice_tool,
|
create_invoice_tool,
|
||||||
],
|
],
|
||||||
description="Help the user gather args for these tools in order: "
|
description="Help the user gather args for these tools in order: "
|
||||||
"1. SearchFixtures: Search for fixtures for a team in a given month"
|
"1. FindEvents: Find an event to travel to "
|
||||||
"2. SearchFlights: Search for a flight around the match dates"
|
"2. SearchFlights: search for a flight around the event dates "
|
||||||
"3. SearchTrains: Search for trains to visit somewhere before or after the match"
|
"3. CreateInvoice: Create a simple invoice for the cost of that flight ",
|
||||||
"4. BookTrain: Book the train tickets"
|
|
||||||
"5. CreateInvoice: Create a simple invoice for the cost of the flights and train tickets",
|
|
||||||
starter_prompt="Welcome me, give me a description of what you can do, then ask me for the details you need to do your job",
|
starter_prompt="Welcome me, give me a description of what you can do, then ask me for the details you need to do your job",
|
||||||
example_conversation_history="\n ".join(
|
example_conversation_history="\n ".join(
|
||||||
[
|
[
|
||||||
"user: I'd like to travel to a football match",
|
"user: I'd like to travel to an event",
|
||||||
"agent: Sure! Let's start by finding an match you'd like to attend. I know about Premier League fixtures in the UK. Could you tell me which team and month you're interested in?",
|
"agent: Sure! Let's start by finding an event you'd like to attend. I know about events in Australia and New Zealand cities. Could you tell me which city and month you're interested in?",
|
||||||
"user: Wolves in May please",
|
"user: sydney in may please",
|
||||||
"agent: Great! Let's find a match for Wolverhampton Wanderers FC in May.",
|
"agent: Great! Let's find an events in Sydney in May.",
|
||||||
"user_confirmed_tool_run: <user clicks confirm on SearchFixtures tool, passing the full team name as an input>",
|
"user_confirmed_tool_run: <user clicks confirm on FindEvents tool>",
|
||||||
'tool_result: results including {"homeTeam": "Wolverhampton Wanderers FC", "awayTeam": "Manchester United", "date": "2025-05-04"}',
|
"tool_result: { 'event_name': 'Vivid Sydney', 'event_date': '2023-05-01' }",
|
||||||
"agent: Found a match! There's an away game against Manchester United on May 4 2025. Would you like to search for flights around this date?",
|
"agent: Found an event! There's Vivid Sydney on May 1 2025, ending on May 14 2025. Would you like to search for flights around these dates?",
|
||||||
"user: Yes, please",
|
"user: Yes, please",
|
||||||
"agent: Let's search for flights around this date. Could you provide your departure city?",
|
"agent: Let's search for flights around these dates. Could you provide your departure city?",
|
||||||
"user: San Francisco",
|
"user: San Francisco",
|
||||||
"agent: Thanks, searching for flights from San Francisco to the Manchester around 2025-05-03 to 2025-05-05.",
|
"agent: Thanks, searching for flights from San Francisco to Sydney around 2023-02-25 to 2023-02-28.",
|
||||||
"user_confirmed_tool_run: <user clicks confirm on SearchFlights tool>"
|
"user_confirmed_tool_run: <user clicks confirm on SearchFlights tool>"
|
||||||
'tool_result: results including {"flight_number": "CX101", "return_flight_number": "CX102", "price": 850.0}',
|
'tool_result: results including {"flight_number": "CX101", "return_flight_number": "CX102", "price": 850.0}',
|
||||||
"agent: Found some flights! The cheapest is CX101 for $850. Would you like to go for this one?",
|
"agent: Found some flights! The cheapest is CX101 for $850. Would you like to generate an invoice for this flight?",
|
||||||
"user: Yes, please",
|
|
||||||
"agent: Great! So you're flying to the city the match is in, anywhere else you'd like to visit while you're there?",
|
|
||||||
"user: I'd like to visit my friend in Edinburgh after the match",
|
|
||||||
"agent: Got it! Let's search for trains from Manchester to Edinburgh and back.",
|
|
||||||
"user_confirmed_tool_run: <user clicks confirm on SearchTrains tool>",
|
|
||||||
'tool_result: results including {"train_number": "T1234", "price": 30.0}',
|
|
||||||
"agent: Found these train options! The cheapest is T1234 for $30. Would you like to go for this one?",
|
|
||||||
"user: Yes, please",
|
|
||||||
"agent: Great, lets book the train tickets",
|
|
||||||
"user_confirmed_tool_run: <user clicks confirm on BookTrain tool>",
|
|
||||||
'tool_result: results including {"status": "success"}',
|
|
||||||
"agent: Train tickets booked! Would you like me to create an invoice for the flights and train tickets?",
|
|
||||||
"user_confirmed_tool_run: <user clicks confirm on CreateInvoice tool>",
|
"user_confirmed_tool_run: <user clicks confirm on CreateInvoice tool>",
|
||||||
'tool_result: { "status": "success", "invoice": { "flight_number": "CX101", "amount": 850.0 }, invoiceURL: "https://example.com/invoice" }',
|
'tool_result: { "status": "success", "invoice": { "flight_number": "CX101", "amount": 850.0 }, invoiceURL: "https://example.com/invoice" }',
|
||||||
"agent: Invoice generated! Here's the link: https://example.com/invoice",
|
"agent: Invoice generated! Here's the link: https://example.com/invoice",
|
||||||
|
|||||||
@@ -37,10 +37,14 @@ def search_airport(query: str) -> list:
|
|||||||
try:
|
try:
|
||||||
return json.loads(data).get("data", [])
|
return json.loads(data).get("data", [])
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
|
print("Error: Failed to decode JSON response")
|
||||||
|
print(f"Response: {data.decode('utf-8')}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def search_flights(args: dict) -> dict: # _realapi
|
def search_flights_real_api(
|
||||||
|
args: dict,
|
||||||
|
) -> dict: # rename to search_flights to use the real API
|
||||||
"""
|
"""
|
||||||
1) Looks up airport/city codes via search_airport.
|
1) Looks up airport/city codes via search_airport.
|
||||||
2) Finds the first matching skyId/entityId for both origin & destination.
|
2) Finds the first matching skyId/entityId for both origin & destination.
|
||||||
@@ -169,7 +173,7 @@ def search_flights(args: dict) -> dict: # _realapi
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def search_flights_example(args: dict) -> dict:
|
def search_flights(args: dict) -> dict:
|
||||||
"""
|
"""
|
||||||
Returns example flight search results in the requested JSON format.
|
Returns example flight search results in the requested JSON format.
|
||||||
"""
|
"""
|
||||||
@@ -195,5 +199,19 @@ def search_flights_example(args: dict) -> dict:
|
|||||||
"return_flight_code": "NZ527",
|
"return_flight_code": "NZ527",
|
||||||
"return_operating_carrier": "Air New Zealand",
|
"return_operating_carrier": "Air New Zealand",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"operating_carrier": "United Airlines",
|
||||||
|
"outbound_flight_code": "UA100",
|
||||||
|
"price": 1500.00,
|
||||||
|
"return_flight_code": "UA101",
|
||||||
|
"return_operating_carrier": "United Airlines",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"operating_carrier": "Delta Airlines",
|
||||||
|
"outbound_flight_code": "DL200",
|
||||||
|
"price": 1600.00,
|
||||||
|
"return_flight_code": "DL201",
|
||||||
|
"return_operating_carrier": "Delta Airlines",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,3 +104,23 @@ search_fixtures_tool = ToolDefinition(
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
find_events_tool = ToolDefinition(
|
||||||
|
name="FindEvents",
|
||||||
|
description="Find upcoming events to travel to a given city (e.g., 'Melbourne') and a date or month. "
|
||||||
|
"It knows about events in Oceania only (e.g. major Australian and New Zealand cities). "
|
||||||
|
"It will search 1 month either side of the month provided. "
|
||||||
|
"Returns a list of events. ",
|
||||||
|
arguments=[
|
||||||
|
ToolArgument(
|
||||||
|
name="city",
|
||||||
|
type="string",
|
||||||
|
description="Which city to search for events",
|
||||||
|
),
|
||||||
|
ToolArgument(
|
||||||
|
name="month",
|
||||||
|
type="string",
|
||||||
|
description="The month to search for events (will search 1 month either side of the month provided)",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ from temporalio.exceptions import ActivityError
|
|||||||
from temporalio.common import RetryPolicy
|
from temporalio.common import RetryPolicy
|
||||||
|
|
||||||
from models.data_types import ConversationHistory, ToolPromptInput
|
from models.data_types import ConversationHistory, ToolPromptInput
|
||||||
from prompts.agent_prompt_generators import generate_missing_args_prompt, generate_tool_completion_prompt
|
from prompts.agent_prompt_generators import (
|
||||||
|
generate_missing_args_prompt,
|
||||||
|
generate_tool_completion_prompt,
|
||||||
|
)
|
||||||
from shared.config import TEMPORAL_LEGACY_TASK_QUEUE
|
from shared.config import TEMPORAL_LEGACY_TASK_QUEUE
|
||||||
|
|
||||||
# Constants from original file
|
# Constants from original file
|
||||||
@@ -14,12 +17,13 @@ TOOL_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT = timedelta(minutes=30)
|
|||||||
LLM_ACTIVITY_START_TO_CLOSE_TIMEOUT = timedelta(seconds=10)
|
LLM_ACTIVITY_START_TO_CLOSE_TIMEOUT = timedelta(seconds=10)
|
||||||
LLM_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT = timedelta(minutes=30)
|
LLM_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT = timedelta(minutes=30)
|
||||||
|
|
||||||
|
|
||||||
async def handle_tool_execution(
|
async def handle_tool_execution(
|
||||||
current_tool: str,
|
current_tool: str,
|
||||||
tool_data: Dict[str, Any],
|
tool_data: Dict[str, Any],
|
||||||
tool_results: list,
|
tool_results: list,
|
||||||
add_message_callback: callable,
|
add_message_callback: callable,
|
||||||
prompt_queue: Deque[str]
|
prompt_queue: Deque[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Execute a tool after confirmation and handle its result."""
|
"""Execute a tool after confirmation and handle its result."""
|
||||||
workflow.logger.info(f"Confirmed. Proceeding with tool: {current_tool}")
|
workflow.logger.info(f"Confirmed. Proceeding with tool: {current_tool}")
|
||||||
@@ -50,11 +54,12 @@ async def handle_tool_execution(
|
|||||||
add_message_callback("tool_result", dynamic_result)
|
add_message_callback("tool_result", dynamic_result)
|
||||||
prompt_queue.append(generate_tool_completion_prompt(current_tool, dynamic_result))
|
prompt_queue.append(generate_tool_completion_prompt(current_tool, dynamic_result))
|
||||||
|
|
||||||
|
|
||||||
async def handle_missing_args(
|
async def handle_missing_args(
|
||||||
current_tool: str,
|
current_tool: str,
|
||||||
args: Dict[str, Any],
|
args: Dict[str, Any],
|
||||||
tool_data: Dict[str, Any],
|
tool_data: Dict[str, Any],
|
||||||
prompt_queue: Deque[str]
|
prompt_queue: Deque[str],
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Check for missing arguments and handle them if found."""
|
"""Check for missing arguments and handle them if found."""
|
||||||
missing_args = [key for key, value in args.items() if value is None]
|
missing_args = [key for key, value in args.items() if value is None]
|
||||||
@@ -69,13 +74,15 @@ async def handle_missing_args(
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def format_history(conversation_history: ConversationHistory) -> str:
|
def format_history(conversation_history: ConversationHistory) -> str:
|
||||||
"""Format the conversation history into a single string."""
|
"""Format the conversation history into a single string."""
|
||||||
return " ".join(
|
return " ".join(str(msg["response"]) for msg in conversation_history["messages"])
|
||||||
str(msg["response"]) for msg in conversation_history["messages"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def prompt_with_history(conversation_history: ConversationHistory, prompt: str) -> tuple[str, str]:
|
|
||||||
|
def prompt_with_history(
|
||||||
|
conversation_history: ConversationHistory, prompt: str
|
||||||
|
) -> tuple[str, str]:
|
||||||
"""Generate a context-aware prompt with conversation history."""
|
"""Generate a context-aware prompt with conversation history."""
|
||||||
history_string = format_history(conversation_history)
|
history_string = format_history(conversation_history)
|
||||||
context_instructions = (
|
context_instructions = (
|
||||||
@@ -86,16 +93,19 @@ def prompt_with_history(conversation_history: ConversationHistory, prompt: str)
|
|||||||
)
|
)
|
||||||
return (context_instructions, prompt)
|
return (context_instructions, prompt)
|
||||||
|
|
||||||
|
|
||||||
async def continue_as_new_if_needed(
|
async def continue_as_new_if_needed(
|
||||||
conversation_history: ConversationHistory,
|
conversation_history: ConversationHistory,
|
||||||
prompt_queue: Deque[str],
|
prompt_queue: Deque[str],
|
||||||
agent_goal: Any,
|
agent_goal: Any,
|
||||||
max_turns: int,
|
max_turns: int,
|
||||||
add_message_callback: callable
|
add_message_callback: callable,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle workflow continuation if message limit is reached."""
|
"""Handle workflow continuation if message limit is reached."""
|
||||||
if len(conversation_history["messages"]) >= max_turns:
|
if len(conversation_history["messages"]) >= max_turns:
|
||||||
summary_context, summary_prompt = prompt_summary_with_history(conversation_history)
|
summary_context, summary_prompt = prompt_summary_with_history(
|
||||||
|
conversation_history
|
||||||
|
)
|
||||||
summary_input = ToolPromptInput(
|
summary_input = ToolPromptInput(
|
||||||
prompt=summary_prompt, context_instructions=summary_context
|
prompt=summary_prompt, context_instructions=summary_context
|
||||||
)
|
)
|
||||||
@@ -104,21 +114,24 @@ async def continue_as_new_if_needed(
|
|||||||
summary_input,
|
summary_input,
|
||||||
schedule_to_close_timeout=LLM_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT,
|
schedule_to_close_timeout=LLM_ACTIVITY_SCHEDULE_TO_CLOSE_TIMEOUT,
|
||||||
)
|
)
|
||||||
workflow.logger.info(
|
workflow.logger.info(f"Continuing as new after {max_turns} turns.")
|
||||||
f"Continuing as new after {max_turns} turns."
|
|
||||||
)
|
|
||||||
add_message_callback("conversation_summary", conversation_summary)
|
add_message_callback("conversation_summary", conversation_summary)
|
||||||
workflow.continue_as_new(
|
workflow.continue_as_new(
|
||||||
args=[{
|
args=[
|
||||||
"tool_params": {
|
{
|
||||||
"conversation_summary": conversation_summary,
|
"tool_params": {
|
||||||
"prompt_queue": prompt_queue,
|
"conversation_summary": conversation_summary,
|
||||||
},
|
"prompt_queue": prompt_queue,
|
||||||
"agent_goal": agent_goal,
|
},
|
||||||
}]
|
"agent_goal": agent_goal,
|
||||||
|
}
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def prompt_summary_with_history(conversation_history: ConversationHistory) -> tuple[str, str]:
|
|
||||||
|
def prompt_summary_with_history(
|
||||||
|
conversation_history: ConversationHistory,
|
||||||
|
) -> tuple[str, str]:
|
||||||
"""Generate a prompt for summarizing the conversation.
|
"""Generate a prompt for summarizing the conversation.
|
||||||
Used only for continue as new of the workflow."""
|
Used only for continue as new of the workflow."""
|
||||||
history_string = format_history(conversation_history)
|
history_string = format_history(conversation_history)
|
||||||
@@ -127,4 +140,4 @@ def prompt_summary_with_history(conversation_history: ConversationHistory) -> tu
|
|||||||
"Please produce a two sentence summary of this conversation. "
|
"Please produce a two sentence summary of this conversation. "
|
||||||
'Put the summary in the format { "summary": "<plain text>" }'
|
'Put the summary in the format { "summary": "<plain text>" }'
|
||||||
)
|
)
|
||||||
return (context_instructions, actual_prompt)
|
return (context_instructions, actual_prompt)
|
||||||
|
|||||||
Reference in New Issue
Block a user