Skip to content

Commit 5037f68

Browse files
Fix: Google Maps Toolkit Incompatible Parameters (#919)
Co-authored-by: Wendong-Fan <133094783+Wendong-Fan@users.noreply.github.com>
1 parent 0d047e5 commit 5037f68

File tree

3 files changed

+132
-238
lines changed

3 files changed

+132
-238
lines changed

camel/toolkits/google_maps_toolkit.py

Lines changed: 99 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
1414
import os
1515
from functools import wraps
16-
from typing import Any, Callable, List, Optional, Tuple, Union
16+
from typing import Any, Callable, List, Optional, Union
1717

1818
from camel.toolkits.base import BaseToolkit
1919
from camel.toolkits.openai_function import OpenAIFunction
20+
from camel.utils import dependencies_required
2021

2122

2223
def handle_googlemaps_exceptions(
@@ -74,76 +75,65 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
7475
return wrapper
7576

7677

78+
def _format_offset_to_natural_language(offset: int) -> str:
79+
r"""Converts a time offset in seconds to a more natural language
80+
description using hours as the unit, with decimal places to represent
81+
minutes and seconds.
82+
83+
Args:
84+
offset (int): The time offset in seconds. Can be positive,
85+
negative, or zero.
86+
87+
Returns:
88+
str: A string representing the offset in hours, such as
89+
"+2.50 hours" or "-3.75 hours".
90+
"""
91+
# Convert the offset to hours as a float
92+
hours = offset / 3600.0
93+
hours_str = f"{hours:+.2f} hour{'s' if abs(hours) != 1 else ''}"
94+
return hours_str
95+
96+
7797
class GoogleMapsToolkit(BaseToolkit):
7898
r"""A class representing a toolkit for interacting with GoogleMaps API.
79-
8099
This class provides methods for validating addresses, retrieving elevation,
81100
and fetching timezone information using the Google Maps API.
82101
"""
83102

84-
def _import_googlemaps_or_raise(self) -> Any:
85-
r"""Attempts to import the `googlemaps` library and returns it.
103+
@dependencies_required('googlemaps')
104+
def __init__(self) -> None:
105+
import googlemaps
86106

87-
Returns:
88-
module: The `googlemaps` module if successfully imported.
89-
90-
Raises:
91-
ImportError: If the `googlemaps` library is not installed, this
92-
error is raised with a message instructing how to install the
93-
library using pip.
94-
"""
95-
try:
96-
import googlemaps
97-
98-
return googlemaps
99-
except ImportError:
100-
raise ImportError(
101-
"Please install `googlemaps` first. You can install "
102-
"it by running `pip install googlemaps`."
103-
)
104-
105-
def _get_googlemaps_api_key(self) -> str:
106-
r"""Retrieve the Google Maps API key from environment variables.
107-
108-
Returns:
109-
str: The Google Maps API key.
110-
111-
Raises:
112-
ValueError: If the API key is not found in the environment
113-
variables.
114-
"""
115-
# Get `GOOGLEMAPS_API_KEY` here:
116-
# https://console.cloud.google.com/apis/credentials
117-
GOOGLEMAPS_API_KEY = os.environ.get('GOOGLEMAPS_API_KEY')
118-
if not GOOGLEMAPS_API_KEY:
107+
api_key = os.environ.get('GOOGLE_API_KEY')
108+
if not api_key:
119109
raise ValueError(
120-
"`GOOGLEMAPS_API_KEY` not found in environment "
121-
"variables. `GOOGLEMAPS_API_KEY` API keys are "
122-
"generated in the `Credentials` page of the "
123-
"`APIs & Services` tab of "
110+
"`GOOGLE_API_KEY` not found in environment variables. "
111+
"`GOOGLE_API_KEY` API keys are generated in the `Credentials` "
112+
"page of the `APIs & Services` tab of "
124113
"https://console.cloud.google.com/apis/credentials."
125114
)
126-
return GOOGLEMAPS_API_KEY
127115

116+
self.gmaps = googlemaps.Client(key=api_key)
117+
118+
@handle_googlemaps_exceptions
128119
def get_address_description(
129120
self,
130121
address: Union[str, List[str]],
131122
region_code: Optional[str] = None,
132123
locality: Optional[str] = None,
133124
) -> str:
134125
r"""Validates an address via Google Maps API, returns a descriptive
135-
summary.
136-
137-
Validates an address using Google Maps API, returning a summary that
138-
includes information on address completion, formatted address, location
139-
coordinates, and metadata types that are true for the given address.
126+
summary. Validates an address using Google Maps API, returning a
127+
summary that includes information on address completion, formatted
128+
address, location coordinates, and metadata types that are true for
129+
the given address.
140130
141131
Args:
142132
address (Union[str, List[str]]): The address or components to
143133
validate. Can be a single string or a list representing
144134
different parts.
145135
region_code (str, optional): Country code for regional restriction,
146-
helps narrowing down results. (default: :obj:`None`)
136+
helps narrow down results. (default: :obj:`None`)
147137
locality (str, optional): Restricts validation to a specific
148138
locality, e.g., "Mountain View". (default: :obj:`None`)
149139
@@ -157,100 +147,78 @@ def get_address_description(
157147
ImportError: If the `googlemaps` library is not installed.
158148
Exception: For unexpected errors during the address validation.
159149
"""
160-
googlemaps = self._import_googlemaps_or_raise()
161-
google_maps_api_key = self._get_googlemaps_api_key()
162-
try:
163-
gmaps = googlemaps.Client(key=google_maps_api_key)
164-
except Exception as e:
165-
return f"Error: {e!s}"
166-
167-
try:
168-
addressvalidation_result = gmaps.addressvalidation(
169-
[address],
170-
regionCode=region_code,
171-
locality=locality,
172-
enableUspsCass=False,
173-
) # Always False as per requirements
174-
175-
# Check if the result contains an error
176-
if 'error' in addressvalidation_result:
177-
error_info = addressvalidation_result['error']
178-
error_message = error_info.get(
179-
'message', 'An unknown error occurred'
180-
)
181-
error_status = error_info.get('status', 'UNKNOWN_STATUS')
182-
error_code = error_info.get('code', 'UNKNOWN_CODE')
183-
return (
184-
f"Address validation failed with error: {error_message} "
185-
f"Status: {error_status}, Code: {error_code}"
186-
)
187-
188-
# Assuming the successful response structure
189-
# includes a 'result' key
190-
result = addressvalidation_result['result']
191-
verdict = result.get('verdict', {})
192-
address_info = result.get('address', {})
193-
geocode = result.get('geocode', {})
194-
metadata = result.get('metadata', {})
195-
196-
# Construct the descriptive string
197-
address_complete = (
198-
"Yes" if verdict.get('addressComplete', False) else "No"
199-
)
200-
formatted_address = address_info.get(
201-
'formattedAddress', 'Not available'
150+
addressvalidation_result = self.gmaps.addressvalidation(
151+
[address],
152+
regionCode=region_code,
153+
locality=locality,
154+
enableUspsCass=False,
155+
) # Always False as per requirements
156+
157+
# Check if the result contains an error
158+
if 'error' in addressvalidation_result:
159+
error_info = addressvalidation_result['error']
160+
error_message = error_info.get(
161+
'message', 'An unknown error occurred'
202162
)
203-
location = geocode.get('location', {})
204-
latitude = location.get('latitude', 'Not available')
205-
longitude = location.get('longitude', 'Not available')
206-
true_metadata_types = [
207-
key for key, value in metadata.items() if value
208-
]
209-
true_metadata_types_str = (
210-
', '.join(true_metadata_types)
211-
if true_metadata_types
212-
else 'None'
163+
error_status = error_info.get('status', 'UNKNOWN_STATUS')
164+
error_code = error_info.get('code', 'UNKNOWN_CODE')
165+
return (
166+
f"Address validation failed with error: {error_message} "
167+
f"Status: {error_status}, Code: {error_code}"
213168
)
214169

215-
description = (
216-
f"Address completion status: {address_complete}. "
217-
f"Formatted address: {formatted_address}. "
218-
f"Location (latitude, longitude): ({latitude}, {longitude}). "
219-
f"Metadata indicating true types: {true_metadata_types_str}."
220-
)
170+
# Assuming the successful response structure
171+
# includes a 'result' key
172+
result = addressvalidation_result['result']
173+
verdict = result.get('verdict', {})
174+
address_info = result.get('address', {})
175+
geocode = result.get('geocode', {})
176+
metadata = result.get('metadata', {})
177+
178+
# Construct the descriptive string
179+
address_complete = (
180+
"Yes" if verdict.get('addressComplete', False) else "No"
181+
)
182+
formatted_address = address_info.get(
183+
'formattedAddress', 'Not available'
184+
)
185+
location = geocode.get('location', {})
186+
latitude = location.get('latitude', 'Not available')
187+
longitude = location.get('longitude', 'Not available')
188+
true_metadata_types = [key for key, value in metadata.items() if value]
189+
true_metadata_types_str = (
190+
', '.join(true_metadata_types) if true_metadata_types else 'None'
191+
)
221192

222-
return description
223-
except Exception as e:
224-
return f"An unexpected error occurred: {e!s}"
193+
description = (
194+
f"Address completion status: {address_complete}. "
195+
f"Formatted address: {formatted_address}. "
196+
f"Location (latitude, longitude): ({latitude}, {longitude}). "
197+
f"Metadata indicating true types: {true_metadata_types_str}."
198+
)
199+
200+
return description
225201

226202
@handle_googlemaps_exceptions
227-
def get_elevation(self, lat_lng: Tuple) -> str:
203+
def get_elevation(self, lat: float, lng: float) -> str:
228204
r"""Retrieves elevation data for a given latitude and longitude.
229-
230205
Uses the Google Maps API to fetch elevation data for the specified
231206
latitude and longitude. It handles exceptions gracefully and returns a
232207
description of the elevation, including its value in meters and the
233208
data resolution.
234209
235210
Args:
236-
lat_lng (Tuple[float, float]): The latitude and longitude for
237-
which to retrieve elevation data.
211+
lat (float): The latitude of the location to query.
212+
lng (float): The longitude of the location to query.
238213
239214
Returns:
240215
str: A description of the elevation at the specified location(s),
241216
including the elevation in meters and the data resolution. If
242217
elevation data is not available, a message indicating this is
243218
returned.
244219
"""
245-
googlemaps = self._import_googlemaps_or_raise()
246-
google_maps_api_key = self._get_googlemaps_api_key()
247-
try:
248-
gmaps = googlemaps.Client(key=google_maps_api_key)
249-
except Exception as e:
250-
return f"Error: {e!s}"
251-
252220
# Assuming gmaps is a configured Google Maps client instance
253-
elevation_result = gmaps.elevation(lat_lng)
221+
elevation_result = self.gmaps.elevation((lat, lng))
254222

255223
# Extract the elevation data from the first
256224
# (and presumably only) result
@@ -273,52 +241,26 @@ def get_elevation(self, lat_lng: Tuple) -> str:
273241

274242
return description
275243

276-
def _format_offset_to_natural_language(self, offset: int) -> str:
277-
r"""Converts a time offset in seconds to a more natural language
278-
description using hours as the unit, with decimal places to represent
279-
minutes and seconds.
280-
281-
Args:
282-
offset (int): The time offset in seconds. Can be positive,
283-
negative, or zero.
284-
285-
Returns:
286-
str: A string representing the offset in hours, such as
287-
"+2.50 hours" or "-3.75 hours".
288-
"""
289-
# Convert the offset to hours as a float
290-
hours = offset / 3600.0
291-
hours_str = f"{hours:+.2f} hour{'s' if abs(hours) != 1 else ''}"
292-
return hours_str
293-
294244
@handle_googlemaps_exceptions
295-
def get_timezone(self, lat_lng: Tuple) -> str:
245+
def get_timezone(self, lat: float, lng: float) -> str:
296246
r"""Retrieves timezone information for a given latitude and longitude.
297-
298247
This function uses the Google Maps Timezone API to fetch timezone
299248
data for the specified latitude and longitude. It returns a natural
300249
language description of the timezone, including the timezone ID, name,
301250
standard time offset, daylight saving time offset, and the total
302251
offset from Coordinated Universal Time (UTC).
303252
304253
Args:
305-
lat_lng (Tuple[float, float]): The latitude and longitude for
306-
which to retrieve elevation data.
254+
lat (float): The latitude of the location to query.
255+
lng (float): The longitude of the location to query.
307256
308257
Returns:
309258
str: A descriptive string of the timezone information,
310-
including the timezone ID and name, standard time offset,
311-
daylight saving time offset, and total offset from UTC.
259+
including the timezone ID and name, standard time offset,
260+
daylight saving time offset, and total offset from UTC.
312261
"""
313-
googlemaps = self._import_googlemaps_or_raise()
314-
google_maps_api_key = self._get_googlemaps_api_key()
315-
try:
316-
gmaps = googlemaps.Client(key=google_maps_api_key)
317-
except Exception as e:
318-
return f"Error: {e!s}"
319-
320262
# Get timezone information
321-
timezone_dict = gmaps.timezone(lat_lng)
263+
timezone_dict = self.gmaps.timezone((lat, lng))
322264

323265
# Extract necessary information
324266
dst_offset = timezone_dict[
@@ -330,10 +272,10 @@ def get_timezone(self, lat_lng: Tuple) -> str:
330272
timezone_id = timezone_dict['timeZoneId']
331273
timezone_name = timezone_dict['timeZoneName']
332274

333-
raw_offset_str = self._format_offset_to_natural_language(raw_offset)
334-
dst_offset_str = self._format_offset_to_natural_language(dst_offset)
275+
raw_offset_str = _format_offset_to_natural_language(raw_offset)
276+
dst_offset_str = _format_offset_to_natural_language(dst_offset)
335277
total_offset_seconds = dst_offset + raw_offset
336-
total_offset_str = self._format_offset_to_natural_language(
278+
total_offset_str = _format_offset_to_natural_language(
337279
total_offset_seconds
338280
)
339281

@@ -343,9 +285,8 @@ def get_timezone(self, lat_lng: Tuple) -> str:
343285
f"The standard time offset is {raw_offset_str}. "
344286
f"Daylight Saving Time offset is {dst_offset_str}. "
345287
f"The total offset from Coordinated Universal Time (UTC) is "
346-
f"{total_offset_str}, including any "
347-
"Daylight Saving Time adjustment "
348-
f"if applicable. "
288+
f"{total_offset_str}, including any Daylight Saving Time "
289+
f"adjustment if applicable. "
349290
)
350291

351292
return description

poetry.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)