camel-ai 0.1.5.6__py3-none-any.whl → 0.1.5.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of camel-ai might be problematic. Click here for more details.

Files changed (61) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +1 -1
  3. camel/configs/gemini_config.py +15 -14
  4. camel/configs/litellm_config.py +1 -1
  5. camel/configs/openai_config.py +1 -1
  6. camel/configs/zhipuai_config.py +1 -1
  7. camel/models/base_model.py +4 -1
  8. camel/models/litellm_model.py +16 -0
  9. camel/models/ollama_model.py +16 -0
  10. camel/models/zhipuai_model.py +0 -1
  11. camel/toolkits/__init__.py +36 -0
  12. camel/toolkits/base.py +1 -1
  13. camel/toolkits/code_execution.py +1 -1
  14. camel/toolkits/github_toolkit.py +3 -2
  15. camel/toolkits/google_maps_toolkit.py +367 -0
  16. camel/toolkits/math_toolkit.py +79 -0
  17. camel/toolkits/open_api_toolkit.py +548 -0
  18. camel/toolkits/retrieval_toolkit.py +76 -0
  19. camel/toolkits/search_toolkit.py +326 -0
  20. camel/toolkits/slack_toolkit.py +308 -0
  21. camel/toolkits/twitter_toolkit.py +522 -0
  22. camel/toolkits/weather_toolkit.py +173 -0
  23. camel/utils/async_func.py +1 -1
  24. {camel_ai-0.1.5.6.dist-info → camel_ai-0.1.5.7.dist-info}/METADATA +2 -2
  25. {camel_ai-0.1.5.6.dist-info → camel_ai-0.1.5.7.dist-info}/RECORD +52 -53
  26. camel/functions/__init__.py +0 -51
  27. camel/functions/google_maps_function.py +0 -335
  28. camel/functions/math_functions.py +0 -61
  29. camel/functions/open_api_function.py +0 -508
  30. camel/functions/retrieval_functions.py +0 -61
  31. camel/functions/search_functions.py +0 -298
  32. camel/functions/slack_functions.py +0 -286
  33. camel/functions/twitter_function.py +0 -479
  34. camel/functions/weather_functions.py +0 -144
  35. /camel/{functions → toolkits}/open_api_specs/biztoc/__init__.py +0 -0
  36. /camel/{functions → toolkits}/open_api_specs/biztoc/ai-plugin.json +0 -0
  37. /camel/{functions → toolkits}/open_api_specs/biztoc/openapi.yaml +0 -0
  38. /camel/{functions → toolkits}/open_api_specs/coursera/__init__.py +0 -0
  39. /camel/{functions → toolkits}/open_api_specs/coursera/openapi.yaml +0 -0
  40. /camel/{functions → toolkits}/open_api_specs/create_qr_code/__init__.py +0 -0
  41. /camel/{functions → toolkits}/open_api_specs/create_qr_code/openapi.yaml +0 -0
  42. /camel/{functions → toolkits}/open_api_specs/klarna/__init__.py +0 -0
  43. /camel/{functions → toolkits}/open_api_specs/klarna/openapi.yaml +0 -0
  44. /camel/{functions → toolkits}/open_api_specs/nasa_apod/__init__.py +0 -0
  45. /camel/{functions → toolkits}/open_api_specs/nasa_apod/openapi.yaml +0 -0
  46. /camel/{functions → toolkits}/open_api_specs/outschool/__init__.py +0 -0
  47. /camel/{functions → toolkits}/open_api_specs/outschool/ai-plugin.json +0 -0
  48. /camel/{functions → toolkits}/open_api_specs/outschool/openapi.yaml +0 -0
  49. /camel/{functions → toolkits}/open_api_specs/outschool/paths/__init__.py +0 -0
  50. /camel/{functions → toolkits}/open_api_specs/outschool/paths/get_classes.py +0 -0
  51. /camel/{functions → toolkits}/open_api_specs/outschool/paths/search_teachers.py +0 -0
  52. /camel/{functions → toolkits}/open_api_specs/security_config.py +0 -0
  53. /camel/{functions → toolkits}/open_api_specs/speak/__init__.py +0 -0
  54. /camel/{functions → toolkits}/open_api_specs/speak/openapi.yaml +0 -0
  55. /camel/{functions → toolkits}/open_api_specs/web_scraper/__init__.py +0 -0
  56. /camel/{functions → toolkits}/open_api_specs/web_scraper/ai-plugin.json +0 -0
  57. /camel/{functions → toolkits}/open_api_specs/web_scraper/openapi.yaml +0 -0
  58. /camel/{functions → toolkits}/open_api_specs/web_scraper/paths/__init__.py +0 -0
  59. /camel/{functions → toolkits}/open_api_specs/web_scraper/paths/scraper.py +0 -0
  60. /camel/{functions → toolkits}/openai_function.py +0 -0
  61. {camel_ai-0.1.5.6.dist-info → camel_ai-0.1.5.7.dist-info}/WHEEL +0 -0
camel/__init__.py CHANGED
@@ -12,7 +12,7 @@
12
12
  # limitations under the License.
13
13
  # =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
14
14
 
15
- __version__ = '0.1.5.6'
15
+ __version__ = '0.1.5.7'
16
16
 
17
17
  __all__ = [
18
18
  '__version__',
@@ -42,8 +42,8 @@ from camel.utils import get_model_encoding
42
42
  if TYPE_CHECKING:
43
43
  from openai import Stream
44
44
 
45
- from camel.functions import OpenAIFunction
46
45
  from camel.terminators import ResponseTerminator
46
+ from camel.toolkits import OpenAIFunction
47
47
 
48
48
 
49
49
  @dataclass(frozen=True)
@@ -15,10 +15,19 @@
15
15
 
16
16
  from collections.abc import Iterable
17
17
  from dataclasses import asdict, dataclass
18
- from typing import Optional
18
+ from typing import TYPE_CHECKING, Optional
19
19
 
20
20
  from camel.configs.base_config import BaseConfig
21
21
 
22
+ if TYPE_CHECKING:
23
+ from google.generativeai.protos import Schema
24
+ from google.generativeai.types.content_types import (
25
+ FunctionLibraryType,
26
+ ToolConfigType,
27
+ )
28
+ from google.generativeai.types.helper_types import RequestOptionsType
29
+ from google.generativeai.types.safety_types import SafetySettingOptions
30
+
22
31
 
23
32
  @dataclass(frozen=True)
24
33
  class GeminiConfig(BaseConfig):
@@ -72,14 +81,6 @@ class GeminiConfig(BaseConfig):
72
81
  Options for the request.
73
82
  """
74
83
 
75
- from google.generativeai.protos import Schema
76
- from google.generativeai.types.content_types import (
77
- FunctionLibraryType,
78
- ToolConfigType,
79
- )
80
- from google.generativeai.types.helper_types import RequestOptionsType
81
- from google.generativeai.types.safety_types import SafetySettingOptions
82
-
83
84
  candidate_count: Optional[int] = None
84
85
  stop_sequences: Optional[Iterable[str]] = None
85
86
  max_output_tokens: Optional[int] = None
@@ -87,11 +88,11 @@ class GeminiConfig(BaseConfig):
87
88
  top_p: Optional[float] = None
88
89
  top_k: Optional[int] = None
89
90
  response_mime_type: Optional[str] = None
90
- response_schema: Optional[Schema] = None
91
- safety_settings: Optional[SafetySettingOptions] = None
92
- tools: Optional[FunctionLibraryType] = None
93
- tool_config: Optional[ToolConfigType] = None
94
- request_options: Optional[RequestOptionsType] = None
91
+ response_schema: Optional['Schema'] = None
92
+ safety_settings: Optional['SafetySettingOptions'] = None
93
+ tools: Optional['FunctionLibraryType'] = None
94
+ tool_config: Optional['ToolConfigType'] = None
95
+ request_options: Optional['RequestOptionsType'] = None
95
96
 
96
97
 
97
98
  Gemini_API_PARAMS = {param for param in asdict(GeminiConfig()).keys()}
@@ -19,7 +19,7 @@ from typing import TYPE_CHECKING, List, Optional, Union
19
19
  from camel.configs.base_config import BaseConfig
20
20
 
21
21
  if TYPE_CHECKING:
22
- from camel.functions import OpenAIFunction
22
+ from camel.toolkits import OpenAIFunction
23
23
 
24
24
 
25
25
  @dataclass(frozen=True)
@@ -21,7 +21,7 @@ from openai._types import NOT_GIVEN, NotGiven
21
21
  from camel.configs.base_config import BaseConfig
22
22
 
23
23
  if TYPE_CHECKING:
24
- from camel.functions import OpenAIFunction
24
+ from camel.toolkits import OpenAIFunction
25
25
 
26
26
 
27
27
  @dataclass(frozen=True)
@@ -21,7 +21,7 @@ from openai._types import NOT_GIVEN, NotGiven
21
21
  from camel.configs.base_config import BaseConfig
22
22
 
23
23
  if TYPE_CHECKING:
24
- from camel.functions import OpenAIFunction
24
+ from camel.toolkits import OpenAIFunction
25
25
 
26
26
 
27
27
  @dataclass(frozen=True)
@@ -109,7 +109,10 @@ class BaseModelBackend(ABC):
109
109
  Returns:
110
110
  int: The maximum token limit for the given model.
111
111
  """
112
- return self.model_type.token_limit
112
+ return (
113
+ self.model_config_dict.get("max_tokens")
114
+ or self.model_type.token_limit
115
+ )
113
116
 
114
117
  @property
115
118
  def stream(self) -> bool:
@@ -138,3 +138,19 @@ class LiteLLMModel:
138
138
  f"Unexpected argument `{param}` is "
139
139
  "input into LiteLLM model backend."
140
140
  )
141
+
142
+ @property
143
+ def token_limit(self) -> int:
144
+ """Returns the maximum token limit for the given model.
145
+
146
+ Returns:
147
+ int: The maximum token limit for the given model.
148
+ """
149
+ max_tokens = self.model_config_dict.get("max_tokens")
150
+ if isinstance(max_tokens, int):
151
+ return max_tokens
152
+ print(
153
+ "Must set `max_tokens` as an integer in `model_config_dict` when"
154
+ " setting up the model. Using 4096 as default value."
155
+ )
156
+ return 4096
@@ -104,6 +104,22 @@ class OllamaModel:
104
104
  )
105
105
  return response
106
106
 
107
+ @property
108
+ def token_limit(self) -> int:
109
+ """Returns the maximum token limit for the given model.
110
+
111
+ Returns:
112
+ int: The maximum token limit for the given model.
113
+ """
114
+ max_tokens = self.model_config_dict.get("max_tokens")
115
+ if isinstance(max_tokens, int):
116
+ return max_tokens
117
+ print(
118
+ "Must set `max_tokens` as an integer in `model_config_dict` when"
119
+ " setting up the model. Using 4096 as default value."
120
+ )
121
+ return 4096
122
+
107
123
  @property
108
124
  def stream(self) -> bool:
109
125
  r"""Returns whether the model is in stream mode, which sends partial
@@ -118,7 +118,6 @@ class ZhipuAIModel(BaseModelBackend):
118
118
  f"Unexpected argument `{param}` is "
119
119
  "input into ZhipuAI model backend."
120
120
  )
121
- pass
122
121
 
123
122
  @property
124
123
  def stream(self) -> bool:
@@ -11,13 +11,49 @@
11
11
  # See the License for the specific language governing permissions and
12
12
  # limitations under the License.
13
13
  # =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
14
+ # ruff: noqa: I001
15
+ from .openai_function import (
16
+ OpenAIFunction,
17
+ get_openai_function_schema,
18
+ get_openai_tool_schema,
19
+ )
20
+ from .open_api_specs.security_config import openapi_security_config
21
+
22
+ from .google_maps_toolkit import MAP_FUNCS, GoogleMapsToolkit
23
+ from .math_toolkit import MATH_FUNCS, MathToolkit
24
+ from .open_api_toolkit import OPENAPI_FUNCS, OpenAPIToolkit
25
+ from .retrieval_toolkit import RETRIEVAL_FUNCS, RetrievalToolkit
26
+ from .search_toolkit import SEARCH_FUNCS, SearchToolkit
27
+ from .twitter_toolkit import TWITTER_FUNCS, TwitterToolkit
28
+ from .weather_toolkit import WEATHER_FUNCS, WeatherToolkit
29
+ from .slack_toolkit import SLACK_FUNCS, SlackToolkit
14
30
 
15
31
  from .base import BaseToolkit
16
32
  from .code_execution import CodeExecutionToolkit
17
33
  from .github_toolkit import GithubToolkit
18
34
 
19
35
  __all__ = [
36
+ 'OpenAIFunction',
37
+ 'get_openai_function_schema',
38
+ 'get_openai_tool_schema',
39
+ 'openapi_security_config',
40
+ 'MATH_FUNCS',
41
+ 'MAP_FUNCS',
42
+ 'OPENAPI_FUNCS',
43
+ 'RETRIEVAL_FUNCS',
44
+ 'SEARCH_FUNCS',
45
+ 'TWITTER_FUNCS',
46
+ 'WEATHER_FUNCS',
47
+ 'SLACK_FUNCS',
20
48
  'BaseToolkit',
21
49
  'GithubToolkit',
50
+ 'MathToolkit',
51
+ 'GoogleMapsToolkit',
52
+ 'SearchToolkit',
53
+ 'SlackToolkit',
54
+ 'TwitterToolkit',
55
+ 'WeatherToolkit',
56
+ 'RetrievalToolkit',
57
+ 'OpenAPIToolkit',
22
58
  'CodeExecutionToolkit',
23
59
  ]
camel/toolkits/base.py CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  from typing import List
16
16
 
17
- from camel.functions import OpenAIFunction
17
+ from .openai_function import OpenAIFunction
18
18
 
19
19
 
20
20
  class BaseToolkit:
@@ -13,8 +13,8 @@
13
13
  # =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
14
14
  from typing import List, Literal
15
15
 
16
- from camel.functions import OpenAIFunction
17
16
  from camel.interpreters import InternalPythonInterpreter
17
+ from camel.toolkits import OpenAIFunction
18
18
 
19
19
  from .base import BaseToolkit
20
20
 
@@ -17,10 +17,11 @@ from dataclasses import dataclass
17
17
  from datetime import datetime, timedelta
18
18
  from typing import List, Optional
19
19
 
20
- from camel.functions import OpenAIFunction
21
- from camel.toolkits.base import BaseToolkit
22
20
  from camel.utils import dependencies_required
23
21
 
22
+ from .base import BaseToolkit
23
+ from .openai_function import OpenAIFunction
24
+
24
25
 
25
26
  @dataclass
26
27
  class GithubIssue:
@@ -0,0 +1,367 @@
1
+ # =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
2
+ # Licensed under the Apache License, Version 2.0 (the “License”);
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an “AS IS” BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
14
+ import os
15
+ from functools import wraps
16
+ from typing import Any, Callable, List, Optional, Tuple, Union
17
+
18
+ from camel.toolkits.base import BaseToolkit
19
+ from camel.toolkits.openai_function import OpenAIFunction
20
+
21
+
22
+ def handle_googlemaps_exceptions(
23
+ func: Callable[..., Any],
24
+ ) -> Callable[..., Any]:
25
+ r"""Decorator to catch and handle exceptions raised by Google Maps API
26
+ calls.
27
+
28
+ Args:
29
+ func (Callable): The function to be wrapped by the decorator.
30
+
31
+ Returns:
32
+ Callable: A wrapper function that calls the wrapped function and
33
+ handles exceptions.
34
+ """
35
+
36
+ @wraps(func)
37
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
38
+ try:
39
+ from googlemaps.exceptions import ( # type: ignore[import-untyped] # isort: skip
40
+ ApiError,
41
+ HTTPError,
42
+ Timeout,
43
+ TransportError,
44
+ )
45
+ except ImportError:
46
+ raise ImportError(
47
+ "Please install `googlemaps` first. You can install "
48
+ "it by running `pip install googlemaps`."
49
+ )
50
+
51
+ try:
52
+ return func(*args, **kwargs)
53
+ except ApiError as e:
54
+ return (
55
+ 'An exception returned by the remote API. '
56
+ f'Status: {e.status}, Message: {e.message}'
57
+ )
58
+ except HTTPError as e:
59
+ return (
60
+ 'An unexpected HTTP error occurred. '
61
+ f'Status Code: {e.status_code}'
62
+ )
63
+ except Timeout:
64
+ return 'The request timed out.'
65
+ except TransportError as e:
66
+ return (
67
+ 'Something went wrong while trying to execute the '
68
+ f'request. Details: {e.base_exception}'
69
+ )
70
+ except Exception as e:
71
+ return f'An unexpected error occurred: {e}'
72
+
73
+ return wrapper
74
+
75
+
76
+ class GoogleMapsToolkit(BaseToolkit):
77
+ r"""A class representing a toolkit for interacting with GoogleMaps API.
78
+
79
+ This class provides methods for validating addresses, retrieving elevation,
80
+ and fetching timezone information using the Google Maps API.
81
+ """
82
+
83
+ def _import_googlemaps_or_raise(self) -> Any:
84
+ r"""Attempts to import the `googlemaps` library and returns it.
85
+
86
+ Returns:
87
+ module: The `googlemaps` module if successfully imported.
88
+
89
+ Raises:
90
+ ImportError: If the `googlemaps` library is not installed, this
91
+ error is raised with a message instructing how to install the
92
+ library using pip.
93
+ """
94
+ try:
95
+ import googlemaps
96
+
97
+ return googlemaps
98
+ except ImportError:
99
+ raise ImportError(
100
+ "Please install `googlemaps` first. You can install "
101
+ "it by running `pip install googlemaps`."
102
+ )
103
+
104
+ def _get_googlemaps_api_key(self) -> str:
105
+ r"""Retrieve the Google Maps API key from environment variables.
106
+
107
+ Returns:
108
+ str: The Google Maps API key.
109
+
110
+ Raises:
111
+ ValueError: If the API key is not found in the environment
112
+ variables.
113
+ """
114
+ # Get `GOOGLEMAPS_API_KEY` here:
115
+ # https://console.cloud.google.com/apis/credentials
116
+ GOOGLEMAPS_API_KEY = os.environ.get('GOOGLEMAPS_API_KEY')
117
+ if not GOOGLEMAPS_API_KEY:
118
+ raise ValueError(
119
+ "`GOOGLEMAPS_API_KEY` not found in environment "
120
+ "variables. `GOOGLEMAPS_API_KEY` API keys are "
121
+ "generated in the `Credentials` page of the "
122
+ "`APIs & Services` tab of "
123
+ "https://console.cloud.google.com/apis/credentials."
124
+ )
125
+ return GOOGLEMAPS_API_KEY
126
+
127
+ def get_address_description(
128
+ self,
129
+ address: Union[str, List[str]],
130
+ region_code: Optional[str] = None,
131
+ locality: Optional[str] = None,
132
+ ) -> str:
133
+ r"""Validates an address via Google Maps API, returns a descriptive
134
+ summary.
135
+
136
+ Validates an address using Google Maps API, returning a summary that
137
+ includes information on address completion, formatted address, location
138
+ coordinates, and metadata types that are true for the given address.
139
+
140
+ Args:
141
+ address (Union[str, List[str]]): The address or components to
142
+ validate. Can be a single string or a list representing
143
+ different parts.
144
+ region_code (str, optional): Country code for regional restriction,
145
+ helps narrowing down results. (default: :obj:`None`)
146
+ locality (str, optional): Restricts validation to a specific
147
+ locality, e.g., "Mountain View". (default: :obj:`None`)
148
+
149
+ Returns:
150
+ str: Summary of the address validation results, including
151
+ information on address completion, formatted address,
152
+ geographical coordinates (latitude and longitude), and metadata
153
+ types true for the address.
154
+
155
+ Raises:
156
+ ImportError: If the `googlemaps` library is not installed.
157
+ Exception: For unexpected errors during the address validation.
158
+ """
159
+ googlemaps = self._import_googlemaps_or_raise()
160
+ google_maps_api_key = self._get_googlemaps_api_key()
161
+ try:
162
+ gmaps = googlemaps.Client(key=google_maps_api_key)
163
+ except Exception as e:
164
+ return f"Error: {e!s}"
165
+
166
+ try:
167
+ addressvalidation_result = gmaps.addressvalidation(
168
+ [address],
169
+ regionCode=region_code,
170
+ locality=locality,
171
+ enableUspsCass=False,
172
+ ) # Always False as per requirements
173
+
174
+ # Check if the result contains an error
175
+ if 'error' in addressvalidation_result:
176
+ error_info = addressvalidation_result['error']
177
+ error_message = error_info.get(
178
+ 'message', 'An unknown error occurred'
179
+ )
180
+ error_status = error_info.get('status', 'UNKNOWN_STATUS')
181
+ error_code = error_info.get('code', 'UNKNOWN_CODE')
182
+ return (
183
+ f"Address validation failed with error: {error_message} "
184
+ f"Status: {error_status}, Code: {error_code}"
185
+ )
186
+
187
+ # Assuming the successful response structure
188
+ # includes a 'result' key
189
+ result = addressvalidation_result['result']
190
+ verdict = result.get('verdict', {})
191
+ address_info = result.get('address', {})
192
+ geocode = result.get('geocode', {})
193
+ metadata = result.get('metadata', {})
194
+
195
+ # Construct the descriptive string
196
+ address_complete = (
197
+ "Yes" if verdict.get('addressComplete', False) else "No"
198
+ )
199
+ formatted_address = address_info.get(
200
+ 'formattedAddress', 'Not available'
201
+ )
202
+ location = geocode.get('location', {})
203
+ latitude = location.get('latitude', 'Not available')
204
+ longitude = location.get('longitude', 'Not available')
205
+ true_metadata_types = [
206
+ key for key, value in metadata.items() if value
207
+ ]
208
+ true_metadata_types_str = (
209
+ ', '.join(true_metadata_types)
210
+ if true_metadata_types
211
+ else 'None'
212
+ )
213
+
214
+ description = (
215
+ f"Address completion status: {address_complete}. "
216
+ f"Formatted address: {formatted_address}. "
217
+ f"Location (latitude, longitude): ({latitude}, {longitude}). "
218
+ f"Metadata indicating true types: {true_metadata_types_str}."
219
+ )
220
+
221
+ return description
222
+ except Exception as e:
223
+ return f"An unexpected error occurred: {e!s}"
224
+
225
+ @handle_googlemaps_exceptions
226
+ def get_elevation(self, lat_lng: Tuple) -> str:
227
+ r"""Retrieves elevation data for a given latitude and longitude.
228
+
229
+ Uses the Google Maps API to fetch elevation data for the specified
230
+ latitude and longitude. It handles exceptions gracefully and returns a
231
+ description of the elevation, including its value in meters and the
232
+ data resolution.
233
+
234
+ Args:
235
+ lat_lng (Tuple[float, float]): The latitude and longitude for
236
+ which to retrieve elevation data.
237
+
238
+ Returns:
239
+ str: A description of the elevation at the specified location(s),
240
+ including the elevation in meters and the data resolution. If
241
+ elevation data is not available, a message indicating this is
242
+ returned.
243
+ """
244
+ googlemaps = self._import_googlemaps_or_raise()
245
+ google_maps_api_key = self._get_googlemaps_api_key()
246
+ try:
247
+ gmaps = googlemaps.Client(key=google_maps_api_key)
248
+ except Exception as e:
249
+ return f"Error: {e!s}"
250
+
251
+ # Assuming gmaps is a configured Google Maps client instance
252
+ elevation_result = gmaps.elevation(lat_lng)
253
+
254
+ # Extract the elevation data from the first
255
+ # (and presumably only) result
256
+ if elevation_result:
257
+ elevation = elevation_result[0]['elevation']
258
+ location = elevation_result[0]['location']
259
+ resolution = elevation_result[0]['resolution']
260
+
261
+ # Format the elevation data into a natural language description
262
+ description = (
263
+ f"The elevation at latitude {location['lat']}, "
264
+ f"longitude {location['lng']} "
265
+ f"is approximately {elevation:.2f} meters above sea level, "
266
+ f"with a data resolution of {resolution:.2f} meters."
267
+ )
268
+ else:
269
+ description = (
270
+ "Elevation data is not available for the given location."
271
+ )
272
+
273
+ return description
274
+
275
+ def _format_offset_to_natural_language(self, offset: int) -> str:
276
+ r"""Converts a time offset in seconds to a more natural language
277
+ description using hours as the unit, with decimal places to represent
278
+ minutes and seconds.
279
+
280
+ Args:
281
+ offset (int): The time offset in seconds. Can be positive,
282
+ negative, or zero.
283
+
284
+ Returns:
285
+ str: A string representing the offset in hours, such as
286
+ "+2.50 hours" or "-3.75 hours".
287
+ """
288
+ # Convert the offset to hours as a float
289
+ hours = offset / 3600.0
290
+ hours_str = f"{hours:+.2f} hour{'s' if abs(hours) != 1 else ''}"
291
+ return hours_str
292
+
293
+ @handle_googlemaps_exceptions
294
+ def get_timezone(self, lat_lng: Tuple) -> str:
295
+ r"""Retrieves timezone information for a given latitude and longitude.
296
+
297
+ This function uses the Google Maps Timezone API to fetch timezone
298
+ data for the specified latitude and longitude. It returns a natural
299
+ language description of the timezone, including the timezone ID, name,
300
+ standard time offset, daylight saving time offset, and the total
301
+ offset from Coordinated Universal Time (UTC).
302
+
303
+ Args:
304
+ lat_lng (Tuple[float, float]): The latitude and longitude for
305
+ which to retrieve elevation data.
306
+
307
+ Returns:
308
+ str: A descriptive string of the timezone information,
309
+ including the timezone ID and name, standard time offset,
310
+ daylight saving time offset, and total offset from UTC.
311
+ """
312
+ googlemaps = self._import_googlemaps_or_raise()
313
+ google_maps_api_key = self._get_googlemaps_api_key()
314
+ try:
315
+ gmaps = googlemaps.Client(key=google_maps_api_key)
316
+ except Exception as e:
317
+ return f"Error: {e!s}"
318
+
319
+ # Get timezone information
320
+ timezone_dict = gmaps.timezone(lat_lng)
321
+
322
+ # Extract necessary information
323
+ dst_offset = timezone_dict[
324
+ 'dstOffset'
325
+ ] # Daylight Saving Time offset in seconds
326
+ raw_offset = timezone_dict[
327
+ 'rawOffset'
328
+ ] # Standard time offset in seconds
329
+ timezone_id = timezone_dict['timeZoneId']
330
+ timezone_name = timezone_dict['timeZoneName']
331
+
332
+ raw_offset_str = self._format_offset_to_natural_language(raw_offset)
333
+ dst_offset_str = self._format_offset_to_natural_language(dst_offset)
334
+ total_offset_seconds = dst_offset + raw_offset
335
+ total_offset_str = self._format_offset_to_natural_language(
336
+ total_offset_seconds
337
+ )
338
+
339
+ # Create a natural language description
340
+ description = (
341
+ f"Timezone ID is {timezone_id}, named {timezone_name}. "
342
+ f"The standard time offset is {raw_offset_str}. "
343
+ f"Daylight Saving Time offset is {dst_offset_str}. "
344
+ f"The total offset from Coordinated Universal Time (UTC) is "
345
+ f"{total_offset_str}, including any "
346
+ "Daylight Saving Time adjustment "
347
+ f"if applicable. "
348
+ )
349
+
350
+ return description
351
+
352
+ def get_tools(self) -> List[OpenAIFunction]:
353
+ r"""Returns a list of OpenAIFunction objects representing the
354
+ functions in the toolkit.
355
+
356
+ Returns:
357
+ List[OpenAIFunction]: A list of OpenAIFunction objects
358
+ representing the functions in the toolkit.
359
+ """
360
+ return [
361
+ OpenAIFunction(self.get_address_description),
362
+ OpenAIFunction(self.get_elevation),
363
+ OpenAIFunction(self.get_timezone),
364
+ ]
365
+
366
+
367
+ MAP_FUNCS: List[OpenAIFunction] = GoogleMapsToolkit().get_tools()