camel-ai 0.1.5.5__py3-none-any.whl → 0.1.5.9__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 (97) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +3 -3
  3. camel/agents/critic_agent.py +1 -1
  4. camel/agents/deductive_reasoner_agent.py +4 -4
  5. camel/agents/embodied_agent.py +1 -1
  6. camel/agents/knowledge_graph_agent.py +13 -17
  7. camel/agents/role_assignment_agent.py +1 -1
  8. camel/agents/search_agent.py +4 -5
  9. camel/agents/task_agent.py +5 -6
  10. camel/configs/__init__.py +15 -0
  11. camel/configs/gemini_config.py +98 -0
  12. camel/configs/groq_config.py +119 -0
  13. camel/configs/litellm_config.py +1 -1
  14. camel/configs/mistral_config.py +81 -0
  15. camel/configs/ollama_config.py +1 -1
  16. camel/configs/openai_config.py +1 -1
  17. camel/configs/vllm_config.py +103 -0
  18. camel/configs/zhipuai_config.py +1 -1
  19. camel/embeddings/__init__.py +2 -0
  20. camel/embeddings/mistral_embedding.py +89 -0
  21. camel/interpreters/__init__.py +2 -0
  22. camel/interpreters/ipython_interpreter.py +167 -0
  23. camel/models/__init__.py +10 -0
  24. camel/models/anthropic_model.py +7 -2
  25. camel/models/azure_openai_model.py +152 -0
  26. camel/models/base_model.py +9 -2
  27. camel/models/gemini_model.py +215 -0
  28. camel/models/groq_model.py +131 -0
  29. camel/models/litellm_model.py +26 -4
  30. camel/models/mistral_model.py +169 -0
  31. camel/models/model_factory.py +33 -5
  32. camel/models/ollama_model.py +21 -2
  33. camel/models/open_source_model.py +11 -3
  34. camel/models/openai_model.py +7 -2
  35. camel/models/stub_model.py +4 -4
  36. camel/models/vllm_model.py +138 -0
  37. camel/models/zhipuai_model.py +7 -4
  38. camel/prompts/__init__.py +2 -2
  39. camel/prompts/task_prompt_template.py +4 -4
  40. camel/prompts/{descripte_video_prompt.py → video_description_prompt.py} +1 -1
  41. camel/retrievers/auto_retriever.py +2 -0
  42. camel/storages/graph_storages/neo4j_graph.py +5 -0
  43. camel/toolkits/__init__.py +36 -0
  44. camel/toolkits/base.py +1 -1
  45. camel/toolkits/code_execution.py +1 -1
  46. camel/toolkits/github_toolkit.py +3 -2
  47. camel/toolkits/google_maps_toolkit.py +367 -0
  48. camel/toolkits/math_toolkit.py +79 -0
  49. camel/toolkits/open_api_toolkit.py +548 -0
  50. camel/toolkits/retrieval_toolkit.py +76 -0
  51. camel/toolkits/search_toolkit.py +326 -0
  52. camel/toolkits/slack_toolkit.py +308 -0
  53. camel/toolkits/twitter_toolkit.py +522 -0
  54. camel/toolkits/weather_toolkit.py +173 -0
  55. camel/types/enums.py +163 -30
  56. camel/utils/__init__.py +4 -0
  57. camel/utils/async_func.py +1 -1
  58. camel/utils/token_counting.py +182 -40
  59. {camel_ai-0.1.5.5.dist-info → camel_ai-0.1.5.9.dist-info}/METADATA +43 -3
  60. camel_ai-0.1.5.9.dist-info/RECORD +165 -0
  61. camel/functions/__init__.py +0 -51
  62. camel/functions/google_maps_function.py +0 -335
  63. camel/functions/math_functions.py +0 -61
  64. camel/functions/open_api_function.py +0 -508
  65. camel/functions/retrieval_functions.py +0 -61
  66. camel/functions/search_functions.py +0 -298
  67. camel/functions/slack_functions.py +0 -286
  68. camel/functions/twitter_function.py +0 -479
  69. camel/functions/weather_functions.py +0 -144
  70. camel_ai-0.1.5.5.dist-info/RECORD +0 -155
  71. /camel/{functions → toolkits}/open_api_specs/biztoc/__init__.py +0 -0
  72. /camel/{functions → toolkits}/open_api_specs/biztoc/ai-plugin.json +0 -0
  73. /camel/{functions → toolkits}/open_api_specs/biztoc/openapi.yaml +0 -0
  74. /camel/{functions → toolkits}/open_api_specs/coursera/__init__.py +0 -0
  75. /camel/{functions → toolkits}/open_api_specs/coursera/openapi.yaml +0 -0
  76. /camel/{functions → toolkits}/open_api_specs/create_qr_code/__init__.py +0 -0
  77. /camel/{functions → toolkits}/open_api_specs/create_qr_code/openapi.yaml +0 -0
  78. /camel/{functions → toolkits}/open_api_specs/klarna/__init__.py +0 -0
  79. /camel/{functions → toolkits}/open_api_specs/klarna/openapi.yaml +0 -0
  80. /camel/{functions → toolkits}/open_api_specs/nasa_apod/__init__.py +0 -0
  81. /camel/{functions → toolkits}/open_api_specs/nasa_apod/openapi.yaml +0 -0
  82. /camel/{functions → toolkits}/open_api_specs/outschool/__init__.py +0 -0
  83. /camel/{functions → toolkits}/open_api_specs/outschool/ai-plugin.json +0 -0
  84. /camel/{functions → toolkits}/open_api_specs/outschool/openapi.yaml +0 -0
  85. /camel/{functions → toolkits}/open_api_specs/outschool/paths/__init__.py +0 -0
  86. /camel/{functions → toolkits}/open_api_specs/outschool/paths/get_classes.py +0 -0
  87. /camel/{functions → toolkits}/open_api_specs/outschool/paths/search_teachers.py +0 -0
  88. /camel/{functions → toolkits}/open_api_specs/security_config.py +0 -0
  89. /camel/{functions → toolkits}/open_api_specs/speak/__init__.py +0 -0
  90. /camel/{functions → toolkits}/open_api_specs/speak/openapi.yaml +0 -0
  91. /camel/{functions → toolkits}/open_api_specs/web_scraper/__init__.py +0 -0
  92. /camel/{functions → toolkits}/open_api_specs/web_scraper/ai-plugin.json +0 -0
  93. /camel/{functions → toolkits}/open_api_specs/web_scraper/openapi.yaml +0 -0
  94. /camel/{functions → toolkits}/open_api_specs/web_scraper/paths/__init__.py +0 -0
  95. /camel/{functions → toolkits}/open_api_specs/web_scraper/paths/scraper.py +0 -0
  96. /camel/{functions → toolkits}/openai_function.py +0 -0
  97. {camel_ai-0.1.5.5.dist-info → camel_ai-0.1.5.9.dist-info}/WHEEL +0 -0
@@ -0,0 +1,522 @@
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 datetime
15
+ import os
16
+ from http import HTTPStatus
17
+ from http.client import responses
18
+ from typing import List, Optional, Tuple, Union
19
+
20
+ import requests
21
+
22
+ from camel.toolkits import OpenAIFunction
23
+ from camel.toolkits.base import BaseToolkit
24
+
25
+ TWEET_TEXT_LIMIT = 280
26
+
27
+
28
+ class TwitterToolkit(BaseToolkit):
29
+ r"""A class representing a toolkit for Twitter operations.
30
+
31
+ This class provides methods for creating a tweet, deleting a tweet, and
32
+ getting the authenticated user's profile information.
33
+ """
34
+
35
+ def create_tweet(
36
+ self,
37
+ *,
38
+ text: str,
39
+ poll_options: Optional[List[str]] = None,
40
+ poll_duration_minutes: Optional[int] = None,
41
+ quote_tweet_id: Optional[Union[int, str]] = None,
42
+ ) -> str:
43
+ r"""Creates a new tweet, optionally including a poll or a quote tweet,
44
+ or simply a text-only tweet.
45
+
46
+ This function sends a POST request to the Twitter API to create a new
47
+ tweet. The tweet can be a text-only tweet, or optionally include a poll
48
+ or be a quote tweet. A confirmation prompt is presented to the user
49
+ before the tweet is created.
50
+
51
+ Args:
52
+ text (str): The text of the tweet. The Twitter character limit for
53
+ a single tweet is 280 characters.
54
+ poll_options (Optional[List[str]]): A list of poll options for a
55
+ tweet with a poll.
56
+ poll_duration_minutes (Optional[int]): Duration of the poll in
57
+ minutes for a tweet with a poll. This is only required
58
+ if the request includes poll_options.
59
+ quote_tweet_id (Optional[Union[int, str]]): Link to the tweet being
60
+ quoted.
61
+
62
+ Note:
63
+ You can only provide either the `quote_tweet_id` parameter or
64
+ the pair of `poll_duration_minutes` and `poll_options` parameters,
65
+ not both.
66
+
67
+ Returns:
68
+ str: A message indicating the success of the tweet creation,
69
+ including the tweet ID and text. If the request to the
70
+ Twitter API is not successful, the return is an error message.
71
+
72
+ Reference:
73
+ https://developer.twitter.com/en/docs/twitter-api/tweets/
74
+ manage-tweets/api-reference/post-tweets
75
+ https://github.com/xdevplatform/Twitter-API-v2-sample-code/blob/
76
+ main/Manage-Tweets/create_tweet.py
77
+ """
78
+ # validate text
79
+ if text is None:
80
+ return "Text cannot be None"
81
+ elif len(text) > TWEET_TEXT_LIMIT:
82
+ return "Text must not exceed 280 characters."
83
+
84
+ # Validate poll options and duration
85
+ if (poll_options is None) != (poll_duration_minutes is None):
86
+ return (
87
+ "Error: Both `poll_options` and `poll_duration_minutes` must "
88
+ "be provided together or not at all."
89
+ )
90
+
91
+ # Validate exclusive parameters
92
+ if quote_tweet_id is not None and (
93
+ poll_options or poll_duration_minutes
94
+ ):
95
+ return (
96
+ "Error: Cannot provide both `quote_tweet_id` and "
97
+ "(`poll_options` or `poll_duration_minutes`)."
98
+ )
99
+
100
+ # Print the parameters that are not None
101
+ params = {
102
+ "text": text,
103
+ "poll_options": poll_options,
104
+ "poll_duration_minutes": poll_duration_minutes,
105
+ "quote_tweet_id": quote_tweet_id,
106
+ }
107
+ print("You are going to create a tweet with following parameters:")
108
+ for key, value in params.items():
109
+ if value is not None:
110
+ print(f"{key}: {value}")
111
+
112
+ # Add a confirmation prompt at the beginning of the function
113
+ confirm = input(
114
+ "Are you sure you want to create this tweet? (yes/no): "
115
+ )
116
+ if confirm.lower() != "yes":
117
+ return "Execution cancelled by the user."
118
+
119
+ oauth = self._get_oauth_session()
120
+ json_data = {}
121
+
122
+ if poll_options is not None and poll_duration_minutes is not None:
123
+ json_data["poll"] = {
124
+ "options": poll_options,
125
+ "duration_minutes": poll_duration_minutes,
126
+ }
127
+
128
+ if quote_tweet_id is not None:
129
+ json_data["quote_tweet_id"] = str(quote_tweet_id) # type: ignore[assignment]
130
+
131
+ json_data["text"] = text # type: ignore[assignment]
132
+
133
+ # Making the request
134
+ response = oauth.post(
135
+ "https://api.twitter.com/2/tweets",
136
+ json=json_data,
137
+ )
138
+
139
+ if response.status_code != HTTPStatus.CREATED:
140
+ error_type = self._handle_http_error(response)
141
+ # use string concatenation to satisfy flake8
142
+ return (
143
+ "Request returned a(n) "
144
+ + str(error_type)
145
+ + ": "
146
+ + str(response.status_code)
147
+ + " "
148
+ + response.text
149
+ )
150
+
151
+ # Saving the response as JSON
152
+ json_response = response.json()
153
+
154
+ tweet_id = json_response["data"]["id"]
155
+ tweet_text = json_response["data"]["text"]
156
+
157
+ response_str = (
158
+ f"Create tweet successful. "
159
+ f"The tweet ID is: {tweet_id}. "
160
+ f"The tweet text is: '{tweet_text}'."
161
+ )
162
+
163
+ return response_str
164
+
165
+ def delete_tweet(self, tweet_id: str) -> str:
166
+ r"""Deletes a tweet with the specified ID for an authorized user.
167
+
168
+ This function sends a DELETE request to the Twitter API to delete
169
+ a tweet with the specified ID. Before sending the request, it
170
+ prompts the user to confirm the deletion.
171
+
172
+ Args:
173
+ tweet_id (str): The ID of the tweet to delete.
174
+
175
+ Returns:
176
+ str: A message indicating the result of the deletion. If the
177
+ deletion was successful, the message includes the ID of the
178
+ deleted tweet. If the deletion was not successful, the message
179
+ includes an error message.
180
+
181
+ Reference:
182
+ https://developer.twitter.com/en/docs/twitter-api/tweets/
183
+ manage-tweets/api-reference/delete-tweets-id
184
+ """
185
+ # Print the parameters that are not None
186
+ if tweet_id is not None:
187
+ print(
188
+ f"You are going to delete a tweet with the following "
189
+ f"ID: {tweet_id}"
190
+ )
191
+
192
+ # Add a confirmation prompt at the beginning of the function
193
+ confirm = input(
194
+ "Are you sure you want to delete this tweet? (yes/no): "
195
+ )
196
+ if confirm.lower() != "yes":
197
+ return "Execution cancelled by the user."
198
+
199
+ oauth = self._get_oauth_session()
200
+
201
+ # Making the request
202
+ response = oauth.delete(
203
+ f"https://api.twitter.com/2/tweets/{tweet_id}",
204
+ )
205
+
206
+ if response.status_code != HTTPStatus.OK:
207
+ error_type = self._handle_http_error(response)
208
+ # use string concatenation to satisfy flake8
209
+ return (
210
+ "Request returned a(n) "
211
+ + str(error_type)
212
+ + ": "
213
+ + str(response.status_code)
214
+ + " "
215
+ + response.text
216
+ )
217
+
218
+ # Saving the response as JSON
219
+ json_response = response.json()
220
+ # `deleted_status` may be True or False.
221
+ # Defaults to False if not found.
222
+ deleted_status = json_response.get("data", {}).get("deleted", False)
223
+ response_str = (
224
+ f"Delete tweet successful: {deleted_status}. "
225
+ f"The tweet ID is: {tweet_id}. "
226
+ )
227
+ return response_str
228
+
229
+ def get_my_user_profile(self) -> str:
230
+ r"""Retrieves and formats the authenticated user's Twitter
231
+ profile info.
232
+
233
+ This function sends a GET request to the Twitter API to retrieve the
234
+ authenticated user's profile information, including their pinned tweet.
235
+ It then formats this information into a readable report.
236
+
237
+ Returns:
238
+ str: A formatted report of the authenticated user's Twitter profile
239
+ information. This includes their ID, name, username,
240
+ description, location, most recent tweet ID, profile image URL,
241
+ account creation date, protection status, verification type,
242
+ public metrics, and pinned tweet information. If the request to
243
+ the Twitter API is not successful,
244
+ the return is an error message.
245
+
246
+ Reference:
247
+ https://developer.twitter.com/en/docs/twitter-api/users/lookup/
248
+ api-reference/get-users-me
249
+ """
250
+ oauth = self._get_oauth_session()
251
+
252
+ tweet_fields = ["created_at", "text"]
253
+ user_fields = [
254
+ "created_at",
255
+ "description",
256
+ "id",
257
+ "location",
258
+ "most_recent_tweet_id",
259
+ "name",
260
+ "pinned_tweet_id",
261
+ "profile_image_url",
262
+ "protected",
263
+ "public_metrics",
264
+ "url",
265
+ "username",
266
+ "verified_type",
267
+ ]
268
+ params = {
269
+ "expansions": "pinned_tweet_id",
270
+ "tweet.fields": ",".join(tweet_fields),
271
+ "user.fields": ",".join(user_fields),
272
+ }
273
+
274
+ response = oauth.get(
275
+ "https://api.twitter.com/2/users/me", params=params
276
+ )
277
+
278
+ if response.status_code != HTTPStatus.OK:
279
+ error_type = self._handle_http_error(response)
280
+ error_message = "Request returned a(n) {}: {} {}".format(
281
+ error_type, response.status_code, response.text
282
+ )
283
+ return error_message
284
+
285
+ json_response = response.json()
286
+
287
+ user_info = json_response.get('data', {})
288
+ tweets = json_response.get('includes', {}).get('tweets', [{}])[0]
289
+
290
+ user_report = ""
291
+ user_report += f"ID: {user_info['id']}. "
292
+ user_report += f"Name: {user_info['name']}. "
293
+ user_report += f"Username: {user_info['username']}. "
294
+
295
+ # Define the part of keys that need to be repeatedly processed
296
+ user_info_keys = [
297
+ 'description',
298
+ 'location',
299
+ 'most_recent_tweet_id',
300
+ 'profile_image_url',
301
+ ]
302
+ for key in user_info_keys:
303
+ value = user_info.get(key)
304
+ if user_info.get(key):
305
+ user_report += (
306
+ f"{key.replace('_', ' ').capitalize()}: {value}. "
307
+ )
308
+
309
+ if 'created_at' in user_info:
310
+ created_at = datetime.datetime.strptime(
311
+ user_info['created_at'], "%Y-%m-%dT%H:%M:%S.%fZ"
312
+ )
313
+ date_str = created_at.strftime('%B %d, %Y at %H:%M:%S')
314
+ user_report += f"Account created at: {date_str}. "
315
+
316
+ protection_status = "private" if user_info['protected'] else "public"
317
+ user_report += (
318
+ f"Protected: This user's Tweets are {protection_status}. "
319
+ )
320
+
321
+ verification_messages = {
322
+ 'blue': (
323
+ "The user has a blue verification, typically reserved for "
324
+ "public figures, celebrities, or global brands. "
325
+ ),
326
+ 'business': (
327
+ "The user has a business verification, typically "
328
+ "reserved for businesses and corporations. "
329
+ ),
330
+ 'government': (
331
+ "The user has a government verification, typically "
332
+ "reserved for government officials or entities. "
333
+ ),
334
+ 'none': "The user is not verified. ",
335
+ }
336
+ verification_type = user_info.get('verified_type', 'none')
337
+ user_report += (
338
+ f"Verified type: {verification_messages.get(verification_type)}"
339
+ )
340
+
341
+ if 'public_metrics' in user_info:
342
+ user_report += "Public metrics: "
343
+ metrics = user_info['public_metrics']
344
+ user_report += (
345
+ f"The user has {metrics.get('followers_count', 0)} followers, "
346
+ f"is following {metrics.get('following_count', 0)} users, "
347
+ f"has made {metrics.get('tweet_count', 0)} tweets, "
348
+ f"is listed in {metrics.get('listed_count', 0)} lists, "
349
+ f"and has received {metrics.get('like_count', 0)} likes. "
350
+ )
351
+
352
+ if 'pinned_tweet_id' in user_info:
353
+ user_report += f"Pinned tweet ID: {user_info['pinned_tweet_id']}. "
354
+
355
+ if 'created_at' in tweets and 'text' in tweets:
356
+ user_report += "\nPinned tweet information: "
357
+ tweet_created_at = datetime.datetime.strptime(
358
+ tweets['created_at'], "%Y-%m-%dT%H:%M:%S.%fZ"
359
+ )
360
+ user_report += (
361
+ f"Pinned tweet created at "
362
+ f"{tweet_created_at.strftime('%B %d, %Y at %H:%M:%S')} "
363
+ f"with text: '{tweets['text']}'."
364
+ )
365
+
366
+ return user_report
367
+
368
+ def get_tools(self) -> List[OpenAIFunction]:
369
+ r"""Returns a list of OpenAIFunction objects representing the
370
+ functions in the toolkit.
371
+
372
+ Returns:
373
+ List[OpenAIFunction]: A list of OpenAIFunction objects
374
+ representing the functions in the toolkit.
375
+ """
376
+ return [
377
+ OpenAIFunction(self.create_tweet),
378
+ OpenAIFunction(self.delete_tweet),
379
+ OpenAIFunction(self.get_my_user_profile),
380
+ ]
381
+
382
+ def _get_twitter_api_key(self) -> Tuple[str, str]:
383
+ r"""Retrieve the Twitter API key and secret from environment variables.
384
+
385
+ Returns:
386
+ Tuple[str, str]: A tuple containing the Twitter API key and secret.
387
+
388
+ Raises:
389
+ ValueError: If the API key or secret is not found in the
390
+ environment variables.
391
+ """
392
+ # Get `TWITTER_CONSUMER_KEY` and `TWITTER_CONSUMER_SECRET` here:
393
+ # https://developer.twitter.com/en/portal/products/free
394
+ TWITTER_CONSUMER_KEY = os.environ.get("TWITTER_CONSUMER_KEY")
395
+ TWITTER_CONSUMER_SECRET = os.environ.get("TWITTER_CONSUMER_SECRET")
396
+
397
+ if not TWITTER_CONSUMER_KEY or not TWITTER_CONSUMER_SECRET:
398
+ missing_keys = ", ".join(
399
+ [
400
+ "TWITTER_CONSUMER_KEY" if not TWITTER_CONSUMER_KEY else "",
401
+ "TWITTER_CONSUMER_SECRET"
402
+ if not TWITTER_CONSUMER_SECRET
403
+ else "",
404
+ ]
405
+ ).strip(", ")
406
+ raise ValueError(
407
+ f"{missing_keys} not found in environment variables. Get them "
408
+ "here: `https://developer.twitter.com/en/portal/products/free`."
409
+ )
410
+ return TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET
411
+
412
+ def _get_oauth_session(self) -> requests.Session:
413
+ r'''Initiates an OAuth1Session with Twitter's API and returns it.
414
+
415
+ The function first fetches a request token, then prompts the user to
416
+ authorize the application. After the user has authorized the
417
+ application and provided a verifier (PIN), the function fetches an
418
+ access token. Finally, a new OAuth1Session is created with the access
419
+ token and returned.
420
+
421
+ Raises:
422
+ RuntimeError: If an error occurs while fetching the OAuth access
423
+ token or the OAuth request token.
424
+
425
+ Returns:
426
+ requests_oauthlib.OAuth1Session: An OAuth1Session object
427
+ authenticated with the user's access token.
428
+
429
+ Reference:
430
+ https://github.com/twitterdev/Twitter-API-v2-sample-code/blob/main/
431
+ Manage-Tweets/create_tweet.py
432
+ https://github.com/twitterdev/Twitter-API-v2-sample-code/blob/main/
433
+ User-Lookup/get_users_me_user_context.py
434
+ '''
435
+ try:
436
+ from requests_oauthlib import OAuth1Session
437
+ except ImportError:
438
+ raise ImportError(
439
+ "Please install `requests_oauthlib` first. You can "
440
+ "install it by running `pip install "
441
+ "requests_oauthlib`."
442
+ )
443
+
444
+ consumer_key, consumer_secret = self._get_twitter_api_key()
445
+
446
+ # Get request token
447
+ request_token_url = (
448
+ "https://api.twitter.com/oauth/request_token"
449
+ "?oauth_callback=oob&x_auth_access_type=write"
450
+ )
451
+ oauth = OAuth1Session(consumer_key, client_secret=consumer_secret)
452
+
453
+ try:
454
+ fetch_response = oauth.fetch_request_token(request_token_url)
455
+ except Exception as e:
456
+ raise RuntimeError(
457
+ f"Error occurred while fetching the OAuth access token: {e}"
458
+ )
459
+
460
+ resource_owner_key = fetch_response.get("oauth_token")
461
+ resource_owner_secret = fetch_response.get("oauth_token_secret")
462
+
463
+ # Get authorization
464
+ base_authorization_url = "https://api.twitter.com/oauth/authorize"
465
+ authorization_url = oauth.authorization_url(base_authorization_url)
466
+ print("Please go here and authorize: %s" % authorization_url)
467
+ verifier = input("Paste the PIN here: ")
468
+
469
+ # Get the access token
470
+ access_token_url = "https://api.twitter.com/oauth/access_token"
471
+ oauth = OAuth1Session(
472
+ consumer_key,
473
+ client_secret=consumer_secret,
474
+ resource_owner_key=resource_owner_key,
475
+ resource_owner_secret=resource_owner_secret,
476
+ verifier=verifier,
477
+ )
478
+
479
+ try:
480
+ oauth_tokens = oauth.fetch_access_token(access_token_url)
481
+ except Exception as e:
482
+ raise RuntimeError(
483
+ f"Error occurred while fetching the OAuth request token: {e}"
484
+ )
485
+
486
+ # Create a new OAuth1Session with the access token
487
+ oauth = OAuth1Session(
488
+ consumer_key,
489
+ client_secret=consumer_secret,
490
+ resource_owner_key=oauth_tokens["oauth_token"],
491
+ resource_owner_secret=oauth_tokens["oauth_token_secret"],
492
+ )
493
+ return oauth
494
+
495
+ def _handle_http_error(self, response: requests.Response) -> str:
496
+ r"""Handles the HTTP response by checking the status code and
497
+ returning an appropriate message if there is an error.
498
+
499
+ Args:
500
+ response (requests.Response): The HTTP response to handle.
501
+
502
+ Returns:
503
+ str: A string describing the error, if any. If there is no error,
504
+ the function returns an "Unexpected Exception" message.
505
+
506
+ Reference:
507
+ https://github.com/tweepy/tweepy/blob/master/tweepy/client.py#L64
508
+ """
509
+ if response.status_code in responses:
510
+ # For 5xx server errors, return "Twitter Server Error"
511
+ if 500 <= response.status_code < 600:
512
+ return "Twitter Server Error"
513
+ else:
514
+ error_message = responses[response.status_code] + " Error"
515
+ return error_message
516
+ elif not 200 <= response.status_code < 300:
517
+ return "HTTP Exception"
518
+ else:
519
+ return "Unexpected Exception"
520
+
521
+
522
+ TWITTER_FUNCS: List[OpenAIFunction] = TwitterToolkit().get_tools()
@@ -0,0 +1,173 @@
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 typing import List, Literal
16
+
17
+ from camel.toolkits.base import BaseToolkit
18
+ from camel.toolkits.openai_function import OpenAIFunction
19
+
20
+
21
+ class WeatherToolkit(BaseToolkit):
22
+ r"""A class representing a toolkit for interacting with weather data.
23
+
24
+ This class provides methods for fetching weather data for a given city
25
+ using the OpenWeatherMap API.
26
+ """
27
+
28
+ def get_openweathermap_api_key(self) -> str:
29
+ r"""Retrieve the OpenWeatherMap API key from environment variables.
30
+
31
+ Returns:
32
+ str: The OpenWeatherMap API key.
33
+
34
+ Raises:
35
+ ValueError: If the API key is not found in the environment
36
+ variables.
37
+ """
38
+ # Get `OPENWEATHERMAP_API_KEY` here: https://openweathermap.org
39
+ OPENWEATHERMAP_API_KEY = os.environ.get('OPENWEATHERMAP_API_KEY')
40
+ if not OPENWEATHERMAP_API_KEY:
41
+ raise ValueError(
42
+ "`OPENWEATHERMAP_API_KEY` not found in environment "
43
+ "variables. Get `OPENWEATHERMAP_API_KEY` here: "
44
+ "`https://openweathermap.org`."
45
+ )
46
+ return OPENWEATHERMAP_API_KEY
47
+
48
+ def get_weather_data(
49
+ self,
50
+ city: str,
51
+ temp_units: Literal['kelvin', 'celsius', 'fahrenheit'] = 'kelvin',
52
+ wind_units: Literal[
53
+ 'meters_sec', 'miles_hour', 'knots', 'beaufort'
54
+ ] = 'meters_sec',
55
+ visibility_units: Literal['meters', 'miles'] = 'meters',
56
+ time_units: Literal['unix', 'iso', 'date'] = 'unix',
57
+ ) -> str:
58
+ r"""Fetch and return a comprehensive weather report for a given city
59
+ as a string. The report includes current weather conditions,
60
+ temperature, wind details, visibility, and sunrise/sunset times,
61
+ all formatted as a readable string.
62
+
63
+ The function interacts with the OpenWeatherMap API to
64
+ retrieve the data.
65
+
66
+ Args:
67
+ city (str): The name of the city for which the weather information
68
+ is desired. Format "City, CountryCode" (e.g., "Paris, FR"
69
+ for Paris, France). If the country code is not provided,
70
+ the API will search for the city in all countries, which
71
+ may yield incorrect results if multiple cities with the
72
+ same name exist.
73
+ temp_units (Literal['kelvin', 'celsius', 'fahrenheit']): Units for
74
+ temperature. (default: :obj:`kelvin`)
75
+ wind_units
76
+ (Literal['meters_sec', 'miles_hour', 'knots', 'beaufort']):
77
+ Units for wind speed. (default: :obj:`meters_sec`)
78
+ visibility_units (Literal['meters', 'miles']): Units for visibility
79
+ distance. (default: :obj:`meters`)
80
+ time_units (Literal['unix', 'iso', 'date']): Format for sunrise and
81
+ sunset times. (default: :obj:`unix`)
82
+
83
+ Returns:
84
+ str: A string containing the fetched weather data, formatted in a
85
+ readable manner. If an error occurs, a message indicating the
86
+ error will be returned instead.
87
+
88
+ Example of return string:
89
+ "Weather in Paris, FR: 15°C, feels like 13°C. Max temp: 17°C,
90
+ Min temp : 12°C.
91
+ Wind: 5 m/s at 270 degrees. Visibility: 10 kilometers.
92
+ Sunrise at 05:46:05 (UTC), Sunset at 18:42:20 (UTC)."
93
+
94
+ Note:
95
+ Please ensure that the API key is valid and has permissions
96
+ to access the weather data.
97
+ """
98
+ # NOTE: This tool may not work as expected since the input arguments
99
+ # like `time_units` should be enum types which are not supported yet.
100
+
101
+ try:
102
+ import pyowm
103
+ except ImportError:
104
+ raise ImportError(
105
+ "Please install `pyowm` first. You can install it by running "
106
+ "`pip install pyowm`."
107
+ )
108
+
109
+ OPENWEATHERMAP_API_KEY = self.get_openweathermap_api_key()
110
+ owm = pyowm.OWM(OPENWEATHERMAP_API_KEY)
111
+ mgr = owm.weather_manager()
112
+
113
+ try:
114
+ observation = mgr.weather_at_place(city)
115
+ weather = observation.weather
116
+
117
+ # Temperature
118
+ temperature = weather.temperature(temp_units)
119
+
120
+ # Wind
121
+ wind_data = observation.weather.wind(unit=wind_units)
122
+ wind_speed = wind_data.get('speed')
123
+ # 'N/A' if the degree is not available
124
+ wind_deg = wind_data.get('deg', 'N/A')
125
+
126
+ # Visibility
127
+ visibility_distance = observation.weather.visibility_distance
128
+ visibility = (
129
+ str(visibility_distance)
130
+ if visibility_units == 'meters'
131
+ else str(observation.weather.visibility(unit='miles'))
132
+ )
133
+
134
+ # Sunrise and Sunset
135
+ sunrise_time = str(weather.sunrise_time(timeformat=time_units))
136
+ sunset_time = str(weather.sunset_time(timeformat=time_units))
137
+
138
+ # Compile all the weather details into a report string
139
+ weather_report = (
140
+ f"Weather in {city}: "
141
+ f"{temperature['temp']}°{temp_units.title()}, "
142
+ f"feels like "
143
+ f"{temperature['feels_like']}°{temp_units.title()}. "
144
+ f"Max temp: {temperature['temp_max']}°{temp_units.title()}, "
145
+ f"Min temp: {temperature['temp_min']}°{temp_units.title()}. "
146
+ f"Wind: {wind_speed} {wind_units} at {wind_deg} degrees. "
147
+ f"Visibility: {visibility} {visibility_units}. "
148
+ f"Sunrise at {sunrise_time}, Sunset at {sunset_time}."
149
+ )
150
+
151
+ return weather_report
152
+
153
+ except Exception as e:
154
+ error_message = (
155
+ f"An error occurred while fetching weather data for {city}: "
156
+ f"{e!s}."
157
+ )
158
+ return error_message
159
+
160
+ def get_tools(self) -> List[OpenAIFunction]:
161
+ r"""Returns a list of OpenAIFunction objects representing the
162
+ functions in the toolkit.
163
+
164
+ Returns:
165
+ List[OpenAIFunction]: A list of OpenAIFunction objects
166
+ representing the functions in the toolkit.
167
+ """
168
+ return [
169
+ OpenAIFunction(self.get_weather_data),
170
+ ]
171
+
172
+
173
+ WEATHER_FUNCS: List[OpenAIFunction] = WeatherToolkit().get_tools()