camel-ai 0.2.3a1__py3-none-any.whl → 0.2.3a2__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 (87) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +93 -69
  3. camel/agents/knowledge_graph_agent.py +4 -6
  4. camel/bots/__init__.py +16 -2
  5. camel/bots/discord_app.py +138 -0
  6. camel/bots/slack/__init__.py +30 -0
  7. camel/bots/slack/models.py +158 -0
  8. camel/bots/slack/slack_app.py +255 -0
  9. camel/configs/__init__.py +1 -2
  10. camel/configs/anthropic_config.py +2 -5
  11. camel/configs/base_config.py +6 -6
  12. camel/configs/groq_config.py +2 -3
  13. camel/configs/ollama_config.py +1 -2
  14. camel/configs/openai_config.py +2 -23
  15. camel/configs/samba_config.py +2 -2
  16. camel/configs/togetherai_config.py +1 -1
  17. camel/configs/vllm_config.py +1 -1
  18. camel/configs/zhipuai_config.py +2 -3
  19. camel/embeddings/openai_embedding.py +2 -2
  20. camel/loaders/__init__.py +2 -0
  21. camel/loaders/chunkr_reader.py +163 -0
  22. camel/loaders/firecrawl_reader.py +3 -3
  23. camel/loaders/unstructured_io.py +35 -33
  24. camel/messages/__init__.py +1 -0
  25. camel/models/__init__.py +2 -4
  26. camel/models/anthropic_model.py +32 -26
  27. camel/models/azure_openai_model.py +39 -36
  28. camel/models/base_model.py +31 -20
  29. camel/models/gemini_model.py +37 -29
  30. camel/models/groq_model.py +29 -23
  31. camel/models/litellm_model.py +44 -61
  32. camel/models/mistral_model.py +32 -29
  33. camel/models/model_factory.py +66 -76
  34. camel/models/nemotron_model.py +33 -23
  35. camel/models/ollama_model.py +42 -47
  36. camel/models/{openai_compatibility_model.py → openai_compatible_model.py} +31 -49
  37. camel/models/openai_model.py +48 -29
  38. camel/models/reka_model.py +30 -28
  39. camel/models/samba_model.py +82 -177
  40. camel/models/stub_model.py +2 -2
  41. camel/models/togetherai_model.py +37 -43
  42. camel/models/vllm_model.py +43 -50
  43. camel/models/zhipuai_model.py +33 -27
  44. camel/retrievers/auto_retriever.py +28 -10
  45. camel/retrievers/vector_retriever.py +58 -47
  46. camel/societies/babyagi_playing.py +6 -3
  47. camel/societies/role_playing.py +5 -3
  48. camel/storages/graph_storages/graph_element.py +3 -5
  49. camel/storages/key_value_storages/json.py +6 -1
  50. camel/toolkits/__init__.py +20 -7
  51. camel/toolkits/arxiv_toolkit.py +155 -0
  52. camel/toolkits/ask_news_toolkit.py +653 -0
  53. camel/toolkits/base.py +2 -3
  54. camel/toolkits/code_execution.py +6 -7
  55. camel/toolkits/dalle_toolkit.py +6 -6
  56. camel/toolkits/{openai_function.py → function_tool.py} +34 -11
  57. camel/toolkits/github_toolkit.py +9 -10
  58. camel/toolkits/google_maps_toolkit.py +7 -7
  59. camel/toolkits/google_scholar_toolkit.py +146 -0
  60. camel/toolkits/linkedin_toolkit.py +7 -7
  61. camel/toolkits/math_toolkit.py +8 -8
  62. camel/toolkits/open_api_toolkit.py +5 -5
  63. camel/toolkits/reddit_toolkit.py +7 -7
  64. camel/toolkits/retrieval_toolkit.py +5 -5
  65. camel/toolkits/search_toolkit.py +9 -9
  66. camel/toolkits/slack_toolkit.py +11 -11
  67. camel/toolkits/twitter_toolkit.py +378 -452
  68. camel/toolkits/weather_toolkit.py +6 -6
  69. camel/toolkits/whatsapp_toolkit.py +177 -0
  70. camel/types/__init__.py +6 -1
  71. camel/types/enums.py +40 -85
  72. camel/types/openai_types.py +3 -0
  73. camel/types/unified_model_type.py +104 -0
  74. camel/utils/__init__.py +0 -2
  75. camel/utils/async_func.py +7 -7
  76. camel/utils/commons.py +32 -3
  77. camel/utils/token_counting.py +30 -212
  78. camel/workforce/role_playing_worker.py +1 -1
  79. camel/workforce/single_agent_worker.py +1 -1
  80. camel/workforce/task_channel.py +4 -3
  81. camel/workforce/workforce.py +4 -4
  82. camel_ai-0.2.3a2.dist-info/LICENSE +201 -0
  83. {camel_ai-0.2.3a1.dist-info → camel_ai-0.2.3a2.dist-info}/METADATA +27 -56
  84. {camel_ai-0.2.3a1.dist-info → camel_ai-0.2.3a2.dist-info}/RECORD +85 -76
  85. {camel_ai-0.2.3a1.dist-info → camel_ai-0.2.3a2.dist-info}/WHEEL +1 -1
  86. camel/bots/discord_bot.py +0 -206
  87. camel/models/open_source_model.py +0 -170
@@ -15,505 +15,431 @@ import datetime
15
15
  import os
16
16
  from http import HTTPStatus
17
17
  from http.client import responses
18
- from typing import List, Optional, Tuple, Union
18
+ from typing import Any, Dict, List, Optional, Union
19
19
 
20
20
  import requests
21
+ from requests_oauthlib import OAuth1
21
22
 
22
- from camel.toolkits import OpenAIFunction
23
+ from camel.toolkits import FunctionTool
23
24
  from camel.toolkits.base import BaseToolkit
25
+ from camel.utils import api_keys_required
24
26
 
25
27
  TWEET_TEXT_LIMIT = 280
26
28
 
27
29
 
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.
30
+ @api_keys_required(
31
+ "TWITTER_CONSUMER_KEY",
32
+ "TWITTER_CONSUMER_SECRET",
33
+ "TWITTER_ACCESS_TOKEN",
34
+ "TWITTER_ACCESS_TOKEN_SECRET",
35
+ )
36
+ def create_tweet(
37
+ text: str,
38
+ poll_options: Optional[List[str]] = None,
39
+ poll_duration_minutes: Optional[int] = None,
40
+ quote_tweet_id: Optional[Union[int, str]] = None,
41
+ ) -> str:
42
+ r"""Creates a new tweet, optionally including a poll or a quote tweet,
43
+ or simply a text-only tweet.
44
+
45
+ This function sends a POST request to the Twitter API to create a new
46
+ tweet. The tweet can be a text-only tweet, or optionally include a poll
47
+ or be a quote tweet. A confirmation prompt is presented to the user
48
+ before the tweet is created.
49
+
50
+ Args:
51
+ text (str): The text of the tweet. The Twitter character limit for
52
+ a single tweet is 280 characters.
53
+ poll_options (Optional[List[str]]): A list of poll options for a
54
+ tweet with a poll.
55
+ poll_duration_minutes (Optional[int]): Duration of the poll in
56
+ minutes for a tweet with a poll. This is only required
57
+ if the request includes poll_options.
58
+ quote_tweet_id (Optional[Union[int, str]]): Link to the tweet being
59
+ quoted.
60
+
61
+ Returns:
62
+ str: A message indicating the success of the tweet creation,
63
+ including the tweet ID and text. If the request to the
64
+ Twitter API is not successful, the return is an error message.
65
+
66
+ Note:
67
+ You can only provide either the `quote_tweet_id` parameter or
68
+ the pair of `poll_duration_minutes` and `poll_options` parameters,
69
+ not both.
70
+
71
+ Reference:
72
+ https://developer.x.com/en/docs/x-api/tweets/manage-tweets/api-reference/post-tweets
33
73
  """
74
+ auth = OAuth1(
75
+ os.getenv("TWITTER_CONSUMER_KEY"),
76
+ os.getenv("TWITTER_CONSUMER_SECRET"),
77
+ os.getenv("TWITTER_ACCESS_TOKEN"),
78
+ os.getenv("TWITTER_ACCESS_TOKEN_SECRET"),
79
+ )
80
+ url = "https://api.x.com/2/tweets"
81
+
82
+ # Validate text
83
+ if text is None:
84
+ return "Text cannot be None"
85
+
86
+ if len(text) > TWEET_TEXT_LIMIT:
87
+ return f"Text must not exceed {TWEET_TEXT_LIMIT} characters."
88
+
89
+ # Validate poll options and duration
90
+ if (poll_options is None) != (poll_duration_minutes is None):
91
+ return (
92
+ "Error: Both `poll_options` and `poll_duration_minutes` must "
93
+ "be provided together or not at all."
94
+ )
34
95
 
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): "
96
+ # Validate exclusive parameters
97
+ if quote_tweet_id is not None and (poll_options or poll_duration_minutes):
98
+ return (
99
+ "Error: Cannot provide both `quote_tweet_id` and "
100
+ "(`poll_options` or `poll_duration_minutes`)."
115
101
  )
116
- if confirm.lower() != "yes":
117
- return "Execution cancelled by the user."
118
102
 
119
- oauth = self._get_oauth_session()
120
- json_data = {}
103
+ payload: Dict[str, Any] = {"text": text}
121
104
 
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
- }
105
+ if poll_options is not None and poll_duration_minutes is not None:
106
+ payload["poll"] = {
107
+ "options": poll_options,
108
+ "duration_minutes": poll_duration_minutes,
109
+ }
127
110
 
128
- if quote_tweet_id is not None:
129
- json_data["quote_tweet_id"] = str(quote_tweet_id) # type: ignore[assignment]
111
+ if quote_tweet_id is not None:
112
+ payload["quote_tweet_id"] = str(quote_tweet_id)
130
113
 
131
- json_data["text"] = text # type: ignore[assignment]
114
+ # Making the request
115
+ response = requests.post(url, auth=auth, json=payload)
132
116
 
133
- # Making the request
134
- response = oauth.post(
135
- "https://api.twitter.com/2/tweets",
136
- json=json_data,
117
+ if response.status_code != HTTPStatus.CREATED:
118
+ error_type = _handle_http_error(response)
119
+ return (
120
+ f"Request returned a(n) {error_type}: "
121
+ f"{response.status_code} {response.text}"
137
122
  )
138
123
 
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
- )
124
+ json_response = response.json()
125
+ tweet_id = json_response["data"]["id"]
126
+ tweet_text = json_response["data"]["text"]
162
127
 
163
- return response_str
128
+ return f"Create tweet {tweet_id} successful with content {tweet_text}."
164
129
 
165
- def delete_tweet(self, tweet_id: str) -> str:
166
- r"""Deletes a tweet with the specified ID for an authorized user.
167
130
 
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.
131
+ @api_keys_required(
132
+ "TWITTER_CONSUMER_KEY",
133
+ "TWITTER_CONSUMER_SECRET",
134
+ "TWITTER_ACCESS_TOKEN",
135
+ "TWITTER_ACCESS_TOKEN_SECRET",
136
+ )
137
+ def delete_tweet(tweet_id: str) -> str:
138
+ r"""Deletes a tweet with the specified ID for an authorized user.
171
139
 
172
- Args:
173
- tweet_id (str): The ID of the tweet to delete.
140
+ This function sends a DELETE request to the Twitter API to delete
141
+ a tweet with the specified ID. Before sending the request, it
142
+ prompts the user to confirm the deletion.
174
143
 
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."
144
+ Args:
145
+ tweet_id (str): The ID of the tweet to delete.
198
146
 
199
- oauth = self._get_oauth_session()
147
+ Returns:
148
+ str: A message indicating the result of the deletion. If the
149
+ deletion was successful, the message includes the ID of the
150
+ deleted tweet. If the deletion was not successful, the message
151
+ includes an error message.
200
152
 
201
- # Making the request
202
- response = oauth.delete(
203
- f"https://api.twitter.com/2/tweets/{tweet_id}",
153
+ Reference:
154
+ https://developer.x.com/en/docs/x-api/tweets/manage-tweets/api-reference/delete-tweets-id
155
+ """
156
+ auth = OAuth1(
157
+ os.getenv("TWITTER_CONSUMER_KEY"),
158
+ os.getenv("TWITTER_CONSUMER_SECRET"),
159
+ os.getenv("TWITTER_ACCESS_TOKEN"),
160
+ os.getenv("TWITTER_ACCESS_TOKEN_SECRET"),
161
+ )
162
+ url = f"https://api.x.com/2/tweets/{tweet_id}"
163
+ response = requests.delete(url, auth=auth)
164
+
165
+ if response.status_code != HTTPStatus.OK:
166
+ error_type = _handle_http_error(response)
167
+ return (
168
+ f"Request returned a(n) {error_type}: "
169
+ f"{response.status_code} {response.text}"
204
170
  )
205
171
 
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}. "
172
+ json_response = response.json()
173
+
174
+ # `deleted_status` may be True or False.
175
+ # Defaults to False if not found.
176
+ deleted_status = json_response.get("data", {}).get("deleted", False)
177
+ if not deleted_status:
178
+ return (
179
+ f"The tweet with ID {tweet_id} was not deleted. "
180
+ "Please check the tweet ID and try again."
226
181
  )
227
- return response_str
228
182
 
229
- def get_my_user_profile(self) -> str:
230
- r"""Retrieves and formats the authenticated user's Twitter
231
- profile info.
183
+ return f"Delete tweet {tweet_id} successful."
232
184
 
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
185
 
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
- }
186
+ @api_keys_required(
187
+ "TWITTER_CONSUMER_KEY",
188
+ "TWITTER_CONSUMER_SECRET",
189
+ "TWITTER_ACCESS_TOKEN",
190
+ "TWITTER_ACCESS_TOKEN_SECRET",
191
+ )
192
+ def get_my_user_profile() -> str:
193
+ r"""Retrieves the authenticated user's Twitter profile info.
273
194
 
274
- response = oauth.get(
275
- "https://api.twitter.com/2/users/me", params=params
195
+ This function sends a GET request to the Twitter API to retrieve the
196
+ authenticated user's profile information, including their pinned tweet.
197
+ It then formats this information into a readable report.
198
+
199
+ Returns:
200
+ str: A formatted report of the authenticated user's Twitter profile
201
+ information. This includes their ID, name, username,
202
+ description, location, most recent tweet ID, profile image URL,
203
+ account creation date, protection status, verification type,
204
+ public metrics, and pinned tweet information. If the request to
205
+ the Twitter API is not successful, the return is an error message.
206
+
207
+ Reference:
208
+ https://developer.x.com/en/docs/x-api/users/lookup/api-reference/get-users-me
209
+ """
210
+ return _get_user_info()
211
+
212
+
213
+ @api_keys_required(
214
+ "TWITTER_CONSUMER_KEY",
215
+ "TWITTER_CONSUMER_SECRET",
216
+ "TWITTER_ACCESS_TOKEN",
217
+ "TWITTER_ACCESS_TOKEN_SECRET",
218
+ )
219
+ def get_user_by_username(username: str) -> str:
220
+ r"""Retrieves one user's Twitter profile info by username (handle).
221
+
222
+ This function sends a GET request to the Twitter API to retrieve the
223
+ user's profile information, including their pinned tweet.
224
+ It then formats this information into a readable report.
225
+
226
+ Args:
227
+ username (str): The username (handle) of the user to retrieve.
228
+
229
+ Returns:
230
+ str: A formatted report of the user's Twitter profile information.
231
+ This includes their ID, name, username, description, location,
232
+ most recent tweet ID, profile image URL, account creation date,
233
+ protection status, verification type, public metrics, and
234
+ pinned tweet information. If the request to the Twitter API is
235
+ not successful, the return is an error message.
236
+
237
+ Reference:
238
+ https://developer.x.com/en/docs/x-api/users/lookup/api-reference/get-users-by-username-username
239
+ """
240
+ return _get_user_info(username)
241
+
242
+
243
+ def _get_user_info(username: Optional[str] = None) -> str:
244
+ r"""Generates a formatted report of the user information from the
245
+ JSON response.
246
+
247
+ Args:
248
+ username (Optional[str], optional): The username of the user to
249
+ retrieve. If None, the function retrieves the authenticated
250
+ user's profile information. (default: :obj:`None`)
251
+
252
+ Returns:
253
+ str: A formatted report of the user's Twitter profile information.
254
+ """
255
+ oauth = OAuth1(
256
+ os.getenv("TWITTER_CONSUMER_KEY"),
257
+ os.getenv("TWITTER_CONSUMER_SECRET"),
258
+ os.getenv("TWITTER_ACCESS_TOKEN"),
259
+ os.getenv("TWITTER_ACCESS_TOKEN_SECRET"),
260
+ )
261
+ url = (
262
+ f"https://api.x.com/2/users/by/username/{username}"
263
+ if username
264
+ else "https://api.x.com/2/users/me"
265
+ )
266
+
267
+ tweet_fields = ["created_at", "text"]
268
+ user_fields = [
269
+ "created_at",
270
+ "description",
271
+ "id",
272
+ "location",
273
+ "most_recent_tweet_id",
274
+ "name",
275
+ "pinned_tweet_id",
276
+ "profile_image_url",
277
+ "protected",
278
+ "public_metrics",
279
+ "url",
280
+ "username",
281
+ "verified_type",
282
+ ]
283
+ params = {
284
+ "expansions": "pinned_tweet_id",
285
+ "tweet.fields": ",".join(tweet_fields),
286
+ "user.fields": ",".join(user_fields),
287
+ }
288
+
289
+ response = requests.get(url, auth=oauth, params=params)
290
+
291
+ if response.status_code != HTTPStatus.OK:
292
+ error_type = _handle_http_error(response)
293
+ return (
294
+ f"Request returned a(n) {error_type}: "
295
+ f"{response.status_code} {response.text}"
276
296
  )
277
297
 
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
298
+ json_response = response.json()
299
+
300
+ user_info = json_response.get("data", {})
301
+ pinned_tweet = json_response.get("includes", {}).get("tweets", [{}])[0]
302
+
303
+ user_report_entries = [
304
+ f"ID: {user_info['id']}",
305
+ f"Name: {user_info['name']}",
306
+ f"Username: {user_info['username']}",
307
+ ]
308
+
309
+ # Define the part of keys that need to be repeatedly processed
310
+ user_info_keys = [
311
+ "description",
312
+ "location",
313
+ "most_recent_tweet_id",
314
+ "profile_image_url",
315
+ ]
316
+ for key in user_info_keys:
317
+ if not (value := user_info.get(key)):
318
+ continue
319
+ new_key = key.replace('_', ' ').capitalize()
320
+ user_report_entries.append(f"{new_key}: {value}")
321
+
322
+ if "created_at" in user_info:
323
+ created_at = datetime.datetime.strptime(
324
+ user_info["created_at"], "%Y-%m-%dT%H:%M:%S.%fZ"
325
+ )
326
+ date_str = created_at.strftime('%B %d, %Y at %H:%M:%S')
327
+ user_report_entries.append(f"Account created at: {date_str}")
328
+
329
+ protection_status = "private" if user_info["protected"] else "public"
330
+ user_report_entries.append(
331
+ f"Protected: This user's Tweets are {protection_status}"
332
+ )
333
+
334
+ verification_messages = {
335
+ "blue": (
336
+ "The user has a blue verification, typically reserved for "
337
+ "public figures, celebrities, or global brands"
338
+ ),
339
+ "business": (
340
+ "The user has a business verification, typically "
341
+ "reserved for businesses and corporations"
342
+ ),
343
+ "government": (
344
+ "The user has a government verification, typically "
345
+ "reserved for government officials or entities"
346
+ ),
347
+ "none": "The user is not verified",
348
+ }
349
+ verification_type = user_info.get("verified_type", "none")
350
+ user_report_entries.append(
351
+ f"Verified type: {verification_messages.get(verification_type)}"
352
+ )
353
+
354
+ if "public_metrics" in user_info:
355
+ metrics = user_info["public_metrics"]
356
+ user_report_entries.append(
357
+ f"Public metrics: "
358
+ f"The user has {metrics.get('followers_count', 0)} followers, "
359
+ f"is following {metrics.get('following_count', 0)} users, "
360
+ f"has made {metrics.get('tweet_count', 0)} tweets, "
361
+ f"is listed in {metrics.get('listed_count', 0)} lists, "
362
+ f"and has received {metrics.get('like_count', 0)} likes"
363
+ )
284
364
 
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}. "
365
+ if "pinned_tweet_id" in user_info:
366
+ user_report_entries.append(
367
+ f"Pinned tweet ID: {user_info['pinned_tweet_id']}"
319
368
  )
320
369
 
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)}"
370
+ if "created_at" in pinned_tweet and "text" in pinned_tweet:
371
+ tweet_created_at = datetime.datetime.strptime(
372
+ pinned_tweet["created_at"], "%Y-%m-%dT%H:%M:%S.%fZ"
373
+ )
374
+ user_report_entries.append(
375
+ f"Pinned tweet information: Pinned tweet created at "
376
+ f"{tweet_created_at.strftime('%B %d, %Y at %H:%M:%S')} "
377
+ f"with text: '{pinned_tweet['text']}'"
339
378
  )
340
379
 
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.
380
+ return "\n".join(user_report_entries)
371
381
 
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
 
382
- def _get_twitter_api_key(self) -> Tuple[str, str]:
383
- r"""Retrieve the Twitter API key and secret from environment variables.
383
+ def _handle_http_error(response: requests.Response) -> str:
384
+ r"""Handles the HTTP response by checking the status code and
385
+ returning an appropriate message if there is an error.
384
386
 
385
- Returns:
386
- Tuple[str, str]: A tuple containing the Twitter API key and secret.
387
+ Args:
388
+ response (requests.Response): The HTTP response to handle.
387
389
 
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.
390
+ Returns:
391
+ str: A string describing the error, if any. If there is no error,
392
+ the function returns an "Unexpected Exception" message.
424
393
 
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
- )
394
+ Reference:
395
+ https://github.com/tweepy/tweepy/blob/master/tweepy/client.py#L64
396
+ """
397
+ if response.status_code in responses:
398
+ # For 5xx server errors, return "Twitter Server Error"
399
+ if 500 <= response.status_code < 600:
400
+ return "Twitter Server Error"
401
+ else:
402
+ error_message = responses[response.status_code] + " Error"
403
+ return error_message
404
+ elif not 200 <= response.status_code < 300:
405
+ return "HTTP Exception"
406
+ else:
407
+ return "Unexpected Exception"
478
408
 
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
409
 
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.
410
+ TWITTER_FUNCS = [
411
+ FunctionTool(create_tweet),
412
+ FunctionTool(delete_tweet),
413
+ FunctionTool(get_my_user_profile),
414
+ FunctionTool(get_user_by_username),
415
+ ]
498
416
 
499
- Args:
500
- response (requests.Response): The HTTP response to handle.
501
417
 
502
- Returns:
503
- str: A string describing the error, if any. If there is no error,
504
- the function returns an "Unexpected Exception" message.
418
+ class TwitterToolkit(BaseToolkit):
419
+ r"""A class representing a toolkit for Twitter operations.
420
+
421
+ This class provides methods for creating a tweet, deleting a tweet, and
422
+ getting the authenticated user's profile information.
505
423
 
506
- Reference:
507
- https://github.com/tweepy/tweepy/blob/master/tweepy/client.py#L64
424
+ References:
425
+ https://developer.x.com/en/portal/dashboard
426
+
427
+ Notes:
428
+ To use this toolkit, you need to set the following environment
429
+ variables:
430
+ - TWITTER_CONSUMER_KEY: The consumer key for the Twitter API.
431
+ - TWITTER_CONSUMER_SECRET: The consumer secret for the Twitter API.
432
+ - TWITTER_ACCESS_TOKEN: The access token for the Twitter API.
433
+ - TWITTER_ACCESS_TOKEN_SECRET: The access token secret for the Twitter
434
+ API.
435
+ """
436
+
437
+ def get_tools(self) -> List[FunctionTool]:
438
+ r"""Returns a list of FunctionTool objects representing the
439
+ functions in the toolkit.
440
+
441
+ Returns:
442
+ List[FunctionTool]: A list of FunctionTool objects
443
+ representing the functions in the toolkit.
508
444
  """
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"
445
+ return TWITTER_FUNCS