camel-ai 0.2.3__py3-none-any.whl → 0.2.3a0__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.
- camel/__init__.py +1 -1
- camel/agents/chat_agent.py +69 -93
- camel/agents/knowledge_graph_agent.py +6 -4
- camel/bots/__init__.py +2 -16
- camel/bots/discord_bot.py +206 -0
- camel/configs/__init__.py +2 -1
- camel/configs/anthropic_config.py +5 -2
- camel/configs/base_config.py +6 -6
- camel/configs/groq_config.py +3 -2
- camel/configs/ollama_config.py +2 -1
- camel/configs/openai_config.py +23 -2
- camel/configs/samba_config.py +2 -2
- camel/configs/togetherai_config.py +1 -1
- camel/configs/vllm_config.py +1 -1
- camel/configs/zhipuai_config.py +3 -2
- camel/embeddings/openai_embedding.py +2 -2
- camel/loaders/__init__.py +0 -2
- camel/loaders/firecrawl_reader.py +3 -3
- camel/loaders/unstructured_io.py +33 -35
- camel/messages/__init__.py +0 -1
- camel/models/__init__.py +4 -2
- camel/models/anthropic_model.py +26 -32
- camel/models/azure_openai_model.py +36 -39
- camel/models/base_model.py +20 -31
- camel/models/gemini_model.py +29 -37
- camel/models/groq_model.py +23 -29
- camel/models/litellm_model.py +61 -44
- camel/models/mistral_model.py +29 -32
- camel/models/model_factory.py +76 -66
- camel/models/nemotron_model.py +23 -33
- camel/models/ollama_model.py +47 -42
- camel/models/open_source_model.py +170 -0
- camel/models/{openai_compatible_model.py → openai_compatibility_model.py} +49 -31
- camel/models/openai_model.py +29 -48
- camel/models/reka_model.py +28 -30
- camel/models/samba_model.py +177 -82
- camel/models/stub_model.py +2 -2
- camel/models/togetherai_model.py +43 -37
- camel/models/vllm_model.py +50 -43
- camel/models/zhipuai_model.py +27 -33
- camel/retrievers/auto_retriever.py +10 -28
- camel/retrievers/vector_retriever.py +47 -58
- camel/societies/babyagi_playing.py +3 -6
- camel/societies/role_playing.py +3 -5
- camel/storages/graph_storages/graph_element.py +5 -3
- camel/storages/key_value_storages/json.py +1 -6
- camel/toolkits/__init__.py +7 -20
- camel/toolkits/base.py +3 -2
- camel/toolkits/code_execution.py +7 -6
- camel/toolkits/dalle_toolkit.py +6 -6
- camel/toolkits/github_toolkit.py +10 -9
- camel/toolkits/google_maps_toolkit.py +7 -7
- camel/toolkits/linkedin_toolkit.py +7 -7
- camel/toolkits/math_toolkit.py +8 -8
- camel/toolkits/open_api_toolkit.py +5 -5
- camel/toolkits/{function_tool.py → openai_function.py} +11 -34
- camel/toolkits/reddit_toolkit.py +7 -7
- camel/toolkits/retrieval_toolkit.py +5 -5
- camel/toolkits/search_toolkit.py +9 -9
- camel/toolkits/slack_toolkit.py +11 -11
- camel/toolkits/twitter_toolkit.py +452 -378
- camel/toolkits/weather_toolkit.py +6 -6
- camel/types/__init__.py +1 -6
- camel/types/enums.py +85 -40
- camel/types/openai_types.py +0 -3
- camel/utils/__init__.py +2 -0
- camel/utils/async_func.py +7 -7
- camel/utils/commons.py +3 -32
- camel/utils/token_counting.py +212 -30
- camel/workforce/role_playing_worker.py +1 -1
- camel/workforce/single_agent_worker.py +1 -1
- camel/workforce/task_channel.py +3 -4
- camel/workforce/workforce.py +4 -4
- {camel_ai-0.2.3.dist-info → camel_ai-0.2.3a0.dist-info}/METADATA +56 -27
- {camel_ai-0.2.3.dist-info → camel_ai-0.2.3a0.dist-info}/RECORD +76 -85
- {camel_ai-0.2.3.dist-info → camel_ai-0.2.3a0.dist-info}/WHEEL +1 -1
- camel/bots/discord_app.py +0 -138
- camel/bots/slack/__init__.py +0 -30
- camel/bots/slack/models.py +0 -158
- camel/bots/slack/slack_app.py +0 -255
- camel/loaders/chunkr_reader.py +0 -163
- camel/toolkits/arxiv_toolkit.py +0 -155
- camel/toolkits/ask_news_toolkit.py +0 -653
- camel/toolkits/google_scholar_toolkit.py +0 -146
- camel/toolkits/whatsapp_toolkit.py +0 -177
- camel/types/unified_model_type.py +0 -104
- camel_ai-0.2.3.dist-info/LICENSE +0 -201
|
@@ -15,431 +15,505 @@ import datetime
|
|
|
15
15
|
import os
|
|
16
16
|
from http import HTTPStatus
|
|
17
17
|
from http.client import responses
|
|
18
|
-
from typing import
|
|
18
|
+
from typing import List, Optional, Tuple, Union
|
|
19
19
|
|
|
20
20
|
import requests
|
|
21
|
-
from requests_oauthlib import OAuth1
|
|
22
21
|
|
|
23
|
-
from camel.toolkits import
|
|
22
|
+
from camel.toolkits import OpenAIFunction
|
|
24
23
|
from camel.toolkits.base import BaseToolkit
|
|
25
|
-
from camel.utils import api_keys_required
|
|
26
24
|
|
|
27
25
|
TWEET_TEXT_LIMIT = 280
|
|
28
26
|
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
"
|
|
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
|
|
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
|
-
)
|
|
28
|
+
class TwitterToolkit(BaseToolkit):
|
|
29
|
+
r"""A class representing a toolkit for Twitter operations.
|
|
95
30
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"Error: Cannot provide both `quote_tweet_id` and "
|
|
100
|
-
"(`poll_options` or `poll_duration_minutes`)."
|
|
101
|
-
)
|
|
31
|
+
This class provides methods for creating a tweet, deleting a tweet, and
|
|
32
|
+
getting the authenticated user's profile information.
|
|
33
|
+
"""
|
|
102
34
|
|
|
103
|
-
|
|
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.
|
|
104
66
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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,
|
|
109
106
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
error_type = _handle_http_error(response)
|
|
119
|
-
return (
|
|
120
|
-
f"Request returned a(n) {error_type}: "
|
|
121
|
-
f"{response.status_code} {response.text}"
|
|
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): "
|
|
122
115
|
)
|
|
116
|
+
if confirm.lower() != "yes":
|
|
117
|
+
return "Execution cancelled by the user."
|
|
123
118
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
tweet_text = json_response["data"]["text"]
|
|
127
|
-
|
|
128
|
-
return f"Create tweet {tweet_id} successful with content {tweet_text}."
|
|
119
|
+
oauth = self._get_oauth_session()
|
|
120
|
+
json_data = {}
|
|
129
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
|
+
}
|
|
130
127
|
|
|
131
|
-
|
|
132
|
-
|
|
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.
|
|
128
|
+
if quote_tweet_id is not None:
|
|
129
|
+
json_data["quote_tweet_id"] = str(quote_tweet_id) # type: ignore[assignment]
|
|
139
130
|
|
|
140
|
-
|
|
141
|
-
a tweet with the specified ID. Before sending the request, it
|
|
142
|
-
prompts the user to confirm the deletion.
|
|
131
|
+
json_data["text"] = text # type: ignore[assignment]
|
|
143
132
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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.
|
|
133
|
+
# Making the request
|
|
134
|
+
response = oauth.post(
|
|
135
|
+
"https://api.twitter.com/2/tweets",
|
|
136
|
+
json=json_data,
|
|
137
|
+
)
|
|
152
138
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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}'."
|
|
170
161
|
)
|
|
171
162
|
|
|
172
|
-
|
|
163
|
+
return response_str
|
|
173
164
|
|
|
174
|
-
|
|
175
|
-
|
|
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."
|
|
181
|
-
)
|
|
165
|
+
def delete_tweet(self, tweet_id: str) -> str:
|
|
166
|
+
r"""Deletes a tweet with the specified ID for an authorized user.
|
|
182
167
|
|
|
183
|
-
|
|
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.
|
|
184
171
|
|
|
172
|
+
Args:
|
|
173
|
+
tweet_id (str): The ID of the tweet to delete.
|
|
185
174
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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."
|
|
194
198
|
|
|
195
|
-
|
|
196
|
-
authenticated user's profile information, including their pinned tweet.
|
|
197
|
-
It then formats this information into a readable report.
|
|
199
|
+
oauth = self._get_oauth_session()
|
|
198
200
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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.
|
|
201
|
+
# Making the request
|
|
202
|
+
response = oauth.delete(
|
|
203
|
+
f"https://api.twitter.com/2/tweets/{tweet_id}",
|
|
204
|
+
)
|
|
206
205
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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)
|
|
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
|
|
241
228
|
|
|
229
|
+
def get_my_user_profile(self) -> str:
|
|
230
|
+
r"""Retrieves and formats the authenticated user's Twitter
|
|
231
|
+
profile info.
|
|
242
232
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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.
|
|
246
236
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
+
}
|
|
251
273
|
|
|
252
|
-
|
|
253
|
-
|
|
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}"
|
|
274
|
+
response = oauth.get(
|
|
275
|
+
"https://api.twitter.com/2/users/me", params=params
|
|
296
276
|
)
|
|
297
277
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
)
|
|
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
|
|
364
284
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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}. "
|
|
368
319
|
)
|
|
369
320
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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)}"
|
|
378
339
|
)
|
|
379
340
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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.
|
|
393
371
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return error_message
|
|
404
|
-
elif not 200 <= response.status_code < 300:
|
|
405
|
-
return "HTTP Exception"
|
|
406
|
-
else:
|
|
407
|
-
return "Unexpected Exception"
|
|
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
|
+
]
|
|
408
381
|
|
|
382
|
+
def _get_twitter_api_key(self) -> Tuple[str, str]:
|
|
383
|
+
r"""Retrieve the Twitter API key and secret from environment variables.
|
|
409
384
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
FunctionTool(delete_tweet),
|
|
413
|
-
FunctionTool(get_my_user_profile),
|
|
414
|
-
FunctionTool(get_user_by_username),
|
|
415
|
-
]
|
|
385
|
+
Returns:
|
|
386
|
+
Tuple[str, str]: A tuple containing the Twitter API key and secret.
|
|
416
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.
|
|
417
424
|
|
|
418
|
-
|
|
419
|
-
|
|
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
|
+
)
|
|
420
478
|
|
|
421
|
-
|
|
422
|
-
|
|
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
|
|
423
494
|
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
"""
|
|
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.
|
|
436
498
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
functions in the toolkit.
|
|
499
|
+
Args:
|
|
500
|
+
response (requests.Response): The HTTP response to handle.
|
|
440
501
|
|
|
441
502
|
Returns:
|
|
442
|
-
|
|
443
|
-
|
|
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
|
|
444
508
|
"""
|
|
445
|
-
|
|
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"
|