Compare commits

...

1 Commits

Author SHA1 Message Date
Yuan Teoh
e347fab270 chore: add samples for multi agent blog 2025-08-25 16:07:43 -07:00
5 changed files with 669 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
# Multi-Agent Cymbal Travel Agency
> [!NOTE]
> This demo is NOT actively maintained.
## Introduction
This demo showcases a simplified travel agency called Cymbal Travel Agency. Check out our medium posting for more information.
Cymbal Travel Agency demonstrates how three agents can collaborate to research and book flights and hotels:
* **Customer Service Agent:** This agent is the primary interface for the customer. It receives travel requests, clarifies details, and relays informations to other agents.
* **Flight Agent:** This is a specialized agent that helps queries flight databases, list users' flight tickets and handles flight booking details.
* **Hotel Agent:** This is a specialized agent that helps searches for hotel accommodations, list users' hotel bookings, and manages hotel reservations.
![cymbal travel agency architecture](./architecture.png)
## Quickstart Demo
### Set up database
For this demo, I used an existing dataset from the [GenAI Databases Retrieval App v0.4.0](https://github.com/GoogleCloudPlatform/genai-databases-retrieval-app/tree/v0.4.0/data). For simpler set up, please follow the [README](https://github.com/GoogleCloudPlatform/genai-databases-retrieval-app/blob/v0.4.0/README.md) instructions and sets up via [run_database_init.py](https://github.com/GoogleCloudPlatform/genai-databases-retrieval-app/blob/v0.4.0/retrieval_service/run_database_init.py).
After parsing the dataset to your database, please ensure that your database consist of the following tables:
* airports
* amenities
* flights
* policies
* tickets
Next, we will generate some data for hotels:
<details>
<summary>SQL to create and insert hotels-related tables</summary>
```
CREATE TABLE hotels (
name VARCHAR(255) NOT NULL,
rating NUMERIC(2,1) NOT NULL,
price INTEGER,
city VARCHAR(255) NOT NULL
);
INSERT INTO hotels (name, rating, price, city) VALUES
('Rocky Mountain Retreat', 4, 285, 'Estes Park'),
('The Mile High Inn', 3, 210, 'Denver'),
('Aspen Creek Lodge', 5, 495, 'Aspen'),
('Breckenridge Vista', 4, 320, 'Breckenridge'),
('Garden of the Gods Resort', 5, 450, 'Colorado Springs'),
('Boulder Creek Hotel', 4, 250, 'Boulder'),
('The Vail Chalet', 5, 580, 'Vail'),
('Durango Junction Inn', 3, 185, 'Durango'),
('Union Station Hotel', 4, 350, 'Denver'),
('Telluride Mountain Suites', 5, 510, 'Telluride'),
('The Winter Park Lodge', 4, 290, 'Winter Park'),
('Steamboat Hot Springs', 3, 205, 'Steamboat Springs'),
('The Maroon Bells Inn', 4, 380, 'Aspen'),
('Crested Butte Getaway', 3, 240, 'Crested Butte'),
('The Denver Skyline', 4, 305, 'Denver'),
('Pikes Peak Inn', 3, 195, 'Colorado Springs'),
('Silverthorne Peaks Hotel', 4, 270, 'Silverthorne'),
('The Palisade Retreat', 5, 410, 'Palisade'),
('Grand Lake Lodge', 3, 230, 'Grand Lake'),
('Snowmass Village Resort', 5, 530, 'Snowmass Village'),
('The Copper Mountain Inn', 4, 315, 'Copper Mountain'),
('Keystone Lakeside Lodge', 3, 225, 'Keystone'),
('Arapahoe Basin Chalet', 4, 280, 'Dillon'),
('The Monarch Pass Lodge', 3, 190, 'Monarch'),
('Purgatory Pines Hotel', 4, 265, 'Durango'),
('The Aspen Peak Suites', 5, 520, 'Aspen'),
('Vail Village Hotel', 5, 610, 'Vail'),
('Steamboat River Inn', 4, 300, 'Steamboat Springs'),
('Telluride Grand Resort', 5, 550, 'Telluride'),
('Crested Butte Mountain Lodge', 4, 275, 'Crested Butte'),
('The Central Park Grand', 5, 650, 'Manhattan'),
('Brooklyn Bridge View', 4, 310, 'Brooklyn'),
('The Greenwich Village Inn', 3, 205, 'Manhattan'),
('Times Square Lights', 4, 380, 'Manhattan'),
('The Chelsea Art House', 4, 290, 'Manhattan'),
('Hotel Wall Street', 3, 230, 'Manhattan'),
('Queensboro River Hotel', 3, 185, 'Queens'),
('The NoMad Boutique', 5, 520, 'Manhattan'),
('The Harlem Jazz', 4, 240, 'Manhattan'),
('Staten Island Ferry Hotel', 3, 160, 'Staten Island'),
('The Upper East Side Manor', 5, 710, 'Manhattan'),
('The Broadway Performer', 4, 350, 'Manhattan'),
('The Plaza Tower', 5, 800, 'Manhattan'),
('Long Island City Loft', 3, 195, 'Queens'),
('The Battery Park Stay', 4, 270, 'Manhattan'),
('The SoHo Gallery', 5, 480, 'Manhattan'),
('The Bronx Botanical', 3, 170, 'The Bronx'),
('The Hudson Yards View', 4, 305, 'Manhattan'),
('The West Village Hideaway', 4, 330, 'Manhattan'),
('The Midtown Oasis', 5, 620, 'Manhattan');
CREATE TABLE
"bookings" ( "user_id" TEXT,
"user_name" TEXT,
"user_email" TEXT,
"hotel_name" TEXT,
"hotel_city" TEXT,
"hotel_rating" FLOAT,
"hotel_total_price" FLOAT,
"check_in_date" TEXT,
"number_of_nights" INTEGER );
```
</details>
### Set up Toolbox
This demo utilizes [MCP Toolbox for Databases][https://github.com/googleapis/genai-toolbox]. Please follow the installation guidelines and install Toolbox locally.
Update the `tools.yaml` file with your database source information. For the simplicity of this demo, we did not utilize any Auth Services, hence, user-informations are all parsed automatically in `tools` with a `default` field.
Run Toolbox:
```
./toolbox
```
### Set up Cymbal Travel Agency application
1. [Install python][install-python] and set up a python [virtual environment][venv].
1. Install requirements:
```bash
pip install -r requirements.txt
```
1. Run the application:
```bash
python app.py
```
[install-python]: https://cloud.google.com/python/docs/setup#installing_python
[venv]: https://cloud.google.com/python/docs/setup#installing_and_using_virtualenv

129
samples/multiagent/app.py Normal file
View File

@@ -0,0 +1,129 @@
import uuid
import os
import asyncio
from toolbox_core import auth_methods
from toolbox_llamaindex import ToolboxClient
from llama_index.core.workflow import Context
from llama_index.core.agent.workflow import AgentWorkflow, FunctionAgent
from llama_index.llms.google_genai import GoogleGenAI
TOOLBOX_URL = "http://127.0.0.1:5000"
CS_PROMPT = """
You are a friendly and professional customer service agent for Cymbal Travel Agency, a travel booking service.
Your primary responsibilities are to:
1. **Welcome users** and greet them warmly.
2. **Answer general knowledge questions** related to travel (e.g. questions about airports or specific airport such as SFO). If you are unsure of a specific information or do not have tool to access it, you should let the user know.
3. **Manage conversational flow** and ask clarifying questions to understand the user's intent.
**Specialized Agent:**
- **flight agent**: If a user asks a question specifically about **searching, listing and booking a flight**.
- **hotel agent**: If a user asks a question specifically about **searching, listing and booking a hotel**.
**You can call the specialized agents** to help you with certain tasks. You can do this however many times you want until you have all the informations needed.
**If you already have the informamtion you need, feel free to response directly to the user instead of calling another agent. If you're unsure, please check with the other agents.**
"""
HOTEL_PROMPT = """
You are the dedicated hotel specialist. Your expertise is dedicated exclusively to hotel and accommodation services. The customer service agent will reach out to you regarding to question around your specialties.
Your primary responsibilities are to:
1. **Search for hotels** based on specific criteria (hotel name, city, rating, price range).
2. **Book or reserve hotel rooms**.
3. **List bookings** that are under a specific name.
You **must** focus solely on hotel and accommodation-related tasks. Do not answer questions about flights, rental cars, activities, or general travel knowledge.
**Your communication style should be helpful and detailed, providing rich information to help the customer service agent choose the best accommodation.**
"""
FLIGHT_PROMPT = """
You are the dedicated flight specialist. Your expertise is dedicated exclusively to flights. The customer service agent will reach out to you regarding to questions around your specialties.
Your primary responsibilities are to:
1. **Search for flights** based on user criteria (origin, destination, dates).
2. **Book or reserve flight tickets** on behalf of the user.
3. **Provide detailed information about flights** (e.g., flight numbers, departure/arrival times, layovers, airline, and fare rules).
4. **List flight tickets** that are under user's name.
5. **Answer questions on Cymbal Air Flight's policy**.
You **must** focus solely on flight-related tasks. Do not answer questions about hotels, rental cars, activities, or general travel knowledge.
**Your communication style should be efficient and informative, directly addressing the customer service agent's flight-related questions.**
"""
async def run_app():
# load model
llm = GoogleGenAI(
model="gemini-2.5-flash",
vertexai_config={"project": "project-id", "location": "us-central1"},
)
# Alternatively, you can also load the gemini model using google api key
# llm = GoogleGenAI(
# model="gemini-2.5-flash",
# api_key=os.getenv("GOOGLE_API_KEY"),
# )
# load tools from Toolbox
general_tools, hotel_tools, flight_tools = await get_tools()
# build agents
customer_service_agent = FunctionAgent(
name="CustomerServiceAgent",
description="Answer user's queries and route to the right agents for flight and hotel related queries",
system_prompt=CS_PROMPT,
llm=llm,
tools=general_tools,
)
hotel_agent = FunctionAgent(
name="HotelAgent",
description="Handles hotel and accommodation services, including searching, booking and list bookings of hotels",
system_prompt=HOTEL_PROMPT,
llm=llm,
tools=hotel_tools,
)
flight_agent = FunctionAgent(
name="FlightAgent",
description="Handles flights-related services, including searching, booking and list tickets of flights",
system_prompt=FLIGHT_PROMPT,
llm=llm,
tools=flight_tools,
)
# set up agent workflow
agent_workflow = AgentWorkflow(
agents = [customer_service_agent, hotel_agent, flight_agent],
root_agent=customer_service_agent.name,
initial_state={},
)
# use Context to maintain state between runs
ctx = Context(agent_workflow)
# start application
print("\nCymbal Travel Agency: What question do you have?")
while True:
user_input = input("\nUser: ")
resp = await agent_workflow.run(user_msg=user_input, ctx=ctx)
print("\nCymbal Travel Agency:", resp)
async def get_tools():
"""
This function grab tools from Toolbox.
"""
auth_token_provider = auth_methods.aget_google_id_token(TOOLBOX_URL)
client = ToolboxClient(TOOLBOX_URL, client_headers={"Authorization": auth_token_provider})
general_tools = await client.aload_toolset("general_tools")
hotel_tools = await client.aload_toolset("hotel_tools")
flight_tools = await client.aload_toolset("flight_tools")
return (general_tools, hotel_tools, flight_tools)
if __name__ == "__main__":
asyncio.run(run_app())

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -0,0 +1,92 @@
aiohappyeyeballs==2.6.1
aiohttp==3.12.15
aiosignal==1.4.0
aiosqlite==0.21.0
annotated-types==0.7.0
anyio==4.10.0
attrs==25.3.0
banks==2.2.0
beautifulsoup4==4.13.5
cachetools==5.5.2
certifi==2025.8.3
charset-normalizer==3.4.3
click==8.2.1
colorama==0.4.6
dataclasses-json==0.6.7
defusedxml==0.7.1
Deprecated==1.2.18
dirtyjson==1.0.8
distro==1.9.0
filetype==1.2.0
frozenlist==1.7.0
fsspec==2025.7.0
google-auth==2.40.3
google-genai==1.31.0
greenlet==3.2.4
griffe==1.12.1
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
idna==3.10
Jinja2==3.1.6
jiter==0.10.0
joblib==1.5.1
llama-cloud==0.1.35
llama-cloud-services==0.6.54
llama-index==0.13.3
llama-index-cli==0.5.0
llama-index-core==0.13.3
llama-index-embeddings-openai==0.5.0
llama-index-indices-managed-llama-cloud==0.9.2
llama-index-instrumentation==0.4.0
llama-index-llms-google-genai==0.3.0
llama-index-llms-openai==0.5.4
llama-index-readers-file==0.5.2
llama-index-readers-llama-parse==0.5.0
llama-index-workflows==1.3.0
llama-parse==0.6.54
MarkupSafe==3.0.2
marshmallow==3.26.1
multidict==6.6.4
mypy_extensions==1.1.0
nest-asyncio==1.6.0
networkx==3.5
nltk==3.9.1
numpy==2.3.2
openai==1.101.0
packaging==25.0
pandas==2.2.3
pillow==11.3.0
platformdirs==4.3.8
propcache==0.3.2
pyasn1==0.6.1
pyasn1_modules==0.4.2
pydantic==2.11.7
pydantic_core==2.33.2
pypdf==6.0.0
python-dateutil==2.9.0.post0
python-dotenv==1.1.1
pytz==2025.2
PyYAML==6.0.2
regex==2025.7.34
requests==2.32.5
rsa==4.9.1
setuptools==80.9.0
six==1.17.0
sniffio==1.3.1
soupsieve==2.7
SQLAlchemy==2.0.43
striprtf==0.0.26
tenacity==9.1.2
tiktoken==0.11.0
toolbox-core==0.5.0
toolbox-llamaindex==0.5.0
tqdm==4.67.1
typing-inspect==0.9.0
typing-inspection==0.4.1
typing_extensions==4.15.0
tzdata==2025.2
urllib3==2.5.0
websockets==15.0.1
wrapt==1.17.3
yarl==1.20.1

View File

@@ -0,0 +1,307 @@
sources:
my-source:
kind: alloydb-postgres
project: my-project
region: my-region
cluster: my-cluster
instance: my-instance
database: my-db
user: my-user
password: my-pass
tools:
search_airports:
kind: postgres-sql
source: my-source
description: |
Use this tool to list all airports matching search criteria.
Takes at least one of country, city, name, or all and returns all matching airports.
The agent can decide to return the results directly to the user.
parameters:
- name: country
type: string
description: Country
default: ""
- name: city
type: string
description: City
default: ""
- name: name
type: string
description: Airport name
default: ""
statement: |
SELECT * FROM airports
WHERE (CAST($1 AS TEXT) = '' OR country ILIKE $1)
AND (CAST($2 AS TEXT) = '' OR city ILIKE $2)
AND (CAST($3 AS TEXT) = '' OR name ILIKE '%' || $3 || '%')
LIMIT 10
list_flights:
kind: postgres-sql
source: my-source
description: |
Use this tool to list flights information matching search criteria.
Takes an arrival airport, a departure airport, or both, filters by date and returns all matching flights.
If 3-letter iata code is not provided for departure_airport or arrival_airport, use `search_airports` tool to get iata code information.
Do NOT guess a date, ask user for date input if it is not given. Date must be in the following format: YYYY-MM-DD.
The agent can decide to return the results directly to the user.
parameters:
- name: departure_airport
type: string
description: Departure airport 3-letter code
default: ""
- name: arrival_airport
type: string
description: Arrival airport 3-letter code
default: ""
- name: date
type: string
description: Date of flight departure
statement: |
SELECT * FROM flights
WHERE (CAST($1 AS TEXT) = '' OR departure_airport ILIKE $1)
AND (CAST($2 AS TEXT) = '' OR arrival_airport ILIKE $2)
AND departure_time >= CAST($3 AS timestamp)
AND departure_time < CAST($3 AS timestamp) + interval '1 day'
LIMIT 10
search_flights_by_number:
kind: postgres-sql
source: my-source
description: |
Use this tool to get information for a specific flight.
Takes an airline code and flight number and returns info on the flight.
Do NOT use this tool with a flight id. Do NOT guess an airline code or flight number.
A airline code is a code for an airline service consisting of two-character
airline designator and followed by flight number, which is 1 to 4 digit number.
For example, if given CY 0123, the airline is "CY", and flight_number is "123".
Another example for this is DL 1234, the airline is "DL", and flight_number is "1234".
If the tool returns more than one option choose the date closest to the current date.
parameters:
- name: airline
type: string
description: Airline unique 2 letter identifier
- name: flight_number
type: string
description: 1 to 4 digit number
statement: |
SELECT * FROM flights
WHERE airline = $1
AND flight_number = $2
LIMIT 10
search_amenities:
kind: postgres-sql
source: my-source
description: |
Use this tool to search amenities by name or to recommended airport amenities at SFO.
If user provides flight info, use 'search_flights_by_number' tool
first to get gate info and location.
Only recommend amenities that are returned by this query.
Find amenities close to the user by matching the terminal and then comparing
the gate numbers. Gate number iterate by letter and number, example A1 A2 A3
B1 B2 B3 C1 C2 C3. Gate A3 is close to A2 and B1.
parameters:
- name: query
type: string
description: Search query
statement: |
SELECT name, description, location, terminal, category, hour
FROM amenities
WHERE (embedding <=> embedding('gemini-embedding-001', $1)::vector) < 0.5
ORDER BY (embedding <=> embedding('gemini-embedding-001', $1)::vector)
LIMIT 5
search_policies:
kind: postgres-sql
source: my-source
description: |
Use this tool to search for cymbal air passenger policy.
Policy that are listed is unchangeable.
You will not answer any questions outside of the policy given.
Policy includes information on ticket purchase and changes, baggage, check-in and boarding, special assistance, overbooking, flight delays and cancellations.
parameters:
- name: query
type: string
description: Search query
statement: |
SELECT content
FROM policies
WHERE (embedding <=> embedding('gemini-embedding-001', $1)::vector) < 0.5
ORDER BY (embedding <=> embedding('gemini-embedding-001', $1)::vector)
LIMIT 5
insert_ticket:
kind: postgres-sql
source: my-source
description: |
Use this tool to book a flight ticket for the user. Use the default value for user_id, user_name, and user_email.
parameters:
- name: user_id
type: string
description: User ID of the logged in user.
default: "8888"
- name: user_name
type: string
description: Name of the logged in user.
default: "test user"
- name: user_email
type: string
description: Email ID of the logged in user.
default: "test_user@example.com"
- name: airline
type: string
description: Airline unique 2 letter identifier
- name: flight_number
type: string
description: 1 to 4 digit number
- name: departure_airport
type: string
description: Departure airport 3-letter code
- name: departure_time
type: string
description: Flight departure datetime
- name: arrival_airport
type: string
description: Arrival airport 3-letter code
- name: arrival_time
type: string
description: Flight arrival datetime
statement: |
INSERT INTO tickets (
user_id,
user_name,
user_email,
airline,
flight_number,
departure_airport,
departure_time,
arrival_airport,
arrival_time
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);
list_tickets:
kind: postgres-sql
source: my-source
description: |
Use this tool to list a user's flight tickets. Use the default value for user_id.
parameters:
- name: user_id
type: string
description: User ID of the logged in user.
default: "8888"
statement: |
SELECT user_name, airline, flight_number, departure_airport, arrival_airport, departure_time, arrival_time FROM tickets
WHERE user_id = $1
list_hotels:
kind: postgres-sql
source: my-source
description: |
Use this tool to list all hotels matching search criteria.
Takes at least one of name, minimum rating, maximum rating, minimum price, maximum price, or city and returns all matching hotels.
The agent can decide to return the results directly to the user.
parameters:
- name: name
type: string
description: name of the hotel
default: ""
- name: minimum_rating
type: float
description: minimum rating of the hotel.
default: 0
- name: maximum_rating
type: float
description: maximum rating of the hotel.
default: 5
- name: minimum_price
type: float
description: minimum price of the hotel.
default: 0
- name: maximum_price
type: float
description: maximum price of the hotel.
default: 1000
- name: city
type: string
description: city of the hotel.
default: ""
statement: |
SELECT * FROM hotels
WHERE (CAST($1 AS TEXT) = '' OR name ILIKE '%' || $1)
AND rating >= $2 OR rating <= $3
AND price >= $4 OR price <= $5
AND (CAST($6 AS TEXT) = '' OR city ILIKE '%' || $6 || '%')
LIMIT 10;
book_hotel:
kind: postgres-sql
source: my-source
description: |
Use this tool to book a hotel for the user. Use the default value for user_id, user_name, and user_email.
parameters:
- name: user_id
type: string
description: User ID of the logged in user.
default: "8888"
- name: user_name
type: string
description: Name of the logged in user.
default: "test user"
- name: user_email
type: string
description: Email ID of the logged in user.
default: "test_user@example.com"
- name: hotel_name
type: string
description: name of the hotel.
- name: hotel_city
type: string
description: city of the hotel.
- name: hotel_rating
type: float
description: rating of the hotel.
- name: hotel_total_price
type: float
description: price of the hotel booking. If user book for two nights, the price will be hotel price * 2.
- name: check_in_date
type: string
description: hotel check in date of the user.
- name: nights
type: integer
description: number of nights that user is staying.
statement: |
INSERT INTO bookings (
user_id,
user_name,
user_email,
hotel_name,
hotel_city,
hotel_rating,
hotel_total_price,
check_in_date,
number_of_nights
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);
list_bookings:
kind: postgres-sql
source: my-source
description: Use this tool to list a user's hotel bookings. Use the default value for user_id.
parameters:
- name: user_id
type: string
description: user id of the logged in user.
default: "8888"
statement: |
SELECT user_name, hotel_name, hotel_city, hotel_rating, hotel_total_price, check_in_date, number_of_nights FROM bookings
WHERE user_id = $1;
toolsets:
general_tools:
- search_airports
- search_amenities
hotel_tools:
- list_hotels
- book_hotel
- list_bookings
flight_tools:
- list_flights
- search_policies
- search_flights_by_number
- insert_ticket
- list_tickets