/* * Copyright 2025 CloudWeGo Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package main import ( "context" "fmt" "hash/fnv" "github.com/cloudwego/eino/components/tool" "github.com/cloudwego/eino/components/tool/utils" tool2 "github.com/cloudwego/eino-examples/adk/common/tool" ) type WeatherRequest struct { City string `json:"city" jsonschema_description:"City name to get weather for"` Date string `json:"date" jsonschema_description:"Date in YYYY-MM-DD format"` } type WeatherResponse struct { City string `json:"city"` Temperature int `json:"temperature"` Condition string `json:"condition"` Date string `json:"date"` } type FlightBookingRequest struct { From string `json:"from" jsonschema_description:"Departure city"` To string `json:"to" jsonschema_description:"Destination city"` Date string `json:"date" jsonschema_description:"Departure date in YYYY-MM-DD format"` Passengers int `json:"passengers" jsonschema_description:"Number of passengers"` PreferredTime string `json:"preferred_time" jsonschema_description:"Preferred departure time (morning/afternoon/evening)"` } type FlightBookingResponse struct { BookingID string `json:"booking_id"` Airline string `json:"airline"` FlightNo string `json:"flight_no"` From string `json:"from"` To string `json:"to"` Date string `json:"date"` DepartureTime string `json:"departure_time"` ArrivalTime string `json:"arrival_time"` Price int `json:"price"` Status string `json:"status"` } type HotelBookingRequest struct { City string `json:"city" jsonschema_description:"City to book hotel in"` CheckIn string `json:"check_in" jsonschema_description:"Check-in date in YYYY-MM-DD format"` CheckOut string `json:"check_out" jsonschema_description:"Check-out date in YYYY-MM-DD format"` Guests int `json:"guests" jsonschema_description:"Number of guests"` RoomType string `json:"room_type" jsonschema_description:"Room type preference (standard/deluxe/suite)"` } type HotelBookingResponse struct { BookingID string `json:"booking_id"` HotelName string `json:"hotel_name"` City string `json:"city"` CheckIn string `json:"check_in"` CheckOut string `json:"check_out"` RoomType string `json:"room_type"` PricePerNight int `json:"price_per_night"` TotalPrice int `json:"total_price"` Amenities []string `json:"amenities"` Status string `json:"status"` } type AttractionRequest struct { City string `json:"city" jsonschema_description:"City to search attractions in"` Category string `json:"category" jsonschema_description:"Category of attractions (museum, park, landmark, etc.)"` } type AttractionResponse struct { Attractions []Attraction `json:"attractions"` } type Attraction struct { Name string `json:"name"` Description string `json:"description"` Rating float64 `json:"rating"` OpenHours string `json:"open_hours"` TicketPrice int `json:"ticket_price"` Category string `json:"category"` } func NewWeatherTool(ctx context.Context) (tool.BaseTool, error) { return utils.InferTool("get_weather", "Get weather information for a specific city and date", func(ctx context.Context, req *WeatherRequest) (*WeatherResponse, error) { weathers := map[string]WeatherResponse{ "Tokyo": {City: "Tokyo", Temperature: 22, Condition: "Partly Cloudy", Date: req.Date}, "Beijing": {City: "Beijing", Temperature: 18, Condition: "Sunny", Date: req.Date}, "Paris": {City: "Paris", Temperature: 15, Condition: "Rainy", Date: req.Date}, "New York": {City: "New York", Temperature: 12, Condition: "Cloudy", Date: req.Date}, } if weather, exists := weathers[req.City]; exists { return &weather, nil } conditions := []string{"Sunny", "Cloudy", "Rainy", "Partly Cloudy"} hashInput := req.City + req.Date return &WeatherResponse{ City: req.City, Temperature: consistentHashing(hashInput+"temp", 10, 30), Condition: conditions[consistentHashing(hashInput+"cond", 0, len(conditions)-1)], Date: req.Date, }, nil }) } func NewFlightBookingTool(ctx context.Context) (tool.BaseTool, error) { baseTool, err := utils.InferTool("book_flight", "Book a flight between cities. This requires user review before confirmation.", func(ctx context.Context, req *FlightBookingRequest) (*FlightBookingResponse, error) { airlines := []string{"Japan Airlines", "ANA", "United Airlines", "Delta", "Air China"} hashInput := req.From + req.To + req.Date airlineIdx := consistentHashing(hashInput+"airline", 0, len(airlines)-1) depHour := consistentHashing(hashInput+"dephour", 6, 20) depMin := consistentHashing(hashInput+"depmin", 0, 59) duration := consistentHashing(hashInput+"duration", 2, 14) arrHour := (depHour + duration) % 24 arrMin := depMin return &FlightBookingResponse{ BookingID: fmt.Sprintf("FL-%s-%d", req.Date, consistentHashing(hashInput+"id", 10000, 99999)), Airline: airlines[airlineIdx], FlightNo: fmt.Sprintf("%s%d", airlines[airlineIdx][:2], consistentHashing(hashInput+"flightno", 100, 999)), From: req.From, To: req.To, Date: req.Date, DepartureTime: fmt.Sprintf("%02d:%02d", depHour, depMin), ArrivalTime: fmt.Sprintf("%02d:%02d", arrHour, arrMin), Price: consistentHashing(hashInput+"price", 300, 1500) * req.Passengers, Status: "confirmed", }, nil }) if err != nil { return nil, err } return &tool2.InvokableReviewEditTool{InvokableTool: baseTool}, nil } func NewHotelBookingTool(ctx context.Context) (tool.BaseTool, error) { baseTool, err := utils.InferTool("book_hotel", "Book a hotel in a city. This requires user review before confirmation.", func(ctx context.Context, req *HotelBookingRequest) (*HotelBookingResponse, error) { hotelNames := []string{"Grand Hyatt", "Marriott", "Hilton", "Sheraton", "Ritz-Carlton"} amenitiesList := [][]string{ {"WiFi", "Pool", "Gym", "Spa", "Restaurant"}, {"WiFi", "Breakfast", "Parking", "Business Center"}, {"WiFi", "Pool", "Restaurant", "Bar", "Concierge"}, } hashInput := req.City + req.CheckIn + req.CheckOut hotelIdx := consistentHashing(hashInput+"hotel", 0, len(hotelNames)-1) amenitiesIdx := consistentHashing(hashInput+"amenities", 0, len(amenitiesList)-1) pricePerNight := consistentHashing(hashInput+"price", 100, 400) switch req.RoomType { case "deluxe": pricePerNight = int(float64(pricePerNight) * 1.5) case "suite": pricePerNight = pricePerNight * 2 } nights := 3 return &HotelBookingResponse{ BookingID: fmt.Sprintf("HT-%s-%d", req.CheckIn, consistentHashing(hashInput+"id", 10000, 99999)), HotelName: fmt.Sprintf("%s %s", req.City, hotelNames[hotelIdx]), City: req.City, CheckIn: req.CheckIn, CheckOut: req.CheckOut, RoomType: req.RoomType, PricePerNight: pricePerNight, TotalPrice: pricePerNight * nights, Amenities: amenitiesList[amenitiesIdx], Status: "confirmed", }, nil }) if err != nil { return nil, err } return &tool2.InvokableReviewEditTool{InvokableTool: baseTool}, nil } func NewAttractionSearchTool(ctx context.Context) (tool.BaseTool, error) { return utils.InferTool("search_attractions", "Search for tourist attractions in a city", func(ctx context.Context, req *AttractionRequest) (*AttractionResponse, error) { attractionsByCity := map[string][]Attraction{ "Tokyo": { {Name: "Senso-ji Temple", Description: "Ancient Buddhist temple in Asakusa", Rating: 4.6, OpenHours: "6:00-17:00", TicketPrice: 0, Category: "landmark"}, {Name: "Tokyo Skytree", Description: "Tallest tower in Japan with observation decks", Rating: 4.5, OpenHours: "10:00-21:00", TicketPrice: 2100, Category: "landmark"}, {Name: "Meiji Shrine", Description: "Shinto shrine dedicated to Emperor Meiji", Rating: 4.7, OpenHours: "5:00-18:00", TicketPrice: 0, Category: "landmark"}, {Name: "Ueno Park", Description: "Large public park with museums and zoo", Rating: 4.4, OpenHours: "5:00-23:00", TicketPrice: 0, Category: "park"}, }, "Beijing": { {Name: "Forbidden City", Description: "Ancient imperial palace complex", Rating: 4.8, OpenHours: "8:30-17:00", TicketPrice: 60, Category: "historic site"}, {Name: "Great Wall", Description: "Historic fortification stretching thousands of miles", Rating: 4.9, OpenHours: "6:00-18:00", TicketPrice: 45, Category: "landmark"}, {Name: "Temple of Heaven", Description: "Imperial sacrificial altar", Rating: 4.6, OpenHours: "6:00-22:00", TicketPrice: 35, Category: "park"}, }, } if attractions, exists := attractionsByCity[req.City]; exists { if req.Category != "" { var filtered []Attraction for _, a := range attractions { if a.Category == req.Category { filtered = append(filtered, a) } } return &AttractionResponse{Attractions: filtered}, nil } return &AttractionResponse{Attractions: attractions}, nil } return &AttractionResponse{ Attractions: []Attraction{ {Name: fmt.Sprintf("%s Central Park", req.City), Description: "Popular city park", Rating: 4.3, OpenHours: "6:00-22:00", TicketPrice: 0, Category: "park"}, {Name: fmt.Sprintf("%s National Museum", req.City), Description: "Major museum with local history", Rating: 4.5, OpenHours: "9:00-17:00", TicketPrice: 15, Category: "museum"}, }, }, nil }) } func GetAllTravelTools(ctx context.Context) ([]tool.BaseTool, error) { weatherTool, err := NewWeatherTool(ctx) if err != nil { return nil, err } flightTool, err := NewFlightBookingTool(ctx) if err != nil { return nil, err } hotelTool, err := NewHotelBookingTool(ctx) if err != nil { return nil, err } attractionTool, err := NewAttractionSearchTool(ctx) if err != nil { return nil, err } return []tool.BaseTool{weatherTool, flightTool, hotelTool, attractionTool}, nil } func consistentHashing(s string, min, max int) int { h := fnv.New32a() h.Write([]byte(s)) hash := h.Sum32() return min + int(hash)%(max-min+1) }